From 678268642bb9e9fa0144ce47b13222bd4ce909ef Mon Sep 17 00:00:00 2001 From: Sofia Date: Fri, 24 Jul 2026 19:35:53 +0300 Subject: [PATCH] Make shotgun functional --- godot/scenes/player/shotgun.tscn | 2 +- rust/src/game_manager.rs | 6 +- rust/src/goal.rs | 2 +- rust/src/killbox.rs | 3 +- rust/src/lib.rs | 1 - rust/src/map.rs | 3 +- rust/src/net/util.rs | 37 +++++++++- rust/src/{ => player}/ball.rs | 11 ++- rust/src/player/mod.rs | 27 ++++++-- rust/src/player/shootable.rs | 6 ++ rust/src/player/soldier_mesh.rs | 4 +- rust/src/player/weapon.rs | 112 +++++++++++++++++++++++++------ 12 files changed, 175 insertions(+), 39 deletions(-) rename rust/src/{ => player}/ball.rs (86%) create mode 100644 rust/src/player/shootable.rs diff --git a/godot/scenes/player/shotgun.tscn b/godot/scenes/player/shotgun.tscn index 871ed14..2e1d021 100644 --- a/godot/scenes/player/shotgun.tscn +++ b/godot/scenes/player/shotgun.tscn @@ -4,7 +4,7 @@ [ext_resource type="PackedScene" uid="uid://b8nwd3323kwlc" path="res://mesh/shotgun.tscn" id="3_0xpw2"] [ext_resource type="AudioStream" uid="uid://bq553wxrelpdr" path="res://raw_assets/sfx/LaserWithRecoveryEditedCompressed.mp3" id="3_8oj30"] -[node name="shotgun" type="Raygun" unique_id=573018429 node_paths=PackedStringArray("sound_emitter", "bullet_source")] +[node name="shotgun" type="Shotgun" unique_id=786130999 node_paths=PackedStringArray("sound_emitter", "bullet_source")] sound_emitter = NodePath("sound_emitter") bullet_prefab = ExtResource("1_0xpw2") bullet_source = NodePath("bullet_source") diff --git a/rust/src/game_manager.rs b/rust/src/game_manager.rs index dba797f..b4c4a97 100644 --- a/rust/src/game_manager.rs +++ b/rust/src/game_manager.rs @@ -8,7 +8,6 @@ use crate::{ Announcement::{self, FirstBlood}, Announcer, }, - ball::Ball, chat::{Chat, ChatMessage}, map::Map, map_resource::MapResource, @@ -20,7 +19,10 @@ use crate::{ }, util::NetVector3, }, - player::{DamageSource, IPlayer, LocalPlayer, NetTransform, RemotePlayer, weapon::WeaponType}, + player::{ + DamageSource, IPlayer, LocalPlayer, NetTransform, RemotePlayer, ball::Ball, + weapon::WeaponType, + }, team_resource::TeamResource, }; diff --git a/rust/src/goal.rs b/rust/src/goal.rs index c50d54f..2ec4f6d 100644 --- a/rust/src/goal.rs +++ b/rust/src/goal.rs @@ -3,7 +3,7 @@ use godot::{ prelude::*, }; -use crate::{ball::Ball, game_manager::Game, net::network_manager::NetworkManager}; +use crate::{game_manager::Game, net::network_manager::NetworkManager, player::ball::Ball}; #[derive(GodotClass)] #[class(base=Area3D, init)] diff --git a/rust/src/killbox.rs b/rust/src/killbox.rs index 98e6c0b..29acc49 100644 --- a/rust/src/killbox.rs +++ b/rust/src/killbox.rs @@ -4,10 +4,9 @@ use godot::{ }; use crate::{ - ball::Ball, game_manager::Game, net::network_manager::NetworkManager, - player::{DamageSource, IPlayer}, + player::{DamageSource, IPlayer, ball::Ball}, }; #[derive(GodotClass)] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 92c46d3..f2808db 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,7 +1,6 @@ use godot::prelude::*; pub mod announcer; -pub mod ball; pub mod chat; pub mod cli_parser; pub mod game_manager; diff --git a/rust/src/map.rs b/rust/src/map.rs index ab58f72..39f248a 100644 --- a/rust/src/map.rs +++ b/rust/src/map.rs @@ -1,10 +1,9 @@ use godot::{classes::CharacterBody3D, prelude::*}; use crate::{ - ball::Ball, game_manager::Game, net::util::NetVector3, - player::{IPlayer, LocalPlayer, NetTransform, RemotePlayer}, + player::{IPlayer, LocalPlayer, NetTransform, RemotePlayer, ball::Ball}, }; #[derive(GodotClass)] diff --git a/rust/src/net/util.rs b/rust/src/net/util.rs index 6b072c9..8bf007b 100644 --- a/rust/src/net/util.rs +++ b/rust/src/net/util.rs @@ -30,8 +30,8 @@ impl From for Vector3 { pub fn cast_ray( from: Vector3, to: Vector3, - world: Option>, - exclude: Array, + world: &Option>, + exclude: &Array, ) -> Option { let ray = PhysicsRayQueryParameters3D::create(from, to); if let Some(mut ray) = ray { @@ -62,6 +62,39 @@ pub fn cast_ray( } } +pub fn shotgun_ray( + from: Vector3, + to: Vector3, + up: Vector3, + world: &Option>, + exclude: &Array, + density: u8, + angle: f32, + distance: f32, +) -> Vec { + let mut results = Vec::new(); + + let original_dir = (to - from).normalized(); + let step = angle / density as f32; + + let up = up.normalized(); + let right = original_dir.cross(up).normalized(); + + for y in 0..density { + let y_rot = -(angle / 2.) + step * y as f32; + for x in 0..density { + let x_rot = -(angle / 2.) + step * x as f32; + let dir = original_dir.rotated(up, x_rot).rotated(right, y_rot); + let to = from + dir * distance; + if let Some(res) = cast_ray(from, to, world, exclude) { + results.push(res); + } + } + } + + results +} + pub struct RaycastResult { pub position: Vector3, pub collider: Gd, diff --git a/rust/src/ball.rs b/rust/src/player/ball.rs similarity index 86% rename from rust/src/ball.rs rename to rust/src/player/ball.rs index e30e40b..0e0bbba 100644 --- a/rust/src/ball.rs +++ b/rust/src/player/ball.rs @@ -5,7 +5,7 @@ use godot::{ use crate::{ net::{network_manager::BallSync, util::NetVector3}, - player::{IPlayer, NetTransform}, + player::{DamageSource, IPlayer, NetTransform, shootable::Shootable}, }; #[derive(GodotClass)] @@ -80,3 +80,12 @@ impl Ball { self.base_mut().set_linear_velocity(sync.velocity.into()); } } + +#[godot_dyn] +impl Shootable for Ball { + fn on_shoot(&mut self, _: DamageSource, _: i32, dir: Vector3) { + let prev_velocity = self.base().get_linear_velocity(); + self.base_mut() + .set_linear_velocity(prev_velocity + dir * 10.); + } +} diff --git a/rust/src/player/mod.rs b/rust/src/player/mod.rs index f61d0a0..95d0b38 100644 --- a/rust/src/player/mod.rs +++ b/rust/src/player/mod.rs @@ -23,14 +23,17 @@ use crate::{ }, player::{ effects::PlayerEffects, + shootable::Shootable, soldier_mesh::SoldierMesh, - weapon::{BallWeapon, Raygun, Weapon, WeaponType}, + weapon::{BallWeapon, Raygun, Shotgun, Weapon, WeaponType}, }, ui::{chatbox::ChatBox, cli::CommandLinePanel, settings::SettingsPanel}, }; +pub mod ball; pub mod effects; pub mod player_hand_remover; +pub mod shootable; pub mod soldier_mesh; pub mod weapon; @@ -307,6 +310,7 @@ impl IPlayer for LocalPlayer { self.player_id.unwrap_or(0), camera_origin.get_global_position(), to, + camera_origin.get_global_basis().col_b(), deal_damage, ) } else { @@ -372,7 +376,7 @@ impl IPlayer for LocalPlayer { } WeaponType::Shotgun => { if let Some(raygun) = &mesh.bind().shotgun { - self.spawn_weapon(raygun.instantiate_as::().into_dyn().upcast()); + self.spawn_weapon(raygun.instantiate_as::().into_dyn().upcast()); } } WeaponType::Ball => { @@ -580,8 +584,8 @@ impl ICharacterBody3D for LocalPlayer { match cast_ray( camera.get_global_position(), camera.get_global_position() + forward, - self.base().get_world_3d(), - Array::from_iter(vec![self.base().get_rid()]), + &self.base().get_world_3d(), + &Array::from_iter(vec![self.base().get_rid()]), ) { Some(res) => Some(res.position), None => Some(camera.get_global_position() + forward * 100.), @@ -736,6 +740,13 @@ impl LocalPlayer { } } +#[godot_dyn] +impl Shootable for LocalPlayer { + fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) { + self.take_damage(source, damage, true); + } +} + #[derive(GodotClass)] #[class(base=CharacterBody3D, init)] pub struct RemotePlayer { @@ -842,6 +853,7 @@ impl IPlayer for RemotePlayer { rid, self.player_id.unwrap_or(0), camera_origin.get_global_position(), + camera_origin.get_basis().col_b(), to, deal_damage, ) @@ -978,6 +990,13 @@ impl RemotePlayer { pub fn on_take_damage(source: DamageSourceSignal, source_player: u16, damage: i32); } +#[godot_dyn] +impl Shootable for RemotePlayer { + fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) { + self.take_damage(source, damage, true); + } +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum DamageSource { Player(u16), diff --git a/rust/src/player/shootable.rs b/rust/src/player/shootable.rs new file mode 100644 index 0000000..9630fa7 --- /dev/null +++ b/rust/src/player/shootable.rs @@ -0,0 +1,6 @@ +use crate::player::DamageSource; +use godot::prelude::*; + +pub trait Shootable { + fn on_shoot(&mut self, source: DamageSource, damage: i32, dir: Vector3); +} diff --git a/rust/src/player/soldier_mesh.rs b/rust/src/player/soldier_mesh.rs index 63c6c7d..3a0ef42 100644 --- a/rust/src/player/soldier_mesh.rs +++ b/rust/src/player/soldier_mesh.rs @@ -5,7 +5,7 @@ use godot::{ use crate::player::{ player_hand_remover::PlayerHandRemover, - weapon::{BallWeapon, Raygun, Weapon}, + weapon::{BallWeapon, Raygun, Shotgun, Weapon}, }; #[derive(GodotClass)] @@ -62,7 +62,7 @@ impl SoldierMesh { pub fn spawn_shotgun(&mut self) -> Option> { if let Some(shotgun) = &self.shotgun { - let node = shotgun.instantiate_as::(); + let node = shotgun.instantiate_as::(); self.spawn_weapon(node.into_dyn().upcast()) } else { None diff --git a/rust/src/player/weapon.rs b/rust/src/player/weapon.rs index 8255197..52f9293 100644 --- a/rust/src/player/weapon.rs +++ b/rust/src/player/weapon.rs @@ -1,11 +1,12 @@ +use std::{collections::HashSet, f32::consts::PI}; + use godot::{classes::AudioStreamPlayer3D, prelude::*}; use serde::{Deserialize, Serialize}; use crate::{ - ball::Ball, game_manager::{Game, GameOption}, - net::util::cast_ray, - player::{DamageSource, IPlayer}, + net::util::{cast_ray, shotgun_ray}, + player::{DamageSource, IPlayer, ball::Ball, shootable::Shootable}, }; pub trait Weapon { @@ -15,6 +16,7 @@ pub trait Weapon { player_id: u16, from: Vector3, to: Vector3, + up: Vector3, deal_damage: bool, ) -> bool; fn get_type(&self) -> WeaponType; @@ -60,6 +62,7 @@ impl Weapon for Raygun { player_id: u16, from: Vector3, to: Vector3, + _: Vector3, deal_damage: bool, ) -> bool { if self.cooldown_remaining > 0. { @@ -74,8 +77,8 @@ impl Weapon for Raygun { let target = cast_ray( from, to, - self.base().get_world_3d(), - Array::from_iter(vec![player]), + &self.base().get_world_3d(), + &Array::from_iter(vec![player]), ); let dir = (to - source.get_global_position()).normalized(); @@ -100,23 +103,15 @@ impl Weapon for Raygun { } if deal_damage && let Some(target) = target { - if let Ok(mut player) = target.collider.clone().try_dynify::() { + if let Ok(mut shootable) = target.collider.clone().try_dynify::() { + godot_print!("{}", target.collider); self.run_deferred(move |_| { - if let Some(game) = Game::singleton() - && game.bind().opts.is_true(GameOption::FriendlyFire) - { - player.dyn_bind_mut().take_damage( - DamageSource::Player(player_id), - 100, - true, - ); - } + shootable.dyn_bind_mut().on_shoot( + DamageSource::Player(player_id), + 100, + dir.normalized(), + ); }); - } else if let Ok(mut ball) = target.collider.try_cast::() { - let prev_velocity = ball.bind().base().get_linear_velocity(); - ball.bind_mut() - .base_mut() - .set_linear_velocity(prev_velocity + dir * 10.); } } } @@ -131,6 +126,81 @@ impl Weapon for Raygun { } } +#[derive(GodotClass)] +#[class(base=Node3D, init)] +pub struct Shotgun { + #[export] + sound_emitter: Option>, + #[export] + bullet_prefab: Option>, + #[export] + bullet_source: Option>, + + #[export] + cooldown: f64, + + cooldown_remaining: f64, + + base: Base, +} + +#[godot_dyn] +impl Weapon for Shotgun { + fn shoot( + &mut self, + player: Rid, + player_id: u16, + from: Vector3, + to: Vector3, + up: Vector3, + deal_damage: bool, + ) -> bool { + // TODO do effects + let mut colliders = HashSet::new(); + + for result in shotgun_ray( + from, + to, + up, + &self.base().get_world_3d(), + &Array::from_iter(vec![player]), + 10, + PI / 2., + 10., + ) { + colliders.insert(result.collider); + } + + 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; + + if let Ok(mut shootable) = collider.clone().try_dynify::() { + godot_print!("{} ({})", collider, delta.length()); + self.run_deferred(move |_| { + shootable.dyn_bind_mut().on_shoot( + DamageSource::Player(player_id), + (500. / delta.length()) as i32, + delta.normalized(), + ); + }); + } + } + } + + true + } + + fn get_type(&self) -> WeaponType { + WeaponType::Shotgun + } +} + #[derive(GodotClass)] #[class(base=Node3D, init)] pub struct Beam { @@ -183,7 +253,7 @@ pub struct BallWeapon { #[godot_dyn] impl Weapon for BallWeapon { - fn shoot(&mut self, _: Rid, _: u16, _: Vector3, _: Vector3, _: bool) -> bool { + fn shoot(&mut self, _: Rid, _: u16, _: Vector3, _: Vector3, _: Vector3, _: bool) -> bool { true }