diff --git a/common/src/comp/inputs.rs b/common/src/comp/inputs.rs index 27b7b75614..1ae61033b8 100644 --- a/common/src/comp/inputs.rs +++ b/common/src/comp/inputs.rs @@ -4,12 +4,18 @@ use vek::*; #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct Respawning; +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct MoveDir(pub Vec2); + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Attacking { pub time: f32, pub applied: bool, } +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct OnGround; + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Jumping; @@ -29,10 +35,18 @@ impl Attacking { } } +impl Component for MoveDir { + type Storage = VecStorage; +} + impl Component for Attacking { type Storage = FlaggedStorage>; } +impl Component for OnGround { + type Storage = NullStorage; +} + impl Component for Jumping { type Storage = NullStorage; } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index cd9b36a153..92d785dd77 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -20,6 +20,8 @@ pub use controller::Controller; pub use inputs::Attacking; pub use inputs::Gliding; pub use inputs::Jumping; +pub use inputs::MoveDir; +pub use inputs::OnGround; pub use inputs::Respawning; pub use player::Player; pub use stats::Dying; diff --git a/common/src/state.rs b/common/src/state.rs index 630f7c8f56..de959c47c4 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -109,6 +109,8 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/src/sys/actions.rs b/common/src/sys/actions.rs deleted file mode 100644 index feb21a4251..0000000000 --- a/common/src/sys/actions.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{comp::Attacking, state::DeltaTime}; -use specs::{Entities, Join, Read, System, WriteStorage}; - -// Basic ECS AI agent system -pub struct Sys; - -impl<'a> System<'a> for Sys { - type SystemData = ( - Entities<'a>, - Read<'a, DeltaTime>, - WriteStorage<'a, Attacking>, - ); - - fn run(&mut self, (entities, dt, mut attacks): Self::SystemData) { - for attack in (&mut attacks).join() { - attack.time += dt.0; - } - - let finished_attacks = (&entities, &mut attacks) - .join() - .filter(|(_, a)| a.time > 0.50) // TODO: constant - .map(|(e, _)| e) - .collect::>(); - - for entity in finished_attacks { - attacks.remove(entity); - } - } -} diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 0e5e2f80f9..0bd445ab1e 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -4,9 +4,8 @@ use rand::{seq::SliceRandom, thread_rng}; use specs::{Entities, Join, ReadStorage, System, WriteStorage}; use vek::*; -// Basic ECS AI agent system +/// This system will allow NPCs to modify their controller pub struct Sys; - impl<'a> System<'a> for Sys { type SystemData = ( Entities<'a>, diff --git a/common/src/sys/animation.rs b/common/src/sys/animation.rs index 09e194667d..29d5b08d7a 100644 --- a/common/src/sys/animation.rs +++ b/common/src/sys/animation.rs @@ -1,15 +1,68 @@ -use crate::{comp::AnimationInfo, state::DeltaTime}; -use specs::{Join, Read, System, WriteStorage}; +use crate::{ + comp::{phys, Animation, AnimationInfo, Attacking, Gliding, Jumping, OnGround}, + state::DeltaTime, +}; +use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; -// Basic ECS AI agent system +/// This system will apply the animation that fits best to the users actions pub struct Sys; - impl<'a> System<'a> for Sys { - type SystemData = (Read<'a, DeltaTime>, WriteStorage<'a, AnimationInfo>); + type SystemData = ( + Entities<'a>, + Read<'a, DeltaTime>, + ReadStorage<'a, phys::Vel>, + ReadStorage<'a, OnGround>, + ReadStorage<'a, Jumping>, + ReadStorage<'a, Gliding>, + ReadStorage<'a, Attacking>, + WriteStorage<'a, AnimationInfo>, + ); - fn run(&mut self, (dt, mut animation_infos): Self::SystemData) { - for mut animation_info in (&mut animation_infos).join() { + fn run( + &mut self, + (entities, dt, velocities, on_grounds, jumpings, glidings, attackings, mut animation_infos): Self::SystemData, + ) { + for (entity, vel, on_ground, jumping, gliding, attacking, mut animation_info) in ( + &entities, + &velocities, + on_grounds.maybe(), + jumpings.maybe(), + glidings.maybe(), + attackings.maybe(), + &mut animation_infos, + ) + .join() + { animation_info.time += dt.0 as f64; + let moving = vel.0.magnitude() > 3.0; + + fn impossible_animation() -> Animation { + warn!("Impossible animation"); + Animation::Idle + } + + let animation = match ( + on_ground.is_some(), + moving, + attacking.is_some(), + gliding.is_some(), + ) { + (true, false, false, false) => Animation::Idle, + (true, true, false, false) => Animation::Run, + (false, _, false, false) => Animation::Jump, + (_, _, false, true) => Animation::Gliding, + (_, _, true, false) => Animation::Attack, + (_, _, true, true) => impossible_animation(), + }; + + let last = animation_info.clone(); + let changed = last.animation != animation; + + *animation_info = AnimationInfo { + animation, + time: if changed { 0.0 } else { last.time }, + changed, + }; } } } diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index 58ed6fc40e..921c4bff23 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -1,7 +1,8 @@ use crate::{ comp::{ phys::{ForceUpdate, Ori, Pos, Vel}, - Animation, AnimationInfo, Attacking, Controller, Gliding, HealthSource, Jumping, Stats, + Animation, AnimationInfo, Attacking, Controller, Gliding, HealthSource, Jumping, MoveDir, + OnGround, Respawning, Stats, }, state::{DeltaTime, Uid}, terrain::TerrainMap, @@ -11,16 +12,7 @@ use log::warn; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; use vek::*; -const HUMANOID_ACCEL: f32 = 100.0; -const HUMANOID_SPEED: f32 = 500.0; -const HUMANOID_AIR_ACCEL: f32 = 10.0; -const HUMANOID_AIR_SPEED: f32 = 100.0; -const HUMANOID_JUMP_ACCEL: f32 = 16.0; -const GLIDE_ACCEL: f32 = 15.0; -const GLIDE_SPEED: f32 = 45.0; -// Gravity is 9.81 * 4, so this makes gravity equal to .15 -const GLIDE_ANTIGRAV: f32 = 9.81 * 3.95; - +/// This system is responsible for validating controller inputs pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( @@ -32,8 +24,11 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, + WriteStorage<'a, MoveDir>, + WriteStorage<'a, OnGround>, WriteStorage<'a, Jumping>, WriteStorage<'a, Attacking>, + WriteStorage<'a, Respawning>, WriteStorage<'a, Gliding>, WriteStorage<'a, ForceUpdate>, ); @@ -49,65 +44,64 @@ impl<'a> System<'a> for Sys { positions, mut velocities, mut orientations, - mut jumps, - mut attacks, - mut glides, + mut move_dirs, + mut on_grounds, + mut jumpings, + mut attackings, + mut respawns, + mut glidings, mut force_updates, ): Self::SystemData, ) { - for (entity, controller, stats, pos, mut vel, mut ori) in ( + for (entity, controller, stats, pos, mut vel, mut ori, on_ground) in ( &entities, &controllers, &stats, &positions, &mut velocities, &mut orientations, + on_grounds.maybe(), ) .join() { if stats.is_dead { + // Respawn + if controller.respawn { + respawns.insert(entity, Respawning); + } continue; } - let on_ground = terrain - .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) - .map(|vox| !vox.is_empty()) - .unwrap_or(false) - && vel.0.z <= 0.0; - - let gliding = controller.glide && vel.0.z < 0.0; - let move_dir = if controller.move_dir.magnitude() > 1.0 { - controller.move_dir.normalized() + // Glide + if controller.glide && on_ground.is_none() && attackings.get(entity).is_none() { + glidings.insert(entity, Gliding); } else { - controller.move_dir - }; - - if on_ground { - // Move player according to move_dir - if vel.0.magnitude() < HUMANOID_SPEED { - vel.0 += Vec2::broadcast(dt.0) * move_dir * HUMANOID_ACCEL; - } - - // Jump - if controller.jump && vel.0.z <= 0.0 { - vel.0.z = HUMANOID_JUMP_ACCEL; - } - } else if gliding && vel.0.magnitude() < GLIDE_SPEED { - let anti_grav = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2; - vel.0.z += dt.0 * anti_grav * Vec2::::from(vel.0 * 0.15).magnitude().min(1.0); - vel.0 += Vec2::broadcast(dt.0) * move_dir * GLIDE_ACCEL; - } else if vel.0.magnitude() < HUMANOID_AIR_SPEED { - vel.0 += Vec2::broadcast(dt.0) * move_dir * HUMANOID_AIR_ACCEL; + glidings.remove(entity); } - // Set direction based on velocity - if vel.0.magnitude_squared() != 0.0 { - ori.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0); - } + // Move dir + move_dirs.insert( + entity, + MoveDir(if controller.move_dir.magnitude() > 1.0 { + controller.move_dir.normalized() + } else { + controller.move_dir + }), + ); // Attack - if controller.attack && attacks.get(entity).is_none() { - attacks.insert(entity, Attacking::start()); + if controller.attack + && attackings.get(entity).is_none() + && glidings.get(entity).is_none() + { + attackings.insert(entity, Attacking::start()); + } + + // Jump + if on_grounds.get(entity).is_some() && controller.jump && vel.0.z <= 0.0 { + jumpings.insert(entity, Jumping); + } else { + jumpings.remove(entity); } } } diff --git a/common/src/sys/inputs.rs b/common/src/sys/inputs.rs index 6365f53ba4..702328de87 100644 --- a/common/src/sys/inputs.rs +++ b/common/src/sys/inputs.rs @@ -1,7 +1,8 @@ use crate::{ comp::{ phys::{ForceUpdate, Ori, Pos, Vel}, - Animation, AnimationInfo, Attacking, Controller, Gliding, HealthSource, Jumping, Stats, + Animation, AnimationInfo, Attacking, Gliding, HealthSource, Jumping, MoveDir, OnGround, + Respawning, Stats, }, state::{DeltaTime, Uid}, terrain::TerrainMap, @@ -11,7 +12,17 @@ use log::warn; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; use vek::*; -// Basic ECS AI agent system +const HUMANOID_ACCEL: f32 = 100.0; +const HUMANOID_SPEED: f32 = 500.0; +const HUMANOID_AIR_ACCEL: f32 = 10.0; +const HUMANOID_AIR_SPEED: f32 = 100.0; +const HUMANOID_JUMP_ACCEL: f32 = 16.0; +const GLIDE_ACCEL: f32 = 15.0; +const GLIDE_SPEED: f32 = 45.0; +// Gravity is 9.81 * 4, so this makes gravity equal to .15 +const GLIDE_ANTIGRAV: f32 = 9.81 * 3.95; + +/// This system is responsible for handling accepted inputs like moving or attacking pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( @@ -20,11 +31,13 @@ impl<'a> System<'a> for Sys { Read<'a, DeltaTime>, ReadExpect<'a, TerrainMap>, ReadStorage<'a, Pos>, + ReadStorage<'a, OnGround>, + ReadStorage<'a, MoveDir>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, WriteStorage<'a, AnimationInfo>, WriteStorage<'a, Stats>, - ReadStorage<'a, Controller>, + WriteStorage<'a, Respawning>, WriteStorage<'a, Jumping>, WriteStorage<'a, Gliding>, WriteStorage<'a, Attacking>, @@ -39,88 +52,99 @@ impl<'a> System<'a> for Sys { dt, terrain, positions, + on_grounds, + move_dirs, mut velocities, mut orientations, mut animation_infos, mut stats, - controllers, - mut jumps, - glides, - mut attacks, + mut respawnings, + mut jumpings, + glidings, + mut attackings, mut force_updates, ): Self::SystemData, ) { - for (entity, pos, controller, stats, mut ori, mut vel) in ( + // Attacks + (&entities, &uids, &positions, &orientations, &mut attackings) + .join() + .filter_map(|(entity, uid, pos, ori, mut attacking)| { + if !attacking.applied { + // Go through all other entities + for (b, pos_b, stat_b, mut vel_b) in + (&entities, &positions, &mut stats, &mut velocities).join() + { + // Check if it is a hit + if entity != b + && !stat_b.is_dead + && pos.0.distance_squared(pos_b.0) < 50.0 + && ori.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0 + { + // Deal damage + stat_b.hp.change_by(-10, HealthSource::Attack { by: *uid }); // TODO: variable damage and weapon + vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0; + vel_b.0.z = 15.0; + if let Err(err) = force_updates.insert(b, ForceUpdate) { + warn!("Inserting ForceUpdate for an entity failed: {:?}", err); + } + } + } + attacking.applied = true; + } + + if attacking.time > 0.5 { + Some(entity) + } else { + attacking.time += dt.0; + None + } + }) + .collect::>() + .into_iter() + .for_each(|e| { + attackings.remove(e); + }); + + // Apply movement inputs + for (entity, mut vel, mut ori, on_ground, move_dir, jumping, gliding) in ( &entities, - &positions, - &controllers, - &stats, - &mut orientations, &mut velocities, + &mut orientations, + on_grounds.maybe(), + move_dirs.maybe(), + jumpings.maybe(), + glidings.maybe(), ) .join() { - let on_ground = terrain - .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) - .map(|vox| !vox.is_empty()) - .unwrap_or(false) - && vel.0.z <= 0.0; - - let animation = if on_ground { - if controller.move_dir.magnitude() > 0.01 { - Animation::Run - } else if attacks.get(entity).is_some() { - Animation::Attack - } else { - Animation::Idle - } - } else if controller.glide { - Animation::Gliding - } else { - Animation::Jump - }; - - let last = animation_infos - .get_mut(entity) - .cloned() - .unwrap_or(AnimationInfo::default()); - let changed = last.animation != animation; - - if let Err(err) = animation_infos.insert( - entity, - AnimationInfo { - animation, - time: if changed { 0.0 } else { last.time }, - changed, - }, - ) { - warn!("Inserting AnimationInfo for an entity failed: {:?}", err); - } - } - - for (entity, &uid, pos, ori, attacking) in - (&entities, &uids, &positions, &orientations, &mut attacks).join() - { - if !attacking.applied { - for (b, pos_b, stat_b, mut vel_b) in - (&entities, &positions, &mut stats, &mut velocities).join() - { - // Check if it is a hit - if entity != b - && !stat_b.is_dead - && pos.0.distance_squared(pos_b.0) < 50.0 - && ori.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0 - { - // Deal damage - stat_b.hp.change_by(-10, HealthSource::Attack { by: uid }); // TODO: variable damage and weapon - vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0; - vel_b.0.z = 15.0; - if let Err(err) = force_updates.insert(b, ForceUpdate) { - warn!("Inserting ForceUpdate for an entity failed: {:?}", err); + // Move player according to move_dir + if let Some(move_dir) = move_dir { + vel.0 += Vec2::broadcast(dt.0) + * move_dir.0 + * match (on_ground.is_some(), gliding.is_some()) { + (true, false) if vel.0.magnitude() < HUMANOID_SPEED => HUMANOID_ACCEL, + (false, true) if vel.0.magnitude() < GLIDE_SPEED => GLIDE_ACCEL, + (false, false) if vel.0.magnitude() < HUMANOID_AIR_SPEED => { + HUMANOID_AIR_ACCEL } - } - } - attacking.applied = true; + _ => 0.0, + }; + } + + // Jump + if jumping.is_some() { + vel.0.z = HUMANOID_JUMP_ACCEL; + } + + // Glide + if gliding.is_some() && vel.0.magnitude() < GLIDE_SPEED && vel.0.z < 0.0 { + let anti_grav = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2; + vel.0.z += dt.0 * anti_grav * Vec2::::from(vel.0 * 0.15).magnitude().min(1.0); + } + + // Set direction based on velocity + if vel.0.magnitude_squared() != 0.0 { + ori.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0); } } } diff --git a/common/src/sys/mod.rs b/common/src/sys/mod.rs index e6a5917c5a..97861dea9f 100644 --- a/common/src/sys/mod.rs +++ b/common/src/sys/mod.rs @@ -1,4 +1,3 @@ -pub mod actions; pub mod agent; pub mod animation; pub mod controller; @@ -12,17 +11,15 @@ use specs::DispatcherBuilder; // System names const AGENT_SYS: &str = "agent_sys"; const CONTROLLER_SYS: &str = "controller_sys"; -const INPUTS_SYS: &str = "inputs_sys"; -const ACTIONS_SYS: &str = "actions_sys"; const PHYS_SYS: &str = "phys_sys"; +const INPUTS_SYS: &str = "inputs_sys"; const ANIMATION_SYS: &str = "animation_sys"; const STATS_SYS: &str = "stats_sys"; pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { dispatch_builder.add(agent::Sys, AGENT_SYS, &[]); - dispatch_builder.add(phys::Sys, PHYS_SYS, &[]); - dispatch_builder.add(actions::Sys, ACTIONS_SYS, &[]); dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[]); + dispatch_builder.add(phys::Sys, PHYS_SYS, &[]); dispatch_builder.add(inputs::Sys, INPUTS_SYS, &[]); dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[]); dispatch_builder.add(stats::Sys, STATS_SYS, &[INPUTS_SYS]); diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 796b3310e2..270f276964 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -1,18 +1,15 @@ use crate::{ comp::{ phys::{Pos, Vel}, - Stats, + OnGround, Stats, }, state::DeltaTime, terrain::TerrainMap, vol::{ReadVol, Vox}, }; -use specs::{Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; +use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; use vek::*; -// Basic ECS physics system -pub struct Sys; - const GRAVITY: f32 = 9.81 * 4.0; const FRIC_GROUND: f32 = 0.15; const FRIC_AIR: f32 = 0.015; @@ -38,34 +35,53 @@ fn integrate_forces(dt: f32, mut lv: Vec3, damp: f32) -> Vec3 { lv } +/// This system applies forces and calculates new positions and velocities +pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( + Entities<'a>, ReadExpect<'a, TerrainMap>, Read<'a, DeltaTime>, ReadStorage<'a, Stats>, WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, + WriteStorage<'a, OnGround>, ); - fn run(&mut self, (terrain, dt, stats, mut positions, mut velocities): Self::SystemData) { - for (stats, pos, vel) in (&stats, &mut positions, &mut velocities).join() { + fn run( + &mut self, + (entities, terrain, dt, stats, mut positions, mut velocities, mut on_grounds): Self::SystemData, + ) { + for (entity, stats, pos, vel) in (&entities, &stats, &mut positions, &mut velocities).join() + { // Disable while dead TODO: Replace with client states if stats.is_dead { continue; } - let on_ground = terrain - .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) - .map(|vox| !vox.is_empty()) - .unwrap_or(false) - && vel.0.z <= 0.0; - // Movement pos.0 += vel.0 * dt.0; + // Update OnGround component + if terrain + .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) + .map(|vox| !vox.is_empty()) + .unwrap_or(false) + && vel.0.z <= 0.0 + { + on_grounds.insert(entity, OnGround); + } else { + on_grounds.remove(entity); + } + // Integrate forces // Friction is assumed to be a constant dependent on location - let friction = 50.0 * if on_ground { FRIC_GROUND } else { FRIC_AIR }; + let friction = 50.0 + * if on_grounds.get(entity).is_some() { + FRIC_GROUND + } else { + FRIC_AIR + }; vel.0 = integrate_forces(dt.0, vel.0, friction); // Basic collision with terrain diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs index 3897da618f..22dc10d48b 100644 --- a/common/src/sys/stats.rs +++ b/common/src/sys/stats.rs @@ -5,9 +5,8 @@ use crate::{ use log::warn; use specs::{Entities, Join, Read, System, WriteStorage}; -// Basic ECS AI agent system +/// This system kills players pub struct Sys; - impl<'a> System<'a> for Sys { type SystemData = ( Entities<'a>,