Add blood decals for clients as well

This commit is contained in:
Sofia 2026-08-07 23:54:07 +03:00
parent 3ef771d0b6
commit 5ccfc651a1
10 changed files with 89 additions and 20 deletions

View File

@ -31,6 +31,7 @@ use crate::{
buffs::{Buff, BuffKind}, buffs::{Buff, BuffKind},
local_player::LocalPlayer, local_player::LocalPlayer,
remote_player::RemotePlayer, remote_player::RemotePlayer,
shootable::Shootable,
telegrenade::Telegrenade, telegrenade::Telegrenade,
weapon::WeaponType, 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::<dyn Shootable>() {
shootable
.dyn_bind_mut()
.on_shoot_visual(source, damage, delta.into());
}
}
}
// Handle shooting, either from the server of from a client // Handle shooting, either from the server of from a client
pub fn handle_shoot( pub fn handle_shoot(
&mut self, &mut self,

View File

@ -181,6 +181,8 @@ impl Shootable for ShootableTrigger {
self.signals().on_shot().emit(id, damage, dir); self.signals().on_shot().emit(id, damage, dir);
} }
} }
fn on_shoot_visual(&mut self, _: DamageSource, _: i32, _: Vector3) {}
} }
#[derive(GodotClass)] #[derive(GodotClass)]

View File

@ -357,6 +357,7 @@ pub enum Package {
Jump(u16), Jump(u16),
Shoot(u16, NetVector3, NetTransform, f32), Shoot(u16, NetVector3, NetTransform, f32),
Punch(u16, NetVector3, NetTransform, f32), Punch(u16, NetVector3, NetTransform, f32),
OnShot(u16, DamageSource, i32, NetVector3),
TakeDamage(DamageSource, u16, i32), TakeDamage(DamageSource, u16, i32),
Kill(DamageSource, u16), Kill(DamageSource, u16),
SpawnBall(NetTransform, NetVector3, Option<u16>), SpawnBall(NetTransform, NetVector3, Option<u16>),

View File

@ -7,6 +7,7 @@ use crate::{
game::{Game, GameManager, opts::GameOption}, game::{Game, GameManager, opts::GameOption},
game_settings::GameSettingsManager, game_settings::GameSettingsManager,
net::network_manager::{NetworkManager, Package, PeerKind}, net::network_manager::{NetworkManager, Package, PeerKind},
player,
popup_queue::{Popup, PopupQueue}, popup_queue::{Popup, PopupQueue},
ui::cli::{CliColor, CommandLinePanel}, ui::cli::{CliColor, CommandLinePanel},
}; };
@ -262,6 +263,12 @@ impl NetworkManager {
game.bind_mut().on_trigger_deactivate(id); 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);
}
}
_ => {} _ => {}
}, },
}, },

View File

@ -100,4 +100,6 @@ impl Shootable for Ball {
self.base_mut() self.base_mut()
.set_linear_velocity(prev_velocity + delta.normalized() * force); .set_linear_velocity(prev_velocity + delta.normalized() * force);
} }
fn on_shoot_visual(&mut self, _: DamageSource, _: i32, _: Vector3) {}
} }

View File

@ -112,14 +112,15 @@ impl PlayerEffects {
s.base().get_basis().col_c(), s.base().get_basis().col_c(),
&s.base().get_world_3d(), &s.base().get_world_3d(),
&Array::new(), &Array::new(),
(20. / delta.length()) as u8, 10,
// Calculate the particular spread of blood
(((PI / 4.) * force) / delta.length()).min(PI / 2.), (((PI / 4.) * force) / delta.length()).min(PI / 2.),
10. + delta.length() / 3., 10. + delta.length() / 3.,
None, None,
); );
let mut decal_results = Vec::new(); 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 if let Some(result) = results
.get(s.rng.randi_range(0, results.len() as i32 - 1) as usize) .get(s.rng.randi_range(0, results.len() as i32 - 1) as usize)
.cloned() .cloned()
@ -127,7 +128,6 @@ impl PlayerEffects {
decal_results.push(result); decal_results.push(result);
} }
} }
godot_print!("{:?}", decal_results);
for result in decal_results { for result in decal_results {
let mut decal = prefab.instantiate_as::<Node3D>(); let mut decal = prefab.instantiate_as::<Node3D>();

View File

@ -940,11 +940,27 @@ impl LocalPlayer {
#[godot_dyn] #[godot_dyn]
impl Shootable for LocalPlayer { impl Shootable for LocalPlayer {
fn on_shoot(&mut self, source: DamageSource, damage: i32, delta: Vector3) { fn on_shoot(&mut self, source: DamageSource, damage: i32, delta: Vector3) {
if self.can_take_damage(&source) if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let Some(fx) = &mut self.effects && 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); 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.);
}
});
}
} }

View File

@ -10,6 +10,7 @@ use godot::{
use crate::{ use crate::{
game::{Game, opts::GameOption}, game::{Game, opts::GameOption},
map::{killbox::KillboxKind, pickup::PickupKind}, map::{killbox::KillboxKind, pickup::PickupKind},
net::network_manager::{NetworkManager, Package, PeerKind},
player::{ player::{
DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon, DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon,
buffs::{Buff, Buffs}, buffs::{Buff, Buffs},
@ -502,11 +503,27 @@ impl RemotePlayer {
#[godot_dyn] #[godot_dyn]
impl Shootable for RemotePlayer { impl Shootable for RemotePlayer {
fn on_shoot(&mut self, source: DamageSource, damage: i32, delta: Vector3) { fn on_shoot(&mut self, source: DamageSource, damage: i32, delta: Vector3) {
if self.can_take_damage(&source) if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let Some(fx) = &mut self.effects && 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); 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.);
}
});
}
} }

View File

@ -2,5 +2,6 @@ use crate::player::DamageSource;
use godot::prelude::*; use godot::prelude::*;
pub trait Shootable { 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);
} }

View File

@ -1,4 +1,7 @@
use std::{collections::HashSet, f32::consts::PI}; use std::{
collections::{HashMap, HashSet},
f32::consts::PI,
};
use godot::{ use godot::{
classes::{AudioStreamPlayer3D, GpuParticles3D, RandomNumberGenerator}, classes::{AudioStreamPlayer3D, GpuParticles3D, RandomNumberGenerator},
@ -224,7 +227,7 @@ impl Weapon for Shotgun {
vfx.set_emitting(true); vfx.set_emitting(true);
} }
let mut colliders = HashSet::new(); let mut colliders: HashMap<Gd<Object>, Vec<Vector3>> = HashMap::new();
let results = shotgun_ray( let results = shotgun_ray(
from, from,
to, to,
@ -255,17 +258,17 @@ impl Weapon for Shotgun {
} }
for result in &results { 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 { if deal_damage {
for collider in colliders { for (collider, positions) in colliders {
let collider_pos = if let Ok(node) = collider.clone().try_cast::<Node3D>() { let position = positions.iter().sum::<Vector3>() / positions.len() as f32;
node.get_global_position() let delta = position - from;
} else {
Vector3::ZERO
};
let delta = collider_pos - from;
if let Ok(mut shootable) = collider.clone().try_dynify::<dyn Shootable>() { if let Ok(mut shootable) = collider.clone().try_dynify::<dyn Shootable>() {
self.run_deferred(move |_| { self.run_deferred(move |_| {