From e5d8f6ae752d728667715c48b0fa4c23c0ddcefd Mon Sep 17 00:00:00 2001 From: Alan Wang <34356748+alanlwang1@users.noreply.github.com> Date: Tue, 2 May 2023 22:56:41 -0700 Subject: [PATCH] Wind attacks (#25) * implement attacks * add wgpu-glyph for text * refactor respawn cooldown, add attack cooldown * add text for displaying cooldowns * add ammo for attacks --- Cargo.lock | 105 +++++++++++++ assets/Inconsolata-Regular.ttf | Bin 0 -> 92124 bytes client/Cargo.toml | 1 + client/src/inputs/mod.rs | 8 +- client/src/lib.rs | 88 +++++++++++ client/src/player.rs | 29 +++- common/src/core/command.rs | 39 ++++- common/src/core/states.rs | 28 ++-- server/src/executor/command_handlers.rs | 187 +++++++++++++++++++----- server/src/executor/mod.rs | 30 +++- server/src/game_loop.rs | 8 +- server/src/simulation/physics_state.rs | 2 + 12 files changed, 459 insertions(+), 66 deletions(-) create mode 100644 assets/Inconsolata-Regular.ttf 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 0000000000000000000000000000000000000000..3e547460b79225335bbc935a679a59da6dc99eae GIT binary patch literal 92124 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 literal 0 HcmV?d00001 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(