diff --git a/chat-cli/src/main.rs b/chat-cli/src/main.rs index 548829b25c..886b5f6ec6 100644 --- a/chat-cli/src/main.rs +++ b/chat-cli/src/main.rs @@ -19,8 +19,13 @@ fn main() { .expect("Failed to create client instance"); loop { - let events = client.tick(Input::default(), clock.get_last_delta()) - .expect("Failed to tick client"); + let events = match client.tick(Input::default(), clock.get_last_delta()) { + Ok(events) => events, + Err(err) => { + println!("Error: {:?}", err); + break; + }, + }; for event in events { match event { diff --git a/client/src/lib.rs b/client/src/lib.rs index 3069184921..7c2fc8017e 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -14,9 +14,12 @@ use std::{ }; use vek::*; use threadpool; -use specs::Builder; +use specs::{ + Builder, + saveload::MarkerAllocator, +}; use common::{ - comp, + comp::{self, Uid}, state::State, terrain::TerrainChunk, net::PostBox, @@ -36,7 +39,7 @@ pub struct Client { tick: u64, state: State, - player: Option, + player: Option, // Testing world: World, @@ -80,7 +83,6 @@ impl Client { // TODO: Get rid of this pub fn with_test_state(mut self) -> Self { self.chunk = Some(self.world.generate_chunk(Vec3::zero())); - self.player = Some(self.state.new_test_player()); self } @@ -99,15 +101,32 @@ impl Client { pub fn state_mut(&mut self) -> &mut State { &mut self.state } /// Get an entity from its UID, creating it if it does not exists - pub fn get_or_create_entity(&mut self, uid: u64) -> EcsEntity { - self.state.ecs_world_mut().create_entity() - .with(comp::Uid(uid)) - .build() + pub fn get_or_create_entity(&mut self, uid: Uid) -> EcsEntity { + // Find the ECS entity from its UID + let ecs_entity = self.state().ecs_world() + .read_resource::() + .retrieve_entity_internal(uid.into()); + + // Return the entity or create it + if let Some(ecs_entity) = ecs_entity { + ecs_entity + } else { + let ecs_entity = self.state.ecs_world_mut().create_entity() + .build(); + + // Allocate it the specific UID given + self.state + .ecs_world_mut() + .write_resource::() + .allocate(ecs_entity, Some(uid.into())); + + ecs_entity + } } /// Get the player entity #[allow(dead_code)] - pub fn player(&self) -> Option { + pub fn player(&self) -> Option { self.player } @@ -145,12 +164,12 @@ impl Client { frontend_events.append(&mut self.handle_new_messages()?); // Step 3 - if let Some(p) = self.player { + if let Some(ecs_entity) = self.player.and_then(|uid| self.state().get_entity(uid)) { // TODO: remove this const PLAYER_VELOCITY: f32 = 100.0; // TODO: Set acceleration instead - self.state.write_component(p, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY))); + self.state.write_component(ecs_entity, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY))); } // Tick the client's LocalState (step 3) @@ -182,12 +201,16 @@ impl Client { match msg { ServerMsg::Shutdown => return Err(Error::ServerShutdown), ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), + ServerMsg::SetPlayerEntity(uid) => self.player = Some(uid), ServerMsg::EntityPhysics { uid, pos, vel, dir } => { let ecs_entity = self.get_or_create_entity(uid); self.state.write_component(ecs_entity, pos); self.state.write_component(ecs_entity, vel); self.state.write_component(ecs_entity, dir); }, + ServerMsg::EntityDeleted(uid) => { + self.state.delete_entity(uid); + }, } } } else if let Some(err) = self.postbox.status() { diff --git a/common/Cargo.toml b/common/Cargo.toml index ca660532d1..6ce742c38d 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [dependencies] specs = { version = "0.14", features = ["serde"] } -shred = "0.7" +shred = { version = "0.7", features = ["nightly"] } vek = { version = "0.9", features = ["serde"] } dot_vox = "1.0" threadpool = "1.7" diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 528f1a3468..684d6b1798 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -8,8 +8,10 @@ use specs::World as EcsWorld; pub fn register_local_components(ecs_world: &mut EcsWorld) { ecs_world.register::(); + ecs_world.add_resource(UidAllocator::new()); ecs_world.register::(); ecs_world.register::(); ecs_world.register::(); + ecs_world.register::(); } diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 81eb09a249..73b1000d8a 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -28,3 +28,14 @@ pub struct Dir(pub Vec3); impl Component for Dir { type Storage = VecStorage; } + +// UpdateKind +#[derive(Copy, Clone, Debug)] +pub enum UpdateKind { + Passive, + Force, +} + +impl Component for UpdateKind { + type Storage = VecStorage; +} diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index ea80e1038f..b613b790d7 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,13 +1,18 @@ -use crate::comp::phys; +use crate::comp::{ + Uid, + phys, +}; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum ServerMsg { Shutdown, Chat(String), + SetPlayerEntity(Uid), EntityPhysics { - uid: u64, + uid: Uid, pos: phys::Pos, vel: phys::Vel, dir: phys::Dir, }, + EntityDeleted(Uid), } diff --git a/common/src/state.rs b/common/src/state.rs index 14442ecb56..de33bf6986 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -90,6 +90,14 @@ impl State { self } + /// Get an entity from its UID, if it exists + pub fn get_entity(&self, uid: comp::Uid) -> Option { + // Find the ECS entity from its UID + self.ecs_world + .read_resource::() + .retrieve_entity_internal(uid.into()) + } + /// Delete an entity from the state's ECS, if it exists pub fn delete_entity(&mut self, uid: comp::Uid) { // Find the ECS entity from its UID @@ -103,16 +111,6 @@ impl State { } } - // TODO: Get rid of this - pub fn new_test_player(&mut self) -> EcsEntity { - self.ecs_world - .create_entity() - .with(comp::phys::Pos(Vec3::default())) - .with(comp::phys::Vel(Vec3::default())) - .with(comp::phys::Dir(Vec3::default())) - .build() - } - /// Write a component attributed to a particular entity pub fn write_component(&mut self, entity: EcsEntity, comp: C) { let _ = self.ecs_world.write_storage().insert(entity, comp); diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 2fddf26e4c..f959114998 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -3,7 +3,7 @@ use log::info; use server::{Input, Event, Server}; use common::clock::Clock; -const FPS: u64 = 60; +const TPS: u64 = 20; fn main() { // Init logging @@ -24,9 +24,9 @@ fn main() { for event in events { match event { - Event::ClientConnected { uid } => println!("Client {} connected!", uid), - Event::ClientDisconnected { uid } => println!("Client {} disconnected!", uid), - Event::Chat { uid, msg } => println!("[Client {}] {}", uid, msg), + Event::ClientConnected { uid } => info!("Client {} connected!", uid), + Event::ClientDisconnected { uid } => info!("Client {} disconnected!", uid), + Event::Chat { uid, msg } => info!("[Client {}] {}", uid, msg), } } @@ -34,6 +34,6 @@ fn main() { server.cleanup(); // Wait for the next tick - clock.tick(Duration::from_millis(1000 / FPS)); + clock.tick(Duration::from_millis(1000 / TPS)); } } diff --git a/server/src/client.rs b/server/src/client.rs index dd5418b002..ffc876e1bc 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -35,7 +35,6 @@ impl Clients { for client in &mut self.clients { // Consume any errors, deal with them later let _ = client.postbox.send(msg.clone()); - println!("Sending message..."); } } @@ -44,7 +43,6 @@ impl Clients { if client.uid != uid { // Consume any errors, deal with them later let _ = client.postbox.send(msg.clone()); - println!("Sending message..."); } } } diff --git a/server/src/lib.rs b/server/src/lib.rs index ee8618283a..856ab9635d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -61,12 +61,8 @@ impl Server { /// Create a new `Server`. #[allow(dead_code)] pub fn new() -> Result { - let mut state = State::new(); - - state.ecs_world_mut().add_resource(comp::UidAllocator::new()); - Ok(Self { - state, + state: State::new(), world: World::new(), postoffice: PostOffice::new(SocketAddr::from(([0; 4], 59003)))?, @@ -93,6 +89,9 @@ impl Server { .with(comp::phys::Pos(Vec3::zero())) .with(comp::phys::Vel(Vec3::zero())) .with(comp::phys::Dir(Vec3::unit_y())) + // When the player is first created, force a physics notification to everyone + // including themselves. + .with(comp::phys::UpdateKind::Force) } /// Get a reference to the server's world. @@ -154,19 +153,21 @@ impl Server { fn handle_new_connections(&mut self) -> Result, Error> { let mut frontend_events = Vec::new(); - for postbox in self.postoffice.new_connections() { + for mut postbox in self.postoffice.new_connections() { let ecs_entity = self.build_player().build(); let uid = self.state.read_component(ecs_entity).unwrap(); - frontend_events.push(Event::ClientConnected { - uid, - }); + let _ = postbox.send(ServerMsg::SetPlayerEntity(uid)); self.clients.add(Client { uid, postbox, last_ping: self.state.get_time(), }); + + frontend_events.push(Event::ClientConnected { + uid, + }); } Ok(frontend_events) @@ -178,6 +179,7 @@ impl Server { let state = &mut self.state; let mut new_chat_msgs = Vec::new(); + let mut disconnected_clients = Vec::new(); self.clients.remove_if(|client| { let mut disconnected = false; @@ -202,10 +204,7 @@ impl Server { } if disconnected { - state.delete_entity(client.uid); - frontend_events.push(Event::ClientDisconnected { - uid: client.uid, - }); + disconnected_clients.push(client.uid); true } else { false @@ -222,23 +221,45 @@ impl Server { }); } + // Handle client disconnects + for uid in disconnected_clients { + self.clients.notify_all(ServerMsg::EntityDeleted(uid)); + + frontend_events.push(Event::ClientDisconnected { + uid, + }); + + state.delete_entity(uid); + } + Ok(frontend_events) } /// Sync client states with the most up to date information fn sync_clients(&mut self) { - for (&uid, &pos, &vel, &dir) in ( + for (&uid, &pos, &vel, &dir, update_kind) in ( &self.state.ecs_world().read_storage::(), &self.state.ecs_world().read_storage::(), &self.state.ecs_world().read_storage::(), &self.state.ecs_world().read_storage::(), + &mut self.state.ecs_world().write_storage::(), ).join() { - self.clients.notify_all_except(uid, ServerMsg::EntityPhysics { - uid: uid.into(), + let msg = ServerMsg::EntityPhysics { + uid, pos, vel, dir, - }); + }; + + // Sometimes we need to force updated (i.e: teleporting players). This involves sending + // everyone, including the player themselves, of their new physics information. + match update_kind { + comp::phys::UpdateKind::Force => self.clients.notify_all(msg), + comp::phys::UpdateKind::Passive => self.clients.notify_all_except(uid, msg), + } + + // Now that the update has occured, default to a passive update + *update_kind = comp::phys::UpdateKind::Passive; } } } diff --git a/voxygen/src/anim/character/mod.rs b/voxygen/src/anim/character/mod.rs index 06850b38dc..367cdf32ed 100644 --- a/voxygen/src/anim/character/mod.rs +++ b/voxygen/src/anim/character/mod.rs @@ -15,10 +15,10 @@ use super::{ pub struct CharacterSkeleton { head: Bone, chest: Bone, - bl_foot: Bone, - br_foot: Bone, - r_hand: Bone, + belt: Bone, + shorts: Bone, l_hand: Bone, + r_hand: Bone, l_foot: Bone, r_foot: Bone, back: Bone, @@ -29,10 +29,10 @@ impl CharacterSkeleton { Self { head: Bone::default(), chest: Bone::default(), - br_foot: Bone::default(), - bl_foot: Bone::default(), - r_hand: Bone::default(), + belt: Bone::default(), + shorts: Bone::default(), l_hand: Bone::default(), + r_hand: Bone::default(), l_foot: Bone::default(), r_foot: Bone::default(), back: Bone::default(), @@ -47,10 +47,10 @@ impl Skeleton for CharacterSkeleton { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(chest_mat), - FigureBoneData::new(self.bl_foot.compute_base_matrix()), - FigureBoneData::new(self.br_foot.compute_base_matrix()), - FigureBoneData::new(self.r_hand.compute_base_matrix()), + FigureBoneData::new(self.belt.compute_base_matrix()), + FigureBoneData::new(self.shorts.compute_base_matrix()), FigureBoneData::new(self.l_hand.compute_base_matrix()), + FigureBoneData::new(self.r_hand.compute_base_matrix()), FigureBoneData::new(self.l_foot.compute_base_matrix()), FigureBoneData::new(self.r_foot.compute_base_matrix()), FigureBoneData::new(chest_mat * self.back.compute_base_matrix()), diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 5f1eff799b..015feb8d89 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -21,34 +21,30 @@ impl Animation for RunAnimation { time: f64, ) { let wave = (time as f32 * 12.0).sin(); - let wavecos = (time as f32 * 12.0).cos(); let wave_slow = (time as f32 * 6.0 + PI).sin(); - let wavecos_slow = (time as f32 * 6.0 + PI).cos(); let wave_dip = (wave_slow.abs() - 0.5).abs(); - skeleton.head.offset = Vec3::new(0.0, 0.0, 0.0); - skeleton.head.ori = Quaternion::rotation_x(0.0); + skeleton.head.offset = Vec3::unit_z() * 13.0; + skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); - skeleton.chest.offset = Vec3::new(0.0, 0.0, 0.0); - skeleton.chest.ori = Quaternion::rotation_x(0.0); + skeleton.chest.offset = Vec3::unit_z() * 9.0; + skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3); - //skeleton.br_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0); - //skeleton.br_foot.ori = Quaternion::rotation_x(0.0 + wave_slow * 10.1); + skeleton.belt.offset = Vec3::unit_z() * 7.0; + skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2); - skeleton.bl_foot.offset = Vec3::new(0.0, 0.0, 0.0); - skeleton.bl_foot.ori = Quaternion::rotation_x(wave_slow * 2.0); - //skeleton.bl_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0); - //skeleton.bl_foot.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1); + skeleton.shorts.offset = Vec3::unit_z() * 4.0; + skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1); - //skeleton.r_hand.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0); - //skeleton.r_hand.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1); - - skeleton.l_hand.offset = Vec3::new(0.0, 0.0, 0.0); - skeleton.l_hand.ori = Quaternion::rotation_x(wave_slow * 2.0); - - //skeleton.l_hand.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0); - //skeleton.l_hand.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1); + skeleton.l_hand.offset = Vec3::new(-6.0 - wave_dip * 6.0, wave * 5.0, 11.0 - wave_dip * 6.0); + skeleton.r_hand.offset = Vec3::new(6.0 + wave_dip * 6.0, -wave * 5.0, 11.0 - wave_dip * 6.0); + skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0); + skeleton.l_foot.ori = Quaternion::rotation_x(-wave + 1.0); + skeleton.r_foot.offset = Vec3::new(3.5, 1.0 + wave * 8.0, 3.5 - wave_dip * 4.0); + skeleton.r_foot.ori = Quaternion::rotation_x(wave + 1.0); + skeleton.back.offset = Vec3::new(-9.0, 5.0, 18.0); + skeleton.back.ori = Quaternion::rotation_y(2.5); } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index b144ad45d4..4b1fbf0f4d 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -2,15 +2,13 @@ pub mod camera; pub mod figure; pub mod terrain; -// Library use vek::*; use dot_vox; - -// Project -use common::figure::Segment; +use common::{ + comp, + figure::Segment, +}; use client::Client; - -// Crate use crate::{ render::{ Consts, @@ -29,8 +27,6 @@ use crate::{ character::{CharacterSkeleton, RunAnimation}, }, }; - -// Local use self::{ camera::Camera, figure::Figure, @@ -82,15 +78,15 @@ impl Scene { test_figure: Figure::new( renderer, [ - Some(load_segment("dragonhead.vox").generate_mesh(Vec3::new(2.0, -12.0, 2.0))), - Some(load_segment("dragon_body.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))), - Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))), - Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))), - Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, -10.0, -4.0))), - Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))), - None, - None, - None, + Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -5.5, -1.0))), + Some(load_segment("chest.vox").generate_mesh(Vec3::new(-6.0, -3.0, 0.0))), + Some(load_segment("belt.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))), + Some(load_segment("pants.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))), + Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))), + Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))), + Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))), + Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))), + Some(load_segment("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))), None, None, None, @@ -138,6 +134,22 @@ impl Scene { /// Maintain data such as GPU constant buffers, models, etc. To be called once per tick. pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { + // Get player position + let player_pos = match client.player().and_then(|uid| client.state().get_entity(uid)) { + Some(ecs_entity) => { + client + .state() + .ecs_world() + .read_storage::() + .get(ecs_entity) + .expect("There was no position component on the player entity!") + .0 + } + None => Vec3::default(), + }; + // Alter camera position to match player + self.camera.set_focus_pos(player_pos); + // Compute camera matrices let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(); @@ -161,7 +173,11 @@ impl Scene { &mut self.test_figure.skeleton, client.state().get_time(), ); - self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap(); + + // Calculate player model matrix + let model_mat = Mat4::::translation_3d(player_pos); + + self.test_figure.update_locals(renderer, FigureLocals::new(model_mat)).unwrap(); self.test_figure.update_skeleton(renderer).unwrap(); }