Removed mutable pos/vel/ori writes from character state system. Added a movementstate component.

This commit is contained in:
Sam 2022-09-25 19:23:36 -04:00 committed by Joshua Yanovski
parent cead27989b
commit 7c5917e9a9
23 changed files with 324 additions and 174 deletions

View File

@ -40,6 +40,7 @@ use common::{
outcome::Outcome, outcome::Outcome,
recipe::{ComponentRecipeBook, RecipeBook}, recipe::{ComponentRecipeBook, RecipeBook},
resources::{GameMode, PlayerEntity, TimeOfDay}, resources::{GameMode, PlayerEntity, TimeOfDay},
slowjob::SlowJobPool,
spiral::Spiral2d, spiral::Spiral2d,
terrain::{ terrain::{
block::Block, map::MapConfig, neighbors, site::DungeonKindMeta, BiomeKind, SiteKindMeta, block::Block, map::MapConfig, neighbors, site::DungeonKindMeta, BiomeKind, SiteKindMeta,
@ -383,6 +384,10 @@ impl Client {
*state.ecs_mut().write_resource() = PlayerEntity(Some(entity)); *state.ecs_mut().write_resource() = PlayerEntity(Some(entity));
state.ecs_mut().insert(material_stats); state.ecs_mut().insert(material_stats);
state.ecs_mut().insert(ability_map); state.ecs_mut().insert(ability_map);
state
.ecs_mut()
.write_resource::<SlowJobPool>()
.configure("CHUNK_DROP", |_n| 1);
let map_size = map_size_lg.chunks(); let map_size = map_size_lg.chunks();
let max_height = world_map.max_height; let max_height = world_map.max_height;

View File

@ -53,13 +53,14 @@ macro_rules! synced_components {
// remove it from that and then see if it's used for anything else and try to move // remove it from that and then see if it's used for anything else and try to move
// to only being synced for the client's entity. // to only being synced for the client's entity.
skill_set: SkillSet, skill_set: SkillSet,
loot_owner: LootOwner,
// Synced to the client only for its own entity // Synced to the client only for its own entity
combo: Combo, combo: Combo,
active_abilities: ActiveAbilities, active_abilities: ActiveAbilities,
can_build: CanBuild, can_build: CanBuild,
loot_owner: LootOwner, movement_state: MovementState,
} }
}; };
} }
@ -215,6 +216,10 @@ impl NetSync for SkillSet {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for LootOwner {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
// These are synced only from the client's own entity. // These are synced only from the client's own entity.
impl NetSync for Combo { impl NetSync for Combo {
@ -229,6 +234,6 @@ impl NetSync for CanBuild {
const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity; const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;
} }
impl NetSync for LootOwner { impl NetSync for MovementState {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;
} }

View File

@ -675,7 +675,7 @@ impl CharacterAbility {
update.energy.current() >= *energy_cost update.energy.current() >= *energy_cost
}, },
CharacterAbility::LeapMelee { energy_cost, .. } => { CharacterAbility::LeapMelee { energy_cost, .. } => {
update.vel.0.z >= 0.0 && update.energy.try_change_by(-*energy_cost).is_ok() data.vel.0.z >= 0.0 && update.energy.try_change_by(-*energy_cost).is_ok()
}, },
CharacterAbility::BasicAura { CharacterAbility::BasicAura {
energy_cost, energy_cost,

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
comp::{ comp::{
inventory::item::armor::Friction, item::ConsumableKind, ControlAction, Density, Energy, inventory::item::armor::Friction, item::ConsumableKind, ControlAction, Density, Energy,
InputAttr, InputKind, Ori, Pos, Vel, InputAttr, InputKind, MovementState,
}, },
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ServerEvent},
states::{ states::{
@ -19,9 +19,7 @@ use strum::Display;
/// Data returned from character behavior fn's to Character Behavior System. /// Data returned from character behavior fn's to Character Behavior System.
pub struct StateUpdate { pub struct StateUpdate {
pub character: CharacterState, pub character: CharacterState,
pub pos: Pos, pub movement: MovementState,
pub vel: Vel,
pub ori: Ori,
pub density: Density, pub density: Density,
pub energy: Energy, pub energy: Energy,
pub swap_equipped_weapons: bool, pub swap_equipped_weapons: bool,
@ -48,9 +46,7 @@ impl<'a> OutputEvents<'a> {
impl From<&JoinData<'_>> for StateUpdate { impl From<&JoinData<'_>> for StateUpdate {
fn from(data: &JoinData) -> Self { fn from(data: &JoinData) -> Self {
StateUpdate { StateUpdate {
pos: *data.pos, movement: MovementState::default(),
vel: *data.vel,
ori: *data.ori,
density: *data.density, density: *data.density,
energy: *data.energy, energy: *data.energy,
swap_equipped_weapons: false, swap_equipped_weapons: false,

View File

@ -32,6 +32,7 @@ pub mod invite;
pub mod loot_owner; pub mod loot_owner;
#[cfg(not(target_arch = "wasm32"))] pub mod melee; #[cfg(not(target_arch = "wasm32"))] pub mod melee;
#[cfg(not(target_arch = "wasm32"))] mod misc; #[cfg(not(target_arch = "wasm32"))] mod misc;
#[cfg(not(target_arch = "wasm32"))] mod movement;
#[cfg(not(target_arch = "wasm32"))] pub mod ori; #[cfg(not(target_arch = "wasm32"))] pub mod ori;
#[cfg(not(target_arch = "wasm32"))] pub mod pet; #[cfg(not(target_arch = "wasm32"))] pub mod pet;
#[cfg(not(target_arch = "wasm32"))] mod phys; #[cfg(not(target_arch = "wasm32"))] mod phys;
@ -95,6 +96,7 @@ pub use self::{
loot_owner::LootOwner, loot_owner::LootOwner,
melee::{Melee, MeleeConstructor}, melee::{Melee, MeleeConstructor},
misc::Object, misc::Object,
movement::{MovementKind, MovementState, OriUpdate},
ori::Ori, ori::Ori,
pet::Pet, pet::Pet,
phys::{ phys::{

140
common/src/comp/movement.rs Normal file
View File

@ -0,0 +1,140 @@
use crate::{
comp::{Ori, Pos, Vel},
consts::GRAVITY,
resources::DeltaTime,
};
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use std::ops::Mul;
use vek::*;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct MovementState {
kind: MovementKind,
ori: OriUpdate,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum MovementKind {
Stationary,
SlowFall {
lift: f32,
},
Flight {
lift: f32,
dir: Vec2<f32>,
accel: f32,
},
Swim {
dir: Vec3<f32>,
accel: f32,
},
Leap {
dir: Vec2<f32>,
vertical: f32,
forward: f32,
progress: f32,
},
Ground {
dir: Vec2<f32>,
accel: f32,
},
Climb {
dir: Vec3<f32>,
accel: f32,
},
Teleport {
pos: Vec3<f32>,
},
Boost {
dir: Vec3<f32>,
accel: f32,
},
ChangeSpeed {
speed: f32,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum OriUpdate {
New(Ori),
Stationary,
}
impl MovementState {
pub fn with_movement(mut self, kind: MovementKind) -> Self {
self.kind = kind;
self
}
pub fn with_ori_update(mut self, ori: OriUpdate) -> Self {
self.ori = ori;
self
}
pub fn handle_movement(&self, dt: &DeltaTime, pos: &mut Pos, vel: &mut Vel, ori: &mut Ori) {
match self.kind {
MovementKind::Stationary => {},
MovementKind::SlowFall { lift } => {
vel.0.z += dt.0 * lift;
},
MovementKind::Flight { lift, dir, accel } => {
let dir = dir.try_normalized().unwrap_or_default();
vel.0.z += dt.0 * lift;
vel.0 += dir * accel * dt.0;
},
MovementKind::Swim { dir, accel } => {
let dir = dir.try_normalized().unwrap_or_default();
vel.0 += dir * accel * dt.0;
},
MovementKind::Leap {
dir,
vertical,
forward,
progress,
} => {
let dir = dir.try_normalized().unwrap_or_default();
let progress = progress.clamp(0.0, 1.0);
// TODO: Make this += instead of =, will require changing magnitude of strengths
// probably, and potentially other behavior too Multiplication
// by 2 to make `progress` "average" 1.0
vel.0 = dir.mul(forward).with_z(vertical * progress * 2.0);
},
MovementKind::Ground { dir, accel } => {
let dir = dir.try_normalized().unwrap_or_default();
vel.0 += dir * accel * dt.0;
},
MovementKind::Climb { dir, accel } => {
let dir = dir.try_normalized().unwrap_or_default();
vel.0.z += GRAVITY * dt.0;
vel.0 += dir * accel * dt.0;
},
MovementKind::Teleport { pos: new_pos } => pos.0 = new_pos,
MovementKind::Boost { dir, accel } => {
let dir = dir.try_normalized().unwrap_or_default();
vel.0 += dir * accel * dt.0;
},
MovementKind::ChangeSpeed { speed } => {
vel.0 = vel.0.try_normalized().unwrap_or_default() * speed;
},
}
match self.ori {
OriUpdate::Stationary => {},
OriUpdate::New(new_ori) => *ori = new_ori,
}
}
}
impl Default for MovementState {
fn default() -> Self {
Self {
kind: MovementKind::Stationary,
ori: OriUpdate::Stationary,
}
}
}
impl Component for MovementState {
type Storage = DerefFlaggedStorage<Self>;
}

View File

@ -157,8 +157,8 @@ impl CharacterBehavior for Data {
let pitch = xy_dir.rotation_between(look_dir); let pitch = xy_dir.rotation_between(look_dir);
Ori::from(Vec3::new( Ori::from(Vec3::new(
update.ori.look_vec().x, data.ori.look_vec().x,
update.ori.look_vec().y, data.ori.look_vec().y,
0.0, 0.0,
)) ))
.prerotated(pitch) .prerotated(pitch)
@ -169,7 +169,7 @@ impl CharacterBehavior for Data {
let body_offsets = beam_offsets( let body_offsets = beam_offsets(
data.body, data.body,
data.inputs.look_dir, data.inputs.look_dir,
update.ori.look_vec(), data.ori.look_vec(),
rel_vel, rel_vel,
data.physics.on_ground, data.physics.on_ground,
); );

View File

@ -88,7 +88,7 @@ impl CharacterBehavior for Data {
// Shoots all projectiles simultaneously // Shoots all projectiles simultaneously
for i in 0..self.static_data.num_projectiles { for i in 0..self.static_data.num_projectiles {
// Gets offsets // Gets offsets
let body_offsets = data.body.projectile_offsets(update.ori.look_vec()); let body_offsets = data.body.projectile_offsets(data.ori.look_vec());
let pos = Pos(data.pos.0 + body_offsets); let pos = Pos(data.pos.0 + body_offsets);
// Adds a slight spread to the projectiles. First projectile has no spread, // Adds a slight spread to the projectiles. First projectile has no spread,
// and spread increases linearly with number of projectiles created. // and spread increases linearly with number of projectiles created.

View File

@ -3,7 +3,7 @@ use crate::{
self, character_state::OutputEvents, item::MaterialStatManifest, ActiveAbilities, Beam, self, character_state::OutputEvents, item::MaterialStatManifest, ActiveAbilities, Beam,
Body, CharacterState, Combo, ControlAction, Controller, ControllerInputs, Density, Energy, Body, CharacterState, Combo, ControlAction, Controller, ControllerInputs, Density, Energy,
Health, InputAttr, InputKind, Inventory, InventoryAction, Mass, Melee, Ori, PhysicsState, Health, InputAttr, InputKind, Inventory, InventoryAction, Mass, Melee, Ori, PhysicsState,
Pos, SkillSet, StateUpdate, Stats, Vel, Pos, SkillSet, StateUpdate, Stats, Vel, MovementState,
}, },
link::Is, link::Is,
mounting::Rider, mounting::Rider,
@ -118,6 +118,7 @@ pub struct JoinData<'a> {
pub pos: &'a Pos, pub pos: &'a Pos,
pub vel: &'a Vel, pub vel: &'a Vel,
pub ori: &'a Ori, pub ori: &'a Ori,
pub movement: &'a MovementState,
pub mass: &'a Mass, pub mass: &'a Mass,
pub density: &'a Density, pub density: &'a Density,
pub dt: &'a DeltaTime, pub dt: &'a DeltaTime,
@ -144,9 +145,10 @@ pub struct JoinStruct<'a> {
pub entity: Entity, pub entity: Entity,
pub uid: &'a Uid, pub uid: &'a Uid,
pub char_state: FlaggedAccessMut<'a, &'a mut CharacterState, CharacterState>, pub char_state: FlaggedAccessMut<'a, &'a mut CharacterState, CharacterState>,
pub pos: &'a mut Pos, pub pos: &'a Pos,
pub vel: &'a mut Vel, pub vel: &'a Vel,
pub ori: &'a mut Ori, pub ori: &'a Ori,
pub movement: &'a mut MovementState,
pub mass: &'a Mass, pub mass: &'a Mass,
pub density: FlaggedAccessMut<'a, &'a mut Density, Density>, pub density: FlaggedAccessMut<'a, &'a mut Density, Density>,
pub energy: FlaggedAccessMut<'a, &'a mut Energy, Energy>, pub energy: FlaggedAccessMut<'a, &'a mut Energy, Energy>,
@ -180,6 +182,7 @@ impl<'a> JoinData<'a> {
pos: j.pos, pos: j.pos,
vel: j.vel, vel: j.vel,
ori: j.ori, ori: j.ori,
movement: j.movement,
mass: j.mass, mass: j.mass,
density: &j.density, density: &j.density,
energy: &j.energy, energy: &j.energy,

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
comp::{character_state::OutputEvents, CharacterState, StateUpdate}, comp::{character_state::OutputEvents, CharacterState, StateUpdate, MovementKind},
event::ServerEvent, event::ServerEvent,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -59,9 +59,10 @@ impl CharacterBehavior for Data {
max_range: Some(self.static_data.max_range), max_range: Some(self.static_data.max_range),
}); });
} else if let Some(pos) = input_attr.select_pos { } else if let Some(pos) = input_attr.select_pos {
update.pos.0 = pos; update.movement = update.movement.with_movement(MovementKind::Teleport { pos });
} else { } else {
update.pos.0 += *data.inputs.look_dir * 25.0; let pos = data.pos.0 + *data.inputs.look_dir * 25.0;
update.movement = update.movement.with_movement(MovementKind::Teleport { pos });
} }
} }
// Transitions to recover section of stage // Transitions to recover section of stage

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
comp::{character_state::OutputEvents, CharacterState, StateUpdate}, comp::{character_state::OutputEvents, CharacterState, StateUpdate, MovementKind},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
@ -8,6 +8,7 @@ use crate::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
use vek::*;
/// Separated out to condense update portions of character state /// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -36,11 +37,12 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.movement_duration { if self.timer < self.static_data.movement_duration {
// Movement // Movement
if self.static_data.only_up { let dir = if self.static_data.only_up {
update.vel.0.z += self.static_data.speed * data.dt.0; Vec3::unit_z()
} else { } else {
update.vel.0 += *data.inputs.look_dir * self.static_data.speed * data.dt.0; *data.inputs.look_dir
} };
update.movement = update.movement.with_movement(MovementKind::Boost { dir, accel: self.static_data.speed });
update.character = CharacterState::Boost(Data { update.character = CharacterState::Boost(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(data, self.timer, None),
..*self ..*self
@ -50,12 +52,8 @@ impl CharacterBehavior for Data {
if input_is_pressed(data, self.static_data.ability_info.input) { if input_is_pressed(data, self.static_data.ability_info.input) {
reset_state(self, data, output_events, &mut update); reset_state(self, data, output_events, &mut update);
} else { } else {
update.vel.0 = update.vel.0.try_normalized().unwrap_or_default() let speed = data.vel.0.magnitude().min(self.static_data.max_exit_velocity);
* update update.movement = update.movement.with_movement(MovementKind::ChangeSpeed { speed });
.vel
.0
.magnitude()
.min(self.static_data.max_exit_velocity);
update.character = CharacterState::Wielding(wielding::Data { is_sneaking: false }); update.character = CharacterState::Wielding(wielding::Data { is_sneaking: false });
} }
} }

View File

@ -112,7 +112,7 @@ impl CharacterBehavior for Data {
get_crit_data(data, self.static_data.ability_info); get_crit_data(data, self.static_data.ability_info);
let buff_strength = get_buff_strength(data, self.static_data.ability_info); let buff_strength = get_buff_strength(data, self.static_data.ability_info);
// Gets offsets // Gets offsets
let body_offsets = data.body.projectile_offsets(update.ori.look_vec()); let body_offsets = data.body.projectile_offsets(data.ori.look_vec());
let pos = Pos(data.pos.0 + body_offsets); let pos = Pos(data.pos.0 + body_offsets);
let projectile = arrow.create_projectile( let projectile = arrow.create_projectile(
Some(*data.uid), Some(*data.uid),

View File

@ -2,9 +2,8 @@ use crate::{
comp::{ comp::{
character_state::OutputEvents, character_state::OutputEvents,
skills::{ClimbSkill::*, Skill, SKILL_MODIFIERS}, skills::{ClimbSkill::*, Skill, SKILL_MODIFIERS},
CharacterState, Climb, InputKind, Ori, StateUpdate, CharacterState, Climb, InputKind, Ori, StateUpdate, MovementKind, OriUpdate,
}, },
consts::GRAVITY,
event::LocalEvent, event::LocalEvent,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -82,14 +81,6 @@ impl CharacterBehavior for Data {
update.character = CharacterState::Idle(idle::Data::default()); update.character = CharacterState::Idle(idle::Data::default());
return update; return update;
}; };
// Move player
update.vel.0 += Vec2::broadcast(data.dt.0)
* data.inputs.move_dir
* if update.vel.0.magnitude_squared() < self.static_data.movement_speed.powi(2) {
self.static_data.movement_speed.powi(2)
} else {
0.0
};
// Expend energy if climbing // Expend energy if climbing
let energy_use = match climb { let energy_use = match climb {
@ -107,28 +98,33 @@ impl CharacterBehavior for Data {
} }
// Set orientation direction based on wall direction // Set orientation direction based on wall direction
if let Some(ori_dir) = Dir::from_unnormalized(Vec2::from(wall_dir).into()) { let new_ori = if let Some(ori_dir) = Dir::from_unnormalized(Vec2::from(wall_dir).into()) {
// Smooth orientation // Smooth orientation
update.ori = update.ori.slerped_towards( data.ori.slerped_towards(
Ori::from(ori_dir), Ori::from(ori_dir),
if data.physics.on_ground.is_some() { if data.physics.on_ground.is_some() {
9.0 9.0
} else { } else {
2.0 2.0
} * data.dt.0, } * data.dt.0,
); )
} else {
*data.ori
}; };
// Apply Vertical Climbing Movement // Apply Vertical Climbing Movement
match climb { let dir = data.inputs.move_dir.with_z(match climb {
Climb::Down => { Climb::Down => -1.0,
update.vel.0.z += data.dt.0 * (GRAVITY - self.static_data.movement_speed.powi(2)) Climb::Up => 1.0,
}, Climb::Hold => 0.0,
Climb::Up => { });
update.vel.0.z += data.dt.0 * (GRAVITY + self.static_data.movement_speed.powi(2)) let accel = if data.vel.0.magnitude_squared() < self.static_data.movement_speed.powi(2) {
}, self.static_data.movement_speed.powi(2)
Climb::Hold => update.vel.0.z += data.dt.0 * GRAVITY, } else {
} 0.0
};
update.movement = update.movement.with_movement(MovementKind::Climb { dir, accel }).with_ori_update(OriUpdate::New(new_ori));
update update
} }

View File

@ -2,7 +2,7 @@ use super::utils::handle_climb;
use crate::{ use crate::{
comp::{ comp::{
character_state::OutputEvents, fluid_dynamics::angle_of_attack, inventory::slot::EquipSlot, character_state::OutputEvents, fluid_dynamics::angle_of_attack, inventory::slot::EquipSlot,
CharacterState, Ori, StateUpdate, Vel, CharacterState, Ori, StateUpdate, Vel, OriUpdate,
}, },
event::LocalEvent, event::LocalEvent,
outcome::Outcome, outcome::Outcome,
@ -144,7 +144,7 @@ impl CharacterBehavior for Data {
.unwrap_or_else(|| self.ori.slerped_towards(self.ori.uprighted(), slerp_s)) .unwrap_or_else(|| self.ori.slerped_towards(self.ori.uprighted(), slerp_s))
}; };
update.ori = { update.movement = update.movement.with_ori_update(OriUpdate::New({
let slerp_s = { let slerp_s = {
let angle = data.ori.look_dir().angle_between(*data.inputs.look_dir); let angle = data.ori.look_dir().angle_between(*data.inputs.look_dir);
let rate = 0.2 * data.body.base_ori_rate() * PI / angle; let rate = 0.2 * data.body.base_ori_rate() * PI / angle;
@ -183,12 +183,12 @@ impl CharacterBehavior for Data {
) )
}; };
update.ori.slerped_towards( data.ori.slerped_towards(
ori.to_horizontal() ori.to_horizontal()
.prerotated(rot_from_drag * rot_from_accel), .prerotated(rot_from_drag * rot_from_accel),
slerp_s, slerp_s,
) )
}; }));
update.character = CharacterState::Glide(Self { update.character = CharacterState::Glide(Self {
ori, ori,
last_vel: *data.vel, last_vel: *data.vel,

View File

@ -93,7 +93,7 @@ impl CharacterBehavior for Data {
get_crit_data(data, self.static_data.ability_info); get_crit_data(data, self.static_data.ability_info);
let buff_strength = get_buff_strength(data, self.static_data.ability_info); let buff_strength = get_buff_strength(data, self.static_data.ability_info);
// Gets offsets // Gets offsets
let body_offsets = data.body.projectile_offsets(update.ori.look_vec()); let body_offsets = data.body.projectile_offsets(data.ori.look_vec());
let pos = Pos(data.pos.0 + body_offsets); let pos = Pos(data.pos.0 + body_offsets);
let projectile = self.static_data.projectile.create_projectile( let projectile = self.static_data.projectile.create_projectile(
Some(*data.uid), Some(*data.uid),

View File

@ -2,7 +2,7 @@ use super::utils::*;
use crate::{ use crate::{
comp::{ comp::{
character_state::OutputEvents, item::armor::Friction, CharacterState, InventoryAction, character_state::OutputEvents, item::armor::Friction, CharacterState, InventoryAction,
StateUpdate, StateUpdate, MovementKind, OriUpdate,
}, },
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -46,7 +46,7 @@ impl CharacterBehavior for Data {
} else { } else {
let plane_ori = data.inputs.look_dir.xy(); let plane_ori = data.inputs.look_dir.xy();
let orthogonal = vek::Vec2::new(plane_ori.y, -plane_ori.x); let orthogonal = vek::Vec2::new(plane_ori.y, -plane_ori.x);
update.ori = vek::Vec3::new(plane_ori.x, plane_ori.y, 0.0).into(); let new_ori = vek::Vec3::new(plane_ori.x, plane_ori.y, 0.0).into();
let current_planar_velocity = data.vel.0.xy().magnitude(); let current_planar_velocity = data.vel.0.xy().magnitude();
let long_input = data.inputs.move_dir.dot(plane_ori); let long_input = data.inputs.move_dir.dot(plane_ori);
let lat_input = data.inputs.move_dir.dot(orthogonal); let lat_input = data.inputs.move_dir.dot(orthogonal);
@ -79,8 +79,7 @@ impl CharacterBehavior for Data {
if let CharacterState::Skate(skate_data) = &mut update.character { if let CharacterState::Skate(skate_data) = &mut update.character {
skate_data.turn = orthogonal.dot(data.vel.0.xy()); skate_data.turn = orthogonal.dot(data.vel.0.xy());
} }
let delta_vel = acceleration * data.inputs.move_dir; update.movement = update.movement.with_movement(MovementKind::Ground { dir: data.inputs.move_dir, accel: acceleration }).with_ori_update(OriUpdate::New(new_ori));
update.vel.0 += vek::Vec3::new(delta_vel.x, delta_vel.y, 0.0);
} }
update update

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate}, comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate, MovementKind},
consts::GRAVITY, consts::GRAVITY,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -9,7 +9,6 @@ use crate::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
use vek::Vec3;
/// Separated out to condense update portions of character state /// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -62,8 +61,13 @@ impl CharacterBehavior for Data {
match self.static_data.movement_behavior { match self.static_data.movement_behavior {
MovementBehavior::ForwardGround | MovementBehavior::Stationary => {}, MovementBehavior::ForwardGround | MovementBehavior::Stationary => {},
MovementBehavior::AxeHover => { MovementBehavior::AxeHover => {
let new_vel_z = update.vel.0.z + GRAVITY * data.dt.0 * 0.5; update.movement = update.movement.with_movement(if data.physics.on_ground.is_some() {
update.vel.0 = Vec3::new(0.0, 0.0, new_vel_z) + data.inputs.move_dir * 5.0; // 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?
MovementKind::Ground { dir: data.inputs.move_dir, accel: 5.0 }
} else {
MovementKind::SlowFall { lift: GRAVITY * 0.5 }
});
}, },
MovementBehavior::Walking => { MovementBehavior::Walking => {
handle_move(data, &mut update, 0.2); handle_move(data, &mut update, 0.2);

View File

@ -9,7 +9,7 @@ use crate::{
quadruped_low, quadruped_medium, quadruped_small, quadruped_low, quadruped_medium, quadruped_small,
skills::{Skill, SwimSkill, SKILL_MODIFIERS}, skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind, theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
InventoryAction, StateUpdate, InventoryAction, StateUpdate, MovementKind, OriUpdate,
}, },
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE}, consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ServerEvent},
@ -370,13 +370,14 @@ fn basic_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
} * efficiency; } * efficiency;
// Should ability to backpedal be separate from ability to strafe? // Should ability to backpedal be separate from ability to strafe?
update.vel.0 += Vec2::broadcast(data.dt.0) let dir = if data.body.can_strafe() {
* accel
* if data.body.can_strafe() {
data.inputs.move_dir data.inputs.move_dir
* if is_strafing(data, update) { } else {
Vec2::from(*data.ori)
};
let accel_mod = if is_strafing(data, update) {
Lerp::lerp( Lerp::lerp(
Vec2::from(update.ori) Vec2::from(*data.ori)
.try_normalized() .try_normalized()
.unwrap_or_else(Vec2::zero) .unwrap_or_else(Vec2::zero)
.dot( .dot(
@ -392,12 +393,11 @@ fn basic_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
data.body.reverse_move_factor(), data.body.reverse_move_factor(),
) )
} else { } else {
1.0 data.inputs.move_dir.dot(dir).max(0.0)
}
} else {
let fw = Vec2::from(update.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0)
}; };
let accel = accel * accel_mod;
update.movement = update.movement.with_movement(MovementKind::Ground { dir, accel });
} }
/// Handles forced movement /// Handles forced movement
@ -413,10 +413,11 @@ pub fn handle_forced_movement(
// FRIC_GROUND temporarily used to normalize things around expected values // FRIC_GROUND temporarily used to normalize things around expected values
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
}) { }) {
update.vel.0 += Vec2::broadcast(data.dt.0) // TODO: Remove * 2.0 with changes made in sword branch, added as hack for now to keep same behavior
* accel let accel = accel * strength * 2.0;
* (data.inputs.move_dir * 0.5 + Vec2::from(update.ori) * 1.5) // TODO: Remove move_dir portion with changes made in sword branch, added as hack for now to keep same behavior
* strength; let dir = data.inputs.move_dir * 0.5 + Vec2::from(*data.ori) * 1.5;
update.movement = update.movement.with_movement(MovementKind::Ground { dir, accel });
} }
}, },
ForcedMovement::Leap { ForcedMovement::Leap {
@ -426,26 +427,12 @@ pub fn handle_forced_movement(
direction, direction,
} => { } => {
let dir = direction.get_2d_dir(data); let dir = direction.get_2d_dir(data);
// Apply jumping force
update.vel.0 = Vec3::new(
dir.x,
dir.y,
vertical,
)
// Multiply decreasing amount linearly over time (with average of 1)
* 2.0 * progress
// Apply direction
+ Vec3::from(dir)
// Multiply by forward leap strength
* forward
// Control forward movement based on look direction. // Control forward movement based on look direction.
// This allows players to stop moving forward when they // This allows players to stop moving forward when they
// look downward at target // look downward at target
* (1.0 - data.inputs.look_dir.z.abs()); let forward = forward * (1.0 - data.inputs.look_dir.z.abs());
},
ForcedMovement::Hover { move_input } => { update.movement = update.movement.with_movement(MovementKind::Leap { dir, vertical, forward, progress });
update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0)
+ move_input * data.inputs.move_dir.try_normalized().unwrap_or_default();
}, },
} }
} }
@ -494,22 +481,24 @@ pub fn handle_orientation(
} }
* data.dt.0; * data.dt.0;
// very rough guess // very rough guess
let ticks_from_target_guess = ori_absdiff(&update.ori, &target_ori) / half_turns_per_tick; let ticks_from_target_guess = ori_absdiff(data.ori, &target_ori) / half_turns_per_tick;
let instantaneous = ticks_from_target_guess < 1.0; let instantaneous = ticks_from_target_guess < 1.0;
update.ori = if instantaneous { let new_ori = if instantaneous {
target_ori target_ori
} else { } else {
let target_fraction = { let target_fraction = {
// Angle factor used to keep turning rate approximately constant by // Angle factor used to keep turning rate approximately constant by
// counteracting slerp turning more with a larger angle // counteracting slerp turning more with a larger angle
let angle_factor = 2.0 / (1.0 - update.ori.dot(target_ori)).sqrt(); let angle_factor = 2.0 / (1.0 - data.ori.dot(target_ori)).sqrt();
half_turns_per_tick * angle_factor half_turns_per_tick * angle_factor
}; };
update data
.ori .ori
.slerped_towards(target_ori, target_fraction.min(1.0)) .slerped_towards(target_ori, target_fraction.min(1.0))
}; };
update.movement = update.movement.with_ori_update(OriUpdate::New(new_ori));
} }
/// Updates components to move player as if theyre swimming /// Updates components to move player as if theyre swimming
@ -532,7 +521,7 @@ fn swim_move(
let dir = if data.body.can_strafe() { let dir = if data.body.can_strafe() {
data.inputs.move_dir data.inputs.move_dir
} else { } else {
let fw = Vec2::from(update.ori); let fw = Vec2::from(*data.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0) fw * data.inputs.move_dir.dot(fw).max(0.0)
}; };
@ -543,12 +532,9 @@ fn swim_move(
data.inputs.move_z data.inputs.move_z
}; };
update.vel.0 += Vec3::broadcast(data.dt.0) let dir = Vec3::new(dir.x, dir.y, move_z);
* Vec3::new(dir.x, dir.y, move_z) let accel = water_accel * (submersion - 0.2).clamp(0.0, 1.0).powi(2);
.try_normalized() update.movement = update.movement.with_movement(MovementKind::Swim { dir, accel });
.unwrap_or_default()
* water_accel
* (submersion - 0.2).clamp(0.0, 1.0).powi(2);
true true
} else { } else {
@ -575,11 +561,11 @@ pub fn fly_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32)
handle_orientation(data, update, efficiency, None); handle_orientation(data, update, efficiency, None);
// Elevation control // Elevation control
match data.body { let lift = match data.body {
// flappy flappy // flappy flappy
Body::Dragon(_) | Body::BirdLarge(_) | Body::BirdMedium(_) => { Body::Dragon(_) | Body::BirdLarge(_) | Body::BirdMedium(_) => {
let anti_grav = GRAVITY * (1.0 + data.inputs.move_z.min(0.0)); let anti_grav = GRAVITY * (1.0 + data.inputs.move_z.min(0.0));
update.vel.0.z += data.dt.0 * (anti_grav + accel * data.inputs.move_z.max(0.0)); anti_grav + accel * data.inputs.move_z.max(0.0)
}, },
// floaty floaty // floaty floaty
Body::Ship(ship) if ship.can_fly() => { Body::Ship(ship) if ship.can_fly() => {
@ -601,21 +587,23 @@ pub fn fly_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32)
update.density.0 = update.density.0 =
regulate_density(def_density * 0.5, def_density * 1.5, def_density, 0.5).0; regulate_density(def_density * 0.5, def_density * 1.5, def_density, 0.5).0;
}; };
// TODO: Make ships actually specify a lift instead of hacking density
0.0
}, },
// oopsie woopsie // oopsie woopsie
// TODO: refactor to make this state impossible // TODO: refactor to make this state impossible
_ => {}, _ => 0.0,
}; };
update.vel.0 += Vec2::broadcast(data.dt.0) let dir = if data.body.can_strafe() {
* accel
* if data.body.can_strafe() {
data.inputs.move_dir data.inputs.move_dir
} else { } else {
let fw = Vec2::from(update.ori); let fw = Vec2::from(*data.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0) fw * data.inputs.move_dir.dot(fw).max(0.0)
}; };
update.movement = update.movement.with_movement(MovementKind::Flight { lift, dir, accel });
true true
} else { } else {
false false
@ -1195,9 +1183,6 @@ pub enum ForcedMovement {
progress: f32, progress: f32,
direction: MovementDirection, direction: MovementDirection,
}, },
Hover {
move_input: f32,
},
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,6 +1,6 @@
use super::utils::*; use super::utils::*;
use crate::{ use crate::{
comp::{character_state::OutputEvents, CharacterState, StateUpdate}, comp::{character_state::OutputEvents, CharacterState, StateUpdate, MovementKind},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
idle, idle,
@ -23,12 +23,11 @@ impl CharacterBehavior for Data {
handle_climb(data, &mut update); handle_climb(data, &mut update);
{ {
let lift = WALLRUN_ANTIGRAV; let lift = WALLRUN_ANTIGRAV
update.vel.0.z += data.dt.0 * (Vec2::<f32>::from(data.vel.0).magnitude() * 0.075)
* lift
* (Vec2::<f32>::from(update.vel.0).magnitude() * 0.075)
.min(1.0) .min(1.0)
.max(0.2); .max(0.2);
update.movement = update.movement.with_movement(MovementKind::SlowFall { lift });
} }
// fall off wall, hit ground, or enter water // fall off wall, hit ground, or enter water

View File

@ -178,6 +178,7 @@ impl State {
ecs.register::<comp::BeamSegment>(); ecs.register::<comp::BeamSegment>();
ecs.register::<comp::Alignment>(); ecs.register::<comp::Alignment>();
ecs.register::<comp::LootOwner>(); ecs.register::<comp::LootOwner>();
ecs.register::<comp::MovementState>();
// Register components send from clients -> server // Register components send from clients -> server
ecs.register::<comp::Controller>(); ecs.register::<comp::Controller>();

View File

@ -7,8 +7,8 @@ use common::{
comp::{ comp::{
self, character_state::OutputEvents, inventory::item::MaterialStatManifest, self, character_state::OutputEvents, inventory::item::MaterialStatManifest,
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, SkillSet, Inventory, InventoryManip, Mass, Melee, MovementState, Ori, PhysicsState, Poise, Pos,
StateUpdate, Stats, Vel, SkillSet, StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
link::Is, link::Is,
@ -48,6 +48,9 @@ pub struct ReadData<'a> {
alignments: ReadStorage<'a, comp::Alignment>, alignments: ReadStorage<'a, comp::Alignment>,
terrain: ReadExpect<'a, TerrainGrid>, terrain: ReadExpect<'a, TerrainGrid>,
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
positions: ReadStorage<'a, Pos>,
velocities: ReadStorage<'a, Vel>,
orientations: ReadStorage<'a, Ori>,
} }
/// ## Character Behavior System /// ## Character Behavior System
@ -60,13 +63,11 @@ impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
ReadData<'a>, ReadData<'a>,
WriteStorage<'a, CharacterState>, WriteStorage<'a, CharacterState>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
WriteStorage<'a, Density>, WriteStorage<'a, Density>,
WriteStorage<'a, Energy>, WriteStorage<'a, Energy>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
WriteStorage<'a, Poise>, WriteStorage<'a, Poise>,
WriteStorage<'a, MovementState>,
Read<'a, EventBus<Outcome>>, Read<'a, EventBus<Outcome>>,
); );
@ -79,13 +80,11 @@ impl<'a> System<'a> for Sys {
( (
read_data, read_data,
mut character_states, mut character_states,
mut positions,
mut velocities,
mut orientations,
mut densities, mut densities,
mut energies, mut energies,
mut controllers, mut controllers,
mut poises, mut poises,
mut movements,
outcomes, outcomes,
): Self::SystemData, ): Self::SystemData,
) { ) {
@ -101,9 +100,7 @@ impl<'a> System<'a> for Sys {
entity, entity,
uid, uid,
mut char_state, mut char_state,
pos, (pos, vel, ori, mut movement),
vel,
ori,
mass, mass,
density, density,
energy, energy,
@ -118,9 +115,12 @@ impl<'a> System<'a> for Sys {
&read_data.entities, &read_data.entities,
&read_data.uids, &read_data.uids,
&mut character_states, &mut character_states,
&mut positions, (
&mut velocities, &read_data.positions,
&mut orientations, &read_data.velocities,
&read_data.orientations,
&mut movements,
),
&read_data.masses, &read_data.masses,
&mut densities, &mut densities,
&mut energies, &mut energies,
@ -179,6 +179,7 @@ impl<'a> System<'a> for Sys {
pos, pos,
vel, vel,
ori, ori,
movement: &mut movement,
mass, mass,
density, density,
energy, energy,
@ -257,9 +258,9 @@ impl Sys {
}; };
// These components use a different type of change detection. // These components use a different type of change detection.
*join.pos = state_update.pos; // TODO: Would the above comment also apply to movement state? It used to apply
*join.vel = state_update.vel; // to pos/vel/ori
*join.ori = state_update.ori; *join.movement = state_update.movement;
join.controller join.controller
.queued_inputs .queued_inputs

View File

@ -3,8 +3,8 @@ use common::{
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST}, body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
fluid_dynamics::{Fluid, LiquidKind, Wings}, fluid_dynamics::{Fluid, LiquidKind, Wings},
inventory::item::armor::Friction, inventory::item::armor::Friction,
Body, CharacterState, Collider, Density, Immovable, Mass, Ori, PhysicsState, Pos, Body, CharacterState, Collider, Density, Immovable, Mass, MovementState, Ori, PhysicsState,
PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel, Pos, PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
}, },
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY}, consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
@ -124,6 +124,7 @@ pub struct PhysicsRead<'a> {
densities: ReadStorage<'a, Density>, densities: ReadStorage<'a, Density>,
stats: ReadStorage<'a, Stats>, stats: ReadStorage<'a, Stats>,
outcomes: Read<'a, EventBus<Outcome>>, outcomes: Read<'a, EventBus<Outcome>>,
movements: ReadStorage<'a, MovementState>,
} }
#[derive(SystemData)] #[derive(SystemData)]
@ -1357,6 +1358,17 @@ impl<'a> System<'a> for Sys {
ref mut write, ref mut write,
} = physics_data; } = physics_data;
(
&read.movements,
&mut write.positions,
&mut write.velocities,
&mut write.orientations,
)
.par_join()
.for_each(|(movement, pos, vel, ori)| {
movement.handle_movement(&read.dt, pos, vel, ori);
});
let (spatial_grid, voxel_collider_spatial_grid) = rayon::join( let (spatial_grid, voxel_collider_spatial_grid) = rayon::join(
|| { || {
let (spatial_grid, ()) = rayon::join( let (spatial_grid, ()) = rayon::join(

View File

@ -272,6 +272,7 @@ impl StateExt for State {
.with(comp::Buffs::default()) .with(comp::Buffs::default())
.with(comp::Combo::default()) .with(comp::Combo::default())
.with(comp::Auras::default()) .with(comp::Auras::default())
.with(comp::MovementState::default())
} }
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder { fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder {
@ -330,7 +331,8 @@ impl StateExt for State {
.with(comp::Stats::new("Airship".to_string())) .with(comp::Stats::new("Airship".to_string()))
.with(comp::SkillSet::default()) .with(comp::SkillSet::default())
.with(comp::ActiveAbilities::default()) .with(comp::ActiveAbilities::default())
.with(comp::Combo::default()); .with(comp::Combo::default())
.with(comp::MovementState::default());
if mountable { if mountable {
// TODO: Re-add mounting check // TODO: Re-add mounting check
@ -535,6 +537,7 @@ impl StateExt for State {
self.write_component_ignore_entity_dead(entity, comp::Buffs::default()); self.write_component_ignore_entity_dead(entity, comp::Buffs::default());
self.write_component_ignore_entity_dead(entity, comp::Auras::default()); self.write_component_ignore_entity_dead(entity, comp::Auras::default());
self.write_component_ignore_entity_dead(entity, comp::Combo::default()); self.write_component_ignore_entity_dead(entity, comp::Combo::default());
self.write_component_ignore_entity_dead(entity, comp::MovementState::default());
// Make sure physics components are updated // Make sure physics components are updated
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced()); self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());