Remove write to Body from Buff.

Also fix tests and add an inner PhysicsState type that is Copy, to make
it easier to facilitate reading Last<PhysicsState> and read deltas.
This commit is contained in:
Joshua Yanovski 2022-09-28 13:46:39 -07:00
parent 8383a72818
commit 338b377ef4
39 changed files with 185 additions and 128 deletions

View File

@ -655,7 +655,7 @@ impl CharacterAbility {
pub fn requirements_paid(&self, data: &JoinData, update: &mut StateUpdate) -> bool {
match self {
CharacterAbility::Roll { energy_cost, .. } => {
data.physics.on_ground.is_some()
data.physics.state.on_ground.is_some()
&& data.inputs.move_dir.magnitude_squared() > 0.25
&& update.energy.try_change_by(-*energy_cost).is_ok()
},

View File

@ -100,8 +100,8 @@ pub use self::{
ori::Ori,
pet::Pet,
phys::{
Collider, Density, ForceUpdate, Immovable, Mass, PhysicsState, Pos, PosVelOriDefer,
PreviousPhysCache, Scale, Sticky, Vel,
Collider, Density, ForceUpdate, Immovable, Mass, PhysicsState, PhysicsStateFast, Pos,
PosVelOriDefer, PreviousPhysCache, Scale, Sticky, Vel,
},
player::DisconnectReason,
player::{AliasError, Player, MAX_ALIAS_LEN},

View File

@ -170,13 +170,13 @@ impl Component for Immovable {
type Storage = DerefFlaggedStorage<Self, NullStorage<Self>>;
}
// PhysicsState
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PhysicsState {
/// Cheaply copyable components of PhysicsState used for most delta
/// computations.
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PhysicsStateFast {
pub on_ground: Option<Block>,
pub on_ceiling: bool,
pub on_wall: Option<Vec3<f32>>,
pub touch_entities: HashSet<Uid>,
pub in_fluid: Option<Fluid>,
pub ground_vel: Vec3<f32>,
pub footwear: Friction,
@ -184,6 +184,13 @@ pub struct PhysicsState {
pub skating_active: bool,
}
// PhysicsState
#[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PhysicsState {
pub state: PhysicsStateFast,
pub touch_entities: HashSet<Uid>,
}
impl PhysicsState {
pub fn reset(&mut self) {
// Avoid allocation overhead!
@ -191,12 +198,17 @@ impl PhysicsState {
touch_entities.clear();
*self = Self {
touch_entities,
ground_vel: self.ground_vel, /* Preserved, since it's the velocity of the last
* contact point */
..Self::default()
state: PhysicsStateFast {
ground_vel: self.state.ground_vel, /* Preserved, since it's the velocity of the
* last
* contact point */
..Default::default()
},
}
}
}
impl PhysicsStateFast {
pub fn on_surface(&self) -> Option<Vec3<f32>> {
self.on_ground
.map(|_| -Vec3::unit_z())

View File

@ -226,6 +226,11 @@ pub enum ServerEvent {
entity: EcsEntity,
update: comp::MapMarkerChange,
},
/// FIXME: Remove this hack! It is only used for dousing flames.
UpdateBody {
entity: EcsEntity,
new_body: comp::Body,
},
}
pub struct EventBus<E> {

View File

@ -164,14 +164,14 @@ impl CharacterBehavior for Data {
.prerotated(pitch)
};
// Velocity relative to the current ground
let rel_vel = data.vel.0 - data.physics.ground_vel;
let rel_vel = data.vel.0 - data.physics.state.ground_vel;
// Gets offsets
let body_offsets = beam_offsets(
data.body,
data.inputs.look_dir,
data.ori.look_vec(),
rel_vel,
data.physics.on_ground,
data.physics.state.on_ground,
);
let pos = Pos(data.pos.0 + body_offsets);

View File

@ -60,9 +60,9 @@ impl CharacterBehavior for Data {
// If no wall is in front of character or we stopped climbing;
let (wall_dir, climb) = if let (Some(wall_dir), Some(climb), None) = (
data.physics.on_wall,
data.physics.state.on_wall,
data.inputs.climb,
data.physics.on_ground,
data.physics.state.on_ground,
) {
(wall_dir, climb)
} else {
@ -102,7 +102,7 @@ impl CharacterBehavior for Data {
// Smooth orientation
data.ori.slerped_towards(
Ori::from(ori_dir),
if data.physics.on_ground.is_some() {
if data.physics.state.on_ground.is_some() {
9.0
} else {
2.0

View File

@ -19,7 +19,7 @@ impl CharacterBehavior for Data {
handle_jump(data, output_events, &mut update, 1.0);
// Try to Fall/Stand up/Move
if data.physics.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 {
if data.physics.state.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 {
update.character = CharacterState::Idle(idle::Data::default());
}

View File

@ -79,11 +79,11 @@ impl CharacterBehavior for Data {
let mut update = StateUpdate::from(data);
// If player is on ground, end glide
if data.physics.on_ground.is_some()
&& (data.vel.0 - data.physics.ground_vel).magnitude_squared() < 2_f32.powi(2)
if data.physics.state.on_ground.is_some()
&& (data.vel.0 - data.physics.state.ground_vel).magnitude_squared() < 2_f32.powi(2)
{
update.character = CharacterState::GlideWield(glide_wield::Data::from(data));
} else if data.physics.in_liquid().is_some()
} else if data.physics.state.in_liquid().is_some()
|| data
.inventory
.and_then(|inv| inv.equipped(EquipSlot::Glider))
@ -93,6 +93,7 @@ impl CharacterBehavior for Data {
} else if !handle_climb(data, &mut update) {
let air_flow = data
.physics
.state
.in_fluid
.map(|fluid| fluid.relative_flow(data.vel))
.unwrap_or_default();
@ -171,7 +172,7 @@ impl CharacterBehavior for Data {
Quaternion::rotation_3d(
PI / 2.0
* accel_factor
* if data.physics.on_ground.is_some() {
* if data.physics.state.on_ground.is_some() {
-1.0
} else {
1.0

View File

@ -49,7 +49,7 @@ impl CharacterBehavior for Data {
// If still in this state, do the things
if matches!(update.character, CharacterState::GlideWield(_)) {
// If not on the ground while wielding glider enter gliding state
update.character = if data.physics.on_ground.is_none() {
update.character = if data.physics.state.on_ground.is_none() {
CharacterState::Glide(glide::Data::new(
self.span_length,
self.chord_length,
@ -60,7 +60,7 @@ impl CharacterBehavior for Data {
.inventory
.and_then(|inv| inv.equipped(EquipSlot::Glider))
.is_some()
&& data.physics.in_liquid().map_or(true, |depth| depth < 0.5)
&& data.physics.state.in_liquid().map_or(true, |depth| depth < 0.5)
{
CharacterState::GlideWield(Self {
// Glider tilt follows look dir

View File

@ -30,7 +30,7 @@ impl CharacterBehavior for Data {
// Try to Fall/Stand up/Move
if self.is_sneaking
&& (data.physics.on_ground.is_none() || data.physics.in_liquid().is_some())
&& (data.physics.state.on_ground.is_none() || data.physics.state.in_liquid().is_some())
{
update.character = CharacterState::Idle(Data {
is_sneaking: false,

View File

@ -90,7 +90,7 @@ impl CharacterBehavior for Data {
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
} else if data.physics.on_ground.is_some() | data.physics.in_liquid().is_some() {
} else if data.physics.state.on_ground.is_some() | data.physics.state.in_liquid().is_some() {
// Transitions to swing portion of state upon hitting ground
update.character = CharacterState::LeapMelee(Data {
timer: Duration::default(),

View File

@ -19,7 +19,7 @@ impl CharacterBehavior for Data {
handle_jump(data, output_events, &mut update, 1.0);
// Try to Fall/Stand up/Move
if data.physics.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 {
if data.physics.state.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 {
update.character = CharacterState::Idle(idle::Data::default());
}

View File

@ -39,7 +39,7 @@ impl CharacterBehavior for Data {
handle_wield(data, &mut update);
handle_jump(data, output_events, &mut update, 1.0);
if !data.physics.skating_active {
if !data.physics.state.skating_active {
update.character = CharacterState::Idle(idle::Data {
is_sneaking: false,
footwear: Some(self.footwear),

View File

@ -62,7 +62,7 @@ impl CharacterBehavior for Data {
match self.static_data.movement_behavior {
MovementBehavior::ForwardGround | MovementBehavior::Stationary => {},
MovementBehavior::AxeHover => {
update.movement = update.movement.with_movement(if data.physics.on_ground.is_some() {
update.movement = update.movement.with_movement(if data.physics.state.on_ground.is_some() {
// TODO: Just remove axehover entirely with axe rework, it's really janky
// TODO: Should 5 even be used here, or should body accel be used? Maybe just call handle_move?
let dir = Dir::from_unnormalized(data.inputs.move_dir.with_z(0.0));

View File

@ -327,7 +327,7 @@ pub fn handle_skating(data: &JoinData, update: &mut StateUpdate) {
footwear,
});
}
if data.physics.skating_active {
if data.physics.state.skating_active {
update.character =
CharacterState::Skate(skate::Data::new(data, footwear.unwrap_or(Friction::Normal)));
}
@ -338,16 +338,17 @@ pub fn handle_skating(data: &JoinData, update: &mut StateUpdate) {
pub fn handle_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
let submersion = data
.physics
.state
.in_liquid()
.map(|depth| depth / data.body.height());
if input_is_pressed(data, InputKind::Fly)
&& submersion.map_or(true, |sub| sub < 1.0)
&& (data.physics.on_ground.is_none() || data.body.jump_impulse().is_none())
&& (data.physics.state.on_ground.is_none() || data.body.jump_impulse().is_none())
&& data.body.fly_thrust().is_some()
{
fly_move(data, update, efficiency);
} else if let Some(submersion) = (data.physics.on_ground.is_none()
} else if let Some(submersion) = (data.physics.state.on_ground.is_none()
&& data.body.swim_thrust().is_some())
.then_some(submersion)
.flatten()
@ -362,7 +363,7 @@ pub fn handle_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f3
fn basic_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
let accel = if let Some(block) = data.physics.on_ground {
let accel = if let Some(block) = data.physics.state.on_ground {
// FRIC_GROUND temporarily used to normalize things around expected values
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
} else {
@ -409,7 +410,7 @@ pub fn handle_forced_movement(
match movement {
ForcedMovement::Forward { strength } => {
let strength = strength * data.stats.move_speed_modifier * data.stats.friction_modifier;
if let Some(accel) = data.physics.on_ground.map(|block| {
if let Some(accel) = data.physics.state.on_ground.map(|block| {
// FRIC_GROUND temporarily used to normalize things around expected values
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
}) {
@ -474,7 +475,7 @@ pub fn handle_orientation(
// unit is multiples of 180°
let half_turns_per_tick = data.body.base_ori_rate()
* efficiency
* if data.physics.on_ground.is_some() {
* if data.physics.state.on_ground.is_some() {
1.0
} else {
0.2
@ -579,7 +580,7 @@ pub fn fly_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32)
Density((update.density.0 + data.dt.0 * rate * change).clamp(min, max))
};
let def_density = ship.density().0;
if data.physics.in_liquid().is_some() {
if data.physics.state.in_liquid().is_some() {
let hull_density = ship.hull_density().0;
update.density.0 =
regulate_density(def_density * 0.6, hull_density, hull_density, 25.0).0;
@ -660,25 +661,25 @@ pub fn attempt_wield(data: &JoinData<'_>, update: &mut StateUpdate) {
/// Checks that player can `Sit` and updates `CharacterState` if so
pub fn attempt_sit(data: &JoinData<'_>, update: &mut StateUpdate) {
if data.physics.on_ground.is_some() {
if data.physics.state.on_ground.is_some() {
update.character = CharacterState::Sit;
}
}
pub fn attempt_dance(data: &JoinData<'_>, update: &mut StateUpdate) {
if data.physics.on_ground.is_some() && data.body.is_humanoid() {
if data.physics.state.on_ground.is_some() && data.body.is_humanoid() {
update.character = CharacterState::Dance;
}
}
pub fn attempt_talk(data: &JoinData<'_>, update: &mut StateUpdate) {
if data.physics.on_ground.is_some() {
if data.physics.state.on_ground.is_some() {
update.character = CharacterState::Talk;
}
}
pub fn attempt_sneak(data: &JoinData<'_>, update: &mut StateUpdate) {
if data.physics.on_ground.is_some() && data.body.is_humanoid() {
if data.physics.state.on_ground.is_some() && data.body.is_humanoid() {
update.character = Idle(idle::Data {
is_sneaking: true,
footwear: data.character.footwear(),
@ -689,10 +690,11 @@ pub fn attempt_sneak(data: &JoinData<'_>, update: &mut StateUpdate) {
/// Checks that player can `Climb` and updates `CharacterState` if so
pub fn handle_climb(data: &JoinData<'_>, update: &mut StateUpdate) -> bool {
if data.inputs.climb.is_some()
&& data.physics.on_wall.is_some()
&& data.physics.on_ground.is_none()
&& data.physics.state.on_wall.is_some()
&& data.physics.state.on_ground.is_none()
&& !data
.physics
.state
.in_liquid()
.map(|depth| depth > 1.0)
.unwrap_or(false)
@ -708,9 +710,9 @@ pub fn handle_climb(data: &JoinData<'_>, update: &mut StateUpdate) -> bool {
}
pub fn handle_wallrun(data: &JoinData<'_>, update: &mut StateUpdate) -> bool {
if data.physics.on_wall.is_some()
&& data.physics.on_ground.is_none()
&& data.physics.in_liquid().is_none()
if data.physics.state.on_wall.is_some()
&& data.physics.state.on_ground.is_none()
&& data.physics.state.in_liquid().is_none()
&& data.body.can_climb()
{
update.character = CharacterState::Wallrun(wallrun::Data);
@ -895,6 +897,7 @@ pub fn attempt_glide_wield(
.is_some()
&& !data
.physics
.state
.in_liquid()
.map(|depth| depth > 1.0)
.unwrap_or(false)
@ -916,7 +919,7 @@ pub fn handle_jump(
_update: &mut StateUpdate,
strength: f32,
) -> bool {
(input_is_pressed(data, InputKind::Jump) && data.physics.on_ground.is_some())
(input_is_pressed(data, InputKind::Jump) && data.physics.state.on_ground.is_some())
.then(|| data.body.jump_impulse())
.flatten()
.map(|impulse| {

View File

@ -33,9 +33,9 @@ impl CharacterBehavior for Data {
// fall off wall, hit ground, or enter water
// TODO: Rugged way to determine when state change occurs and we need to leave
// this state
if data.physics.on_wall.is_none()
|| data.physics.on_ground.is_some()
|| data.physics.in_liquid().is_some()
if data.physics.state.on_wall.is_none()
|| data.physics.state.on_ground.is_some()
|| data.physics.state.in_liquid().is_some()
{
update.character = CharacterState::Idle(idle::Data::default());
}

View File

@ -28,7 +28,7 @@ impl CharacterBehavior for Data {
handle_jump(data, output_events, &mut update, 1.0);
if self.is_sneaking
&& (data.physics.on_ground.is_none() || data.physics.in_liquid().is_some())
&& (data.physics.state.on_ground.is_none() || data.physics.state.in_liquid().is_some())
{
update.character = CharacterState::Wielding(Data { is_sneaking: false });
}
@ -96,7 +96,7 @@ impl CharacterBehavior for Data {
fn sneak(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
if data.physics.on_ground.is_some() && data.body.is_humanoid() {
if data.physics.state.on_ground.is_some() && data.body.is_humanoid() {
update.character = CharacterState::Wielding(Data { is_sneaking: true });
}
update

View File

@ -575,7 +575,8 @@ impl State {
match event {
LocalEvent::Jump(entity, impulse) => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z);
vel.0.z =
impulse + physics.get(entity).map_or(0.0, |ps| ps.state.ground_vel.z);
}
},
LocalEvent::ApplyImpulse { entity, impulse } => {

View File

@ -31,6 +31,7 @@ pub struct ReadData<'a> {
entities: Entities<'a>,
dt: Read<'a, DeltaTime>,
server_bus: Read<'a, EventBus<ServerEvent>>,
bodies: ReadStorage<'a, Body>,
inventories: ReadStorage<'a, Inventory>,
healths: ReadStorage<'a, Health>,
energies: ReadStorage<'a, Energy>,
@ -48,7 +49,6 @@ impl<'a> System<'a> for Sys {
ReadData<'a>,
WriteStorage<'a, Buffs>,
WriteStorage<'a, Stats>,
WriteStorage<'a, Body>,
WriteStorage<'a, LightEmitter>,
);
@ -58,7 +58,7 @@ impl<'a> System<'a> for Sys {
fn run(
job: &mut Job<Self>,
(read_data, mut buffs, mut stats, mut bodies, mut light_emitters): Self::SystemData,
(read_data, mut buffs, mut stats, mut light_emitters): Self::SystemData,
) {
let mut server_emitter = read_data.server_bus.emitter();
let dt = read_data.dt.0;
@ -74,7 +74,7 @@ impl<'a> System<'a> for Sys {
prof_span!(guard_, "buff campfire deactivate");
(
&read_data.entities,
&mut bodies,
&read_data.bodies,
&read_data.physics_states,
light_emitters_mask, //to improve iteration speed
)
@ -82,16 +82,19 @@ impl<'a> System<'a> for Sys {
.filter(|(_, body, physics_state, _)| {
matches!(&**body, Body::Object(object::Body::CampfireLit))
&& matches!(
physics_state.in_fluid,
physics_state.state.in_fluid,
Some(Fluid::Liquid {
kind: LiquidKind::Water,
..
})
)
})
.for_each(|(e, mut body, _, _)| {
*body = Body::Object(object::Body::Campfire);
light_emitters.remove(e);
.for_each(|(entity, _, _, _)| {
server_emitter.emit(ServerEvent::UpdateBody {
entity,
new_body: Body::Object(object::Body::Campfire),
});
light_emitters.remove(entity);
});
drop(guard_);
@ -108,7 +111,7 @@ impl<'a> System<'a> for Sys {
// Apply buffs to entity based off of their current physics_state
if let Some(physics_state) = physics_state {
if matches!(
physics_state.on_ground.and_then(|b| b.get_sprite()),
physics_state.state.on_ground.and_then(|b| b.get_sprite()),
Some(SpriteKind::EnsnaringVines) | Some(SpriteKind::EnsnaringWeb)
) {
// If on ensnaring vines, apply ensnared debuff
@ -123,7 +126,7 @@ impl<'a> System<'a> for Sys {
});
}
if matches!(
physics_state.on_ground.and_then(|b| b.get_sprite()),
physics_state.state.on_ground.and_then(|b| b.get_sprite()),
Some(SpriteKind::SeaUrchin)
) {
// If touching Sea Urchin apply Bleeding buff
@ -138,7 +141,7 @@ impl<'a> System<'a> for Sys {
});
}
if matches!(
physics_state.in_fluid,
physics_state.state.in_fluid,
Some(Fluid::Liquid {
kind: LiquidKind::Lava,
..
@ -155,7 +158,7 @@ impl<'a> System<'a> for Sys {
)),
});
} else if matches!(
physics_state.in_fluid,
physics_state.state.in_fluid,
Some(Fluid::Liquid {
kind: LiquidKind::Water,
..

View File

@ -4,7 +4,8 @@ use common::{
fluid_dynamics::{Fluid, LiquidKind, Wings},
inventory::item::armor::Friction,
Body, CharacterState, Collider, Density, Immovable, Mass, MovementState, Ori, PhysicsState,
Pos, PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
PhysicsStateFast, Pos, PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky,
Vel,
},
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent},
@ -388,7 +389,7 @@ impl<'a> PhysicsRead<'a> {
)| {
let is_sticky = sticky.is_some();
let is_immovable = immovable.is_some();
let is_mid_air = physics.on_surface().is_none();
let is_mid_air = physics.state.on_surface().is_none();
let mut entity_entity_collision_checks = 0;
let mut entity_entity_collisions = 0;
@ -667,7 +668,7 @@ impl<'a> PhysicsData<'a> {
// Apply physics only if in a loaded chunk
if in_loaded_chunk
// And not already stuck on a block (e.g., for arrows)
&& !(physics_state.on_surface().is_some() && sticky.is_some())
&& !(physics_state.state.on_surface().is_some() && sticky.is_some())
{
// Clamp dt to an effective 10 TPS, to prevent gravity
// from slamming the players into the floor when
@ -675,7 +676,7 @@ impl<'a> PhysicsData<'a> {
// to lag (as observed in the 0.9 release party).
let dt = DeltaTime(read.dt.0.min(0.1));
match physics_state.in_fluid {
match physics_state.state.in_fluid {
None => {
vel.0.z -= dt.0 * GRAVITY;
},
@ -814,8 +815,8 @@ impl<'a> PhysicsData<'a> {
if let Some(state) = character_state {
let footwear = state.footwear().unwrap_or(Friction::Normal);
if footwear != physics_state.footwear {
physics_state.footwear = footwear;
if footwear != physics_state.state.footwear {
physics_state.state.footwear = footwear;
}
}
@ -849,7 +850,7 @@ impl<'a> PhysicsData<'a> {
// velocities or entirely broken position snapping.
let mut tgt_pos = pos.0 + pos_delta;
let was_on_ground = physics_state.on_ground.is_some();
let was_on_ground = physics_state.state.on_ground.is_some();
let block_snap =
body.map_or(false, |b| !matches!(b, Body::Object(_) | Body::Ship(_)));
let climbing =
@ -876,7 +877,7 @@ impl<'a> PhysicsData<'a> {
&mut cpos,
tgt_pos,
&mut vel,
physics_state,
&mut physics_state.state,
Vec3::zero(),
&read.dt,
was_on_ground,
@ -909,7 +910,7 @@ impl<'a> PhysicsData<'a> {
&mut cpos,
tgt_pos,
&mut vel,
physics_state,
&mut physics_state.state,
Vec3::zero(),
&read.dt,
was_on_ground,
@ -921,8 +922,8 @@ impl<'a> PhysicsData<'a> {
);
// Sticky things shouldn't move when on a surface
if physics_state.on_surface().is_some() && sticky.is_some() {
vel.0 = physics_state.ground_vel;
if physics_state.state.on_surface().is_some() && sticky.is_some() {
vel.0 = physics_state.state.ground_vel;
}
tgt_pos = cpos.0;
@ -982,13 +983,13 @@ impl<'a> PhysicsData<'a> {
> block_rpos.xy().map(|e| e.abs()).reduce_partial_max()
{
if block_rpos.z > 0.0 {
physics_state.on_ground = block.copied();
physics_state.state.on_ground = block.copied();
} else {
physics_state.on_ceiling = true;
physics_state.state.on_ceiling = true;
}
vel.0.z = 0.0;
} else {
physics_state.on_wall =
physics_state.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()
@ -1000,11 +1001,11 @@ impl<'a> PhysicsData<'a> {
// Sticky things shouldn't move
if sticky.is_some() {
vel.0 = physics_state.ground_vel;
vel.0 = physics_state.state.ground_vel;
}
}
physics_state.in_fluid = read
physics_state.state.in_fluid = read
.terrain
.get(pos.0.map(|e| e.floor() as i32))
.ok()
@ -1015,7 +1016,7 @@ impl<'a> PhysicsData<'a> {
vel: Vel::zero(),
})
})
.or_else(|| match physics_state.in_fluid {
.or_else(|| match physics_state.state.in_fluid {
Some(Fluid::Liquid { .. }) | None => Some(Fluid::Air {
elevation: pos.0.z,
vel: Vel::default(),
@ -1113,7 +1114,7 @@ impl<'a> PhysicsData<'a> {
return;
}
let mut physics_state_delta = physics_state.clone();
let mut physics_state_delta = physics_state.state;
// deliberately don't use scale yet here, because the
// 11.0/0.8 thing is
// in the comp::Scale for visual reasons
@ -1191,7 +1192,7 @@ impl<'a> PhysicsData<'a> {
// based on the most
// recent terrain that collision was attempted with
if physics_state_delta.on_ground.is_some() {
physics_state.ground_vel = vel_other;
physics_state.state.ground_vel = vel_other;
// Rotate if on ground
ori = ori.rotated(
@ -1199,16 +1200,16 @@ impl<'a> PhysicsData<'a> {
* previous_cache_other.ori.inverse(),
);
}
physics_state.on_ground =
physics_state.on_ground.or(physics_state_delta.on_ground);
physics_state.on_ceiling |= physics_state_delta.on_ceiling;
physics_state.on_wall = physics_state.on_wall.or_else(|| {
physics_state.state.on_ground =
physics_state.state.on_ground.or(physics_state_delta.on_ground);
physics_state.state.on_ceiling |= physics_state_delta.on_ceiling;
physics_state.state.on_wall = physics_state.state.on_wall.or_else(|| {
physics_state_delta
.on_wall
.map(|dir| ori_from.mul_direction(dir))
});
physics_state.in_fluid = match (
physics_state.in_fluid,
physics_state.state.in_fluid = match (
physics_state.state.in_fluid,
physics_state_delta.in_fluid,
) {
(Some(x), Some(y)) => x
@ -1467,7 +1468,7 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
pos: &mut Pos,
tgt_pos: Vec3<f32>,
vel: &mut Vel,
physics_state: &mut PhysicsState,
physics_state: &mut PhysicsStateFast,
ground_vel: Vec3<f32>,
dt: &DeltaTime,
was_on_ground: bool,

View File

@ -86,7 +86,7 @@ impl<'a> System<'a> for Sys {
.and_then(|uid| read_data.uid_allocator.retrieve_entity_internal(uid.into()));
let mut rng = thread_rng();
if physics.on_surface().is_none() && rng.gen_bool(0.05) {
if physics.state.on_surface().is_none() && rng.gen_bool(0.05) {
server_emitter.emit(ServerEvent::Sound {
sound: Sound::new(SoundKind::Projectile, pos.0, 4.0, read_data.time.0),
});
@ -162,7 +162,7 @@ impl<'a> System<'a> for Sys {
}
}
if physics.on_surface().is_some() {
if physics.state.on_surface().is_some() {
let projectile_write = &mut *projectile_write;
for effect in projectile_write.hit_solid.drain(..) {
match effect {

View File

@ -182,7 +182,7 @@ impl<'a> System<'a> for Sys {
arc_strip.collides_with_circle(Disk::new(pos_b2, rad_b))
}
&& (pos_b_ground - pos.0).angle_between(pos_b.0 - pos.0) < max_angle
&& (!shockwave.requires_ground || physics_state_b.on_ground.is_some());
&& (!shockwave.requires_ground || physics_state_b.state.on_ground.is_some());
if hit {
let dir = Dir::from_unnormalized(pos_b.0 - pos.0).unwrap_or(look_dir);

View File

@ -23,6 +23,7 @@ const POISE_REGEN_ACCEL: f32 = 2.0;
#[derive(SystemData)]
pub struct ReadData<'a> {
entities: Entities<'a>,
stats: ReadStorage<'a, Stats>,
dt: Read<'a, DeltaTime>,
time: Read<'a, Time>,
server_bus: Read<'a, EventBus<ServerEvent>>,
@ -39,7 +40,6 @@ pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
ReadData<'a>,
WriteStorage<'a, Stats>,
WriteStorage<'a, SkillSet>,
WriteStorage<'a, Health>,
WriteStorage<'a, Poise>,
@ -56,7 +56,6 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>,
(
read_data,
stats,
mut skill_sets,
mut healths,
mut poises,
@ -72,7 +71,7 @@ impl<'a> System<'a> for Sys {
// Update stats
for (entity, stats, mut health, pos, mut energy, inventory) in (
&read_data.entities,
&stats,
&read_data.stats,
&mut healths,
&read_data.positions,
&mut energies,

View File

@ -3,7 +3,7 @@ mod tests {
use common::{
comp::{
item::MaterialStatManifest, skills::GeneralSkill, CharacterState, Controller, Energy,
Ori, PhysicsState, Poise, Pos, Skill, Stats, Vel,
MovementState, Ori, PhysicsState, Poise, Pos, Skill, Stats, Vel,
},
resources::{DeltaTime, GameMode, Time},
terrain::{MapSizeLg, TerrainChunk},
@ -51,6 +51,7 @@ mod tests {
.ecs_mut()
.create_entity()
.with(CharacterState::Idle(common::states::idle::Data::default()))
.with(MovementState::default())
.with(Pos(Vec3::zero()))
.with(Vel::default())
.with(ori)
@ -105,11 +106,22 @@ mod tests {
Ori::from_unnormalized_vec(testcases[i].0).unwrap_or_default(),
));
}
tick(&mut state, Duration::from_secs_f32(0.033));
let results = state.ecs().read_storage::<Ori>();
let dt = 0.033;
tick(&mut state, Duration::from_secs_f32(dt));
let movement = state.ecs().read_storage::<MovementState>();
let pos = state.ecs().read_storage::<Pos>();
let vel = state.ecs().read_storage::<Vel>();
let ori = state.ecs().read_storage::<Ori>();
let dt = DeltaTime(dt);
for i in 0..TESTCASES {
if let Some(e) = entities[i] {
let result = Dir::from(*results.get(e).expect("Ori missing"));
let mut pos = *pos.get(e).expect("Pos missing");
let mut vel = *vel.get(e).expect("Vel missing");
let mut ori = *ori.get(e).expect("Ori missing");
if let Some(movement) = movement.get(e) {
movement.handle_movement(&dt, &mut pos, &mut vel, &mut ori);
}
let result = Dir::from(ori);
assert!(result.abs_diff_eq(&testcases[i].1, 0.0005));
// println!("{:?}", result);
}

View File

@ -2,11 +2,12 @@ use common::{
comp::{
inventory::item::MaterialStatManifest,
skills::{GeneralSkill, Skill},
Auras, Buffs, CharacterState, Collider, Combo, Controller, Energy, Health, Ori, Pos, Stats,
Vel,
Auras, Buffs, CharacterState, Collider, Combo, Controller, Energy, Health, MovementState,
Ori, Pos, Stats, Vel,
},
resources::{DeltaTime, GameMode, Time},
skillset_builder::SkillSetBuilder,
slowjob::SlowJobPool,
terrain::{
Block, BlockKind, MapSizeLg, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainGrid,
},
@ -42,6 +43,10 @@ pub fn setup() -> State {
Arc::new(TerrainChunk::water(0)),
);
state.ecs_mut().insert(MaterialStatManifest::with_empty());
state
.ecs_mut()
.write_resource::<SlowJobPool>()
.configure("CHUNK_DROP", |_n| 1);
state.ecs_mut().read_resource::<Time>();
state.ecs_mut().read_resource::<DeltaTime>();
for x in 0..2 {
@ -117,6 +122,7 @@ pub fn create_player(state: &mut State) -> Entity {
.with(body)
.with(Controller::default())
.with(CharacterState::default())
.with(MovementState::default())
.with(Buffs::default())
.with(Combo::default())
.with(Auras::default())

View File

@ -966,8 +966,8 @@ impl<'a> AgentData<'a> {
controller.inputs.look_dir,
self.ori.look_vec(),
// Try to match animation by getting some context
self.vel.0 - self.physics_state.ground_vel,
self.physics_state.on_ground,
self.vel.0 - self.physics_state.state.ground_vel,
self.physics_state.state.on_ground,
)
});
let aim_to = Vec3::new(

View File

@ -106,7 +106,7 @@ pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3<f32>)
if let Some(physics) = ecs.read_storage::<PhysicsState>().get(entity) {
//Check if the entity is on a surface. If it is not, reduce knockback.
let mut impulse = impulse
* if physics.on_surface().is_some() {
* if physics.state.on_surface().is_some() {
1.0
} else {
0.4
@ -1359,3 +1359,11 @@ pub fn handle_update_map_marker(
}
}
}
/// FIXME: Remove this hack! It should only be used for dousing flames from
/// campfires.
pub fn handle_update_body(server: &mut Server, entity: EcsEntity, new_body: comp::Body) {
server
.state
.write_component_ignore_entity_dead(entity, new_body);
}

View File

@ -13,7 +13,7 @@ use entity_manipulation::{
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change,
handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook,
handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground, handle_parry,
handle_poise, handle_respawn, handle_teleport_to, handle_update_map_marker,
handle_poise, handle_respawn, handle_teleport_to, handle_update_body, handle_update_map_marker,
};
use group_manip::handle_group;
use information::handle_site_info;
@ -287,6 +287,9 @@ impl Server {
ServerEvent::UpdateMapMarker { entity, update } => {
handle_update_map_marker(self, entity, update)
},
ServerEvent::UpdateBody { entity, new_body } => {
handle_update_body(self, entity, new_body)
},
}
}

View File

@ -134,7 +134,7 @@ impl<'a> System<'a> for Sys {
let is_gliding = matches!(
read_data.char_states.get(entity),
Some(CharacterState::GlideWield(_) | CharacterState::Glide(_))
) && physics_state.on_ground.is_none();
) && physics_state.state.on_ground.is_none();
if let Some(pid) = agent.position_pid_controller.as_mut() {
pid.add_measurement(read_data.time.0, pos.0);
@ -150,8 +150,8 @@ impl<'a> System<'a> for Sys {
let traversal_config = TraversalConfig {
node_tolerance,
slow_factor,
on_ground: physics_state.on_ground.is_some(),
in_liquid: physics_state.in_liquid().is_some(),
on_ground: physics_state.state.on_ground.is_some(),
in_liquid: physics_state.state.in_liquid().is_some(),
min_tgt_dist: 1.0,
can_climb: body.map_or(false, Body::can_climb),
can_fly: body.map_or(false, |b| b.fly_thrust().is_some()),

View File

@ -184,7 +184,7 @@ fn react_if_on_fire(bdata: &mut BehaviorData) -> bool {
if is_on_fire
&& bdata.agent_data.body.map_or(false, |b| b.is_humanoid())
&& bdata.agent_data.physics_state.on_ground.is_some()
&& bdata.agent_data.physics_state.state.on_ground.is_some()
&& bdata
.rng
.gen_bool((2.0 * bdata.read_data.dt.0).clamp(0.0, 1.0) as f64)

View File

@ -44,7 +44,7 @@ impl<'a> System<'a> for Sys {
{
match object {
Object::Bomb { owner } => {
if physics.on_surface().is_some() {
if physics.state.on_surface().is_some() {
server_emitter.emit(ServerEvent::Delete(entity));
server_emitter.emit(ServerEvent::Explosion {
pos: pos.0,

View File

@ -54,7 +54,7 @@ impl<'a> System<'a> for Sys {
.filter(|(_, owner_pos, owner_physics, pet_pos)| {
// Don't teleport pets to the player if they're in the air, nobody wants
// pets to go splat :(
owner_physics.on_ground.is_some()
owner_physics.state.on_ground.is_some()
&& owner_pos.0.distance_squared(pet_pos.0) > LOST_PET_DISTANCE_THRESHOLD.powi(2)
})
.map(|(entity, owner_pos, _, _)| (entity, *owner_pos))

View File

@ -55,7 +55,7 @@ impl<'a> System<'a> for Sys {
)
.join()
{
if physics.map_or(true, |ps| ps.on_ground.is_some()) && velocity.0.z >= 0.0 {
if physics.map_or(true, |ps| ps.state.on_ground.is_some()) && velocity.0.z >= 0.0 {
for (waypoint_pos, waypoint_area) in (&positions, &waypoint_areas).join() {
if player_pos.0.distance_squared(waypoint_pos.0)
< waypoint_area.radius().powi(2)

View File

@ -706,17 +706,17 @@ fn selected_entity_window(
.spacing([40.0, 4.0])
.striped(true)
.show(ui, |ui| {
two_col_row(ui, "On Ground", physics_state.on_ground.map_or("None".to_owned(), |x| format!("{:?}", x)));
two_col_row(ui, "On Ceiling", (if physics_state.on_ceiling { "True" } else { "False " }).to_string());
two_col_row(ui, "On Wall", physics_state.on_wall.map_or("-".to_owned(), |x| format!("{:.1},{:.1},{:.1}", x.x, x.y, x.z )));
two_col_row(ui, "On Ground", physics_state.state.on_ground.map_or("None".to_owned(), |x| format!("{:?}", x)));
two_col_row(ui, "On Ceiling", (if physics_state.state.on_ceiling { "True" } else { "False " }).to_string());
two_col_row(ui, "On Wall", physics_state.state.on_wall.map_or("-".to_owned(), |x| format!("{:.1},{:.1},{:.1}", x.x, x.y, x.z )));
two_col_row(ui, "Touching Entities", physics_state.touch_entities.len().to_string());
two_col_row(ui, "In Fluid", match physics_state.in_fluid {
two_col_row(ui, "In Fluid", match physics_state.state.in_fluid {
Some(Fluid::Air { elevation, .. }) => format!("Air (Elevation: {:.1})", elevation),
Some(Fluid::Liquid { depth, kind, .. }) => format!("{:?} (Depth: {:.1})", kind, depth),
_ => "None".to_owned() });
});
two_col_row(ui, "Footwear", match physics_state.footwear{ Friction::Ski => "Ski", Friction::Skate => "Skate", /* Friction::Snowshoe => "Snowshoe", Friction::Spikes => "Spikes", */ Friction::Normal=>"Normal",}.to_string());
two_col_row(ui, "Footwear", match physics_state.state.footwear{ Friction::Ski => "Ski", Friction::Skate => "Skate", /* Friction::Snowshoe => "Snowshoe", Friction::Spikes => "Spikes", */ Friction::Normal=>"Normal",}.to_string());
});
}
});

View File

@ -9,7 +9,7 @@ use crate::{
};
use client::Client;
use common::{
comp::{Body, CharacterState, PhysicsState, Pos, Vel},
comp::{Body, CharacterState, PhysicsState, PhysicsStateFast, Pos, Vel},
resources::DeltaTime,
terrain::{BlockKind, TerrainChunk},
vol::ReadVol,
@ -72,6 +72,7 @@ impl EventMapper for MovementEventMapper {
.join()
.filter(|(_, e_pos, ..)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
{
let physics = &physics.state;
if let Some(character) = character {
let internal_state = self.event_history.entry(entity).or_default();
@ -187,7 +188,7 @@ impl MovementEventMapper {
/// `SfxEvent`'s which we attach sounds to
fn map_movement_event(
character_state: &CharacterState,
physics_state: &PhysicsState,
physics_state: &PhysicsStateFast,
previous_state: &PreviousEntityState,
vel: Vec3<f32>,
underfoot_block_kind: BlockKind,
@ -228,7 +229,7 @@ impl MovementEventMapper {
/// Maps a limited set of movements for other non-humanoid entities
fn map_non_humanoid_movement_event(
physics_state: &PhysicsState,
physics_state: &PhysicsStateFast,
vel: Vec3<f32>,
underfoot_block_kind: BlockKind,
) -> SfxEvent {
@ -250,7 +251,7 @@ impl MovementEventMapper {
/// Maps a limited set of movements for quadruped entities
fn map_quadruped_movement_event(
physics_state: &PhysicsState,
physics_state: &PhysicsStateFast,
vel: Vec3<f32>,
underfoot_block_kind: BlockKind,
) -> SfxEvent {

View File

@ -3,7 +3,7 @@ use crate::audio::sfx::SfxEvent;
use common::{
comp::{
bird_large, humanoid, quadruped_medium, quadruped_small, Body, CharacterState, InputKind,
Ori, PhysicsState,
Ori, PhysicsStateFast,
},
states,
terrain::{Block, BlockKind},
@ -94,7 +94,7 @@ fn same_previous_event_elapsed_emits() {
fn maps_idle() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(states::idle::Data::default()),
&PhysicsState {
&PhysicsStateFast {
on_ground: Some(Block::empty()),
..Default::default()
},
@ -116,7 +116,7 @@ fn maps_idle() {
fn maps_run_with_sufficient_velocity() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(states::idle::Data::default()),
&PhysicsState {
&PhysicsStateFast {
on_ground: Some(Block::empty()),
..Default::default()
},
@ -138,7 +138,7 @@ fn maps_run_with_sufficient_velocity() {
fn does_not_map_run_with_insufficient_velocity() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(states::idle::Data::default()),
&PhysicsState {
&PhysicsStateFast {
on_ground: Some(Block::empty()),
..Default::default()
},
@ -193,7 +193,7 @@ fn maps_roll() {
is_sneaking: false,
was_combo: None,
}),
&PhysicsState {
&PhysicsStateFast {
on_ground: Some(Block::empty()),
..Default::default()
},
@ -215,7 +215,7 @@ fn maps_roll() {
fn maps_land_on_ground_to_run() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(states::idle::Data::default()),
&PhysicsState {
&PhysicsStateFast {
on_ground: Some(Block::empty()),
..Default::default()
},
@ -255,7 +255,7 @@ fn maps_glide() {
#[test]
fn maps_quadrupeds_running() {
let result = MovementEventMapper::map_non_humanoid_movement_event(
&PhysicsState {
&PhysicsStateFast {
on_ground: Some(Block::empty()),
..Default::default()
},

View File

@ -767,6 +767,7 @@ impl FigureMgr {
.join()
.enumerate()
{
let physics = &physics.state;
// Velocity relative to the current ground
let rel_vel = anim::vek::Vec3::<f32>::from(vel.0 - physics.ground_vel);

View File

@ -528,7 +528,7 @@ impl Scene {
let on_ground = ecs
.read_storage::<comp::PhysicsState>()
.get(scene_data.viewpoint_entity)
.map(|p| p.on_ground.is_some());
.map(|p| p.state.on_ground.is_some());
let (viewpoint_height, viewpoint_eye_height) = scene_data
.state

View File

@ -1061,6 +1061,7 @@ impl PlayState for SessionState {
let fluid = ecs
.read_storage::<comp::PhysicsState>()
.get(entity)?
.state
.in_fluid?;
ecs.read_storage::<Vel>()
.get(entity)
@ -1220,7 +1221,7 @@ impl PlayState for SessionState {
let in_fluid = ecs
.read_storage::<comp::PhysicsState>()
.get(entity)
.and_then(|state| state.in_fluid);
.and_then(|state| state.state.in_fluid);
let character_state = ecs
.read_storage::<comp::CharacterState>()
.get(entity)