diff --git a/Cargo.lock b/Cargo.lock index b7bba182..bdb8118c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,6 +483,7 @@ dependencies = [ "server", "tobj", "wgpu", + "wgpu_glyph", "winit", ] @@ -905,6 +906,44 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glyph_brush" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4edefd123f28a0b1d41ec4a489c2b43020b369180800977801611084f342978d" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "ordered-float", + "rustc-hash", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6010675390f6889e09a21e2c8b575b3ee25667ea8237a8d59423f73cb8c28610" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc32c2334f00ca5ac3695c5009ae35da21da8c62d255b5b96d56e2597a637a38" +dependencies = [ + "ab_glyph", + "approx", + "xi-unicode", +] + [[package]] name = "gpu-alloc" version = "0.5.3" @@ -1297,6 +1336,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.4" @@ -1778,6 +1823,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ordered-float" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.19.0" @@ -2027,6 +2081,28 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2698,6 +2774,17 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44dcf002ae3b32cd25400d6df128c5babec3927cd1eb7ce813cfff20eb6c3746" +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "rand", + "static_assertions", +] + [[package]] name = "typenum" version = "1.16.0" @@ -3053,6 +3140,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wgpu_glyph" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25440d5f32ec39de49c57c15c2d3f9133a7939b069b5ad07e5afd8b78fb8adc" +dependencies = [ + "bytemuck", + "glyph_brush", + "log", + "wgpu", +] + [[package]] name = "wide" version = "0.7.8" @@ -3338,6 +3437,12 @@ dependencies = [ "nom", ] +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" + [[package]] name = "xml-rs" version = "0.8.4" diff --git a/assets/Inconsolata-Regular.ttf b/assets/Inconsolata-Regular.ttf new file mode 100644 index 00000000..3e547460 Binary files /dev/null and b/assets/Inconsolata-Regular.ttf differ diff --git a/client/Cargo.toml b/client/Cargo.toml index bec6747a..dd96e6e3 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -29,6 +29,7 @@ cfg-if = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" hashbrown = "0.13.2" +wgpu_glyph = "0.19.0" bus = "2.4.0" [build-dependencies] diff --git a/client/src/inputs/mod.rs b/client/src/inputs/mod.rs index 426f3064..78ae3e32 100644 --- a/client/src/inputs/mod.rs +++ b/client/src/inputs/mod.rs @@ -1,8 +1,7 @@ use crate::inputs::handlers::{handle_camera_update, handle_game_key_input, GameKeyKind}; use common::communication::commons::Protocol; -use common::core::command::Command; -use common::core::command::Command::{Action, Jump, Spawn}; -use common::core::command::GameAction::Attack; +use common::core::command::Command::{Attack, Jump, Spawn}; +use common::core::command::{Command}; use glm::{vec3, Vec3}; use log::debug; @@ -65,7 +64,8 @@ impl InputEventProcessor { VirtualKeyCode::Space => Some((GameKeyKind::Pressable, Jump)), VirtualKeyCode::LShift => Some((GameKeyKind::Pressable, Spawn)), // match PressRelease keys - VirtualKeyCode::F => Some((GameKeyKind::PressRelease, Action(Attack))), + VirtualKeyCode::LShift => Some((GameKeyKind::PressRelease, Spawn)), + VirtualKeyCode::F => Some((GameKeyKind::PressRelease, Attack)), _ => None, } } diff --git a/client/src/lib.rs b/client/src/lib.rs index 036c8dd8..c502d1e8 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -26,7 +26,9 @@ pub mod inputs; use common::configs::scene_config::ConfigSceneGraph; use common::core::states::GameState; +use common::core::command::Command; use winit::window::Window; +use wgpu_glyph::{ab_glyph, GlyphBrushBuilder, Section, Text, Layout, GlyphBrush, HorizontalAlign}; const MODELS_CONFIG_PATH: &str = "models.json"; const SCENE_CONFIG_PATH: &str = "scene.json"; @@ -49,6 +51,8 @@ struct State { screens: Vec, screen_ind: usize, client_id: u8, + staging_belt: wgpu::util::StagingBelt, + glyph_brush: GlyphBrush<()> } impl State { @@ -347,6 +351,15 @@ impl State { multiview: None, }); + // text + let staging_belt = wgpu::util::StagingBelt::new(1024); + let inconsolata = ab_glyph::FontArc::try_from_slice(include_bytes!( + "../../assets/Inconsolata-Regular.ttf" + )).unwrap(); + + let glyph_brush = GlyphBrushBuilder::using_font(inconsolata) + .build(&device, surface_format); + let screens = screen_objects::get_screens(&texture_bind_group_layout_2d, &device, &queue).await; @@ -371,6 +384,8 @@ impl State { #[cfg(feature = "debug-lobby")] screen_ind: 1, client_id, + staging_belt, + glyph_brush, } } @@ -533,10 +548,83 @@ impl State { } } + let size = &self.window.inner_size(); + + // if player is alive + if !self.player.is_dead { + // render ammo remaining + self.glyph_brush.queue(Section { + screen_position: (30.0, 20.0), + bounds: (size.width as f32, size.height as f32), + text: vec![ + Text::new(&format!("Ammo remaining: {:.1}\n", self.player.ammo_count).as_str()) + .with_color([0.0, 0.0, 0.0, 1.0]) + .with_scale(40.0) + ], + ..Section::default() + }); + // render ability cooldowns + if self.player.on_cooldown.contains_key(&Command::Attack) { + let attack_cooldown = self.player.on_cooldown.get(&Command::Attack).unwrap(); + self.glyph_brush.queue(Section { + screen_position: (30.0, 60.0), + bounds: (size.width as f32, size.height as f32), + text: vec![ + Text::new(&format!("Attack cooldown: {:.1}\n", attack_cooldown).as_str()) + .with_color([0.0, 0.0, 0.0, 1.0]) + .with_scale(40.0) + ], + ..Section::default() + }); + } + } else { + // render respawn cooldown + if self.player.on_cooldown.contains_key(&Command::Spawn) { + let spawn_cooldown = self.player.on_cooldown.get(&Command::Spawn).unwrap(); + self.glyph_brush.queue(Section { + screen_position: (size.width as f32 * 0.5, size.height as f32 * 0.4), + bounds: (size.width as f32, size.height as f32), + text: vec![ + Text::new("You died!\n") + .with_color([1.0, 1.0, 0.0, 1.0]) + .with_scale(100.0), + Text::new("Respawning in ") + .with_color([1.0, 1.0, 0.0, 1.0]) + .with_scale(60.0), + Text::new(&format!("{:.1}", spawn_cooldown).as_str()) + .with_color([1.0, 1.0, 1.0, 1.0]) + .with_scale(60.0), + Text::new(" seconds") + .with_color([1.0, 1.0, 0.0, 1.0]) + .with_scale(60.0), + ], + layout: Layout::default().h_align(HorizontalAlign::Center), + ..Section::default() + }); + } + } + + // Draw the text! + self.glyph_brush + .draw_queued( + &self.device, + &mut self.staging_belt, + &mut encoder, + &view, + size.width, + size.height, + ) + .expect("Draw queued"); + + // Submit the work! + self.staging_belt.finish(); + // submit will accept anything that implements IntoIter self.queue.submit(std::iter::once(encoder.finish())); output.present(); + // Recall unused staging buffers + self.staging_belt.recall(); Ok(()) } } diff --git a/client/src/player.rs b/client/src/player.rs index 9e86ae99..4a2c666c 100644 --- a/client/src/player.rs +++ b/client/src/player.rs @@ -1,11 +1,15 @@ +use common::core::command::Command; use common::core::states::PlayerState; use instant::Duration; - +use std::time::SystemTime; use std::f32::consts::FRAC_PI_2; use std::f32::consts::PI; +use std::collections::HashMap; use winit::dpi::PhysicalPosition; use winit::event::*; + + extern crate nalgebra_glm as glm; use crate::camera::CameraState; @@ -35,20 +39,22 @@ fn spherical_to_cartesian(spherical: &glm::Vec3) -> glm::Vec3 { glm::vec3(x, y, z) } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Player { pub position: glm::TVec3, pub rotation: glm::Quat, - up: glm::TVec3, + pub is_dead: bool, + pub ammo_count: u32, + pub on_cooldown: HashMap, } impl Player { pub fn new(position: glm::TVec3) -> Self { - let up = glm::vec3(0.0, 1.0, 0.0); Self { position, rotation: glm::quat_identity(), - up, + is_dead: false, + ..Default::default() } } @@ -92,7 +98,8 @@ impl PlayerController { }; } - /// update the player's position and camera's position and target based on incoming player state + /// update the player's position, cooldowns, camera's position and target based on incoming player state + /// pub fn update( &mut self, player: &mut Player, @@ -139,5 +146,15 @@ impl PlayerController { + self.scroll * self.scroll_sensitivity * dt) .clamp(PI / 6.0, PI / 3.0); self.scroll = 0.0; + + // update dead status + player.is_dead = incoming_player_state.is_dead; + + // update cooldowns + player.on_cooldown = incoming_player_state.on_cooldown.clone(); + + // update ammo count + player.ammo_count = incoming_player_state.ammo_count; + } } diff --git a/common/src/core/command.rs b/common/src/core/command.rs index b1938630..5ce8c6da 100644 --- a/common/src/core/command.rs +++ b/common/src/core/command.rs @@ -8,23 +8,42 @@ use std::mem; /// Direction of the movement pub type MoveDirection = glm::Vec3; +/* /// Game actions that can be performed by the player -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash)] +#[derive(Debug, Clone, Serialize, Deserialize)] + pub enum GameAction { Attack, Jump, } + +impl PartialEq for GameAction { + fn eq(&self, other: &Self) -> bool { + mem::discriminant(self).eq(&mem::discriminant(other)) + } +} +impl Eq for GameAction {} + +// /// Spawn type that can be issued by the client +// #[derive(Debug, Clone, Serialize, Deserialize)] +// pub enum SpawnType { +// NewSpawn, +// Respawn, +// Dead, +// } +*/ + /// Commands that can be issued by the client -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum Command { Spawn, - Respawn, + //Respawn, Move(MoveDirection), Turn(Quat), Jump, UpdateCamera { forward: glm::Vec3 }, - Action(GameAction), + Attack, } impl Command { @@ -36,13 +55,23 @@ impl Command { } } +impl PartialEq for Command { + fn eq(&self, other: &Self) -> bool { + match self { + Command::Move(x) => match other { + Command::Move(y) => x.eq(y), + _ => false, + }, + _ => mem::discriminant(self).eq(&mem::discriminant(other)), + } + } +} impl Eq for Command {} impl Hash for Command { fn hash(&self, state: &mut H) { mem::discriminant(self).hash(state); match self { - Command::Action(x) => x.hash(state), Command::Move(x) => { let _x = ((x.x * 1000000_f32).round() / 1.0) as i64; let _y = ((x.y * 1000000_f32).round() / 1.0) as i64; diff --git a/common/src/core/states.rs b/common/src/core/states.rs index 4608eb48..0cec7b62 100644 --- a/common/src/core/states.rs +++ b/common/src/core/states.rs @@ -3,7 +3,7 @@ use crate::core::components::{Physics, Transform}; use nalgebra_glm::Vec3; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::time::{Duration, SystemTime}; +use std::time::Duration; #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct PlayerState { @@ -11,9 +11,11 @@ pub struct PlayerState { pub transform: Transform, pub physics: Physics, pub jump_count: u32, + pub ammo_count: u32, pub camera_forward: Vec3, pub connected: bool, - pub on_cooldown: HashMap, + pub is_dead: bool, + pub on_cooldown: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] @@ -35,20 +37,26 @@ impl GameState { } pub fn insert_cooldown(&mut self, id: u32, command: Command, cooldown_in_sec: u64) { - let cd_secs = Duration::from_secs(cooldown_in_sec); - let cd_until = SystemTime::now().checked_add(cd_secs).unwrap(); + let cd_secs = Duration::from_secs(cooldown_in_sec).as_secs_f32(); + //let cd_until = SystemTime::now().checked_add(cd_secs).unwrap(); self.player_mut(id) .unwrap() .on_cooldown - .insert(command, cd_until); + .insert(command, cd_secs); } - pub fn update_cooldowns(&mut self) { - let now = SystemTime::now(); + pub fn update_cooldowns(&mut self, delta_time: f32) { for (_, player_state) in self.players.iter_mut() { - player_state - .on_cooldown - .retain(|_, cd_until| now.lt(cd_until)) + player_state.on_cooldown = player_state.on_cooldown.clone() + .into_iter() + .map(|(key, cooldown)| { + ( + key, + cooldown - delta_time, + ) + }) + .filter(|(_key, cooldown)| *cooldown > 0.0) + .collect(); } } diff --git a/server/src/executor/command_handlers.rs b/server/src/executor/command_handlers.rs index 96fac3f3..6b5744f4 100644 --- a/server/src/executor/command_handlers.rs +++ b/server/src/executor/command_handlers.rs @@ -12,6 +12,7 @@ use derive_more::{Constructor, Display, Error}; use nalgebra::{Isometry3, Vector3, zero}; use nalgebra::UnitQuaternion; use nalgebra_glm::Vec3; +use nalgebra_glm as glm; use rapier3d::geometry::InteractionGroups; use rapier3d::prelude as rapier; use std::fmt::{Debug, format}; @@ -120,35 +121,52 @@ impl CommandHandler for SpawnCommandHandler { // .translation(rapier::vector![0.0, 2.0, 0.0]) // .build(); - if physics_state.get_entity_handles(self.player_id).is_some() { - return Err(HandlerError { - message: "Player already spawned".to_string(), - }); - } + // if player already spawned + let starting_ammo = 5; - let ground_groups = InteractionGroups::new(1.into(), 1.into()); - let collider = rapier::ColliderBuilder::round_cuboid(1.0, 1.0, 1.0, 0.01) - .collision_groups(ground_groups) - .build(); - - let rigid_body = rapier3d::prelude::RigidBodyBuilder::dynamic() - .translation(rapier::vector![0.0, 3.0, 0.0]) - .build(); - physics_state.insert_entity(self.player_id, Some(collider), Some(rigid_body)); - - // Game state (needed because syncing is only for the physical properties of entities) - game_state.players.insert( - self.player_id, - PlayerState { - id: self.player_id, - connected: true, - ..Default::default() - }, - ); + if let Some(player) = game_state.player_mut(self.player_id) { + // if player died and has no spawn cooldown + if player.is_dead && !player.on_cooldown.contains_key(&Command::Spawn) { + // Teleport the player to the desired position. + let new_position = + rapier3d::prelude::Isometry::new(rapier::vector![0.0, 3.0, 0.0], zero()); + if let Some(player_rigid_body) = + physics_state.get_entity_rigid_body_mut(self.player_id) + { + player_rigid_body.set_position(new_position, true); + player_rigid_body.set_linvel(rapier::vector![0.0, 0.0, 0.0], true); + } + player.is_dead = false; + player.ammo_count = starting_ammo; + } + } else { + let ground_groups = InteractionGroups::new(1.into(), 1.into()); + let collider = rapier::ColliderBuilder::round_cuboid(1.0, 1.0, 1.0, 0.01) + .collision_groups(ground_groups) + .build(); + + let rigid_body = rapier3d::prelude::RigidBodyBuilder::dynamic() + .translation(rapier::vector![0.0, 3.0, 0.0]) + .build(); + physics_state.insert_entity(self.player_id, Some(collider), Some(rigid_body)); + + // Game state (needed because syncing is only for the physical properties of entities) + game_state.players.insert( + self.player_id, + PlayerState { + id: self.player_id, + connected: true, + is_dead: false, + ammo_count: starting_ammo, + ..Default::default() + }, + ); + } Ok(()) } } +/* #[derive(Constructor)] pub struct RespawnCommandHandler { player_id: u32, @@ -173,25 +191,18 @@ impl CommandHandler for RespawnCommandHandler { } } // Update all cooldowns in the game state - game_state.update_cooldowns(); + //game_state.update_cooldowns(); // If the player's respawn cooldown has ended, create a new RespawnCommandHandler and handle the respawn command if let Some(player) = game_state.players.get(&self.player_id) { if !player.on_cooldown.contains_key(&Command::Respawn) { - // Teleport the player to the desired position. - let new_position = - rapier3d::prelude::Isometry::new(rapier::vector![0.0, 3.0, 0.0], zero()); - if let Some(player_rigid_body) = - physics_state.get_entity_rigid_body_mut(self.player_id) - { - player_rigid_body.set_position(new_position, true); - player_rigid_body.set_linvel(rapier::vector![0.0, 0.0, 0.0], true); - } + } } Ok(()) } } +*/ #[derive(Constructor)] pub struct UpdateCameraFacingCommandHandler { @@ -386,3 +397,111 @@ impl CommandHandler for JumpCommandHandler { Ok(()) } } + +#[derive(Constructor)] +pub struct AttackCommandHandler { + player_id: u32, +} + +impl CommandHandler for AttackCommandHandler { + fn handle( + &self, + game_state: &mut GameState, + physics_state: &mut PhysicsState, + _: &mut dyn GameEventCollector, + ) -> HandlerResult { + + let player_state = game_state + .player(self.player_id) + .ok_or_else(|| HandlerError::new(format!("Player {} not found", self.player_id)))?; + + // if attack on cooldown, do nothing for now + if game_state.command_on_cooldown(self.player_id, Command::Attack) || player_state.ammo_count == 0 { + return Ok(()); + } + + let player_pos = player_state.transform.translation; + + let player_collider_handle = physics_state + .get_entity_handles(self.player_id) + .ok_or(HandlerError::new(format!( + "Player {} not found", + self.player_id + )))? + .collider + .ok_or(HandlerError::new(format!( + "Player {} does not have a collider", + self.player_id + )))?; + + let player_rigid_body = physics_state + .get_entity_rigid_body_mut(self.player_id) + .unwrap(); + + let camera_forward = Vec3::new( + player_state.camera_forward.x, + 0.0, + player_state.camera_forward.z, + ); + + // turn player towards attack direction (camera_forward) + let rotation = UnitQuaternion::face_towards(&camera_forward, &Vec3::y()); + player_rigid_body.set_rotation(rotation, true); + + // loop over all other players + for (other_player_id, other_player_state) in &game_state.players { + if &self.player_id == other_player_id { + continue; + } + + // get direction from this player to other player + let other_player_pos = other_player_state.transform.translation; + let vec_to_other = glm::normalize(&(other_player_pos - player_pos)); + + // check dot product between direction to other player and attack direction + let angle = glm::angle(&camera_forward, &vec_to_other); + + // if object in attack range + if angle <= std::f32::consts::FRAC_PI_6 { + // send ray to other player (may need multiple later) + let max_toi = 5.0; // max attack distance + let solid = true; + let filter = rapier::QueryFilter::default().exclude_collider(player_collider_handle); + + let ray = rapier::Ray::new(rapier::point![player_pos.x, player_pos.y, player_pos.z], rapier::vector![vec_to_other.x, vec_to_other.y, vec_to_other.z]); + if let Some((handle, toi)) = physics_state.query_pipeline.cast_ray( + &physics_state.bodies, &physics_state.colliders, &ray, max_toi, solid, filter + ) { + let other_player_collider_handle = physics_state + .get_entity_handles(*other_player_id) + .ok_or(HandlerError::new(format!( + "Player {} not found", + self.player_id + )))? + .collider + .ok_or(HandlerError::new(format!( + "Player {} does not have a collider", + self.player_id + )))?; + + // if ray hit the correct target (the other player), apply force + if handle == other_player_collider_handle { + const ATTACK_IMPULSE: f32 = 40.0; // parameter to tune + let other_player_rigid_body = physics_state + .get_entity_rigid_body_mut(*other_player_id) + .unwrap(); + let impulse_vec = vec_to_other * ATTACK_IMPULSE * 2.0 / toi; + other_player_rigid_body.apply_impulse(rapier::vector![impulse_vec.x, impulse_vec.y, impulse_vec.z], true); + } + } + } + + } + game_state.player_mut(self.player_id).unwrap().ammo_count -= 1; + + game_state.insert_cooldown(self.player_id, Command::Attack, 5); + + + Ok(()) + } +} \ No newline at end of file diff --git a/server/src/executor/mod.rs b/server/src/executor/mod.rs index 7714eed7..5d1b2ed3 100644 --- a/server/src/executor/mod.rs +++ b/server/src/executor/mod.rs @@ -1,5 +1,5 @@ use crate::executor::command_handlers::{ - CommandHandler, JumpCommandHandler, MoveCommandHandler, RespawnCommandHandler, + CommandHandler, AttackCommandHandler, JumpCommandHandler, MoveCommandHandler, SpawnCommandHandler, StartupCommandHandler, UpdateCameraFacingCommandHandler, }; use crate::game_loop::ClientCommand; @@ -100,13 +100,14 @@ impl Executor { let handler: Box = match client_command.command { Command::Spawn => Box::new(SpawnCommandHandler::new(client_command.client_id)), - Command::Respawn => Box::new(RespawnCommandHandler::new(client_command.client_id)), + //Command::Respawn => Box::new(RespawnCommandHandler::new(client_command.client_id)), Command::Move(dir) => Box::new(MoveCommandHandler::new(client_command.client_id, dir)), Command::UpdateCamera { forward } => Box::new(UpdateCameraFacingCommandHandler::new( client_command.client_id, forward, )), Command::Jump => Box::new(JumpCommandHandler::new(client_command.client_id)), + Command::Attack => Box::new(AttackCommandHandler::new(client_command.client_id)), _ => { warn!("Unsupported command: {:?}", client_command.command); return; @@ -116,6 +117,7 @@ impl Executor { if let Err(e) = handler.handle(&mut game_state, &mut physics_state, &mut game_events) { error!("Failed to execute command: {:?}", e); } + //game_state.update_cooldowns(); info!("GameState: {:?}", game_state); } @@ -123,11 +125,12 @@ impl Executor { pub(crate) fn step(&self, delta_time: f32) { self.physics_state.borrow_mut().set_delta_time(delta_time); self.physics_state.borrow_mut().step(); + - self.sync_states(); // after physics step, need to sync game state + self.sync_states(delta_time); // after physics step, need to sync game state } - fn sync_states(&self) { + fn sync_states(&self, delta_time: f32) { let mut game_state = self.game_state.lock().unwrap(); let physics_state = self.physics_state.borrow(); @@ -137,17 +140,34 @@ impl Executor { player.transform.translation = rigid_body.position().translation.vector; player.transform.rotation = rigid_body.position().rotation.coords.into(); } + + game_state.update_cooldowns(delta_time); } pub(crate) fn collect_game_events(&self) -> Vec<(GameEvent, Recipients)> { self.game_events.replace(Vec::new()) } + pub(crate) fn update_dead_players(&self) { + let dead_players = self.game_state() + .players + .iter() + .filter(|(_, player)| !player.is_dead && player.transform.translation.y < DEFAULT_RESPAWN_LIMIT) + .map(|(&id, _)| id) + .collect::>(); + + let mut game_state = self.game_state.lock().unwrap(); + for player_id in dead_players.iter() { + game_state.player_mut(*player_id).unwrap().is_dead = true; + game_state.insert_cooldown(*player_id, Command::Spawn, 3); + } + + } pub(crate) fn check_respawn_players(&self) -> Vec { self.game_state() .players .iter() - .filter(|(_, player)| player.transform.translation.y < DEFAULT_RESPAWN_LIMIT) + .filter(|(_, player)| player.is_dead && !player.on_cooldown.contains_key(&Command::Spawn)) .map(|(&id, _)| id) .collect::>() } diff --git a/server/src/game_loop.rs b/server/src/game_loop.rs index 29202749..6d7775c5 100644 --- a/server/src/game_loop.rs +++ b/server/src/game_loop.rs @@ -67,15 +67,19 @@ impl GameLoop<'_> { while self.running.load(Ordering::SeqCst) { let tick_start = Instant::now(); - // check whether player need to respawn + // update list of dead players + self.executor.update_dead_players(); + + // check whether dead players need to respawn let players_to_respawn = self.executor.check_respawn_players(); // consume and collect all messages in the channel let mut commands = self.commands.try_iter().collect::>(); if !players_to_respawn.is_empty() { for client_id in players_to_respawn { - commands.push(ClientCommand::new(client_id, Command::Respawn)); + commands.push(ClientCommand::new(client_id, Command::Spawn)); } } + // send commands to the executor self.executor.plan_and_execute(commands); diff --git a/server/src/simulation/physics_state.rs b/server/src/simulation/physics_state.rs index 453f30e9..4cc864da 100644 --- a/server/src/simulation/physics_state.rs +++ b/server/src/simulation/physics_state.rs @@ -74,6 +74,8 @@ impl PhysicsState { println!("Received contact force event: {:?}", contact_force_event); panic!("Contact force event received") } + + self.query_pipeline.update(&self.bodies, &self.colliders); } pub fn insert_entity(