Move from state components to single CharaterState struct

This makes split animations easy and improves overall code quality
This commit is contained in:
timokoesters 2019-08-23 12:11:37 +02:00
parent 2211f79d28
commit 5d5ccd7b99
No known key found for this signature in database
GPG Key ID: CD80BE9AAEE78097
19 changed files with 447 additions and 474 deletions

View File

@ -444,12 +444,12 @@ impl Client {
self.state.write_component(entity, ori); self.state.write_component(entity, ori);
} }
} }
ServerMsg::EntityActionState { ServerMsg::EntityCharacterState {
entity, entity,
action_state, character_state,
} => { } => {
if let Some(entity) = self.state.ecs().entity_from_uid(entity) { 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) => { ServerMsg::InventoryUpdate(inventory) => {

View File

@ -188,8 +188,8 @@ impl Asset for String {
} }
} }
/// Lazy static to find and cache where the asset directory is.
lazy_static! { lazy_static! {
/// Lazy static to find and cache where the asset directory is.
static ref ASSETS_PATH: PathBuf = { static ref ASSETS_PATH: PathBuf = {
let mut paths = Vec::new(); let mut paths = Vec::new();

View File

@ -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<Self, HashMapStorage<Self>>;
}

View File

@ -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<Self, HashMapStorage<Self>>;
}

View File

@ -1,8 +1,8 @@
mod action_state;
mod admin; mod admin;
mod agent; mod agent;
mod animation; mod animation;
mod body; mod body;
mod character_state;
mod controller; mod controller;
mod inputs; mod inputs;
mod inventory; mod inventory;
@ -13,18 +13,16 @@ mod stats;
mod visual; mod visual;
// Reexports // Reexports
pub use action_state::ActionState;
pub use admin::Admin; pub use admin::Admin;
pub use agent::Agent; pub use agent::Agent;
pub use animation::{Animation, AnimationInfo}; pub use animation::{Animation, AnimationInfo};
pub use body::{humanoid, object, quadruped, quadruped_medium, Body}; pub use body::{humanoid, object, quadruped, quadruped_medium, Body};
pub use character_state::{ActionState, CharacterState, MovementState};
pub use controller::Controller; pub use controller::Controller;
pub use inputs::{ pub use inputs::CanBuild;
Attacking, CanBuild, Gliding, Jumping, MoveDir, OnGround, Respawning, Rolling, Wielding,
};
pub use inventory::{item, Inventory, InventoryUpdate, Item}; pub use inventory::{item, Inventory, InventoryUpdate, Item};
pub use last::Last; 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 player::Player;
pub use stats::{Dying, Exp, HealthSource, Level, Stats}; pub use stats::{Exp, HealthSource, Level, Stats};
pub use visual::LightEmitter; pub use visual::LightEmitter;

View File

@ -34,6 +34,16 @@ impl Component for Scale {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>; type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
} }
// PhysicsState
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PhysicsState {
pub on_ground: bool,
}
impl Component for PhysicsState {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}
// ForceUpdate // ForceUpdate
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ForceUpdate; pub struct ForceUpdate;

View File

@ -1,11 +1,24 @@
use crate::comp;
use parking_lot::Mutex; use parking_lot::Mutex;
use specs::Entity as EcsEntity; use specs::Entity as EcsEntity;
use std::{collections::VecDeque, ops::DerefMut}; use std::{collections::VecDeque, ops::DerefMut};
use vek::*; use vek::*;
pub enum Event { pub enum Event {
LandOnGround { entity: EcsEntity, vel: Vec3<f32> }, LandOnGround {
Explosion { pos: Vec3<f32>, radius: f32 }, entity: EcsEntity,
vel: Vec3<f32>,
},
Explosion {
pos: Vec3<f32>,
radius: f32,
},
Die {
entity: EcsEntity,
cause: comp::HealthSource,
},
Jump(EcsEntity),
Respawn(EcsEntity),
} }
#[derive(Default)] #[derive(Default)]

View File

@ -51,9 +51,9 @@ pub enum ServerMsg {
entity: u64, entity: u64,
ori: comp::Ori, ori: comp::Ori,
}, },
EntityActionState { EntityCharacterState {
entity: u64, entity: u64,
action_state: comp::ActionState, character_state: comp::CharacterState,
}, },
InventoryUpdate(comp::Inventory), InventoryUpdate(comp::Inventory),
TerrainChunkUpdate { TerrainChunkUpdate {

View File

@ -122,7 +122,8 @@ impl State {
ecs.register::<comp::Controller>(); ecs.register::<comp::Controller>();
// Register components send directly from server -> all but one client // Register components send directly from server -> all but one client
ecs.register::<comp::ActionState>(); ecs.register::<comp::CharacterState>();
ecs.register::<comp::PhysicsState>();
// Register components synced from client -> server -> all other clients // Register components synced from client -> server -> all other clients
ecs.register::<comp::Pos>(); ecs.register::<comp::Pos>();
@ -132,27 +133,17 @@ impl State {
// Register client-local components // Register client-local components
ecs.register::<comp::AnimationInfo>(); ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Jumping>();
// Register server-local components // Register server-local components
ecs.register::<comp::Last<comp::Pos>>(); ecs.register::<comp::Last<comp::Pos>>();
ecs.register::<comp::Last<comp::Vel>>(); ecs.register::<comp::Last<comp::Vel>>();
ecs.register::<comp::Last<comp::Ori>>(); ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Last<comp::ActionState>>(); ecs.register::<comp::Last<comp::CharacterState>>();
ecs.register::<comp::Agent>(); ecs.register::<comp::Agent>();
ecs.register::<comp::Respawning>();
ecs.register::<comp::Dying>();
ecs.register::<comp::ForceUpdate>(); ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>(); ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Inventory>(); ecs.register::<comp::Inventory>();
ecs.register::<comp::Admin>(); ecs.register::<comp::Admin>();
// Controller effects
ecs.register::<comp::MoveDir>();
ecs.register::<comp::OnGround>();
ecs.register::<comp::Attacking>();
ecs.register::<comp::Wielding>();
ecs.register::<comp::Rolling>();
ecs.register::<comp::Gliding>();
// Register synced resources used by the ECS. // Register synced resources used by the ECS.
ecs.add_resource_synced(TimeOfDay(0.0)); ecs.add_resource_synced(TimeOfDay(0.0));

View File

@ -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(),
};
}
}
}

View File

@ -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 rand::{seq::SliceRandom, thread_rng};
use specs::{Entities, Join, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, ReadStorage, System, WriteStorage};
use vek::*; use vek::*;
@ -10,14 +10,14 @@ impl<'a> System<'a> for Sys {
Entities<'a>, Entities<'a>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
ReadStorage<'a, ActionState>, ReadStorage<'a, CharacterState>,
WriteStorage<'a, Agent>, WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
); );
fn run( fn run(
&mut self, &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 for (entity, pos, agent, controller) in
(&entities, &positions, &mut agents, &mut controllers).join() (&entities, &positions, &mut agents, &mut controllers).join()
@ -67,12 +67,12 @@ impl<'a> System<'a> for Sys {
const SIGHT_DIST: f32 = 30.0; const SIGHT_DIST: f32 = 30.0;
let mut choose_new = false; 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| { target.map(|target| {
( (
positions.get(target), positions.get(target),
stats.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; 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.glide = true;
controller.jump = true; controller.jump = true;
} }

View File

@ -1,8 +1,11 @@
use crate::{ use crate::{
comp::{ActionState, Animation, AnimationInfo}, comp::{
ActionState::*, Animation, AnimationInfo, CharacterState, MovementState::*, PhysicsState,
},
state::DeltaTime, state::DeltaTime,
}; };
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; 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 /// This system will apply the animation that fits best to the users actions
pub struct Sys; pub struct Sys;
@ -10,37 +13,32 @@ impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, DeltaTime>, Read<'a, DeltaTime>,
ReadStorage<'a, ActionState>, ReadStorage<'a, CharacterState>,
ReadStorage<'a, PhysicsState>,
WriteStorage<'a, AnimationInfo>, WriteStorage<'a, AnimationInfo>,
); );
fn run(&mut self, (entities, dt, action_states, mut animation_infos): Self::SystemData) { fn run(
for (entity, a) in (&entities, &action_states).join() { &mut self,
fn impossible_animation(message: &str) -> Animation { (entities, dt, character_states, physics_states, mut animation_infos): Self::SystemData,
warn!("{}", message);
Animation::Idle
}
let animation = match (
a.on_ground,
a.moving,
a.attacking,
a.gliding,
a.rolling,
a.wielding,
) { ) {
(_, _, true, true, _, _) => impossible_animation("Attack while gliding"), for (entity, character, physics) in (&entities, &character_states, &physics_states).join() {
(_, _, true, _, true, _) => impossible_animation("Roll while attacking"), fn impossible_animation(physics: PhysicsState, character: CharacterState) -> Animation {
(_, _, _, true, true, _) => impossible_animation("Roll while gliding"), warn!("Impossible animation: {:?} {:?}", physics, character);
(_, false, _, _, true, _) => impossible_animation("Roll without moving"), Animation::Roll
(_, true, false, false, true, _) => Animation::Roll, }
(true, false, false, false, false, false) => Animation::Idle,
(true, true, false, false, false, false) => Animation::Run, let animation = match (physics.on_ground, &character.movement, &character.action) {
(false, _, false, false, false, false) => Animation::Jump, (_, Roll { .. }, Idle) => Animation::Roll,
(true, false, false, false, false, true) => Animation::Cidle, (true, Stand, Idle) => Animation::Idle,
(true, true, false, false, false, true) => Animation::Crun, (true, Run, Idle) => Animation::Run,
(false, _, false, false, false, true) => Animation::Cjump, (false, Jump, Idle) => Animation::Jump,
(_, _, false, true, false, _) => Animation::Gliding, (true, Stand, Wield { .. }) => Animation::Cidle,
(_, _, true, false, false, _) => Animation::Attack, (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 let new_time = animation_infos

View File

@ -1,8 +1,9 @@
use crate::{ use crate::{
comp::{Attacking, ForceUpdate, HealthSource, Ori, Pos, Stats, Vel, Wielding}, comp::{ActionState::*, CharacterState, ForceUpdate, HealthSource, Ori, Pos, Stats, Vel},
state::{DeltaTime, Uid}, state::{DeltaTime, Uid},
}; };
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use std::time::Duration;
/// This system is responsible for handling accepted inputs like moving or attacking /// This system is responsible for handling accepted inputs like moving or attacking
pub struct Sys; pub struct Sys;
@ -14,8 +15,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
ReadStorage<'a, Ori>, ReadStorage<'a, Ori>,
WriteStorage<'a, Vel>, WriteStorage<'a, Vel>,
WriteStorage<'a, Attacking>, WriteStorage<'a, CharacterState>,
WriteStorage<'a, Wielding>,
WriteStorage<'a, Stats>, WriteStorage<'a, Stats>,
WriteStorage<'a, ForceUpdate>, WriteStorage<'a, ForceUpdate>,
); );
@ -29,18 +29,26 @@ impl<'a> System<'a> for Sys {
positions, positions,
orientations, orientations,
mut velocities, mut velocities,
mut attackings, mut character_states,
mut wieldings,
mut stats, mut stats,
mut force_updates, mut force_updates,
): Self::SystemData, ): Self::SystemData,
) { ) {
// Attacks // Attacks
(&entities, &uids, &positions, &orientations, &mut attackings) for (entity, uid, pos, ori, mut character) in (
&entities,
&uids,
&positions,
&orientations,
&mut character_states,
)
.join() .join()
.filter_map(|(entity, uid, pos, ori, mut attacking)| { {
if !attacking.applied { let mut todo_end = false;
// Go through all other entities // 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 for (b, pos_b, mut vel_b, stat_b) in
(&entities, &positions, &mut velocities, &mut stats).join() (&entities, &positions, &mut velocities, &mut stats).join()
{ {
@ -59,29 +67,28 @@ impl<'a> System<'a> for Sys {
let _ = force_updates.insert(b, ForceUpdate); let _ = force_updates.insert(b, ForceUpdate);
} }
} }
attacking.applied = true; *applied = true;
} }
if attacking.time > 0.5 { if *time_left == Duration::default() {
Some(entity) todo_end = true;
} else { } else {
attacking.time += dt.0; *time_left = time_left
.checked_sub(Duration::from_secs_f32(dt.0))
.unwrap_or_default();
}
}
if todo_end {
character.action = Wield {
time_left: Duration::default(),
};
}
None if let Wield { time_left } = &mut character.action {
} if *time_left != Duration::default() {
}) *time_left = time_left
.collect::<Vec<_>>() .checked_sub(Duration::from_secs_f32(dt.0))
.into_iter() .unwrap_or_default();
.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;
} }
} }
} }

View File

@ -1,125 +1,135 @@
use crate::comp::{ use crate::{
ActionState, Attacking, Body, Controller, Gliding, Jumping, MoveDir, Respawning, Rolling, comp::{
Stats, Vel, Wielding, 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 /// This system is responsible for validating controller inputs
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, EventBus>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
ReadStorage<'a, Body>, ReadStorage<'a, Body>,
ReadStorage<'a, Vel>, ReadStorage<'a, Vel>,
WriteStorage<'a, ActionState>, ReadStorage<'a, PhysicsState>,
WriteStorage<'a, MoveDir>, WriteStorage<'a, CharacterState>,
WriteStorage<'a, Jumping>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, Wielding>,
WriteStorage<'a, Rolling>,
WriteStorage<'a, Respawning>,
WriteStorage<'a, Gliding>,
); );
fn run( fn run(
&mut self, &mut self,
( (
entities, entities,
event_bus,
mut controllers, mut controllers,
stats, stats,
bodies, bodies,
velocities, velocities,
mut action_states, physics_states,
mut move_dirs, mut character_states,
mut jumpings,
mut attackings,
mut wieldings,
mut rollings,
mut respawns,
mut glidings,
): Self::SystemData, ): 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, &entities,
&mut controllers, &mut controllers,
&stats, &stats,
&bodies, &bodies,
&velocities, &velocities,
// Although this is changed, it is only kept for this system &physics_states,
// as it will be replaced in the action state system &mut character_states,
&mut action_states,
) )
.join() .join()
{ {
if stats.is_dead { if stats.is_dead {
// Respawn // Respawn
if controller.respawn { if controller.respawn {
let _ = respawns.insert(entity, Respawning); event_emitter.emit(Event::Respawn(entity));
} }
continue; continue;
} }
// Move dir // Move
if !a.rolling { controller.move_dir = if controller.move_dir.magnitude_squared() > 1.0 {
let _ = move_dirs.insert(
entity,
MoveDir(if controller.move_dir.magnitude_squared() > 1.0 {
controller.move_dir.normalized() controller.move_dir.normalized()
} else { } else {
controller.move_dir 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 // 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); character.movement = Glide;
a.gliding = true; } else if !controller.glide && character.movement == Glide {
} else { character.movement = Jump;
let _ = glidings.remove(entity);
a.gliding = false;
} }
// Wield // Wield
if controller.attack && !a.wielding && !a.gliding && !a.rolling { if controller.attack
let _ = wieldings.insert(entity, Wielding::start()); && character.action == Idle
a.wielding = true; && (character.movement == Stand || character.movement == Run)
{
character.action = Wield {
time_left: Duration::from_millis(300),
};
} }
// Attack // Attack
if controller.attack if controller.attack
&& !a.attacking && (character.movement == Stand
&& wieldings.get(entity).map(|w| w.applied).unwrap_or(false) || character.movement == Run
&& !a.gliding || character.movement == Jump)
&& !a.rolling
{ {
let _ = attackings.insert(entity, Attacking::start()); // TODO: Check if wield ability exists
a.attacking = true; if let Wield { time_left } = character.action {
if time_left == Duration::default() {
character.action = Attack {
time_left: Duration::from_millis(300),
applied: false,
};
}
}
} }
// Roll // Roll
if controller.roll if controller.roll
&& !a.rolling && (character.action == Idle || character.action.is_wield())
&& a.on_ground && character.movement == Run
&& a.moving && physics.on_ground
&& !a.attacking
&& !a.gliding
{ {
let _ = rollings.insert(entity, Rolling::start()); character.movement = Roll {
a.rolling = true; time_left: Duration::from_millis(600),
};
} }
// Jump // Jump
if controller.jump && a.on_ground && vel.0.z <= 0.0 { if controller.jump && physics.on_ground && vel.0.z <= 0.0 {
let _ = jumpings.insert(entity, Jumping); dbg!();
a.on_ground = false; event_emitter.emit(Event::Jump(entity));
} }
// TODO before merge: reset controller in a final ecs system
// Reset the controller ready for the next tick // Reset the controller ready for the next tick
*controller = Controller::default(); //*controller = Controller::default();
} }
} }
} }

View File

@ -1,4 +1,3 @@
mod action_state;
pub mod agent; pub mod agent;
pub mod animation; pub mod animation;
pub mod combat; pub mod combat;
@ -13,7 +12,6 @@ use specs::DispatcherBuilder;
// System names // System names
const AGENT_SYS: &str = "agent_sys"; const AGENT_SYS: &str = "agent_sys";
const CONTROLLER_SYS: &str = "controller_sys"; const CONTROLLER_SYS: &str = "controller_sys";
const ACTION_STATE_SYS: &str = "action_state_sys";
const PHYS_SYS: &str = "phys_sys"; const PHYS_SYS: &str = "phys_sys";
const MOVEMENT_SYS: &str = "movement_sys"; const MOVEMENT_SYS: &str = "movement_sys";
const COMBAT_SYS: &str = "combat_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(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]);
dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS]); dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[PHYS_SYS]); dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[PHYS_SYS]);
dispatch_builder.add( dispatch_builder.add(combat::Sys, COMBAT_SYS, &[CONTROLLER_SYS]);
action_state::Sys, dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[CONTROLLER_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(stats::Sys, STATS_SYS, &[COMBAT_SYS]); dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]);
} }

