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,
recipe::{ComponentRecipeBook, RecipeBook},
resources::{GameMode, PlayerEntity, TimeOfDay},
slowjob::SlowJobPool,
spiral::Spiral2d,
terrain::{
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().insert(material_stats);
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 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
// to only being synced for the client's entity.
skill_set: SkillSet,
loot_owner: LootOwner,
// Synced to the client only for its own entity
combo: Combo,
active_abilities: ActiveAbilities,
can_build: CanBuild,
loot_owner: LootOwner,
movement_state: MovementState,
}
};
}
@ -215,6 +216,10 @@ impl NetSync for SkillSet {
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.
impl NetSync for Combo {
@ -229,6 +234,6 @@ impl NetSync for CanBuild {
const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;
}
impl NetSync for LootOwner {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
impl NetSync for MovementState {
const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;
}

View File

@ -675,7 +675,7 @@ impl CharacterAbility {
update.energy.current() >= *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 {
energy_cost,

View File

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

View File

@ -32,6 +32,7 @@ pub mod invite;
pub mod loot_owner;
#[cfg(not(target_arch = "wasm32"))] pub mod melee;
#[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 pet;
#[cfg(not(target_arch = "wasm32"))] mod phys;
@ -95,6 +96,7 @@ pub use self::{
loot_owner::LootOwner,
melee::{Melee, MeleeConstructor},
misc::Object,
movement::{MovementKind, MovementState, OriUpdate},
ori::Ori,
pet::Pet,
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);
Ori::from(Vec3::new(
update.ori.look_vec().x,
update.ori.look_vec().y,
data.ori.look_vec().x,
data.ori.look_vec().y,
0.0,
))
.prerotated(pitch)
@ -169,7 +169,7 @@ impl CharacterBehavior for Data {
let body_offsets = beam_offsets(
data.body,
data.inputs.look_dir,
update.ori.look_vec(),
data.ori.look_vec(),
rel_vel,
data.physics.on_ground,
);

View File

@ -88,7 +88,7 @@ impl CharacterBehavior for Data {
// Shoots all projectiles simultaneously
for i in 0..self.static_data.num_projectiles {
// 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);
// Adds a slight spread to the projectiles. First projectile has no spread,
// 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,
Body, CharacterState, Combo, ControlAction, Controller, ControllerInputs, Density, Energy,
Health, InputAttr, InputKind, Inventory, InventoryAction, Mass, Melee, Ori, PhysicsState,
Pos, SkillSet, StateUpdate, Stats, Vel,
Pos, SkillSet, StateUpdate, Stats, Vel, MovementState,
},
link::Is,
mounting::Rider,
@ -118,6 +118,7 @@ pub struct JoinData<'a> {
pub pos: &'a Pos,
pub vel: &'a Vel,
pub ori: &'a Ori,
pub movement: &'a MovementState,
pub mass: &'a Mass,
pub density: &'a Density,
pub dt: &'a DeltaTime,
@ -144,9 +145,10 @@ pub struct JoinStruct<'a> {
pub entity: Entity,
pub uid: &'a Uid,
pub char_state: FlaggedAccessMut<'a, &'a mut CharacterState, CharacterState>,
pub pos: &'a mut Pos,
pub vel: &'a mut Vel,
pub ori: &'a mut Ori,
pub pos: &'a Pos,
pub vel: &'a Vel,
pub ori: &'a Ori,
pub movement: &'a mut MovementState,
pub mass: &'a Mass,
pub density: FlaggedAccessMut<'a, &'a mut Density, Density>,
pub energy: FlaggedAccessMut<'a, &'a mut Energy, Energy>,
@ -180,6 +182,7 @@ impl<'a> JoinData<'a> {
pos: j.pos,
vel: j.vel,
ori: j.ori,
movement: j.movement,
mass: j.mass,
density: &j.density,
energy: &j.energy,

View File

@ -1,5 +1,5 @@
use crate::{
comp::{character_state::OutputEvents, CharacterState, StateUpdate},
comp::{character_state::OutputEvents, CharacterState, StateUpdate, MovementKind},
event::ServerEvent,
states::{
behavior::{CharacterBehavior, JoinData},
@ -59,9 +59,10 @@ impl CharacterBehavior for Data {
max_range: Some(self.static_data.max_range),
});
} else if let Some(pos) = input_attr.select_pos {
update.pos.0 = pos;
update.movement = update.movement.with_movement(MovementKind::Teleport { pos });
} 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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ use super::utils::handle_climb;
use crate::{
comp::{
character_state::OutputEvents, fluid_dynamics::angle_of_attack, inventory::slot::EquipSlot,
CharacterState, Ori, StateUpdate, Vel,
CharacterState, Ori, StateUpdate, Vel, OriUpdate,
},
event::LocalEvent,
outcome::Outcome,
@ -144,7 +144,7 @@ impl CharacterBehavior for Data {
.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 angle = data.ori.look_dir().angle_between(*data.inputs.look_dir);
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()
.prerotated(rot_from_drag * rot_from_accel),
slerp_s,
)
};
}));
update.character = CharacterState::Glide(Self {
ori,
last_vel: *data.vel,

View File

@ -93,7 +93,7 @@ impl CharacterBehavior for Data {
get_crit_data(data, self.static_data.ability_info);
let buff_strength = get_buff_strength(data, self.static_data.ability_info);
// 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 projectile = self.static_data.projectile.create_projectile(
Some(*data.uid),

View File

@ -2,7 +2,7 @@ use super::utils::*;
use crate::{
comp::{
character_state::OutputEvents, item::armor::Friction, CharacterState, InventoryAction,
StateUpdate,
StateUpdate, MovementKind, OriUpdate,
},
states::{
behavior::{CharacterBehavior, JoinData},
@ -46,7 +46,7 @@ impl CharacterBehavior for Data {
} else {
let plane_ori = data.inputs.look_dir.xy();
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 long_input = data.inputs.move_dir.dot(plane_ori);
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 {
skate_data.turn = orthogonal.dot(data.vel.0.xy());
}
let delta_vel = acceleration * data.inputs.move_dir;
update.vel.0 += vek::Vec3::new(delta_vel.x, delta_vel.y, 0.0);
update.movement = update.movement.with_movement(MovementKind::Ground { dir: data.inputs.move_dir, accel: acceleration }).with_ori_update(OriUpdate::New(new_ori));
}
update

View File

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

View File

@ -9,7 +9,7 @@ use crate::{
quadruped_low, quadruped_medium, quadruped_small,
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
InventoryAction, StateUpdate,
InventoryAction, StateUpdate, MovementKind, OriUpdate,
},
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
event::{LocalEvent, ServerEvent},
@ -370,13 +370,14 @@ fn basic_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
} * efficiency;
// Should ability to backpedal be separate from ability to strafe?
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* if data.body.can_strafe() {
let dir = if data.body.can_strafe() {
data.inputs.move_dir
* if is_strafing(data, update) {
} else {
Vec2::from(*data.ori)
};
let accel_mod = if is_strafing(data, update) {
Lerp::lerp(
Vec2::from(update.ori)
Vec2::from(*data.ori)
.try_normalized()
.unwrap_or_else(Vec2::zero)
.dot(
@ -392,12 +393,11 @@ fn basic_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
data.body.reverse_move_factor(),
)
} else {
1.0
}
} else {
let fw = Vec2::from(update.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0)
data.inputs.move_dir.dot(dir).max(0.0)
};
let accel = accel * accel_mod;
update.movement = update.movement.with_movement(MovementKind::Ground { dir, accel });
}
/// Handles forced movement
@ -413,10 +413,11 @@ pub fn handle_forced_movement(
// FRIC_GROUND temporarily used to normalize things around expected values
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
}) {
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* (data.inputs.move_dir * 0.5 + Vec2::from(update.ori) * 1.5)
* strength;
// TODO: Remove * 2.0 with changes made in sword branch, added as hack for now to keep same behavior
let accel = accel * strength * 2.0;
// TODO: Remove move_dir portion with changes made in sword branch, added as hack for now to keep same behavior
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 {
@ -426,26 +427,12 @@ pub fn handle_forced_movement(
direction,
} => {
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.
// This allows players to stop moving forward when they
// look downward at target
* (1.0 - data.inputs.look_dir.z.abs());
},
ForcedMovement::Hover { move_input } => {
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();
let forward = forward * (1.0 - data.inputs.look_dir.z.abs());
update.movement = update.movement.with_movement(MovementKind::Leap { dir, vertical, forward, progress });
},
}
}
@ -494,22 +481,24 @@ pub fn handle_orientation(
}
* data.dt.0;
// 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;
update.ori = if instantaneous {
let new_ori = if instantaneous {
target_ori
} else {
let target_fraction = {
// Angle factor used to keep turning rate approximately constant by
// 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
};
update
data
.ori
.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
@ -532,7 +521,7 @@ fn swim_move(
let dir = if data.body.can_strafe() {
data.inputs.move_dir
} else {
let fw = Vec2::from(update.ori);
let fw = Vec2::from(*data.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0)
};
@ -543,12 +532,9 @@ fn swim_move(
data.inputs.move_z
};
update.vel.0 += Vec3::broadcast(data.dt.0)
* Vec3::new(dir.x, dir.y, move_z)
.try_normalized()
.unwrap_or_default()
* water_accel
* (submersion - 0.2).clamp(0.0, 1.0).powi(2);
let dir = Vec3::new(dir.x, dir.y, move_z);
let accel = water_accel * (submersion - 0.2).clamp(0.0, 1.0).powi(2);
update.movement = update.movement.with_movement(MovementKind::Swim { dir, accel });
true
} else {
@ -575,11 +561,11 @@ pub fn fly_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32)
handle_orientation(data, update, efficiency, None);
// Elevation control
match data.body {
let lift = match data.body {
// flappy flappy
Body::Dragon(_) | Body::BirdLarge(_) | Body::BirdMedium(_) => {
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
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 =
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
// TODO: refactor to make this state impossible
_ => {},
_ => 0.0,
};
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* if data.body.can_strafe() {
let dir = if data.body.can_strafe() {
data.inputs.move_dir
} else {
let fw = Vec2::from(update.ori);
let fw = Vec2::from(*data.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0)
};
update.movement = update.movement.with_movement(MovementKind::Flight { lift, dir, accel });
true
} else {
false
@ -1195,9 +1183,6 @@ pub enum ForcedMovement {
progress: f32,
direction: MovementDirection,
},
Hover {
move_input: f32,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

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

View File

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

View File

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

View File

@ -3,8 +3,8 @@ use common::{
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
fluid_dynamics::{Fluid, LiquidKind, Wings},
inventory::item::armor::Friction,
Body, CharacterState, Collider, Density, Immovable, Mass, Ori, PhysicsState, Pos,
PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
Body, CharacterState, Collider, Density, Immovable, Mass, MovementState, Ori, PhysicsState,
Pos, PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
},
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent},
@ -124,6 +124,7 @@ pub struct PhysicsRead<'a> {
densities: ReadStorage<'a, Density>,
stats: ReadStorage<'a, Stats>,
outcomes: Read<'a, EventBus<Outcome>>,
movements: ReadStorage<'a, MovementState>,
}
#[derive(SystemData)]
@ -1357,6 +1358,17 @@ impl<'a> System<'a> for Sys {
ref mut write,
} = 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, ()) = rayon::join(

View File

@ -272,6 +272,7 @@ impl StateExt for State {
.with(comp::Buffs::default())
.with(comp::Combo::default())
.with(comp::Auras::default())
.with(comp::MovementState::default())
}
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::SkillSet::default())
.with(comp::ActiveAbilities::default())
.with(comp::Combo::default());
.with(comp::Combo::default())
.with(comp::MovementState::default());
if mountable {
// 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::Auras::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
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());