diff --git a/client/src/lib.rs b/client/src/lib.rs index 3e0eeaa217..4931912b66 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -167,9 +167,11 @@ impl Client { _ => {}, } - // Update the server about the player's currently playing animation - if let Some(animation) = self.state.read_storage().get(self.player).cloned() { - self.postbox.send_message(ClientMsg::PlayerAnimation(animation)); + // Update the server about the player's currently playing animation and the previous one + if let Some(animation_history) = self.state.read_storage::().get(self.player).cloned() { + if Some(animation_history.current) != animation_history.last { + self.postbox.send_message(ClientMsg::PlayerAnimation(animation_history)); + } } // Request chunks from the server @@ -228,9 +230,9 @@ impl Client { }, None => {}, }, - ServerMsg::EntityAnimation { entity, animation } => match self.state.ecs().entity_from_uid(entity) { + ServerMsg::EntityAnimation { entity, animation_history } => match self.state.ecs().entity_from_uid(entity) { Some(entity) => { - self.state.write_component(entity, animation); + self.state.write_component(entity, animation_history); }, None => {}, }, diff --git a/common/src/comp/character.rs b/common/src/comp/character.rs index e34919236e..2ed29831f6 100644 --- a/common/src/comp/character.rs +++ b/common/src/comp/character.rs @@ -19,6 +19,12 @@ pub enum Gender { } #[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct AnimationHistory { + pub last: Option, + pub current: Animation, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Animation { Idle, Run, @@ -55,6 +61,6 @@ impl Component for Character { type Storage = FlaggedStorage>; } -impl Component for Animation { +impl Component for AnimationHistory { type Storage = FlaggedStorage>; } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index c67f27cee2..d10012c976 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -7,4 +7,5 @@ pub mod phys; pub use agent::{Agent, Control}; pub use character::Character; pub use player::Player; +pub use character::AnimationHistory; pub use character::Animation; diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 01c35a7962..95eaadecbf 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -11,7 +11,7 @@ pub enum ClientMsg { Ping, Pong, Chat(String), - PlayerAnimation(comp::character::Animation), + PlayerAnimation(comp::character::AnimationHistory), PlayerPhysics { pos: comp::phys::Pos, vel: comp::phys::Vel, diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 1585c5f32f..3372b524fb 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -25,7 +25,7 @@ pub enum ServerMsg { }, EntityAnimation { entity: u64, - animation: comp::Animation, + animation_history: comp::AnimationHistory, }, TerrainChunkUpdate { key: Vec3, diff --git a/common/src/state.rs b/common/src/state.rs index 84a6736362..6a12cc6d7d 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -100,7 +100,7 @@ impl State { ecs.internal_mut().register::(); ecs.internal_mut().register::(); ecs.internal_mut().register::(); - ecs.internal_mut().register::(); + ecs.internal_mut().register::(); ecs.internal_mut().register::(); ecs.internal_mut().register::(); diff --git a/common/src/sys/control.rs b/common/src/sys/control.rs index a590e13c47..b2568c6975 100644 --- a/common/src/sys/control.rs +++ b/common/src/sys/control.rs @@ -3,7 +3,7 @@ use specs::{Join, Read, ReadStorage, System, WriteStorage, Entities}; use vek::*; // Crate -use crate::comp::{Control, Animation, phys::{Pos, Vel, Dir}}; +use crate::comp::{Control, Animation, AnimationHistory, phys::{Pos, Vel, Dir}}; // Basic ECS AI agent system pub struct Sys; @@ -13,7 +13,7 @@ impl<'a> System<'a> for Sys { Entities<'a>, WriteStorage<'a, Vel>, WriteStorage<'a, Dir>, - WriteStorage<'a, Animation>, + WriteStorage<'a, AnimationHistory>, ReadStorage<'a, Control>, ); @@ -23,12 +23,20 @@ impl<'a> System<'a> for Sys { // Apply physics to the player: acceleration and non-linear decceleration vel.0 += control.move_dir * 2.0 - vel.0.map(|e| e * e.abs() + e) * 0.03; - if control.move_dir.magnitude() > 0.01 { - dir.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0); - anims.insert(entity, Animation::Run); - } else { - anims.insert(entity, Animation::Idle); - } + let animation = + if control.move_dir.magnitude() > 0.01 { + dir.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0); + Animation::Run + } else { + Animation::Idle + }; + + let last_animation = anims.get_mut(entity).map(|h| h.current); + + anims.insert(entity, AnimationHistory { + last: last_animation, + current: animation, + }); } } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 0eb84959b5..fa9a390eb7 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -15,6 +15,7 @@ use common::{ net::PostOffice, state::{State, Uid}, terrain::TerrainChunk, + comp::character::Animation, }; use specs::{ join::Join, saveload::MarkedBuilder, world::EntityBuilder as EcsEntityBuilder, Builder, @@ -235,28 +236,7 @@ impl Server { match client.state { ClientState::Connecting => match msg { ClientMsg::Connect { player, character } => { - - // Write client components - state.write_component(entity, player); - state.write_component(entity, comp::phys::Pos(Vec3::zero())); - state.write_component(entity, comp::phys::Vel(Vec3::zero())); - state.write_component(entity, comp::phys::Dir(Vec3::unit_y())); - if let Some(character) = character { - state.write_component(entity, character); - } - state.write_component(entity, comp::phys::ForceUpdate); - - client.state = ClientState::Connected; - - // Return a handshake with the state of the current world - client.notify(ServerMsg::Handshake { - ecs_state: state.ecs().gen_state_package(), - player_entity: state - .ecs() - .uid_from_entity(entity) - .unwrap() - .into(), - }); + Self::initialize_client(state, entity, client, player, character); } _ => disconnect = true, }, @@ -266,7 +246,7 @@ impl Server { ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong), ClientMsg::Pong => {} ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)), - ClientMsg::PlayerAnimation(animation) => state.write_component(entity, animation), + ClientMsg::PlayerAnimation(animation_history) => state.write_component(entity, animation_history), ClientMsg::PlayerPhysics { pos, vel, dir } => { state.write_component(entity, pos); state.write_component(entity, vel); @@ -341,6 +321,63 @@ impl Server { Ok(frontend_events) } + /// Initialize a new client states with important information + fn initialize_client( + state: &mut State, + entity: specs::Entity, + client: &mut Client, + player: comp::Player, + character: Option, + ) { + // Save player metadata (for example the username) + state.write_component(entity, player); + + // Give the player it's character if he wants one + // (Chat only clients don't need one for example) + if let Some(character) = character { + state.write_component(entity, character); + + // Every character has to have these components + state.write_component(entity, comp::phys::Pos(Vec3::zero())); + state.write_component(entity, comp::phys::Vel(Vec3::zero())); + state.write_component(entity, comp::phys::Dir(Vec3::unit_y())); + // Make sure everything is accepted + state.write_component(entity, comp::phys::ForceUpdate); + + // Set initial animation + state.write_component(entity, comp::AnimationHistory { + last: None, + current: Animation::Idle + }); + } + + client.state = ClientState::Connected; + + // Return a handshake with the state of the current world + // (All components Sphynx tracks) + client.notify(ServerMsg::Handshake { + ecs_state: state.ecs().gen_state_package(), + player_entity: state + .ecs() + .uid_from_entity(entity) + .unwrap() + .into(), + }); + + // Sync logical information other players have authority over, not the server + for (other_entity, &uid, &animation_history) in ( + &state.ecs().internal().entities(), + &state.ecs().internal().read_storage::(), + &state.ecs().internal().read_storage::(), + ).join() { + // AnimationHistory + client.postbox.send_message(ServerMsg::EntityAnimation { + entity: uid.into(), + animation_history: animation_history, + }); + } + } + /// Sync client states with the most up to date information fn sync_clients(&mut self) { // Sync 'logical' state using Sphynx @@ -369,17 +406,30 @@ impl Server { } // Sync animation states - for (entity, &uid, &animation) in ( + for (entity, &uid, &animation_history) in ( &self.state.ecs().internal().entities(), &self.state.ecs().internal().read_storage::(), - &self.state.ecs().internal().read_storage::(), + &self.state.ecs().internal().read_storage::(), ).join() { + // Check if we need to sync + if Some(animation_history.current) == animation_history.last { + continue; + } + self.clients.notify_connected_except(entity, ServerMsg::EntityAnimation { entity: uid.into(), - animation, + animation_history, }); } + // Update animation last/current state + for (entity, mut animation_history) in ( + &self.state.ecs().internal().entities(), + &mut self.state.ecs().internal().write_storage::() + ).join() { + animation_history.last = Some(animation_history.current); + } + // Remove all force flags self.state.ecs_mut().internal_mut().write_storage::().clear(); } diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 75b727b07f..66f1a65b27 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -83,18 +83,18 @@ impl Figures { pub fn maintain(&mut self, renderer: &mut Renderer, client: &mut Client) { let time = client.state().get_time(); let ecs = client.state_mut().ecs_mut().internal_mut(); - for (entity, pos, dir, character, animation) in ( + for (entity, pos, dir, character, animation_history) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), - &ecs.read_storage::(), + &ecs.read_storage::(), ).join() { let state = self.states .entry(entity) .or_insert_with(|| FigureState::new(renderer, CharacterSkeleton::new())); - let target_skeleton = match animation { + let target_skeleton = match animation_history.current { comp::character::Animation::Idle => IdleAnimation::update_skeleton(&mut state.skeleton, time), comp::character::Animation::Run => RunAnimation::update_skeleton(&mut state.skeleton, time), };