View File

@ -1,10 +1,14 @@
use crate::{ 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, state::DeltaTime,
terrain::TerrainMap, terrain::TerrainMap,
vol::{ReadVol, Vox}, vol::{ReadVol, Vox},
}; };
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use std::time::Duration;
use vek::*; use vek::*;
const HUMANOID_ACCEL: f32 = 70.0; const HUMANOID_ACCEL: f32 = 70.0;
@ -13,7 +17,6 @@ const WIELD_ACCEL: f32 = 70.0;
const WIELD_SPEED: f32 = 120.0; const WIELD_SPEED: f32 = 120.0;
const HUMANOID_AIR_ACCEL: f32 = 10.0; const HUMANOID_AIR_ACCEL: f32 = 10.0;
const HUMANOID_AIR_SPEED: f32 = 100.0; const HUMANOID_AIR_SPEED: f32 = 100.0;
const HUMANOID_JUMP_ACCEL: f32 = 18.0;
const ROLL_ACCEL: f32 = 160.0; const ROLL_ACCEL: f32 = 160.0;
const ROLL_SPEED: f32 = 550.0; const ROLL_SPEED: f32 = 550.0;
const GLIDE_ACCEL: f32 = 15.0; const GLIDE_ACCEL: f32 = 15.0;
@ -30,13 +33,10 @@ impl<'a> System<'a> for Sys {
Entities<'a>, Entities<'a>,
ReadExpect<'a, TerrainMap>, ReadExpect<'a, TerrainMap>,
Read<'a, DeltaTime>, Read<'a, DeltaTime>,
ReadStorage<'a, MoveDir>,
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
ReadStorage<'a, ActionState>, ReadStorage<'a, Controller>,
WriteStorage<'a, Jumping>, ReadStorage<'a, PhysicsState>,
WriteStorage<'a, Wielding>, WriteStorage<'a, CharacterState>,
WriteStorage<'a, Rolling>,
WriteStorage<'a, OnGround>,
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>, WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>, WriteStorage<'a, Ori>,
@ -48,73 +48,56 @@ impl<'a> System<'a> for Sys {
entities, entities,
terrain, terrain,
dt, dt,
move_dirs,
stats, stats,
action_states, controllers,
mut jumpings, physics_states,
mut wieldings, mut character_states,
mut rollings,
mut on_grounds,
mut positions, mut positions,
mut velocities, mut velocities,
mut orientations, mut orientations,
): Self::SystemData, ): Self::SystemData,
) { ) {
// Apply movement inputs // 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, &entities,
&stats, &stats,
&action_states, &controllers,
move_dirs.maybe(), &physics_states,
&mut character_states,
&mut positions, &mut positions,
&mut velocities, &mut velocities,
&mut orientations, &mut orientations,
) )
.join() .join()
{ {
// Disable while dead TODO: Replace with client states?
if stats.is_dead { if stats.is_dead {
continue; continue;
} }
// Move player according to move_dir // Move player according to move_dir
if let Some(move_dir) = move_dir {
vel.0 += Vec2::broadcast(dt.0) vel.0 += Vec2::broadcast(dt.0)
* move_dir.0 * controller.move_dir
* match (a.on_ground, a.gliding, a.rolling, a.wielding) { * match (physics.on_ground, &character.movement) {
(true, false, false, false) (true, Run) if vel.0.magnitude_squared() < HUMANOID_SPEED.powf(2.0) => {
if vel.0.magnitude_squared() < HUMANOID_SPEED.powf(2.0) =>
{
HUMANOID_ACCEL HUMANOID_ACCEL
} }
(false, true, false, false) (false, Glide) if vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) => {
if vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) =>
{
GLIDE_ACCEL GLIDE_ACCEL
} }
(false, false, false, false) (false, Jump) if vel.0.magnitude_squared() < HUMANOID_AIR_SPEED.powf(2.0) => {
if vel.0.magnitude_squared() < HUMANOID_AIR_SPEED.powf(2.0) =>
{
HUMANOID_AIR_ACCEL HUMANOID_AIR_ACCEL
} }
(true, false, true, _) (true, Roll { .. }) if vel.0.magnitude_squared() < ROLL_SPEED.powf(2.0) => {
if vel.0.magnitude_squared() < ROLL_SPEED.powf(2.0) =>
{
ROLL_ACCEL ROLL_ACCEL
} }
(true, false, false, true)
if vel.0.magnitude_squared() < WIELD_SPEED.powf(2.0) =>
{
WIELD_ACCEL
}
_ => 0.0, _ => 0.0,
}; };
// Set direction based on move direction when on the ground // Set direction based on move direction when on the ground
let ori_dir = if a.gliding || a.rolling { let ori_dir = if character.movement == Glide || character.movement.is_roll() {
Vec2::from(vel.0) Vec2::from(vel.0)
} else { } else {
move_dir.0 controller.move_dir
}; };
if ori_dir.magnitude_squared() > 0.0001 if ori_dir.magnitude_squared() > 0.0001
&& (ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared() && (ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
@ -123,30 +106,29 @@ impl<'a> System<'a> for Sys {
ori.0 = vek::ops::Slerp::slerp( ori.0 = vek::ops::Slerp::slerp(
ori.0, ori.0,
ori_dir.into(), ori_dir.into(),
if a.on_ground { 12.0 } else { 2.0 } * dt.0, if physics.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);
}
// Glide // Glide
if a.gliding && vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) && vel.0.z < 0.0 { if character.movement == Glide
let _ = wieldings.remove(entity); && 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; let lift = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2;
vel.0.z += dt.0 * lift * Vec2::<f32>::from(vel.0 * 0.15).magnitude().min(1.0); vel.0.z += dt.0 * lift * Vec2::<f32>::from(vel.0 * 0.15).magnitude().min(1.0);
} }
// Roll // Roll
if let Some(time) = rollings.get_mut(entity).map(|r| &mut r.time) { if let Roll { time_left } = &mut character.movement {
let _ = wieldings.remove(entity); character.action = Idle;
*time += dt.0; if *time_left == Duration::default() || vel.0.magnitude_squared() < 10.0 {
if *time > 0.6 || !a.moving { character.movement = Run;
rollings.remove(entity); } else {
*time_left = time_left
.checked_sub(Duration::from_secs_f32(dt.0))
.unwrap_or_default();
} }
} }
} }

