diff --git a/client/src/lib.rs b/client/src/lib.rs index db85e3211e..04b159ff30 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -160,6 +160,9 @@ impl Client { self.state.write_component(self.player, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY) * 0.1)); if input.move_dir.magnitude() > 0.01 { self.state.write_component(self.player, comp::phys::Dir(input.move_dir.normalized().into())); + self.state.write_component(self.player, comp::Animation::Run); + } else { + self.state.write_component(self.player, comp::Animation::Idle); } } @@ -178,6 +181,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)); + } + // Request chunks from the server if let Some(pos) = self.state.read_storage::().get(self.player) { let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); @@ -234,6 +242,12 @@ impl Client { }, None => {}, }, + ServerMsg::EntityAnimation { entity, animation } => match self.state.ecs().entity_from_uid(entity) { + Some(entity) => { + self.state.write_component(entity, animation); + }, + None => {}, + }, ServerMsg::TerrainChunkUpdate { key, chunk } => { self.state.insert_chunk(key, *chunk); self.pending_chunks.remove(&key); diff --git a/common/src/comp/character.rs b/common/src/comp/character.rs index 02cef3eff6..e34919236e 100644 --- a/common/src/comp/character.rs +++ b/common/src/comp/character.rs @@ -18,6 +18,12 @@ pub enum Gender { Unspecified, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum Animation { + Idle, + Run, +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Character { race: Race, @@ -27,6 +33,7 @@ pub struct Character { belt: (), arms: (), feet: (), + } impl Character { @@ -47,3 +54,7 @@ impl Character { impl Component for Character { type Storage = FlaggedStorage>; } + +impl Component for Animation { + type Storage = FlaggedStorage>; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index f5fa9254b7..4b12f8becf 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -5,3 +5,4 @@ pub mod phys; // Reexports pub use character::Character; pub use player::Player; +pub use character::Animation; diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 5a599633d7..01c35a7962 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -6,10 +6,12 @@ pub enum ClientMsg { Connect { player: comp::Player, character: Option, + }, Ping, Pong, Chat(String), + PlayerAnimation(comp::character::Animation), PlayerPhysics { pos: comp::phys::Pos, vel: comp::phys::Vel, diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index e75a69d34f..1585c5f32f 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -23,6 +23,10 @@ pub enum ServerMsg { vel: comp::phys::Vel, dir: comp::phys::Dir, }, + EntityAnimation { + entity: u64, + animation: comp::Animation, + }, TerrainChunkUpdate { key: Vec3, chunk: Box, diff --git a/common/src/state.rs b/common/src/state.rs index 480928f800..3a26e6126e 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -100,6 +100,7 @@ impl State { ecs.internal_mut().register::(); ecs.internal_mut().register::(); ecs.internal_mut().register::(); + ecs.internal_mut().register::(); // Register resources used by the ECS ecs.internal_mut().add_resource(TimeOfDay(0.0)); diff --git a/server/src/lib.rs b/server/src/lib.rs index 3a2316adf3..dd71df87cc 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -234,6 +234,7 @@ impl Server { 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); @@ -257,6 +258,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::PlayerPhysics { pos, vel, dir } => { state.write_component(entity, pos); state.write_component(entity, vel); @@ -347,11 +349,25 @@ impl Server { }; match force_update { + + Some(_) => self.clients.notify_connected(msg), None => self.clients.notify_connected_except(entity, msg), } } + // Sync animation states + for (entity, &uid, &animation) in ( + &self.state.ecs().internal().entities(), + &self.state.ecs().internal().read_storage::(), + &self.state.ecs().internal().read_storage::(), + ).join() { + self.clients.notify_connected_except(entity, ServerMsg::EntityAnimation { + entity: uid.into(), + animation, + }); + } + // Remove all force flags self.state.ecs_mut().internal_mut().write_storage::().clear(); } diff --git a/voxygen/src/anim/character/idle.rs b/voxygen/src/anim/character/idle.rs new file mode 100644 index 0000000000..5c29f48287 --- /dev/null +++ b/voxygen/src/anim/character/idle.rs @@ -0,0 +1,48 @@ +// Standard +use std::f32::consts::PI; + +// Library +use vek::*; + +// Local +use super::{ + CharacterSkeleton, + super::Animation, +}; + +pub struct IdleAnimation; + +//TODO: Make it actually good, possibly add the head rotating slightly, add breathing, etc. +impl Animation for IdleAnimation { + type Skeleton = CharacterSkeleton; + type Dependency = f64; + + fn update_skeleton( + skeleton: &mut Self::Skeleton, + time: f64, + ) { + skeleton.head.offset = Vec3::unit_z() * 13.0 / 11.0; + skeleton.head.ori = Quaternion::rotation_z(0.0); + + skeleton.chest.offset = Vec3::unit_z() * 9.0 / 11.0; + skeleton.chest.ori = Quaternion::rotation_z(0.0); + + skeleton.belt.offset = Vec3::unit_z() * 7.0 / 11.0; + skeleton.belt.ori = Quaternion::rotation_z(0.0); + + skeleton.shorts.offset = Vec3::unit_z() * 4.0 / 11.0; + skeleton.shorts.ori = Quaternion::rotation_z(0.0); + + skeleton.l_hand.offset = Vec3::new(-8.0, 0.0, 9.0) / 11.0; + skeleton.r_hand.offset = Vec3::new(8.0, 0.0, 9.0 ) / 11.0; + + skeleton.l_foot.offset = Vec3::new(-3.5, 0.0, 3.0) / 11.0; + skeleton.l_foot.ori = Quaternion::rotation_x(0.0); + skeleton.r_foot.offset = Vec3::new(3.5, 0.0, 3.0) / 11.0; + skeleton.r_foot.ori = Quaternion::rotation_x(0.0); + + skeleton.back.offset = Vec3::new(-9.0, 5.0, 18.0); + skeleton.back.ori = Quaternion::rotation_y(2.5); + skeleton.back.scale = Vec3::one(); + } +} \ No newline at end of file diff --git a/voxygen/src/anim/character/mod.rs b/voxygen/src/anim/character/mod.rs index 367cdf32ed..8072b29ba4 100644 --- a/voxygen/src/anim/character/mod.rs +++ b/voxygen/src/anim/character/mod.rs @@ -1,7 +1,9 @@ pub mod run; +pub mod idle; // Reexports pub use self::run::RunAnimation; +pub use self::idle::IdleAnimation; // Crate use crate::render::FigureBoneData; diff --git a/voxygen/src/menu/main/client_init.rs b/voxygen/src/menu/main/client_init.rs index 25473bdabb..124939f957 100644 --- a/voxygen/src/menu/main/client_init.rs +++ b/voxygen/src/menu/main/client_init.rs @@ -22,10 +22,10 @@ pub struct ClientInit { impl ClientInit { pub fn new( connection_args: (String, u16, bool), - client_args: (comp::Player, Option, u64), + client_args: (comp::Player, Option, Option, u64), ) -> Self { let (server_address, default_port, prefer_ipv6) = connection_args; - let (player, character, view_distance) = client_args; + let (player, character, animation, view_distance) = client_args; let (tx, rx) = channel(); diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 7f12830ac5..7461b53066 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -95,6 +95,7 @@ impl PlayState for MainMenuState { ( comp::Player::new(username.clone()), Some(comp::Character::test()), + Some(comp::Animation::Idle), 300, ), ))); diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 5b61c7ec32..0ed1092728 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -8,6 +8,7 @@ use client::Client; use common::{ comp, figure::Segment, + msg }; use crate::{ Error, @@ -27,6 +28,7 @@ use crate::{ character::{ CharacterSkeleton, RunAnimation, + IdleAnimation, }, }, mesh::Meshable, @@ -81,17 +83,21 @@ 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) in ( + for (entity, pos, dir, character, animation) in ( &ecs.entities(), &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())); - RunAnimation::update_skeleton(&mut state.skeleton, time); + match animation { + comp::character::Animation::Idle => IdleAnimation::update_skeleton(&mut state.skeleton, time), + comp::character::Animation::Run => RunAnimation::update_skeleton(&mut state.skeleton, time), + } state.update(renderer, pos.0, dir.0); }