From 5d5ccd7b992c751d7fb51d553b1d08ba760800c2 Mon Sep 17 00:00:00 2001 From: timokoesters Date: Fri, 23 Aug 2019 12:11:37 +0200 Subject: [PATCH] Move from state components to single CharaterState struct This makes split animations easy and improves overall code quality --- client/src/lib.rs | 6 +- common/src/assets/mod.rs | 2 +- common/src/comp/action_state.rs | 29 ----- common/src/comp/character_state.rs | 68 ++++++++++ common/src/comp/mod.rs | 12 +- common/src/comp/phys.rs | 10 ++ common/src/event.rs | 17 ++- common/src/msg/server.rs | 4 +- common/src/state.rs | 15 +-- common/src/sys/action_state.rs | 69 ----------- common/src/sys/agent.rs | 13 +- common/src/sys/animation.rs | 54 ++++---- common/src/sys/combat.rs | 63 +++++----- common/src/sys/controller.rs | 132 +++++++++++--------- common/src/sys/mod.rs | 11 +- common/src/sys/movement.rs | 136 +++++++++----------- common/src/sys/phys.rs | 58 +++++---- common/src/sys/stats.rs | 29 +++-- server/src/lib.rs | 193 ++++++++++++++--------------- 19 files changed, 447 insertions(+), 474 deletions(-) delete mode 100644 common/src/comp/action_state.rs create mode 100644 common/src/comp/character_state.rs delete mode 100644 common/src/sys/action_state.rs diff --git a/client/src/lib.rs b/client/src/lib.rs index bb1bbf2873..5f4ca2798d 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -444,12 +444,12 @@ impl Client { self.state.write_component(entity, ori); } } - ServerMsg::EntityActionState { + ServerMsg::EntityCharacterState { entity, - action_state, + character_state, } => { if let Some(entity) = self.state.ecs().entity_from_uid(entity) { - self.state.write_component(entity, action_state); + self.state.write_component(entity, character_state); } } ServerMsg::InventoryUpdate(inventory) => { diff --git a/common/src/assets/mod.rs b/common/src/assets/mod.rs index 9f34be133a..2aa0e04fdb 100644 --- a/common/src/assets/mod.rs +++ b/common/src/assets/mod.rs @@ -188,8 +188,8 @@ impl Asset for String { } } -/// Lazy static to find and cache where the asset directory is. lazy_static! { + /// Lazy static to find and cache where the asset directory is. static ref ASSETS_PATH: PathBuf = { let mut paths = Vec::new(); diff --git a/common/src/comp/action_state.rs b/common/src/comp/action_state.rs deleted file mode 100644 index d638318538..0000000000 --- a/common/src/comp/action_state.rs +++ /dev/null @@ -1,29 +0,0 @@ -use specs::{Component, FlaggedStorage, HashMapStorage}; -use specs_idvs::IDVStorage; - -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] -pub struct ActionState { - pub moving: bool, - pub on_ground: bool, - pub attacking: bool, - pub rolling: bool, - pub gliding: bool, - pub wielding: bool, -} - -impl Default for ActionState { - fn default() -> Self { - Self { - moving: false, - on_ground: false, - attacking: false, - rolling: false, - gliding: false, - wielding: false, - } - } -} - -impl Component for ActionState { - type Storage = FlaggedStorage>; -} diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs new file mode 100644 index 0000000000..a7cffd1724 --- /dev/null +++ b/common/src/comp/character_state.rs @@ -0,0 +1,68 @@ +use specs::{Component, FlaggedStorage, HashMapStorage}; +use specs_idvs::IDVStorage; +use std::time::Duration; + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum MovementState { + Stand, + Run, + Jump, + Glide, + Roll { time_left: Duration }, + //Swim, +} + +impl MovementState { + pub fn is_roll(&self) -> bool { + if let Self::Roll { .. } = self { + true + } else { + false + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum ActionState { + Idle, + Wield { time_left: Duration }, + Attack { time_left: Duration, applied: bool }, + //Carry, +} + +impl ActionState { + pub fn is_wield(&self) -> bool { + if let Self::Wield { .. } = self { + true + } else { + false + } + } + + pub fn is_attack(&self) -> bool { + if let Self::Attack { .. } = self { + true + } else { + false + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct CharacterState { + pub movement: MovementState, + pub action: ActionState, +} + +impl Default for CharacterState { + fn default() -> Self { + Self { + movement: MovementState::Jump, + action: ActionState::Idle, + } + } +} + +impl Component for CharacterState { + type Storage = FlaggedStorage>; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index da82ee09af..22b9d4c52e 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -1,8 +1,8 @@ -mod action_state; mod admin; mod agent; mod animation; mod body; +mod character_state; mod controller; mod inputs; mod inventory; @@ -13,18 +13,16 @@ mod stats; mod visual; // Reexports -pub use action_state::ActionState; pub use admin::Admin; pub use agent::Agent; pub use animation::{Animation, AnimationInfo}; pub use body::{humanoid, object, quadruped, quadruped_medium, Body}; +pub use character_state::{ActionState, CharacterState, MovementState}; pub use controller::Controller; -pub use inputs::{ - Attacking, CanBuild, Gliding, Jumping, MoveDir, OnGround, Respawning, Rolling, Wielding, -}; +pub use inputs::CanBuild; pub use inventory::{item, Inventory, InventoryUpdate, Item}; pub use last::Last; -pub use phys::{ForceUpdate, Ori, Pos, Scale, Vel}; +pub use phys::{ForceUpdate, Ori, PhysicsState, Pos, Scale, Vel}; pub use player::Player; -pub use stats::{Dying, Exp, HealthSource, Level, Stats}; +pub use stats::{Exp, HealthSource, Level, Stats}; pub use visual::LightEmitter; diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 5c7732886e..f8787a633a 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -34,6 +34,16 @@ impl Component for Scale { type Storage = FlaggedStorage>; } +// PhysicsState +#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct PhysicsState { + pub on_ground: bool, +} + +impl Component for PhysicsState { + type Storage = FlaggedStorage>; +} + // ForceUpdate #[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct ForceUpdate; diff --git a/common/src/event.rs b/common/src/event.rs index 464610d921..acaac6cf48 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -1,11 +1,24 @@ +use crate::comp; use parking_lot::Mutex; use specs::Entity as EcsEntity; use std::{collections::VecDeque, ops::DerefMut}; use vek::*; pub enum Event { - LandOnGround { entity: EcsEntity, vel: Vec3 }, - Explosion { pos: Vec3, radius: f32 }, + LandOnGround { + entity: EcsEntity, + vel: Vec3, + }, + Explosion { + pos: Vec3, + radius: f32, + }, + Die { + entity: EcsEntity, + cause: comp::HealthSource, + }, + Jump(EcsEntity), + Respawn(EcsEntity), } #[derive(Default)] diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 6dbf86234c..5b12429a2a 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -51,9 +51,9 @@ pub enum ServerMsg { entity: u64, ori: comp::Ori, }, - EntityActionState { + EntityCharacterState { entity: u64, - action_state: comp::ActionState, + character_state: comp::CharacterState, }, InventoryUpdate(comp::Inventory), TerrainChunkUpdate { diff --git a/common/src/state.rs b/common/src/state.rs index c73a3602b6..cb89aff217 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -122,7 +122,8 @@ impl State { ecs.register::(); // Register components send directly from server -> all but one client - ecs.register::(); + ecs.register::(); + ecs.register::(); // Register components synced from client -> server -> all other clients ecs.register::(); @@ -132,27 +133,17 @@ impl State { // Register client-local components ecs.register::(); - ecs.register::(); // Register server-local components ecs.register::>(); ecs.register::>(); ecs.register::>(); - ecs.register::>(); + ecs.register::>(); ecs.register::(); - ecs.register::(); - ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); - // Controller effects - ecs.register::(); - ecs.register::(); - ecs.register::(); - ecs.register::(); - ecs.register::(); - ecs.register::(); // Register synced resources used by the ECS. ecs.add_resource_synced(TimeOfDay(0.0)); diff --git a/common/src/sys/action_state.rs b/common/src/sys/action_state.rs deleted file mode 100644 index fd8d1ee11a..0000000000 --- a/common/src/sys/action_state.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::{ - comp::{ActionState, Attacking, Controller, Gliding, OnGround, Rolling, Vel, Wielding}, - sys::movement::MOVEMENT_THRESHOLD_VEL, -}; -use specs::{Entities, Join, ReadStorage, System, WriteStorage}; - -/// This system will set the ActionState component as specified by other components -pub struct Sys; -impl<'a> System<'a> for Sys { - type SystemData = ( - Entities<'a>, - ReadStorage<'a, Controller>, - ReadStorage<'a, Vel>, - ReadStorage<'a, OnGround>, - ReadStorage<'a, Gliding>, - ReadStorage<'a, Attacking>, - ReadStorage<'a, Wielding>, - ReadStorage<'a, Rolling>, - WriteStorage<'a, ActionState>, - ); - - fn run( - &mut self, - ( - entities, - controllers, // To make sure it only runs on the single client and the server - velocities, - on_grounds, - glidings, - attackings, - wieldings, - rollings, - mut action_states, - ): Self::SystemData, - ) { - for ( - _entity, - vel, - _controller, - on_ground, - gliding, - attacking, - wielding, - rolling, - action_state, - ) in ( - &entities, - &velocities, - &controllers, - on_grounds.maybe(), - glidings.maybe(), - attackings.maybe(), - wieldings.maybe(), - rollings.maybe(), - &mut action_states, - ) - .join() - { - *action_state = ActionState { - on_ground: on_ground.is_some(), - moving: vel.0.magnitude_squared() > MOVEMENT_THRESHOLD_VEL.powf(2.0), - attacking: attacking.is_some(), - wielding: wielding.is_some(), - rolling: rolling.is_some(), - gliding: gliding.is_some(), - }; - } - } -} diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 4a1c3d9d4e..1ce71eee45 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -1,4 +1,4 @@ -use crate::comp::{ActionState, Agent, Controller, Pos, Stats}; +use crate::comp::{Agent, CharacterState, Controller, MovementState::Glide, Pos, Stats}; use rand::{seq::SliceRandom, thread_rng}; use specs::{Entities, Join, ReadStorage, System, WriteStorage}; use vek::*; @@ -10,14 +10,14 @@ impl<'a> System<'a> for Sys { Entities<'a>, ReadStorage<'a, Pos>, ReadStorage<'a, Stats>, - ReadStorage<'a, ActionState>, + ReadStorage<'a, CharacterState>, WriteStorage<'a, Agent>, WriteStorage<'a, Controller>, ); fn run( &mut self, - (entities, positions, stats, action_states, mut agents, mut controllers): Self::SystemData, + (entities, positions, stats, character_states, mut agents, mut controllers): Self::SystemData, ) { for (entity, pos, agent, controller) in (&entities, &positions, &mut agents, &mut controllers).join() @@ -67,12 +67,12 @@ impl<'a> System<'a> for Sys { const SIGHT_DIST: f32 = 30.0; let mut choose_new = false; - if let Some((Some(target_pos), Some(target_stats), Some(a))) = + if let Some((Some(target_pos), Some(target_stats), Some(target_character))) = target.map(|target| { ( positions.get(target), stats.get(target), - action_states.get(target), + character_states.get(target), ) }) { @@ -97,7 +97,8 @@ impl<'a> System<'a> for Sys { controller.roll = true; } - if a.gliding && target_pos.0.z > pos.0.z + 5.0 { + if target_character.movement == Glide && target_pos.0.z > pos.0.z + 5.0 + { controller.glide = true; controller.jump = true; } diff --git a/common/src/sys/animation.rs b/common/src/sys/animation.rs index d03938668f..35f56bacaa 100644 --- a/common/src/sys/animation.rs +++ b/common/src/sys/animation.rs @@ -1,8 +1,11 @@ use crate::{ - comp::{ActionState, Animation, AnimationInfo}, + comp::{ + ActionState::*, Animation, AnimationInfo, CharacterState, MovementState::*, PhysicsState, + }, state::DeltaTime, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; +use std::fmt::Debug; /// This system will apply the animation that fits best to the users actions pub struct Sys; @@ -10,37 +13,32 @@ impl<'a> System<'a> for Sys { type SystemData = ( Entities<'a>, Read<'a, DeltaTime>, - ReadStorage<'a, ActionState>, + ReadStorage<'a, CharacterState>, + ReadStorage<'a, PhysicsState>, WriteStorage<'a, AnimationInfo>, ); - fn run(&mut self, (entities, dt, action_states, mut animation_infos): Self::SystemData) { - for (entity, a) in (&entities, &action_states).join() { - fn impossible_animation(message: &str) -> Animation { - warn!("{}", message); - Animation::Idle + fn run( + &mut self, + (entities, dt, character_states, physics_states, mut animation_infos): Self::SystemData, + ) { + for (entity, character, physics) in (&entities, &character_states, &physics_states).join() { + fn impossible_animation(physics: PhysicsState, character: CharacterState) -> Animation { + warn!("Impossible animation: {:?} {:?}", physics, character); + Animation::Roll } - let animation = match ( - a.on_ground, - a.moving, - a.attacking, - a.gliding, - a.rolling, - a.wielding, - ) { - (_, _, true, true, _, _) => impossible_animation("Attack while gliding"), - (_, _, true, _, true, _) => impossible_animation("Roll while attacking"), - (_, _, _, true, true, _) => impossible_animation("Roll while gliding"), - (_, false, _, _, true, _) => impossible_animation("Roll without moving"), - (_, true, false, false, true, _) => Animation::Roll, - (true, false, false, false, false, false) => Animation::Idle, - (true, true, false, false, false, false) => Animation::Run, - (false, _, false, false, false, false) => Animation::Jump, - (true, false, false, false, false, true) => Animation::Cidle, - (true, true, false, false, false, true) => Animation::Crun, - (false, _, false, false, false, true) => Animation::Cjump, - (_, _, false, true, false, _) => Animation::Gliding, - (_, _, true, false, false, _) => Animation::Attack, + + let animation = match (physics.on_ground, &character.movement, &character.action) { + (_, Roll { .. }, Idle) => Animation::Roll, + (true, Stand, Idle) => Animation::Idle, + (true, Run, Idle) => Animation::Run, + (false, Jump, Idle) => Animation::Jump, + (true, Stand, Wield { .. }) => Animation::Cidle, + (true, Run, Wield { .. }) => Animation::Crun, + (false, Jump, Wield { .. }) => Animation::Cjump, + (_, Glide, Idle) => Animation::Gliding, + (_, _, Attack { .. }) => Animation::Attack, + _ => impossible_animation(physics.clone(), character.clone()), }; let new_time = animation_infos diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index ba0e8a8e37..6181854063 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -1,8 +1,9 @@ use crate::{ - comp::{Attacking, ForceUpdate, HealthSource, Ori, Pos, Stats, Vel, Wielding}, + comp::{ActionState::*, CharacterState, ForceUpdate, HealthSource, Ori, Pos, Stats, Vel}, state::{DeltaTime, Uid}, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; +use std::time::Duration; /// This system is responsible for handling accepted inputs like moving or attacking pub struct Sys; @@ -14,8 +15,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Pos>, ReadStorage<'a, Ori>, WriteStorage<'a, Vel>, - WriteStorage<'a, Attacking>, - WriteStorage<'a, Wielding>, + WriteStorage<'a, CharacterState>, WriteStorage<'a, Stats>, WriteStorage<'a, ForceUpdate>, ); @@ -29,18 +29,26 @@ impl<'a> System<'a> for Sys { positions, orientations, mut velocities, - mut attackings, - mut wieldings, + mut character_states, mut stats, mut force_updates, ): Self::SystemData, ) { // Attacks - (&entities, &uids, &positions, &orientations, &mut attackings) + for (entity, uid, pos, ori, mut character) in ( + &entities, + &uids, + &positions, + &orientations, + &mut character_states, + ) .join() - .filter_map(|(entity, uid, pos, ori, mut attacking)| { - if !attacking.applied { - // Go through all other entities + { + let mut todo_end = false; + + // Go through all other entities + if let Attack { time_left, applied } = &mut character.action { + if !*applied { for (b, pos_b, mut vel_b, stat_b) in (&entities, &positions, &mut velocities, &mut stats).join() { @@ -59,29 +67,28 @@ impl<'a> System<'a> for Sys { let _ = force_updates.insert(b, ForceUpdate); } } - attacking.applied = true; + *applied = true; } - if attacking.time > 0.5 { - Some(entity) + if *time_left == Duration::default() { + todo_end = true; } else { - attacking.time += dt.0; - - None + *time_left = time_left + .checked_sub(Duration::from_secs_f32(dt.0)) + .unwrap_or_default(); } - }) - .collect::>() - .into_iter() - .for_each(|e| { - attackings.remove(e); - }); - { - // Wields - for wielding in (&mut wieldings).join() { - if !wielding.applied && wielding.time > 0.3 { - wielding.applied = true; - } else { - wielding.time += dt.0; + } + if todo_end { + character.action = Wield { + time_left: Duration::default(), + }; + } + + if let Wield { time_left } = &mut character.action { + if *time_left != Duration::default() { + *time_left = time_left + .checked_sub(Duration::from_secs_f32(dt.0)) + .unwrap_or_default(); } } } diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index a32ba98af8..fb663e4895 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -1,125 +1,135 @@ -use crate::comp::{ - ActionState, Attacking, Body, Controller, Gliding, Jumping, MoveDir, Respawning, Rolling, - Stats, Vel, Wielding, +use crate::{ + comp::{ + ActionState::*, Body, CharacterState, Controller, MovementState::*, PhysicsState, Stats, + Vel, + }, + event::{Event, EventBus}, }; -use specs::{Entities, Join, ReadStorage, System, WriteStorage}; +use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; +use std::time::Duration; /// This system is responsible for validating controller inputs pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( Entities<'a>, + Read<'a, EventBus>, WriteStorage<'a, Controller>, ReadStorage<'a, Stats>, ReadStorage<'a, Body>, ReadStorage<'a, Vel>, - WriteStorage<'a, ActionState>, - WriteStorage<'a, MoveDir>, - WriteStorage<'a, Jumping>, - WriteStorage<'a, Attacking>, - WriteStorage<'a, Wielding>, - WriteStorage<'a, Rolling>, - WriteStorage<'a, Respawning>, - WriteStorage<'a, Gliding>, + ReadStorage<'a, PhysicsState>, + WriteStorage<'a, CharacterState>, ); fn run( &mut self, ( entities, + event_bus, mut controllers, stats, bodies, velocities, - mut action_states, - mut move_dirs, - mut jumpings, - mut attackings, - mut wieldings, - mut rollings, - mut respawns, - mut glidings, + physics_states, + mut character_states, ): Self::SystemData, ) { - for (entity, controller, stats, body, vel, mut a) in ( + let mut event_emitter = event_bus.emitter(); + + for (entity, controller, stats, body, vel, physics, mut character) in ( &entities, &mut controllers, &stats, &bodies, &velocities, - // Although this is changed, it is only kept for this system - // as it will be replaced in the action state system - &mut action_states, + &physics_states, + &mut character_states, ) .join() { if stats.is_dead { // Respawn if controller.respawn { - let _ = respawns.insert(entity, Respawning); + event_emitter.emit(Event::Respawn(entity)); } continue; } - // Move dir - if !a.rolling { - let _ = move_dirs.insert( - entity, - MoveDir(if controller.move_dir.magnitude_squared() > 1.0 { - controller.move_dir.normalized() - } else { - controller.move_dir - }), - ); + // Move + controller.move_dir = if controller.move_dir.magnitude_squared() > 1.0 { + controller.move_dir.normalized() + } else { + controller.move_dir + }; + + if character.movement == Stand && controller.move_dir.magnitude_squared() > 0.0 { + character.movement = Run; + } else if character.movement == Run && controller.move_dir.magnitude_squared() == 0.0 { + character.movement = Stand; } // Glide - if controller.glide && !a.on_ground && !a.attacking && !a.rolling && body.is_humanoid() + if controller.glide + && !physics.on_ground + && (character.action == Idle || character.action.is_wield()) + && character.movement == Jump + // TODO: Ask zesterer if we can remove this + && body.is_humanoid() { - let _ = glidings.insert(entity, Gliding); - a.gliding = true; - } else { - let _ = glidings.remove(entity); - a.gliding = false; + character.movement = Glide; + } else if !controller.glide && character.movement == Glide { + character.movement = Jump; } // Wield - if controller.attack && !a.wielding && !a.gliding && !a.rolling { - let _ = wieldings.insert(entity, Wielding::start()); - a.wielding = true; + if controller.attack + && character.action == Idle + && (character.movement == Stand || character.movement == Run) + { + character.action = Wield { + time_left: Duration::from_millis(300), + }; } // Attack if controller.attack - && !a.attacking - && wieldings.get(entity).map(|w| w.applied).unwrap_or(false) - && !a.gliding - && !a.rolling + && (character.movement == Stand + || character.movement == Run + || character.movement == Jump) { - let _ = attackings.insert(entity, Attacking::start()); - a.attacking = true; + // TODO: Check if wield ability exists + if let Wield { time_left } = character.action { + if time_left == Duration::default() { + character.action = Attack { + time_left: Duration::from_millis(300), + applied: false, + }; + } + } } // Roll if controller.roll - && !a.rolling - && a.on_ground - && a.moving - && !a.attacking - && !a.gliding + && (character.action == Idle || character.action.is_wield()) + && character.movement == Run + && physics.on_ground { - let _ = rollings.insert(entity, Rolling::start()); - a.rolling = true; + character.movement = Roll { + time_left: Duration::from_millis(600), + }; } // Jump - if controller.jump && a.on_ground && vel.0.z <= 0.0 { - let _ = jumpings.insert(entity, Jumping); - a.on_ground = false; + if controller.jump && physics.on_ground && vel.0.z <= 0.0 { + dbg!(); + event_emitter.emit(Event::Jump(entity)); } + // TODO before merge: reset controller in a final ecs system + // Reset the controller ready for the next tick - *controller = Controller::default(); + //*controller = Controller::default(); } } } diff --git a/common/src/sys/mod.rs b/common/src/sys/mod.rs index 7664b4af8a..dc6be61e98 100644 --- a/common/src/sys/mod.rs +++ b/common/src/sys/mod.rs @@ -1,4 +1,3 @@ -mod action_state; pub mod agent; pub mod animation; pub mod combat; @@ -13,7 +12,6 @@ use specs::DispatcherBuilder; // System names const AGENT_SYS: &str = "agent_sys"; const CONTROLLER_SYS: &str = "controller_sys"; -const ACTION_STATE_SYS: &str = "action_state_sys"; const PHYS_SYS: &str = "phys_sys"; const MOVEMENT_SYS: &str = "movement_sys"; const COMBAT_SYS: &str = "combat_sys"; @@ -25,12 +23,7 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]); dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS]); dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[PHYS_SYS]); - dispatch_builder.add( - action_state::Sys, - ACTION_STATE_SYS, - &[CONTROLLER_SYS, PHYS_SYS], - ); - dispatch_builder.add(combat::Sys, COMBAT_SYS, &[ACTION_STATE_SYS]); - dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[ACTION_STATE_SYS]); + dispatch_builder.add(combat::Sys, COMBAT_SYS, &[CONTROLLER_SYS]); + dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[CONTROLLER_SYS]); dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]); } diff --git a/common/src/sys/movement.rs b/common/src/sys/movement.rs index 5fadb37a4c..f7c11e2862 100644 --- a/common/src/sys/movement.rs +++ b/common/src/sys/movement.rs @@ -1,10 +1,14 @@ use crate::{ - comp::{ActionState, Jumping, MoveDir, OnGround, Ori, Pos, Rolling, Stats, Vel, Wielding}, + comp::{ + ActionState::*, CharacterState, Controller, MovementState::*, Ori, PhysicsState, Pos, + Stats, Vel, + }, state::DeltaTime, terrain::TerrainMap, vol::{ReadVol, Vox}, }; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; +use std::time::Duration; use vek::*; const HUMANOID_ACCEL: f32 = 70.0; @@ -13,7 +17,6 @@ const WIELD_ACCEL: f32 = 70.0; const WIELD_SPEED: f32 = 120.0; const HUMANOID_AIR_ACCEL: f32 = 10.0; const HUMANOID_AIR_SPEED: f32 = 100.0; -const HUMANOID_JUMP_ACCEL: f32 = 18.0; const ROLL_ACCEL: f32 = 160.0; const ROLL_SPEED: f32 = 550.0; const GLIDE_ACCEL: f32 = 15.0; @@ -30,13 +33,10 @@ impl<'a> System<'a> for Sys { Entities<'a>, ReadExpect<'a, TerrainMap>, Read<'a, DeltaTime>, - ReadStorage<'a, MoveDir>, ReadStorage<'a, Stats>, - ReadStorage<'a, ActionState>, - WriteStorage<'a, Jumping>, - WriteStorage<'a, Wielding>, - WriteStorage<'a, Rolling>, - WriteStorage<'a, OnGround>, + ReadStorage<'a, Controller>, + ReadStorage<'a, PhysicsState>, + WriteStorage<'a, CharacterState>, WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, @@ -48,105 +48,87 @@ impl<'a> System<'a> for Sys { entities, terrain, dt, - move_dirs, stats, - action_states, - mut jumpings, - mut wieldings, - mut rollings, - mut on_grounds, + controllers, + physics_states, + mut character_states, mut positions, mut velocities, mut orientations, ): Self::SystemData, ) { // Apply movement inputs - for (entity, stats, a, move_dir, mut pos, mut vel, mut ori) in ( + for (entity, stats, controller, physics, mut character, mut pos, mut vel, mut ori) in ( &entities, &stats, - &action_states, - move_dirs.maybe(), + &controllers, + &physics_states, + &mut character_states, &mut positions, &mut velocities, &mut orientations, ) .join() { - // Disable while dead TODO: Replace with client states? if stats.is_dead { continue; } // Move player according to move_dir - if let Some(move_dir) = move_dir { - vel.0 += Vec2::broadcast(dt.0) - * move_dir.0 - * match (a.on_ground, a.gliding, a.rolling, a.wielding) { - (true, false, false, false) - if vel.0.magnitude_squared() < HUMANOID_SPEED.powf(2.0) => - { - HUMANOID_ACCEL - } - (false, true, false, false) - if vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) => - { - GLIDE_ACCEL - } - (false, false, false, false) - if vel.0.magnitude_squared() < HUMANOID_AIR_SPEED.powf(2.0) => - { - HUMANOID_AIR_ACCEL - } - (true, false, true, _) - if vel.0.magnitude_squared() < ROLL_SPEED.powf(2.0) => - { - ROLL_ACCEL - } - (true, false, false, true) - if vel.0.magnitude_squared() < WIELD_SPEED.powf(2.0) => - { - WIELD_ACCEL - } - _ => 0.0, - }; - - // Set direction based on move direction when on the ground - let ori_dir = if a.gliding || a.rolling { - Vec2::from(vel.0) - } else { - move_dir.0 + vel.0 += Vec2::broadcast(dt.0) + * controller.move_dir + * match (physics.on_ground, &character.movement) { + (true, Run) if vel.0.magnitude_squared() < HUMANOID_SPEED.powf(2.0) => { + HUMANOID_ACCEL + } + (false, Glide) if vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) => { + GLIDE_ACCEL + } + (false, Jump) if vel.0.magnitude_squared() < HUMANOID_AIR_SPEED.powf(2.0) => { + HUMANOID_AIR_ACCEL + } + (true, Roll { .. }) if vel.0.magnitude_squared() < ROLL_SPEED.powf(2.0) => { + ROLL_ACCEL + } + _ => 0.0, }; - if ori_dir.magnitude_squared() > 0.0001 - && (ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared() - > 0.001 - { - ori.0 = vek::ops::Slerp::slerp( - ori.0, - ori_dir.into(), - if a.on_ground { 12.0 } else { 2.0 } * dt.0, - ); - } - } - // Jump - if jumpings.get(entity).is_some() { - vel.0.z = HUMANOID_JUMP_ACCEL; - jumpings.remove(entity); + // Set direction based on move direction when on the ground + let ori_dir = if character.movement == Glide || character.movement.is_roll() { + Vec2::from(vel.0) + } else { + controller.move_dir + }; + if ori_dir.magnitude_squared() > 0.0001 + && (ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared() + > 0.001 + { + ori.0 = vek::ops::Slerp::slerp( + ori.0, + ori_dir.into(), + if physics.on_ground { 12.0 } else { 2.0 } * dt.0, + ); } // Glide - if a.gliding && vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) && vel.0.z < 0.0 { - let _ = wieldings.remove(entity); + if character.movement == Glide + && vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) + && vel.0.z < 0.0 + { + character.action = Idle; let lift = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2; vel.0.z += dt.0 * lift * Vec2::::from(vel.0 * 0.15).magnitude().min(1.0); } // Roll - if let Some(time) = rollings.get_mut(entity).map(|r| &mut r.time) { - let _ = wieldings.remove(entity); - *time += dt.0; - if *time > 0.6 || !a.moving { - rollings.remove(entity); + if let Roll { time_left } = &mut character.movement { + character.action = Idle; + if *time_left == Duration::default() || vel.0.magnitude_squared() < 10.0 { + character.movement = Run; + } else { + *time_left = time_left + .checked_sub(Duration::from_secs_f32(dt.0)) + .unwrap_or_default(); } } } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index a60a78ca39..352404fb69 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -1,16 +1,14 @@ -use crate::{ - comp::HealthSource, - comp::{ - ActionState, Body, Jumping, MoveDir, OnGround, Ori, Pos, Rolling, Scale, Stats, Vel, - Wielding, +use { + crate::{ + comp::{Body, CharacterState, MovementState::*, Ori, PhysicsState, Pos, Scale, Stats, Vel}, + event::{Event, EventBus}, + state::DeltaTime, + terrain::TerrainMap, + vol::{ReadVol, Vox}, }, - event::{Event, EventBus}, - state::DeltaTime, - terrain::TerrainMap, - vol::{ReadVol, Vox}, + specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}, + vek::*, }; -use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; -use vek::*; const GRAVITY: f32 = 9.81 * 4.0; const FRIC_GROUND: f32 = 0.15; @@ -37,10 +35,10 @@ impl<'a> System<'a> for Sys { ReadExpect<'a, TerrainMap>, Read<'a, DeltaTime>, Read<'a, EventBus>, - ReadStorage<'a, ActionState>, ReadStorage<'a, Scale>, ReadStorage<'a, Body>, - WriteStorage<'a, OnGround>, + WriteStorage<'a, CharacterState>, + WriteStorage<'a, PhysicsState>, WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, @@ -53,10 +51,10 @@ impl<'a> System<'a> for Sys { terrain, dt, event_bus, - action_states, scales, bodies, - mut on_grounds, + mut character_states, + mut physics_states, mut positions, mut velocities, mut orientations, @@ -65,9 +63,8 @@ impl<'a> System<'a> for Sys { let mut event_emitter = event_bus.emitter(); // Apply movement inputs - for (entity, a, scale, b, mut pos, mut vel, mut ori) in ( + for (entity, scale, b, mut pos, mut vel, mut ori) in ( &entities, - &action_states, scales.maybe(), &bodies, &mut positions, @@ -76,12 +73,14 @@ impl<'a> System<'a> for Sys { ) .join() { + let mut character_state = character_states.get(entity).cloned().unwrap_or_default(); + let mut physics_state = physics_states.get(entity).cloned().unwrap_or_default(); let scale = scale.map(|s| s.0).unwrap_or(1.0); // Integrate forces // Friction is assumed to be a constant dependent on location let friction = 50.0 - * if on_grounds.get(entity).is_some() { + * if physics_state.on_ground { FRIC_GROUND } else { FRIC_AIR @@ -108,7 +107,7 @@ impl<'a> System<'a> for Sys { if terrain .get(block_pos) - .map(|vox| vox.is_solid()) + .map(|vox| !vox.is_empty()) .unwrap_or(false) { let player_aabb = Aabb { @@ -128,8 +127,8 @@ impl<'a> System<'a> for Sys { false }; - let was_on_ground = a.on_ground; - on_grounds.remove(entity); // Assume we're in the air - unless we can prove otherwise + let was_on_ground = physics_state.on_ground; + physics_state.on_ground = false; let mut on_ground = false; let mut attempts = 0; // Don't loop infinitely here @@ -183,7 +182,7 @@ impl<'a> System<'a> for Sys { .filter(|(block_pos, _)| { terrain .get(*block_pos) - .map(|vox| vox.is_solid()) + .map(|vox| !vox.is_empty()) .unwrap_or(false) }) // Find the maximum of the minimum collision axes (this bit is weird, trust me that it works) @@ -216,6 +215,7 @@ impl<'a> System<'a> for Sys { if !was_on_ground { event_emitter.emit(Event::LandOnGround { entity, vel: vel.0 }); + character_state.movement = Stand; } } @@ -257,13 +257,16 @@ impl<'a> System<'a> for Sys { if attempts == MAX_ATTEMPTS { pos.0 = old_pos; - vel.0 = Vec3::zero(); break; } } if on_ground { - let _ = on_grounds.insert(entity, OnGround); + physics_state.on_ground = true; + + if !was_on_ground { + character_state.movement = Stand; + } // If the space below us is free, then "snap" to the ground } else if collision_with(pos.0 - Vec3::unit_z() * 1.05, near_iter.clone()) && vel.0.z < 0.0 @@ -271,8 +274,13 @@ impl<'a> System<'a> for Sys { && was_on_ground { pos.0.z = (pos.0.z - 0.05).floor(); - let _ = on_grounds.insert(entity, OnGround); + physics_state.on_ground = true; + } else if was_on_ground { + character_state.movement = Jump; } + + let _ = character_states.insert(entity, character_state); + let _ = physics_states.insert(entity, physics_state); } // Apply pushback diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs index 270ddf7860..1ebd322a2c 100644 --- a/common/src/sys/stats.rs +++ b/common/src/sys/stats.rs @@ -1,5 +1,6 @@ use crate::{ - comp::{Dying, HealthSource, Stats}, + comp::{HealthSource, Stats}, + event::{Event, EventBus}, state::DeltaTime, }; use log::warn; @@ -11,27 +12,29 @@ impl<'a> System<'a> for Sys { type SystemData = ( Entities<'a>, Read<'a, DeltaTime>, + Read<'a, EventBus>, WriteStorage<'a, Stats>, - WriteStorage<'a, Dying>, ); - fn run(&mut self, (entities, dt, mut stats, mut dyings): Self::SystemData) { + fn run(&mut self, (entities, dt, event_bus, mut stats): Self::SystemData) { + let mut event_emitter = event_bus.emitter(); + for (entity, mut stat) in (&entities, &mut stats).join() { if stat.should_die() && !stat.is_dead { - let _ = dyings.insert( + event_emitter.emit(Event::Die { entity, - Dying { - cause: match stat.health.last_change { - Some(change) => change.2, - None => { - warn!("Nothing caused an entity to die!"); - HealthSource::Unknown - } - }, + cause: match stat.health.last_change { + Some(change) => change.2, + None => { + warn!("Nothing caused an entity to die!"); + HealthSource::Unknown + } }, - ); + }); + stat.is_dead = true; } + if let Some(change) = &mut stat.health.last_change { change.1 += f64::from(dt.0); } diff --git a/server/src/lib.rs b/server/src/lib.rs index 1a1db64ab9..5e71695c89 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -37,6 +37,7 @@ use vek::*; use world::{ChunkSupplement, World}; const CLIENT_TIMEOUT: f64 = 20.0; // Seconds +const HUMANOID_JUMP_ACCEL: f32 = 18.0; pub enum Event { ClientConnected { @@ -153,8 +154,7 @@ impl Server { .with(comp::Controller::default()) .with(body) .with(comp::Stats::new(name)) - .with(comp::ActionState::default()) - .with(comp::ForceUpdate) + .with(comp::CharacterState::default()) } /// Build a static object entity @@ -175,8 +175,7 @@ impl Server { ..comp::LightEmitter::default() }) //.with(comp::LightEmitter::default()) - .with(comp::ActionState::default()) - .with(comp::ForceUpdate) + .with(comp::CharacterState::default()) } pub fn create_player_character( @@ -195,7 +194,7 @@ impl Server { state.write_component(entity, comp::Pos(spawn_point)); state.write_component(entity, comp::Vel(Vec3::zero())); state.write_component(entity, comp::Ori(Vec3::unit_y())); - state.write_component(entity, comp::ActionState::default()); + state.write_component(entity, comp::CharacterState::default()); state.write_component(entity, comp::Inventory::default()); state.write_component(entity, comp::InventoryUpdate); // Make sure physics are accepted. @@ -221,6 +220,9 @@ impl Server { let terrain = self.state.ecs().read_resource::(); let mut block_change = self.state.ecs().write_resource::(); let mut stats = self.state.ecs().write_storage::(); + let mut positions = self.state.ecs().write_storage::(); + let mut velocities = self.state.ecs().write_storage::(); + let mut force_updates = self.state.ecs().write_storage::(); for event in self.state.ecs().read_resource::().recv_all() { match event { @@ -250,6 +252,74 @@ impl Server { .cast(); } } + GameEvent::Jump(entity) => { + if let Some(vel) = velocities.get_mut(entity) { + vel.0.z = HUMANOID_JUMP_ACCEL; + let _ = force_updates.insert(entity, comp::ForceUpdate); + } + } + GameEvent::Die { entity, cause } => { + // Chat message + if let Some(player) = + self.state.ecs().read_storage::().get(entity) + { + let msg = if let comp::HealthSource::Attack { by } = cause { + self.state() + .ecs() + .entity_from_uid(by.into()) + .and_then(|attacker| { + self.state + .ecs() + .read_storage::() + .get(attacker) + .map(|attacker_alias| { + format!( + "{} was killed by {}", + &player.alias, &attacker_alias.alias + ) + }) + }) + } else { + None + } + .unwrap_or(format!("{} died", &player.alias)); + + self.clients.notify_registered(ServerMsg::kill(msg)); + } + + // Give EXP to the client + if let Some(entity_stats) = stats.get(entity).cloned() { + if let comp::HealthSource::Attack { by } = cause { + self.state.ecs().entity_from_uid(by.into()).map(|attacker| { + if let Some(attacker_stats) = stats.get_mut(attacker) { + // TODO: Discuss whether we should give EXP by Player Killing or not. + attacker_stats.exp.change_by( + entity_stats.health.maximum() as f64 / 10.0 + + entity_stats.level.level() as f64 * 10.0, + ); + } + }); + } + } + + if let Some(client) = self.clients.get_mut(&entity) { + let _ = velocities.insert(entity, comp::Vel(Vec3::zero())); + let _ = force_updates.insert(entity, comp::ForceUpdate); + client.force_state(ClientState::Dead); + } else { + //let _ = self.state.ecs_mut().delete_entity_synced(entity); + continue; + } + } + GameEvent::Respawn(entity) => { + // Only clients can respawn + if let Some(client) = self.clients.get_mut(&entity) { + client.allow_state(ClientState::Character); + stats.get_mut(entity).map(|stats| stats.revive()); + positions.get_mut(entity).map(|pos| pos.0.z += 20.0); + force_updates.insert(entity, comp::ForceUpdate); + } + } } } } @@ -357,7 +427,7 @@ impl Server { .with(comp::Controller::default()) .with(body) .with(stats) - .with(comp::ActionState::default()) + .with(comp::CharacterState::default()) .with(comp::Agent::enemy()) .with(comp::Scale(scale)) .build(); @@ -475,13 +545,6 @@ impl Server { // 7) Finish the tick, pass control back to the frontend. - // Cleanup - let ecs = self.state.ecs_mut(); - for entity in ecs.entities().join() { - ecs.write_storage::().remove(entity); - ecs.write_storage::().remove(entity); - } - Ok(frontend_events) } @@ -885,12 +948,12 @@ impl Server { state.write_component(entity, player); // Sync physics of all entities - for (&uid, &pos, vel, ori, action_state) in ( + for (&uid, &pos, vel, ori, character_state) in ( &state.ecs().read_storage::(), &state.ecs().read_storage::(), // We assume all these entities have a position state.ecs().read_storage::().maybe(), state.ecs().read_storage::().maybe(), - state.ecs().read_storage::().maybe(), + state.ecs().read_storage::().maybe(), ) .join() { @@ -910,10 +973,10 @@ impl Server { ori, }); } - if let Some(action_state) = action_state.copied() { - client.notify(ServerMsg::EntityActionState { + if let Some(character_state) = character_state.copied() { + client.notify(ServerMsg::EntityCharacterState { entity: uid.into(), - action_state, + character_state, }); } } @@ -928,84 +991,7 @@ impl Server { self.clients .notify_registered(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package())); - // TODO: Move this into some new method like `handle_sys_outputs` right after ticking the world - // Handle deaths. let ecs = self.state.ecs_mut(); - let clients = &mut self.clients; - let todo_kill = (&ecs.entities(), &ecs.read_storage::()) - .join() - .map(|(entity, dying)| { - // Chat message - if let Some(player) = ecs.read_storage::().get(entity) { - let msg = if let comp::HealthSource::Attack { by } = dying.cause { - ecs.entity_from_uid(by.into()).and_then(|attacker| { - ecs.read_storage::() - .get(attacker) - .map(|attacker_alias| { - format!( - "{} was killed by {}", - &player.alias, &attacker_alias.alias - ) - }) - }) - } else { - None - } - .unwrap_or(format!("{} died", &player.alias)); - - clients.notify_registered(ServerMsg::kill(msg)); - } - - // Give EXP to the client - let mut stats = ecs.write_storage::(); - if let Some(entity_stats) = stats.get(entity).cloned() { - if let comp::HealthSource::Attack { by } = dying.cause { - ecs.entity_from_uid(by.into()).map(|attacker| { - if let Some(attacker_stats) = stats.get_mut(attacker) { - // TODO: Discuss whether we should give EXP by Player Killing or not. - attacker_stats.exp.change_by( - entity_stats.health.maximum() as f64 / 10.0 - + entity_stats.level.level() as f64 * 10.0, - ); - } - }); - } - } - - entity - }) - .collect::>(); - - // Actually kill them - for entity in todo_kill { - if let Some(client) = self.clients.get_mut(&entity) { - let _ = ecs.write_storage().insert(entity, comp::Vel(Vec3::zero())); - let _ = ecs.write_storage().insert(entity, comp::ForceUpdate); - client.force_state(ClientState::Dead); - } else { - let _ = ecs.delete_entity_synced(entity); - continue; - } - } - - // Handle respawns - let todo_respawn = (&ecs.entities(), &ecs.read_storage::()) - .join() - .map(|(entity, _)| entity) - .collect::>(); - - for entity in todo_respawn { - if let Some(client) = self.clients.get_mut(&entity) { - client.allow_state(ClientState::Character); - ecs.write_storage::() - .get_mut(entity) - .map(|stats| stats.revive()); - ecs.write_storage::() - .get_mut(entity) - .map(|pos| pos.0.z += 20.0); - let _ = ecs.write_storage().insert(entity, comp::ForceUpdate); - } - } // Sync physics for (entity, &uid, &pos, force_update) in ( @@ -1043,7 +1029,7 @@ impl Server { let mut last_pos = ecs.write_storage::>(); let mut last_vel = ecs.write_storage::>(); let mut last_ori = ecs.write_storage::>(); - let mut last_action_state = ecs.write_storage::>(); + let mut last_character_state = ecs.write_storage::>(); if let Some(client_pos) = ecs.read_storage::().get(entity) { if last_pos @@ -1099,16 +1085,19 @@ impl Server { } } - if let Some(client_action_state) = ecs.read_storage::().get(entity) { - if last_action_state + if let Some(client_character_state) = + ecs.read_storage::().get(entity) + { + if last_character_state .get(entity) - .map(|&l| l != *client_action_state) + .map(|&l| l != *client_character_state) .unwrap_or(true) { - let _ = last_action_state.insert(entity, comp::Last(*client_action_state)); - let msg = ServerMsg::EntityActionState { + let _ = + last_character_state.insert(entity, comp::Last(*client_character_state)); + let msg = ServerMsg::EntityCharacterState { entity: uid.into(), - action_state: *client_action_state, + character_state: *client_character_state, }; match force_update { Some(_) => clients.notify_ingame_if(msg, in_vd),