diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index a440eb9ca7..12fbd4b6f0 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -120,8 +120,7 @@ impl Tool { prepare_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(500), projectile: Projectile { - hit_ground: vec![projectile::Effect::Stick], - hit_wall: vec![projectile::Effect::Stick], + hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ projectile::Effect::Damage(HealthChange { // TODO: This should not be fixed (?) @@ -162,8 +161,7 @@ impl Tool { prepare_duration: Duration::from_millis(0), recover_duration: Duration::from_millis(200), projectile: Projectile { - hit_ground: vec![projectile::Effect::Vanish], - hit_wall: vec![projectile::Effect::Vanish], + hit_solid: vec![projectile::Effect::Vanish], hit_entity: vec![ projectile::Effect::Damage(HealthChange { // TODO: This should not be fixed (?) @@ -190,11 +188,7 @@ impl Tool { prepare_duration: Duration::from_millis(800), recover_duration: Duration::from_millis(50), projectile: Projectile { - hit_ground: vec![ - projectile::Effect::Explode { power: 1.4 }, - projectile::Effect::Vanish, - ], - hit_wall: vec![ + hit_solid: vec![ projectile::Effect::Explode { power: 1.4 }, projectile::Effect::Vanish, ], @@ -250,8 +244,7 @@ impl Tool { prepare_duration: Duration::from_millis(0), recover_duration: Duration::from_millis(300), projectile: Projectile { - hit_ground: vec![projectile::Effect::Stick], - hit_wall: vec![projectile::Effect::Stick], + hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![projectile::Effect::Stick, projectile::Effect::Possess], time_left: Duration::from_secs(10), owner: None, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 7bc82719d9..8e721fed78 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -35,7 +35,7 @@ pub use inventory::{ }; pub use last::Last; pub use location::{Waypoint, WaypointArea}; -pub use phys::{ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel}; +pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel}; pub use player::Player; pub use projectile::Projectile; pub use stats::{Exp, HealthChange, HealthSource, Level, Stats}; diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 202eed9cf9..01fe96dc46 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -43,6 +43,17 @@ impl Component for Mass { type Storage = FlaggedStorage>; } +// Mass +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Collider { + Box { radius: f32, z_min: f32, z_max: f32 }, + Point, +} + +impl Component for Collider { + type Storage = FlaggedStorage>; +} + #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct Gravity(pub f32); @@ -61,6 +72,7 @@ impl Component for Sticky { #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct PhysicsState { pub on_ground: bool, + pub on_ceiling: bool, pub on_wall: Option>, pub touch_entity: Option, pub in_fluid: bool, diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index 8ff384a87a..741a6004a0 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -17,8 +17,7 @@ pub enum Effect { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Projectile { // TODO: use SmallVec for these effects - pub hit_ground: Vec, - pub hit_wall: Vec, + pub hit_solid: Vec, pub hit_entity: Vec, /// Time left until the projectile will despawn pub time_left: Duration, @@ -28,12 +27,7 @@ pub struct Projectile { impl Projectile { pub fn set_owner(&mut self, new_owner: Uid) { self.owner = Some(new_owner); - for e in self - .hit_ground - .iter_mut() - .chain(self.hit_wall.iter_mut()) - .chain(self.hit_entity.iter_mut()) - { + for e in self.hit_solid.iter_mut().chain(self.hit_entity.iter_mut()) { if let Effect::Damage(comp::HealthChange { cause: comp::HealthSource::Projectile { owner, .. }, .. diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 7c6c29aa38..2e7ad564c5 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -19,6 +19,7 @@ sum_type! { MountState(comp::MountState), Mounting(comp::Mounting), Mass(comp::Mass), + Collider(comp::Collider), Gravity(comp::Gravity), Sticky(comp::Sticky), Loadout(comp::Loadout), @@ -44,6 +45,7 @@ sum_type! { MountState(PhantomData), Mounting(PhantomData), Mass(PhantomData), + Collider(PhantomData), Gravity(PhantomData), Sticky(PhantomData), Loadout(PhantomData), @@ -69,6 +71,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::Collider(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Loadout(comp) => sync::handle_insert(comp, entity, world), @@ -92,6 +95,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::Collider(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Loadout(comp) => sync::handle_modify(comp, entity, world), @@ -117,6 +121,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::MountState(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Mounting(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Mass(_) => sync::handle_remove::(entity, world), + EcsCompPhantom::Collider(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Gravity(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Sticky(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Loadout(_) => sync::handle_remove::(entity, world), diff --git a/common/src/state.rs b/common/src/state.rs index 62f492e513..5812399cd7 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -118,6 +118,7 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 45cf64efdc..e5a737a82f 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Body, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, Scale, Sticky, Vel}, + comp::{Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, Scale, Sticky, Vel}, event::{EventBus, ServerEvent}, state::DeltaTime, sync::Uid, @@ -47,8 +47,8 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Scale>, ReadStorage<'a, Sticky>, ReadStorage<'a, Mass>, + ReadStorage<'a, Collider>, ReadStorage<'a, Gravity>, - ReadStorage<'a, Body>, WriteStorage<'a, PhysicsState>, WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, @@ -67,8 +67,8 @@ impl<'a> System<'a> for Sys { scales, stickies, masses, + colliders, gravities, - bodies, mut physics_states, mut positions, mut velocities, @@ -79,11 +79,11 @@ impl<'a> System<'a> for Sys { let mut event_emitter = event_bus.emitter(); // Apply movement inputs - for (entity, scale, sticky, _b, mut pos, mut vel, _ori, _) in ( + for (entity, scale, sticky, collider, mut pos, mut vel, _ori, _) in ( &entities, scales.maybe(), stickies.maybe(), - &bodies, + &colliders, &mut positions, &mut velocities, &mut orientations, @@ -93,30 +93,17 @@ impl<'a> System<'a> for Sys { { let mut physics_state = physics_states.get(entity).cloned().unwrap_or_default(); - if sticky.is_some() && (physics_state.on_wall.is_some() || physics_state.on_ground) { + if sticky.is_some() + && (physics_state.on_ground + || physics_state.on_ceiling + || physics_state.on_wall.is_some()) + { + vel.0 = Vec3::zero(); continue; } let scale = scale.map(|s| s.0).unwrap_or(1.0); - // Basic collision with terrain - // TODO: rename this, not just the player entity - let player_rad = 0.3 * scale; // half-width of the player's AABB - let player_height = 1.5 * scale; - - // Probe distances - let hdist = player_rad.ceil() as i32; - let vdist = player_height.ceil() as i32; - // Neighbouring blocks iterator - let near_iter = (-hdist..hdist + 1) - .map(move |i| { - (-hdist..hdist + 1).map(move |j| { - (1 - BlockKind::MAX_HEIGHT.ceil() as i32..vdist + 1).map(move |k| (i, j, k)) - }) - }) - .flatten() - .flatten(); - let old_vel = *vel; // Integrate forces // Friction is assumed to be a constant dependent on location @@ -150,216 +137,293 @@ impl<'a> System<'a> for Sys { Vec3::zero() }; - // Function for determining whether the player at a specific position collides - // with the ground - let collision_with = |pos: Vec3, hit: &dyn Fn(&Block) -> bool, near_iter| { - for (i, j, k) in near_iter { - let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k); + match collider { + Collider::Box { + radius, + z_min, + z_max, + } => { + // Scale collider + let radius = *radius * scale; + let z_min = *z_min * scale; + let z_max = *z_max * scale; - if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) { - let player_aabb = Aabb { - min: pos + Vec3::new(-player_rad, -player_rad, 0.0), - max: pos + Vec3::new(player_rad, player_rad, player_height), - }; - let block_aabb = Aabb { - min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) - + Vec3::new(1.0, 1.0, block.get_height()), - }; + // Probe distances + let hdist = radius.ceil() as i32; + // Neighbouring blocks iterator + let near_iter = (-hdist..hdist + 1) + .map(move |i| { + (-hdist..hdist + 1).map(move |j| { + (1 - BlockKind::MAX_HEIGHT.ceil() as i32 + z_min.floor() as i32 + ..z_max.ceil() as i32 + 1) + .map(move |k| (i, j, k)) + }) + }) + .flatten() + .flatten(); - if player_aabb.collides_with_aabb(block_aabb) { - return true; + // Function for determining whether the player at a specific position collides + // with the ground + let collision_with = |pos: Vec3, + hit: &dyn Fn(&Block) -> bool, + near_iter| { + for (i, j, k) in near_iter { + let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k); + + if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) { + let player_aabb = Aabb { + min: pos + Vec3::new(-radius, -radius, z_min), + max: pos + Vec3::new(radius, radius, z_max), + }; + let block_aabb = Aabb { + min: block_pos.map(|e| e as f32), + max: block_pos.map(|e| e as f32) + + Vec3::new(1.0, 1.0, block.get_height()), + }; + + if player_aabb.collides_with_aabb(block_aabb) { + return true; + } + } } - } - } - false - }; - - let was_on_ground = physics_state.on_ground; - physics_state.on_ground = false; - - let mut on_ground = false; - let mut attempts = 0; // Don't loop infinitely here - - // Don't jump too far at once - let increments = (pos_delta.map(|e| e.abs()).reduce_partial_max() / 0.3) - .ceil() - .max(1.0); - let old_pos = pos.0; - for _ in 0..increments as usize { - pos.0 += pos_delta / increments; - - const MAX_ATTEMPTS: usize = 16; - - // While the player is colliding with the terrain... - while collision_with(pos.0, &|block| block.is_solid(), near_iter.clone()) - && attempts < MAX_ATTEMPTS - { - // Calculate the player's AABB - let player_aabb = Aabb { - min: pos.0 + Vec3::new(-player_rad, -player_rad, 0.0), - max: pos.0 + Vec3::new(player_rad, player_rad, player_height), + false }; - // Determine the block that we are colliding with most (based on minimum - // collision axis) - let (_block_pos, block_aabb, block_height) = near_iter - .clone() - // Calculate the block's position in world space - .map(|(i, j, k)| pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k)) - // Make sure the block is actually solid - .filter_map(|block_pos| { - if let Some(block) = terrain - .get(block_pos) - .ok() - .filter(|block| block.is_solid()) - { - // Calculate block AABB - Some(( - block_pos, - Aabb { - min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.get_height()), - }, - block.get_height(), - )) - } else { - None + let was_on_ground = physics_state.on_ground; + physics_state.on_ground = false; + + let mut on_ground = false; + let mut on_ceiling = false; + let mut attempts = 0; // Don't loop infinitely here + + // Don't jump too far at once + let increments = (pos_delta.map(|e| e.abs()).reduce_partial_max() / 0.3) + .ceil() + .max(1.0); + let old_pos = pos.0; + for _ in 0..increments as usize { + pos.0 += pos_delta / increments; + + const MAX_ATTEMPTS: usize = 16; + + // While the player is colliding with the terrain... + while collision_with(pos.0, &|block| block.is_solid(), near_iter.clone()) + && attempts < MAX_ATTEMPTS + { + // Calculate the player's AABB + let player_aabb = Aabb { + min: pos.0 + Vec3::new(-radius, -radius, z_min), + max: pos.0 + Vec3::new(radius, radius, z_max), + }; + + // Determine the block that we are colliding with most (based on minimum + // collision axis) + let (_block_pos, block_aabb, block_height) = near_iter + .clone() + // Calculate the block's position in world space + .map(|(i, j, k)| pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k)) + // Make sure the block is actually solid + .filter_map(|block_pos| { + if let Some(block) = terrain + .get(block_pos) + .ok() + .filter(|block| block.is_solid()) + { + // Calculate block AABB + Some(( + block_pos, + Aabb { + min: block_pos.map(|e| e as f32), + max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.get_height()), + }, + block.get_height(), + )) + } else { + None + } + }) + // Determine whether the block's AABB collides with the player's AABB + .filter(|(_, block_aabb, _)| block_aabb.collides_with_aabb(player_aabb)) + // Find the maximum of the minimum collision axes (this bit is weird, trust me that it works) + .min_by_key(|(_, block_aabb, _)| { + ((block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5) + .map(|e| e.abs()) + .sum() + * 1_000_000.0) as i32 + }) + .expect("Collision detected, but no colliding blocks found!"); + + // Find the intrusion vector of the collision + let dir = player_aabb.collision_vector_with_aabb(block_aabb); + + // Determine an appropriate resolution vector (i.e: the minimum distance + // needed to push out of the block) + let max_axis = dir.map(|e| e.abs()).reduce_partial_min(); + let resolve_dir = -dir.map(|e| { + if e.abs().to_bits() == max_axis.to_bits() { + e + } else { + 0.0 + } + }); + + // When the resolution direction is pointing upwards, we must be on the + // ground + if resolve_dir.z > 0.0 && vel.0.z <= 0.0 { + on_ground = true; + + if !was_on_ground { + event_emitter + .emit(ServerEvent::LandOnGround { entity, vel: vel.0 }); + } + } else if resolve_dir.z < 0.0 && vel.0.z >= 0.0 { + on_ceiling = true; } - }) - // Determine whether the block's AABB collides with the player's AABB - .filter(|(_, block_aabb, _)| block_aabb.collides_with_aabb(player_aabb)) - // Find the maximum of the minimum collision axes (this bit is weird, trust me that it works) - .min_by_key(|(_, block_aabb, _)| { - ((block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5) - .map(|e| e.abs()) - .sum() - * 1_000_000.0) as i32 - }) - .expect("Collision detected, but no colliding blocks found!"); - // Find the intrusion vector of the collision - let dir = player_aabb.collision_vector_with_aabb(block_aabb); + // When the resolution direction is non-vertical, we must be colliding + // with a wall If the space above is free... + if !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &|block| block.is_solid(), near_iter.clone()) + // ...and we're being pushed out horizontally... + && resolve_dir.z == 0.0 + // ...and the vertical resolution direction is sufficiently great... + && -dir.z > 0.1 + // ...and we're falling/standing OR there is a block *directly* beneath our current origin (note: not hitbox)... + && (vel.0.z <= 0.0 || terrain + .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) + .map(|block| block.is_solid()) + .unwrap_or(false)) + // ...and there is a collision with a block beneath our current hitbox... + && collision_with( + old_pos + resolve_dir - Vec3::unit_z() * 1.05, + &|block| block.is_solid(), + near_iter.clone(), + ) + { + // ...block-hop! + pos.0.z = (pos.0.z + 0.1).floor() + block_height; + vel.0.z = 0.0; + on_ground = true; + break; + } else { + // Correct the velocity + vel.0 = vel.0.map2(resolve_dir, |e, d| { + if d * e.signum() < 0.0 { 0.0 } else { e } + }); + } - // Determine an appropriate resolution vector (i.e: the minimum distance needed - // to push out of the block) - let max_axis = dir.map(|e| e.abs()).reduce_partial_min(); - let resolve_dir = -dir.map(|e| { - if e.abs().to_bits() == max_axis.to_bits() { - e - } else { - 0.0 + // Resolve the collision normally + pos.0 += resolve_dir; + + attempts += 1; } - }); - // When the resolution direction is pointing upwards, we must be on the ground - if resolve_dir.z > 0.0 && vel.0.z <= 0.0 { - on_ground = true; - - if !was_on_ground { - event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 }); + if attempts == MAX_ATTEMPTS { + pos.0 = old_pos; + break; } } - // When the resolution direction is non-vertical, we must be colliding with a - // wall If the space above is free... - if !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &|block| block.is_solid(), near_iter.clone()) - // ...and we're being pushed out horizontally... - && resolve_dir.z == 0.0 - // ...and the vertical resolution direction is sufficiently great... - && -dir.z > 0.1 - // ...and we're falling/standing OR there is a block *directly* beneath our current origin (note: not hitbox)... - && (vel.0.z <= 0.0 || terrain - .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) - .map(|block| block.is_solid()) - .unwrap_or(false)) - // ...and there is a collision with a block beneath our current hitbox... - && collision_with( - old_pos + resolve_dir - Vec3::unit_z() * 1.05, - &|block| block.is_solid(), + if on_ceiling { + physics_state.on_ceiling = true; + } + + if on_ground { + physics_state.on_ground = true; + // If the space below us is free, then "snap" to the ground + } else if collision_with( + pos.0 - Vec3::unit_z() * 1.05, + &|block| block.is_solid(), + near_iter.clone(), + ) && vel.0.z < 0.0 + && vel.0.z > -1.5 + && was_on_ground + && !collision_with( + pos.0 - Vec3::unit_z() * 0.05, + &|block| { + block.is_solid() + && block.get_height() >= (pos.0.z - 0.05).rem_euclid(1.0) + }, near_iter.clone(), ) { - // ...block-hop! - pos.0.z = (pos.0.z + 0.1).floor() + block_height; - vel.0.z = 0.0; - on_ground = true; - break; - } else { - // Correct the velocity - vel.0 = vel.0.map2( - resolve_dir, - |e, d| if d * e.signum() < 0.0 { 0.0 } else { e }, - ); + let snap_height = terrain + .get( + Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.05) + .map(|e| e.floor() as i32), + ) + .ok() + .filter(|block| block.is_solid()) + .map(|block| block.get_height()) + .unwrap_or(0.0); + pos.0.z = (pos.0.z - 0.05).floor() + snap_height; + physics_state.on_ground = true; } - // Resolve the collision normally - pos.0 += resolve_dir; + let dirs = [ + Vec3::unit_x(), + Vec3::unit_y(), + -Vec3::unit_x(), + -Vec3::unit_y(), + ]; - attempts += 1; - } + if let (wall_dir, true) = + dirs.iter().fold((Vec3::zero(), false), |(a, hit), dir| { + if collision_with( + pos.0 + *dir * 0.01, + &|block| block.is_solid(), + near_iter.clone(), + ) { + (a + dir, true) + } else { + (a, hit) + } + }) + { + physics_state.on_wall = Some(wall_dir); + } else { + physics_state.on_wall = None; + } - if attempts == MAX_ATTEMPTS { - pos.0 = old_pos; - break; - } + // Figure out if we're in water + physics_state.in_fluid = + collision_with(pos.0, &|block| block.is_fluid(), near_iter.clone()); + }, + Collider::Point => { + let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta).ignore_error().cast(); + + pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist; + + // Can't fair since we do ignore_error above + if block.unwrap().is_some() { + let block_center = pos.0.map(|e| e.floor()) + 0.5; + let block_rpos = (pos.0 - block_center) + .try_normalized() + .unwrap_or(Vec3::zero()); + + // See whether we're on the top/bottom of a block, or the side + if block_rpos.z.abs() + > block_rpos.xy().map(|e| e.abs()).reduce_partial_max() + { + if block_rpos.z > 0.0 { + physics_state.on_ground = true; + } else { + physics_state.on_ceiling = true; + } + vel.0.z = 0.0; + } else { + physics_state.on_wall = + Some(if block_rpos.x.abs() > block_rpos.y.abs() { + vel.0.x = 0.0; + Vec3::unit_x() * -block_rpos.x.signum() + } else { + vel.0.y = 0.0; + Vec3::unit_y() * -block_rpos.y.signum() + }); + } + } + }, } - if on_ground { - physics_state.on_ground = true; - // If the space below us is free, then "snap" to the ground - } else if collision_with( - pos.0 - Vec3::unit_z() * 1.05, - &|block| block.is_solid(), - near_iter.clone(), - ) && vel.0.z < 0.0 - && vel.0.z > -1.5 - && was_on_ground - && !collision_with( - pos.0 - Vec3::unit_z() * 0.05, - &|block| { - block.is_solid() && block.get_height() >= (pos.0.z - 0.05).rem_euclid(1.0) - }, - near_iter.clone(), - ) - { - let snap_height = terrain - .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.05).map(|e| e.floor() as i32)) - .ok() - .filter(|block| block.is_solid()) - .map(|block| block.get_height()) - .unwrap_or(0.0); - pos.0.z = (pos.0.z - 0.05).floor() + snap_height; - physics_state.on_ground = true; - } - - let dirs = [ - Vec3::unit_x(), - Vec3::unit_y(), - -Vec3::unit_x(), - -Vec3::unit_y(), - ]; - - if let (wall_dir, true) = dirs.iter().fold((Vec3::zero(), false), |(a, hit), dir| { - if collision_with( - pos.0 + *dir * 0.01, - &|block| block.is_solid(), - near_iter.clone(), - ) { - (a + dir, true) - } else { - (a, hit) - } - }) { - physics_state.on_wall = Some(wall_dir); - } else { - physics_state.on_wall = None; - } - - // Figure out if we're in water - physics_state.in_fluid = - collision_with(pos.0, &|block| block.is_fluid(), near_iter.clone()); - let _ = physics_states.insert(entity, physics_state); } @@ -369,7 +433,7 @@ impl<'a> System<'a> for Sys { scales.maybe(), masses.maybe(), &mut velocities, - &bodies, + &colliders, !&mountings, stickies.maybe(), &mut physics_states, @@ -389,7 +453,7 @@ impl<'a> System<'a> for Sys { &positions, scales.maybe(), masses.maybe(), - &bodies, + &colliders, !&mountings, ) .join() diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index bf39c3ed02..dc7c8212a6 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -57,28 +57,9 @@ impl<'a> System<'a> for Sys { ) .join() { - // Hit ground - if physics.on_ground { - for effect in projectile.hit_ground.drain(..) { - match effect { - projectile::Effect::Explode { power } => { - server_emitter.emit(ServerEvent::Explosion { - pos: pos.0, - power, - owner: projectile.owner, - }) - }, - projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy { - entity, - cause: HealthSource::World, - }), - _ => {}, - } - } - } - // Hit wall - else if physics.on_wall.is_some() { - for effect in projectile.hit_wall.drain(..) { + // Hit something solid + if physics.on_wall.is_some() || physics.on_ground || physics.on_ceiling { + for effect in projectile.hit_solid.drain(..) { match effect { projectile::Effect::Explode { power } => { server_emitter.emit(ServerEvent::Explosion { diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index b5c178a23a..519fdcb44b 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -92,11 +92,21 @@ impl StateExt for State { .with(pos) .with(comp::Vel(Vec3::zero())) .with(comp::Ori::default()) + .with(comp::Collider::Box { + radius: 0.4, + z_min: 0.0, + z_max: 1.75, + }) .with(comp::Controller::default()) .with(body) .with(stats) .with(comp::Alignment::Npc) .with(comp::Energy::new(500)) + .with(comp::Collider::Box { + radius: 0.4, + z_min: 0.0, + z_max: 1.75, + }) .with(comp::Gravity(1.0)) .with(comp::CharacterState::default()) .with(loadout) @@ -111,6 +121,11 @@ impl StateExt for State { .with(comp::Ori::default()) .with(comp::Body::Object(object)) .with(comp::Mass(100.0)) + .with(comp::Collider::Box { + radius: 0.4, + z_min: 0.0, + z_max: 0.9, + }) .with(comp::Gravity(1.0)) //.with(comp::LightEmitter::default()) } @@ -129,6 +144,7 @@ impl StateExt for State { .with(vel) .with(comp::Ori(Dir::from_unnormalized(vel.0).unwrap_or_default())) .with(comp::Mass(0.0)) + .with(comp::Collider::Point) .with(body) .with(projectile) .with(comp::Sticky) @@ -154,6 +170,11 @@ impl StateExt for State { self.write_component(entity, comp::Pos(spawn_point)); self.write_component(entity, comp::Vel(Vec3::zero())); self.write_component(entity, comp::Ori::default()); + self.write_component(entity, comp::Collider::Box { + radius: 0.4, + z_min: 0.0, + z_max: 1.75, + }); self.write_component(entity, comp::Gravity(1.0)); self.write_component(entity, comp::CharacterState::default()); self.write_component(entity, comp::Alignment::Owned(entity)); diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 9b388e61cd..3c90aec221 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -1,8 +1,8 @@ use super::SysTimer; use common::{ comp::{ - Body, CanBuild, CharacterState, Energy, Gravity, Item, LightEmitter, Loadout, Mass, - MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel, + Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter, Loadout, + Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel, }, msg::EcsCompPacket, sync::{CompSyncPackage, EntityPackage, EntitySyncPackage, Uid, UpdateTracker, WorldSyncExt}, @@ -49,6 +49,7 @@ pub struct TrackedComps<'a> { pub mounting: ReadStorage<'a, Mounting>, pub mount_state: ReadStorage<'a, MountState>, pub mass: ReadStorage<'a, Mass>, + pub collider: ReadStorage<'a, Collider>, pub sticky: ReadStorage<'a, Sticky>, pub gravity: ReadStorage<'a, Gravity>, pub loadout: ReadStorage<'a, Loadout>, @@ -104,6 +105,10 @@ impl<'a> TrackedComps<'a> { .cloned() .map(|c| comps.push(c.into())); self.mass.get(entity).copied().map(|c| comps.push(c.into())); + self.collider + .get(entity) + .copied() + .map(|c| comps.push(c.into())); self.sticky .get(entity) .copied() @@ -142,6 +147,7 @@ pub struct ReadTrackers<'a> { pub mounting: ReadExpect<'a, UpdateTracker>, pub mount_state: ReadExpect<'a, UpdateTracker>, pub mass: ReadExpect<'a, UpdateTracker>, + pub collider: ReadExpect<'a, UpdateTracker>, pub sticky: ReadExpect<'a, UpdateTracker>, pub gravity: ReadExpect<'a, UpdateTracker>, pub loadout: ReadExpect<'a, UpdateTracker>, @@ -173,6 +179,7 @@ impl<'a> ReadTrackers<'a> { .with_component(&comps.uid, &*self.mounting, &comps.mounting, filter) .with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter) .with_component(&comps.uid, &*self.mass, &comps.mass, filter) + .with_component(&comps.uid, &*self.collider, &comps.collider, filter) .with_component(&comps.uid, &*self.sticky, &comps.sticky, filter) .with_component(&comps.uid, &*self.gravity, &comps.gravity, filter) .with_component(&comps.uid, &*self.loadout, &comps.loadout, filter) @@ -201,6 +208,7 @@ pub struct WriteTrackers<'a> { mounting: WriteExpect<'a, UpdateTracker>, mount_state: WriteExpect<'a, UpdateTracker>, mass: WriteExpect<'a, UpdateTracker>, + collider: WriteExpect<'a, UpdateTracker>, sticky: WriteExpect<'a, UpdateTracker>, gravity: WriteExpect<'a, UpdateTracker>, loadout: WriteExpect<'a, UpdateTracker>, @@ -221,6 +229,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { trackers.mounting.record_changes(&comps.mounting); trackers.mount_state.record_changes(&comps.mount_state); trackers.mass.record_changes(&comps.mass); + trackers.collider.record_changes(&comps.collider); trackers.sticky.record_changes(&comps.sticky); trackers.gravity.record_changes(&comps.gravity); trackers.loadout.record_changes(&comps.loadout); @@ -242,6 +251,7 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); + world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); diff --git a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs index 6d7d8325ba..f8b6b91187 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs @@ -88,6 +88,7 @@ fn maps_idle() { &CharacterState::Idle {}, &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -111,6 +112,7 @@ fn maps_run_with_sufficient_velocity() { &CharacterState::Idle {}, &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -134,6 +136,7 @@ fn does_not_map_run_with_insufficient_velocity() { &CharacterState::Idle {}, &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -157,6 +160,7 @@ fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() { &CharacterState::Idle {}, &PhysicsState { on_ground: false, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -183,6 +187,7 @@ fn maps_roll() { }), &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -206,6 +211,7 @@ fn maps_land_on_ground_to_run() { &CharacterState::Idle {}, &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -229,6 +235,7 @@ fn maps_glider_open() { &CharacterState::Glide {}, &PhysicsState { on_ground: false, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -252,6 +259,7 @@ fn maps_glide() { &CharacterState::Glide {}, &PhysicsState { on_ground: false, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -275,6 +283,7 @@ fn maps_glider_close_when_closing_mid_flight() { &CharacterState::Idle {}, &PhysicsState { on_ground: false, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -299,6 +308,7 @@ fn maps_glider_close_when_landing() { &CharacterState::Idle {}, &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -335,6 +345,7 @@ fn maps_wield_while_equipping() { }), &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -369,6 +380,7 @@ fn maps_unwield() { &CharacterState::default(), &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false, @@ -391,6 +403,7 @@ fn maps_quadrupeds_running() { let result = MovementEventMapper::map_non_humanoid_movement_event( &PhysicsState { on_ground: true, + on_ceiling: false, on_wall: None, touch_entity: None, in_fluid: false,