View File

@ -1,16 +1,14 @@
use crate::{ use {
comp::HealthSource, crate::{
comp::{ comp::{Body, CharacterState, MovementState::*, Ori, PhysicsState, Pos, Scale, Stats, Vel},
ActionState, Body, Jumping, MoveDir, OnGround, Ori, Pos, Rolling, Scale, Stats, Vel,
Wielding,
},
event::{Event, EventBus}, event::{Event, EventBus},
state::DeltaTime, state::DeltaTime,
terrain::TerrainMap, terrain::TerrainMap,
vol::{ReadVol, Vox}, 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 GRAVITY: f32 = 9.81 * 4.0;
const FRIC_GROUND: f32 = 0.15; const FRIC_GROUND: f32 = 0.15;
@ -37,10 +35,10 @@ impl<'a> System<'a> for Sys {
ReadExpect<'a, TerrainMap>, ReadExpect<'a, TerrainMap>,
Read<'a, DeltaTime>, Read<'a, DeltaTime>,
Read<'a, EventBus>, Read<'a, EventBus>,
ReadStorage<'a, ActionState>,
ReadStorage<'a, Scale>, ReadStorage<'a, Scale>,
ReadStorage<'a, Body>, ReadStorage<'a, Body>,
WriteStorage<'a, OnGround>, WriteStorage<'a, CharacterState>,
WriteStorage<'a, PhysicsState>,
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>, WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>, WriteStorage<'a, Ori>,
@ -53,10 +51,10 @@ impl<'a> System<'a> for Sys {
terrain, terrain,
dt, dt,
event_bus, event_bus,
action_states,
scales, scales,
bodies, bodies,
mut on_grounds, mut character_states,
mut physics_states,
mut positions, mut positions,
mut velocities, mut velocities,
mut orientations, mut orientations,
@ -65,9 +63,8 @@ impl<'a> System<'a> for Sys {
let mut event_emitter = event_bus.emitter(); let mut event_emitter = event_bus.emitter();
// Apply movement inputs // 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, &entities,
&action_states,
scales.maybe(), scales.maybe(),
&bodies, &bodies,
&mut positions, &mut positions,
@ -76,12 +73,14 @@ impl<'a> System<'a> for Sys {
) )
.join() .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); let scale = scale.map(|s| s.0).unwrap_or(1.0);
// Integrate forces // Integrate forces
// Friction is assumed to be a constant dependent on location // Friction is assumed to be a constant dependent on location
let friction = 50.0 let friction = 50.0
* if on_grounds.get(entity).is_some() { * if physics_state.on_ground {
FRIC_GROUND FRIC_GROUND
} else { } else {
FRIC_AIR FRIC_AIR
@ -108,7 +107,7 @@ impl<'a> System<'a> for Sys {
if terrain if terrain
.get(block_pos) .get(block_pos)
.map(|vox| vox.is_solid()) .map(|vox| !vox.is_empty())
.unwrap_or(false) .unwrap_or(false)
{ {
let player_aabb = Aabb { let player_aabb = Aabb {
@ -128,8 +127,8 @@ impl<'a> System<'a> for Sys {
false false
}; };
let was_on_ground = a.on_ground; let was_on_ground = physics_state.on_ground;
on_grounds.remove(entity); // Assume we're in the air - unless we can prove otherwise physics_state.on_ground = false;
let mut on_ground = false; let mut on_ground = false;
let mut attempts = 0; // Don't loop infinitely here let mut attempts = 0; // Don't loop infinitely here
@ -183,7 +182,7 @@ impl<'a> System<'a> for Sys {
.filter(|(block_pos, _)| { .filter(|(block_pos, _)| {
terrain terrain
.get(*block_pos) .get(*block_pos)
.map(|vox| vox.is_solid()) .map(|vox| !vox.is_empty())
.unwrap_or(false) .unwrap_or(false)
}) })
// Find the maximum of the minimum collision axes (this bit is weird, trust me that it works) // 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 { if !was_on_ground {
event_emitter.emit(Event::LandOnGround { entity, vel: vel.0 }); 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 { if attempts == MAX_ATTEMPTS {
pos.0 = old_pos; pos.0 = old_pos;
vel.0 = Vec3::zero();
break; break;
} }
} }
if on_ground { 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 // 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()) } else if collision_with(pos.0 - Vec3::unit_z() * 1.05, near_iter.clone())
&& vel.0.z < 0.0 && vel.0.z < 0.0
@ -271,8 +274,13 @@ impl<'a> System<'a> for Sys {
&& was_on_ground && was_on_ground
{ {
pos.0.z = (pos.0.z - 0.05).floor(); 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 // Apply pushback

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
comp::{Dying, HealthSource, Stats}, comp::{HealthSource, Stats},
event::{Event, EventBus},
state::DeltaTime, state::DeltaTime,
}; };
use log::warn; use log::warn;
@ -11,16 +12,17 @@ impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, DeltaTime>, Read<'a, DeltaTime>,
Read<'a, EventBus>,
WriteStorage<'a, Stats>, 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() { for (entity, mut stat) in (&entities, &mut stats).join() {
if stat.should_die() && !stat.is_dead { if stat.should_die() && !stat.is_dead {
let _ = dyings.insert( event_emitter.emit(Event::Die {
entity, entity,
Dying {
cause: match stat.health.last_change { cause: match stat.health.last_change {
Some(change) => change.2, Some(change) => change.2,
None => { None => {
@ -28,10 +30,11 @@ impl<'a> System<'a> for Sys {
HealthSource::Unknown HealthSource::Unknown
} }
}, },
}, });
);
stat.is_dead = true; stat.is_dead = true;
} }
if let Some(change) = &mut stat.health.last_change { if let Some(change) = &mut stat.health.last_change {
change.1 += f64::from(dt.0); change.1 += f64::from(dt.0);
} }

View File

@ -37,6 +37,7 @@ use vek::*;
use world::{ChunkSupplement, World}; use world::{ChunkSupplement, World};
const CLIENT_TIMEOUT: f64 = 20.0; // Seconds const CLIENT_TIMEOUT: f64 = 20.0; // Seconds
const HUMANOID_JUMP_ACCEL: f32 = 18.0;
pub enum Event { pub enum Event {
ClientConnected { ClientConnected {
@ -153,8 +154,7 @@ impl Server {
.with(comp::Controller::default()) .with(comp::Controller::default())
.with(body) .with(body)
.with(comp::Stats::new(name)) .with(comp::Stats::new(name))
.with(comp::ActionState::default()) .with(comp::CharacterState::default())
.with(comp::ForceUpdate)
} }
/// Build a static object entity /// Build a static object entity
@ -175,8 +175,7 @@ impl Server {
..comp::LightEmitter::default() ..comp::LightEmitter::default()
}) })
//.with(comp::LightEmitter::default()) //.with(comp::LightEmitter::default())
.with(comp::ActionState::default()) .with(comp::CharacterState::default())
.with(comp::ForceUpdate)
} }
pub fn create_player_character( pub fn create_player_character(
@ -195,7 +194,7 @@ impl Server {
state.write_component(entity, comp::Pos(spawn_point)); state.write_component(entity, comp::Pos(spawn_point));
state.write_component(entity, comp::Vel(Vec3::zero())); state.write_component(entity, comp::Vel(Vec3::zero()));
state.write_component(entity, comp::Ori(Vec3::unit_y())); 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::Inventory::default());
state.write_component(entity, comp::InventoryUpdate); state.write_component(entity, comp::InventoryUpdate);
// Make sure physics are accepted. // Make sure physics are accepted.
@ -221,6 +220,9 @@ impl Server {
let terrain = self.state.ecs().read_resource::<TerrainMap>(); let terrain = self.state.ecs().read_resource::<TerrainMap>();
let mut block_change = self.state.ecs().write_resource::<BlockChange>(); let mut block_change = self.state.ecs().write_resource::<BlockChange>();
let mut stats = self.state.ecs().write_storage::<comp::Stats>(); let mut stats = self.state.ecs().write_storage::<comp::Stats>();
let mut positions = self.state.ecs().write_storage::<comp::Pos>();
let mut velocities = self.state.ecs().write_storage::<comp::Vel>();
let mut force_updates = self.state.ecs().write_storage::<comp::ForceUpdate>();
for event in self.state.ecs().read_resource::<EventBus>().recv_all() { for event in self.state.ecs().read_resource::<EventBus>().recv_all() {
match event { match event {
@ -250,6 +252,74 @@ impl Server {
.cast(); .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::<comp::Player>().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::<comp::Player>()
.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(comp::Controller::default())
.with(body) .with(body)
.with(stats) .with(stats)
.with(comp::ActionState::default()) .with(comp::CharacterState::default())
.with(comp::Agent::enemy()) .with(comp::Agent::enemy())
.with(comp::Scale(scale)) .with(comp::Scale(scale))
.build(); .build();
@ -475,13 +545,6 @@ impl Server {
// 7) Finish the tick, pass control back to the frontend. // 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::<comp::Dying>().remove(entity);
ecs.write_storage::<comp::Respawning>().remove(entity);
}
Ok(frontend_events) Ok(frontend_events)
} }
@ -885,12 +948,12 @@ impl Server {
state.write_component(entity, player); state.write_component(entity, player);
// Sync physics of all entities // 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::<Uid>(), &state.ecs().read_storage::<Uid>(),
&state.ecs().read_storage::<comp::Pos>(), // We assume all these entities have a position &state.ecs().read_storage::<comp::Pos>(), // We assume all these entities have a position
state.ecs().read_storage::<comp::Vel>().maybe(), state.ecs().read_storage::<comp::Vel>().maybe(),
state.ecs().read_storage::<comp::Ori>().maybe(), state.ecs().read_storage::<comp::Ori>().maybe(),
state.ecs().read_storage::<comp::ActionState>().maybe(), state.ecs().read_storage::<comp::CharacterState>().maybe(),
) )
.join() .join()
{ {
@ -910,10 +973,10 @@ impl Server {
ori, ori,
}); });
} }
if let Some(action_state) = action_state.copied() { if let Some(character_state) = character_state.copied() {
client.notify(ServerMsg::EntityActionState { client.notify(ServerMsg::EntityCharacterState {
entity: uid.into(), entity: uid.into(),
action_state, character_state,
}); });
} }
} }
@ -928,84 +991,7 @@ impl Server {
self.clients self.clients
.notify_registered(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package())); .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 ecs = self.state.ecs_mut();
let clients = &mut self.clients;
let todo_kill = (&ecs.entities(), &ecs.read_storage::<comp::Dying>())
.join()
.map(|(entity, dying)| {
// Chat message
if let Some(player) = ecs.read_storage::<comp::Player>().get(entity) {
let msg = if let comp::HealthSource::Attack { by } = dying.cause {
ecs.entity_from_uid(by.into()).and_then(|attacker| {
ecs.read_storage::<comp::Player>()
.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::<comp::Stats>();
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::<Vec<_>>();
// 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::<comp::Respawning>())
.join()
.map(|(entity, _)| entity)
.collect::<Vec<EcsEntity>>();
for entity in todo_respawn {
if let Some(client) = self.clients.get_mut(&entity) {
client.allow_state(ClientState::Character);
ecs.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|stats| stats.revive());
ecs.write_storage::<comp::Pos>()
.get_mut(entity)
.map(|pos| pos.0.z += 20.0);
let _ = ecs.write_storage().insert(entity, comp::ForceUpdate);
}
}
// Sync physics // Sync physics
for (entity, &uid, &pos, force_update) in ( for (entity, &uid, &pos, force_update) in (
@ -1043,7 +1029,7 @@ impl Server {
let mut last_pos = ecs.write_storage::<comp::Last<comp::Pos>>(); let mut last_pos = ecs.write_storage::<comp::Last<comp::Pos>>();
let mut last_vel = ecs.write_storage::<comp::Last<comp::Vel>>(); let mut last_vel = ecs.write_storage::<comp::Last<comp::Vel>>();
let mut last_ori = ecs.write_storage::<comp::Last<comp::Ori>>(); let mut last_ori = ecs.write_storage::<comp::Last<comp::Ori>>();
let mut last_action_state = ecs.write_storage::<comp::Last<comp::ActionState>>(); let mut last_character_state = ecs.write_storage::<comp::Last<comp::CharacterState>>();
if let Some(client_pos) = ecs.read_storage::<comp::Pos>().get(entity) { if let Some(client_pos) = ecs.read_storage::<comp::Pos>().get(entity) {
if last_pos if last_pos
@ -1099,16 +1085,19 @@ impl Server {
} }
} }
if let Some(client_action_state) = ecs.read_storage::<comp::ActionState>().get(entity) { if let Some(client_character_state) =
if last_action_state ecs.read_storage::<comp::CharacterState>().get(entity)
{
if last_character_state
.get(entity) .get(entity)
.map(|&l| l != *client_action_state) .map(|&l| l != *client_character_state)
.unwrap_or(true) .unwrap_or(true)
{ {
let _ = last_action_state.insert(entity, comp::Last(*client_action_state)); let _ =
let msg = ServerMsg::EntityActionState { last_character_state.insert(entity, comp::Last(*client_character_state));
let msg = ServerMsg::EntityCharacterState {
entity: uid.into(), entity: uid.into(),
action_state: *client_action_state, character_state: *client_character_state,
}; };
match force_update { match force_update {
Some(_) => clients.notify_ingame_if(msg, in_vd), Some(_) => clients.notify_ingame_if(msg, in_vd),