From fb799acdbece381d70cca5a480694b53fe30e534 Mon Sep 17 00:00:00 2001 From: Sofia Date: Sun, 26 Jul 2026 00:10:04 +0300 Subject: [PATCH] Implement punching --- godot/project.godot | 5 ++ rust/src/game_manager.rs | 39 +++++++++++++ rust/src/net/network_manager.rs | 1 + rust/src/net/protocol.rs | 33 +++++++++++ rust/src/player/local_player.rs | 99 ++++++++++++++++++-------------- rust/src/player/mod.rs | 91 ++++++++++++++++++++++++++++- rust/src/player/remote_player.rs | 30 ++++------ 7 files changed, 233 insertions(+), 65 deletions(-) diff --git a/godot/project.godot b/godot/project.godot index 7b329bb..3ab3985 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -124,6 +124,11 @@ swap_weapon_down={ "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null) ] } +punch={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null) +] +} [physics] diff --git a/rust/src/game_manager.rs b/rust/src/game_manager.rs index 2a88069..0ba5658 100644 --- a/rust/src/game_manager.rs +++ b/rust/src/game_manager.rs @@ -915,6 +915,27 @@ impl Game { } } + // Handle shooting, either from the server of from a client + pub fn handle_punch( + &mut self, + player_id: u16, + to: Vector3, + transform: NetTransform, + look_up: f32, + deal_damage: bool, + ) -> bool { + let self_id = self.self_id.clone().unwrap_or(0); + if let Some(player) = self.find_player_mut(player_id) { + if player.id() != self_id { + player.try_punch(to, transform, look_up, deal_damage) + } else { + false + } + } else { + false + } + } + // Handle for when a player is killed, run both on client and server pub fn on_kill(&mut self, source: DamageSource, player_id: u16) { if let DamageSource::Player(source_player_id) = source @@ -1155,6 +1176,24 @@ impl Player { } } + pub fn try_punch( + &mut self, + to: Vector3, + transform: NetTransform, + look_up: f32, + deal_damage: bool, + ) -> bool { + if let Some(character) = &mut self.character { + character + .dyn_bind_mut() + .apply_rotation(transform.rotation.into()); + character.dyn_bind_mut().apply_look_up(look_up); + character.dyn_bind_mut().try_punch(to, deal_damage) + } else { + false + } + } + pub fn id(&self) -> u16 { self.data.id } diff --git a/rust/src/net/network_manager.rs b/rust/src/net/network_manager.rs index e2cef2a..d4f2f7c 100644 --- a/rust/src/net/network_manager.rs +++ b/rust/src/net/network_manager.rs @@ -306,6 +306,7 @@ pub enum Package { SelfSync(SyncPackage), Jump(u16), Shoot(u16, NetVector3, NetTransform, f32), + Punch(u16, NetVector3, NetTransform, f32), 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 54ec0da..ab1b2d5 100644 --- a/rust/src/net/protocol.rs +++ b/rust/src/net/protocol.rs @@ -150,6 +150,17 @@ impl NetworkManager { ); } } + Package::Punch(player_id, to, transform, look_up) => { + if let Some(game) = &mut Game::singleton() { + game.bind_mut().handle_punch( + player_id, + to.into(), + transform, + look_up, + false, + ); + } + } Package::TakeDamage(source_player_id, target_player_id, damage) => { if let Some(game) = &mut Game::singleton() { game.bind_mut().handle_take_damage( @@ -360,6 +371,28 @@ impl NetworkManager { } } } + Package::Punch(_, to, transform, look_up) => { + if let Some(game) = &mut Game::singleton() { + let player = + game.bind().find_player_by_addr(&conn.address).cloned(); + if let Some(player) = player { + if game.bind_mut().handle_punch( + player.id(), + to.into(), + transform, + look_up, + true, + ) { + peer.broadcast_reliable(Package::Punch( + player.id(), + to, + transform, + look_up, + )); + } + } + } + } Package::SetTeam(player_id, team) => { if let Some(game) = &mut Game::singleton() { let self_id = game diff --git a/rust/src/player/local_player.rs b/rust/src/player/local_player.rs index 9e62ab3..2c0699c 100644 --- a/rust/src/player/local_player.rs +++ b/rust/src/player/local_player.rs @@ -16,7 +16,7 @@ use crate::{ net::{ network_manager::{ NetworkManager, - Package::{self, Jump, Shoot}, + Package::{self, Jump, Punch, Shoot}, PeerKind, }, util::cast_ray, @@ -71,6 +71,8 @@ pub struct LocalPlayer { hit_sound: Option>, #[export] health: i32, + #[export] + punch_cooldown: f64, pub player_id: Option, @@ -89,6 +91,8 @@ pub struct LocalPlayer { locked: bool, + punch_cd_remaining: f64, + base: Base, } @@ -159,31 +163,13 @@ impl IPlayer for LocalPlayer { fn apply_look_up(&mut self, _look: f32) {} fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { - if self.is_dead { - return false; - } + self.try_shoot_common(to, deal_damage) + } - if self.drop_ball(true) { - return true; - } - - let rid = self.base().get_rid(); - if let Some(mut weapon) = self.get_weapon() - && let Some(camera_origin) = &self.camera_origin - { - if weapon.dyn_bind_mut().shoot( - rid, - self.player_id.unwrap_or(0), - camera_origin.get_global_position(), - to, - camera_origin.get_global_basis().col_b(), - deal_damage, - ) { - weapon.set_rotation(Vector3::RIGHT * 0.3); - true - } else { - false - } + fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool { + if self.punch_cd_remaining <= 0. { + self.punch_cd_remaining = self.punch_cooldown; + self.try_punch_common(to, deal_damage) } else { false } @@ -465,22 +451,7 @@ impl ICharacterBody3D for LocalPlayer { if self.locked { if event.is_action_pressed("shoot") { - let pos = if let Some(camera) = &self.camera { - let forward = camera.get_global_basis().col_c().neg(); - 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()]), - ) { - Some(res) => Some(res.position), - None => Some(camera.get_global_position() + forward * 100.), - } - } else { - None - }; - - if let Some(pos) = pos { + if let Some(pos) = self.raycast_from_camera() { if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { match peer { PeerKind::Client(peer, addr) => { @@ -494,7 +465,34 @@ impl ICharacterBody3D for LocalPlayer { PeerKind::Server(peer, _) => { if self.try_shoot(pos, true) { peer.broadcast_reliable(Shoot( - 0, + self.player_id.unwrap_or(0), + pos.into(), + self.get_transform(), + self.look_up, + )); + } + } + } + } + } + } + + if event.is_action_pressed("punch") { + if let Some(pos) = self.raycast_from_camera() { + if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { + match peer { + PeerKind::Client(peer, addr) => { + if self.try_punch(pos, false) { + peer.send_reliable( + addr, + Punch(0, pos.into(), self.get_transform(), self.look_up), + ); + } + } + PeerKind::Server(peer, _) => { + if self.try_punch(pos, true) { + peer.broadcast_reliable(Punch( + self.player_id.unwrap_or(0), pos.into(), self.get_transform(), self.look_up, @@ -661,6 +659,23 @@ impl LocalPlayer { hit_sound.play(); } } + + fn raycast_from_camera(&self) -> Option { + if let Some(camera) = &self.camera { + let forward = camera.get_global_basis().col_c().neg(); + 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()]), + ) { + Some(res) => Some(res.position), + None => Some(camera.get_global_position() + forward * 100.), + } + } else { + None + } + } } #[godot_dyn] diff --git a/rust/src/player/mod.rs b/rust/src/player/mod.rs index acd1177..30cb350 100644 --- a/rust/src/player/mod.rs +++ b/rust/src/player/mod.rs @@ -1,4 +1,4 @@ -use std::ops::Neg; +use std::{collections::HashSet, f32::consts::PI, ops::Neg}; use godot::{classes::ICharacterBody3D, obj::WithBaseField, prelude::*}; use serde::{Deserialize, Serialize}; @@ -6,8 +6,14 @@ use serde::{Deserialize, Serialize}; use crate::{ game_manager::{Game, GameOption}, killbox::KillboxKind, - net::{network_manager::NetworkManager, util::NetVector3}, - player::weapon::{BallWeapon, Weapon, WeaponType}, + net::{ + network_manager::NetworkManager, + util::{NetVector3, shotgun_ray}, + }, + player::{ + shootable::Shootable, + weapon::{BallWeapon, Weapon, WeaponType}, + }, }; pub mod ball; @@ -40,6 +46,7 @@ pub trait IPlayer { fn get_transform(&self) -> NetTransform; fn jump(&mut self); fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool; + fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool; fn take_damage(&mut self, source_player: DamageSource, damage: i32, kill: bool); fn is_dead(&self) -> bool; fn kill(&mut self, source_player: DamageSource); @@ -57,6 +64,8 @@ pub trait PlayerCommon { fn drop_ball_common(&mut self, with_velocity: bool) -> bool; fn get_look_dir(&self) -> Vector3; fn can_take_damage(&self, source: &DamageSource) -> bool; + fn try_shoot_common(&mut self, to: Vector3, deal_damage: bool) -> bool; + fn try_punch_common(&mut self, to: Vector3, deal_damage: bool) -> bool; } impl PlayerCommon for T { @@ -159,6 +168,82 @@ impl PlayerCommon for T { } true } + + fn try_shoot_common(&mut self, to: Vector3, deal_damage: bool) -> bool { + if self.is_dead() { + return false; + } + + if self.drop_ball(true) { + return true; + } + + let rid = self.base().get_rid(); + if let Some(mut weapon) = self.get_weapon() + && let Some(camera_origin) = &self.get_camera_origin() + { + weapon.dyn_bind_mut().shoot( + rid, + self.get_player_id().unwrap_or(0), + camera_origin.get_global_position(), + to, + camera_origin.get_basis().col_b(), + deal_damage, + ) + } else { + false + } + } + + fn try_punch_common(&mut self, to: Vector3, deal_damage: bool) -> bool { + if self.is_dead() { + return false; + } + if !deal_damage { + return true; + } + + if let Some(origin) = self.get_camera_origin() { + let rid = self.base().get_rid(); + let up = origin.get_basis().col_b(); + let results = shotgun_ray( + origin.get_global_position(), + to, + up, + &self.base().get_world_3d(), + &Array::from_iter(vec![rid]), + 10, + PI / 2., + 2., + ); + + let mut colliders = HashSet::new(); + for result in results { + colliders.insert(result.collider); + } + + 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 - origin.get_global_position(); + + if let Ok(mut shootable) = collider.clone().try_dynify::() { + self.run_deferred(move |s| { + shootable.dyn_bind_mut().on_shoot( + DamageSource::Player(s.get_player_id().unwrap_or(0)), + 500, + delta.normalized(), + ); + }); + } + } + } + true + } } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/rust/src/player/remote_player.rs b/rust/src/player/remote_player.rs index 34d0a8b..5f8601a 100644 --- a/rust/src/player/remote_player.rs +++ b/rust/src/player/remote_player.rs @@ -33,6 +33,8 @@ pub struct RemotePlayer { #[export] health: i32, #[export] + punch_cooldown: f64, + #[export] pub spectator_camera: Option>, #[export] camera_origin: Option>, @@ -52,6 +54,8 @@ pub struct RemotePlayer { is_dead: bool, dead_for: f64, + punch_cd_remaining: f64, + base: Base, } @@ -120,27 +124,13 @@ impl IPlayer for RemotePlayer { } fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { - if self.is_dead { - return false; - } + self.try_shoot_common(to, deal_damage) + } - if self.drop_ball(true) { - return true; - } - - let rid = self.base().get_rid(); - if let Some(mesh) = &mut self.soldier_mesh - && let Some(mut weapon) = mesh.bind().get_weapon() - && let Some(camera_origin) = &self.camera_origin - { - weapon.dyn_bind_mut().shoot( - rid, - self.player_id.unwrap_or(0), - camera_origin.get_global_position(), - to, - camera_origin.get_basis().col_b(), - deal_damage, - ) + fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool { + if self.punch_cd_remaining <= 0. { + self.punch_cd_remaining = self.punch_cooldown; + self.try_punch_common(to, deal_damage) } else { false }