From 5ccfc651a1cbb5b2b6436f70139b9cc4da585cf5 Mon Sep 17 00:00:00 2001 From: Sofia Date: Fri, 7 Aug 2026 23:54:07 +0300 Subject: [PATCH] Add blood decals for clients as well --- rust/src/game/mod.rs | 20 ++++++++++++++++++++ rust/src/map/triggers.rs | 2 ++ rust/src/net/network_manager.rs | 1 + rust/src/net/protocol.rs | 7 +++++++ rust/src/player/ball.rs | 2 ++ rust/src/player/effects.rs | 6 +++--- rust/src/player/local_player.rs | 22 +++++++++++++++++++--- rust/src/player/remote_player.rs | 23 ++++++++++++++++++++--- rust/src/player/shootable.rs | 3 ++- rust/src/player/weapon.rs | 23 +++++++++++++---------- 10 files changed, 89 insertions(+), 20 deletions(-) diff --git a/rust/src/game/mod.rs b/rust/src/game/mod.rs index 35b9ecc..7dd223c 100644 --- a/rust/src/game/mod.rs +++ b/rust/src/game/mod.rs @@ -31,6 +31,7 @@ use crate::{ buffs::{Buff, BuffKind}, local_player::LocalPlayer, remote_player::RemotePlayer, + shootable::Shootable, telegrenade::Telegrenade, weapon::WeaponType, }, @@ -1087,6 +1088,25 @@ impl Game { } } + // Handled whenever a player is shot, run on client-side only + pub fn handle_on_shot( + &mut self, + player_id: u16, + source: DamageSource, + damage: i32, + delta: NetVector3, + ) { + if let Some(player) = self.find_player_mut(player_id) + && let Some(character) = &mut player.character + { + if let Ok(mut shootable) = character.clone().into_gd().try_dynify::() { + shootable + .dyn_bind_mut() + .on_shoot_visual(source, damage, delta.into()); + } + } + } + // Handle shooting, either from the server of from a client pub fn handle_shoot( &mut self, diff --git a/rust/src/map/triggers.rs b/rust/src/map/triggers.rs index 601ee04..550a387 100644 --- a/rust/src/map/triggers.rs +++ b/rust/src/map/triggers.rs @@ -181,6 +181,8 @@ impl Shootable for ShootableTrigger { self.signals().on_shot().emit(id, damage, dir); } } + + fn on_shoot_visual(&mut self, _: DamageSource, _: i32, _: Vector3) {} } #[derive(GodotClass)] diff --git a/rust/src/net/network_manager.rs b/rust/src/net/network_manager.rs index 5a5d7f0..31f6a87 100644 --- a/rust/src/net/network_manager.rs +++ b/rust/src/net/network_manager.rs @@ -357,6 +357,7 @@ pub enum Package { Jump(u16), Shoot(u16, NetVector3, NetTransform, f32), Punch(u16, NetVector3, NetTransform, f32), + OnShot(u16, DamageSource, i32, NetVector3), TakeDamage(DamageSource, u16, i32), Kill(DamageSource, u16), SpawnBall(NetTransform, NetVector3, Option), diff --git a/rust/src/net/protocol.rs b/rust/src/net/protocol.rs index c24a4cf..c9a9f51 100644 --- a/rust/src/net/protocol.rs +++ b/rust/src/net/protocol.rs @@ -7,6 +7,7 @@ use crate::{ game::{Game, GameManager, opts::GameOption}, game_settings::GameSettingsManager, net::network_manager::{NetworkManager, Package, PeerKind}, + player, popup_queue::{Popup, PopupQueue}, ui::cli::{CliColor, CommandLinePanel}, }; @@ -262,6 +263,12 @@ impl NetworkManager { game.bind_mut().on_trigger_deactivate(id); } } + Package::OnShot(player_id, source, damage, delta) => { + if let Some(game) = &mut Game::singleton() { + game.bind_mut() + .handle_on_shot(player_id, source, damage, delta); + } + } _ => {} }, }, diff --git a/rust/src/player/ball.rs b/rust/src/player/ball.rs index bada67e..057a9f5 100644 --- a/rust/src/player/ball.rs +++ b/rust/src/player/ball.rs @@ -100,4 +100,6 @@ impl Shootable for Ball { self.base_mut() .set_linear_velocity(prev_velocity + delta.normalized() * force); } + + fn on_shoot_visual(&mut self, _: DamageSource, _: i32, _: Vector3) {} } diff --git a/rust/src/player/effects.rs b/rust/src/player/effects.rs index da90898..e378b25 100644 --- a/rust/src/player/effects.rs +++ b/rust/src/player/effects.rs @@ -112,14 +112,15 @@ impl PlayerEffects { s.base().get_basis().col_c(), &s.base().get_world_3d(), &Array::new(), - (20. / delta.length()) as u8, + 10, + // Calculate the particular spread of blood (((PI / 4.) * force) / delta.length()).min(PI / 2.), 10. + delta.length() / 3., None, ); let mut decal_results = Vec::new(); - for _ in 0..(force * 5.) as usize { + for _ in 0..(force * force * 4.) as usize { if let Some(result) = results .get(s.rng.randi_range(0, results.len() as i32 - 1) as usize) .cloned() @@ -127,7 +128,6 @@ impl PlayerEffects { decal_results.push(result); } } - godot_print!("{:?}", decal_results); for result in decal_results { let mut decal = prefab.instantiate_as::(); diff --git a/rust/src/player/local_player.rs b/rust/src/player/local_player.rs index 77ccbe5..2b5d589 100644 --- a/rust/src/player/local_player.rs +++ b/rust/src/player/local_player.rs @@ -940,11 +940,27 @@ impl LocalPlayer { #[godot_dyn] impl Shootable for LocalPlayer { fn on_shoot(&mut self, source: DamageSource, damage: i32, delta: Vector3) { - if self.can_take_damage(&source) - && let Some(fx) = &mut self.effects + if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer + && let PeerKind::Server(peer, _) = peer { - fx.bind_mut().shoot_blood(delta, damage as f32 / 100.); + peer.broadcast_reliable(Package::OnShot( + self.player_id.unwrap_or(0), + source, + damage, + delta.into(), + )); + self.on_shoot_visual(source, damage, delta); } self.take_damage(source, damage, true); } + + fn on_shoot_visual(&mut self, source: DamageSource, damage: i32, delta: Vector3) { + self.run_deferred(move |s| { + if s.can_take_damage(&source) + && let Some(fx) = &mut s.effects + { + fx.bind_mut().shoot_blood(delta, damage as f32 / 100.); + } + }); + } } diff --git a/rust/src/player/remote_player.rs b/rust/src/player/remote_player.rs index 9e97245..cea2b64 100644 --- a/rust/src/player/remote_player.rs +++ b/rust/src/player/remote_player.rs @@ -10,6 +10,7 @@ use godot::{ use crate::{ game::{Game, opts::GameOption}, map::{killbox::KillboxKind, pickup::PickupKind}, + net::network_manager::{NetworkManager, Package, PeerKind}, player::{ DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon, buffs::{Buff, Buffs}, @@ -502,11 +503,27 @@ impl RemotePlayer { #[godot_dyn] impl Shootable for RemotePlayer { fn on_shoot(&mut self, source: DamageSource, damage: i32, delta: Vector3) { - if self.can_take_damage(&source) - && let Some(fx) = &mut self.effects + if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer + && let PeerKind::Server(peer, _) = peer { - fx.bind_mut().shoot_blood(delta, damage as f32 / 100.); + peer.broadcast_reliable(Package::OnShot( + self.player_id.unwrap_or(0), + source, + damage, + delta.into(), + )); + self.on_shoot_visual(source, damage, delta); } self.take_damage(source, damage, true); } + + fn on_shoot_visual(&mut self, source: DamageSource, damage: i32, delta: Vector3) { + self.run_deferred(move |s| { + if s.can_take_damage(&source) + && let Some(fx) = &mut s.effects + { + fx.bind_mut().shoot_blood(delta, damage as f32 / 100.); + } + }); + } } diff --git a/rust/src/player/shootable.rs b/rust/src/player/shootable.rs index 9630fa7..9a3ccc5 100644 --- a/rust/src/player/shootable.rs +++ b/rust/src/player/shootable.rs @@ -2,5 +2,6 @@ use crate::player::DamageSource; use godot::prelude::*; pub trait Shootable { - fn on_shoot(&mut self, source: DamageSource, damage: i32, dir: Vector3); + fn on_shoot(&mut self, source: DamageSource, damage: i32, delta: Vector3); + fn on_shoot_visual(&mut self, source: DamageSource, damage: i32, delta: Vector3); } diff --git a/rust/src/player/weapon.rs b/rust/src/player/weapon.rs index 95ed382..1f49bc2 100644 --- a/rust/src/player/weapon.rs +++ b/rust/src/player/weapon.rs @@ -1,4 +1,7 @@ -use std::{collections::HashSet, f32::consts::PI}; +use std::{ + collections::{HashMap, HashSet}, + f32::consts::PI, +}; use godot::{ classes::{AudioStreamPlayer3D, GpuParticles3D, RandomNumberGenerator}, @@ -224,7 +227,7 @@ impl Weapon for Shotgun { vfx.set_emitting(true); } - let mut colliders = HashSet::new(); + let mut colliders: HashMap, Vec> = HashMap::new(); let results = shotgun_ray( from, to, @@ -255,17 +258,17 @@ impl Weapon for Shotgun { } for result in &results { - colliders.insert(result.collider.clone()); + if let Some(positions) = colliders.get_mut(&result.collider) { + positions.push(result.position) + } else { + colliders.insert(result.collider.clone(), vec![result.position]); + } } if deal_damage { - for collider in colliders { - let collider_pos = if let Ok(node) = collider.clone().try_cast::() { - node.get_global_position() - } else { - Vector3::ZERO - }; - let delta = collider_pos - from; + for (collider, positions) in colliders { + let position = positions.iter().sum::() / positions.len() as f32; + let delta = position - from; if let Ok(mut shootable) = collider.clone().try_dynify::() { self.run_deferred(move |_| {