From 81402f2cf42746793444ae2d7b497e0a8db0daa1 Mon Sep 17 00:00:00 2001 From: Sofia Date: Mon, 27 Jul 2026 22:17:05 +0300 Subject: [PATCH] Add initial support for telegrenades --- godot/project.godot | 5 ++++ godot/scenes/misc/game_manager.tscn | 2 ++ godot/scenes/misc/teleport_grenade.tscn | 23 +++++++++++++++ rust/src/game_manager.rs | 25 +++++++++++++++- rust/src/map.rs | 19 ++++++++++++ rust/src/net/network_manager.rs | 1 + rust/src/net/protocol.rs | 16 ++++++++++ rust/src/player/local_player.rs | 39 ++++++++++++++++++++++++- rust/src/player/mod.rs | 35 +++++++++++++++++++++- rust/src/player/remote_player.rs | 15 ++++++++++ rust/src/player/telegrenade.rs | 37 +++++++++++++++++++++++ 11 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 godot/scenes/misc/teleport_grenade.tscn create mode 100644 rust/src/player/telegrenade.rs diff --git a/godot/project.godot b/godot/project.godot index b6e48fc..1dd9f8b 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -130,6 +130,11 @@ punch={ "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) ] } +throw_telegrenade={ +"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":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null) +] +} [physics] diff --git a/godot/scenes/misc/game_manager.tscn b/godot/scenes/misc/game_manager.tscn index c121582..93c8652 100644 --- a/godot/scenes/misc/game_manager.tscn +++ b/godot/scenes/misc/game_manager.tscn @@ -11,6 +11,7 @@ [ext_resource type="PackedScene" uid="uid://hmh2lrrlo2wy" path="res://scenes/player/replay_player.tscn" id="6_5fjlq"] [ext_resource type="PackedScene" uid="uid://dfnbc8sbufa4j" path="res://scenes/misc/ball.tscn" id="7_cv0ql"] [ext_resource type="PackedScene" uid="uid://dc17w5n6y1an" path="res://scenes/misc/replay_ball.tscn" id="8_m1inn"] +[ext_resource type="PackedScene" uid="uid://cuh8bvyx7yy8e" path="res://scenes/misc/teleport_grenade.tscn" id="9_nxoss"] [node name="game_manager" type="GameManager" unique_id=1442044769] respawn_timer = 10.0 @@ -22,4 +23,5 @@ remote_player = ExtResource("5_nxoss") replay_player = ExtResource("6_5fjlq") ball = ExtResource("7_cv0ql") replay_ball = ExtResource("8_m1inn") +telegrenade = ExtResource("9_nxoss") maps = Array[MapResource]([ExtResource("1_glms7"), ExtResource("2_glms7"), ExtResource("5_rotn8")]) diff --git a/godot/scenes/misc/teleport_grenade.tscn b/godot/scenes/misc/teleport_grenade.tscn new file mode 100644 index 0000000..dc1d7e0 --- /dev/null +++ b/godot/scenes/misc/teleport_grenade.tscn @@ -0,0 +1,23 @@ +[gd_scene format=3 uid="uid://cuh8bvyx7yy8e"] + +[sub_resource type="SphereShape3D" id="SphereShape3D_1cgct"] +radius = 0.5088567 + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_bbjda"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_otqol"] +albedo_texture = SubResource("PlaceholderTexture2D_bbjda") + +[sub_resource type="BoxMesh" id="BoxMesh_3g557"] +material = SubResource("StandardMaterial3D_otqol") +size = Vector3(0.1, 0.1, 0.1) + +[node name="teleport_grenade" type="Telegrenade" unique_id=1670808846] +collision_layer = 0 +collision_mask = 4 + +[node name="collision_shape_3d" type="CollisionShape3D" parent="." unique_id=1124839205] +shape = SubResource("SphereShape3D_1cgct") + +[node name="mesh_instance_3d" type="MeshInstance3D" parent="." unique_id=1327678222] +mesh = SubResource("BoxMesh_3g557") diff --git a/rust/src/game_manager.rs b/rust/src/game_manager.rs index eb71545..1a3eea6 100644 --- a/rust/src/game_manager.rs +++ b/rust/src/game_manager.rs @@ -22,7 +22,7 @@ use crate::{ pickup::PickupKind, player::{ DamageSource, IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, - remote_player::RemotePlayer, weapon::WeaponType, + remote_player::RemotePlayer, telegrenade::Telegrenade, weapon::WeaponType, }, replay::{ReplayEvent, ReplayPlayback, ReplayRecorder}, team_resource::TeamResource, @@ -58,6 +58,9 @@ pub struct GameManager { #[export] #[export_group(name = "Prefabs")] pub replay_ball: Option>, + #[export] + #[export_group(name = "Prefabs")] + pub telegrenade: Option>, pub game: Option>, base: Base, @@ -1251,6 +1254,26 @@ impl Game { } }); } + + pub fn spawn_telegrenade( + &mut self, + location: NetVector3, + velocity: NetVector3, + ) -> Option> { + if let Some(map) = &mut self.current_map { + map.bind_mut().spawn_telegrenade(location, velocity) + } else { + None + } + } + + pub fn on_throw_telegrenade(&mut self, player_id: u16, to: NetVector3) { + if let Some(player) = self.find_player_mut(player_id) + && let Some(character) = &mut player.character + { + character.dyn_bind_mut().try_throw_telegrenade(to.into()); + } + } } #[derive(Clone, Debug)] diff --git a/rust/src/map.rs b/rust/src/map.rs index 41a809b..6beec86 100644 --- a/rust/src/map.rs +++ b/rust/src/map.rs @@ -8,6 +8,7 @@ use crate::{ pickup::Pickup, player::{ IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, remote_player::RemotePlayer, + telegrenade::Telegrenade, }, replay::{ReplayPlayer, ReplayPlayerState, StandaloneReplayManager}, }; @@ -201,4 +202,22 @@ impl Map { self.pickups.insert(id, pickup); id } + + pub fn spawn_telegrenade( + &mut self, + location: NetVector3, + velocity: NetVector3, + ) -> Option> { + if let Some(prefab) = &GameManager::singleton().bind().telegrenade { + let mut telegrenade = prefab.instantiate_as::(); + self.base_mut().add_child(&telegrenade); + + telegrenade.set_global_position(location.into()); + telegrenade.set_linear_velocity(velocity.into()); + + Some(telegrenade) + } else { + None + } + } } diff --git a/rust/src/net/network_manager.rs b/rust/src/net/network_manager.rs index 3237e61..0cb95c3 100644 --- a/rust/src/net/network_manager.rs +++ b/rust/src/net/network_manager.rs @@ -315,6 +315,7 @@ pub enum Package { Goal(Vec), Announcement(u16, Announcement), Pickup(u8, u16), + ThrowTelegrenade(u16, NetVector3), // End-of-Game packages FinishGame, diff --git a/rust/src/net/protocol.rs b/rust/src/net/protocol.rs index 87c4f0c..b45d724 100644 --- a/rust/src/net/protocol.rs +++ b/rust/src/net/protocol.rs @@ -231,6 +231,13 @@ impl NetworkManager { game.bind_mut().on_pickup(pickup_id, player_id); } } + Package::ThrowTelegrenade(player_id, to) => { + if let Some(game) = &mut Game::singleton() { + if player_id != game.bind().self_id.unwrap_or(0) { + game.bind_mut().on_throw_telegrenade(player_id, to); + } + } + } _ => {} }, }, @@ -443,6 +450,15 @@ impl NetworkManager { } } } + Package::ThrowTelegrenade(_, to) => { + if let Some(game) = &mut Game::singleton() { + let player = + game.bind().find_player_by_addr(&conn.address).cloned(); + if let Some(player) = player { + game.bind_mut().on_throw_telegrenade(player.id(), to); + } + } + } _ => {} }, }, diff --git a/rust/src/player/local_player.rs b/rust/src/player/local_player.rs index 2e52f3a..b339fee 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, Punch, Shoot}, + Package::{self, Jump, Punch, Shoot, ThrowTelegrenade}, PeerKind, }, util::cast_ray, @@ -28,6 +28,7 @@ use crate::{ remote_player::RemotePlayer, shootable::Shootable, soldier_mesh::SoldierMesh, + telegrenade::Telegrenade, weapon::{BallWeapon, Raygun, Shotgun, Weapon, WeaponType}, }, replay::ReplayEvent, @@ -87,6 +88,8 @@ pub struct LocalPlayer { punch_cd_remaining: f64, + telegrenade: Option>, + base: Base, } @@ -163,6 +166,10 @@ impl IPlayer for LocalPlayer { fn apply_look_up(&mut self, _look: f32) {} + fn try_throw_telegrenade(&mut self, to: Vector3) -> bool { + self.throw_telegrenade_common(to) + } + fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { if self.try_shoot_common(to, deal_damage) { if let Some(game) = &mut Game::singleton() @@ -313,6 +320,14 @@ impl IPlayer for LocalPlayer { PickupKind::Health => self.heal(100), } } + + fn set_telegrenade(&mut self, grenade: Option>) { + self.telegrenade = grenade; + } + + fn as_gd(&self) -> DynGd { + self.to_gd().into_dyn().upcast() + } } #[godot_api] @@ -463,6 +478,28 @@ impl ICharacterBody3D for LocalPlayer { fn input(&mut self, event: Gd) { if self.locked { + if event.is_action_pressed("throw_telegrenade") { + 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_throw_telegrenade(pos) { + peer.send_reliable(addr, ThrowTelegrenade(0, pos.into())); + } + } + PeerKind::Server(peer, _) => { + if self.try_throw_telegrenade(pos) { + peer.broadcast_reliable(ThrowTelegrenade( + self.player_id.unwrap_or(0), + pos.into(), + )); + } + } + } + } + } + } + if event.is_action_pressed("shoot") { if let Some(pos) = self.raycast_from_camera() { if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { diff --git a/rust/src/player/mod.rs b/rust/src/player/mod.rs index f20b7a5..d198032 100644 --- a/rust/src/player/mod.rs +++ b/rust/src/player/mod.rs @@ -1,6 +1,10 @@ use std::{collections::HashSet, f32::consts::PI, ops::Neg}; -use godot::{classes::ICharacterBody3D, obj::WithBaseField, prelude::*}; +use godot::{ + classes::{CharacterBody3D, ICharacterBody3D}, + obj::WithBaseField, + prelude::*, +}; use serde::{Deserialize, Serialize}; use crate::{ @@ -13,6 +17,7 @@ use crate::{ pickup::PickupKind, player::{ shootable::Shootable, + telegrenade::Telegrenade, weapon::{BallWeapon, Weapon, WeaponType}, }, }; @@ -24,6 +29,7 @@ pub mod player_hand_remover; pub mod remote_player; pub mod shootable; pub mod soldier_mesh; +pub mod telegrenade; pub mod weapon; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -61,6 +67,9 @@ pub trait IPlayer { fn get_health(&self) -> i32; fn heal(&mut self, amount: i32); fn on_pickup(&mut self, kind: PickupKind); + fn try_throw_telegrenade(&mut self, to: Vector3) -> bool; + fn set_telegrenade(&mut self, telegrenade: Option>); + fn as_gd(&self) -> DynGd; } pub trait PlayerCommon { @@ -71,6 +80,7 @@ pub trait PlayerCommon { 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; + fn throw_telegrenade_common(&mut self, to: Vector3) -> bool; } impl PlayerCommon for T { @@ -256,6 +266,29 @@ impl PlayerCommon for T { } true } + + fn throw_telegrenade_common(&mut self, to: Vector3) -> bool { + if self.is_dead() { + return false; + } + + self.run_deferred(move |s| { + if let Some(game) = &mut Game::singleton() + && let Some(origin) = s.get_camera_origin() + { + let mut telegrenade = game.bind_mut().spawn_telegrenade( + origin.get_global_position().into(), + (to.normalized() * 10.).into(), + ); + if let Some(telegrenade) = &mut telegrenade { + telegrenade.bind_mut().player = Some(s.as_gd()); + s.set_telegrenade(Some(telegrenade.clone())); + } + } + }); + + true + } } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/rust/src/player/remote_player.rs b/rust/src/player/remote_player.rs index 6c7ed61..3b438e3 100644 --- a/rust/src/player/remote_player.rs +++ b/rust/src/player/remote_player.rs @@ -15,6 +15,7 @@ use crate::{ effects::PlayerEffects, shootable::Shootable, soldier_mesh::SoldierMesh, + telegrenade::Telegrenade, weapon::{Weapon, WeaponType}, }, }; @@ -63,6 +64,8 @@ pub struct RemotePlayer { punch_cd_remaining: f64, + pub telegrenade: Option>, + base: Base, } @@ -134,6 +137,10 @@ impl IPlayer for RemotePlayer { self.try_shoot_common(to, deal_damage) } + fn try_throw_telegrenade(&mut self, to: Vector3) -> bool { + self.throw_telegrenade_common(to) + } + fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool { if self.punch_cd_remaining <= 0. { self.punch_cd_remaining = self.punch_cooldown; @@ -253,6 +260,14 @@ impl IPlayer for RemotePlayer { PickupKind::Health => self.heal(100), } } + + fn set_telegrenade(&mut self, grenade: Option>) { + self.telegrenade = grenade; + } + + fn as_gd(&self) -> DynGd { + self.to_gd().into_dyn().upcast() + } } #[godot_api] diff --git a/rust/src/player/telegrenade.rs b/rust/src/player/telegrenade.rs new file mode 100644 index 0000000..57ff447 --- /dev/null +++ b/rust/src/player/telegrenade.rs @@ -0,0 +1,37 @@ +use godot::{ + classes::{CharacterBody3D, IRigidBody3D, RigidBody3D}, + prelude::*, +}; + +use crate::{ + net::util::NetVector3, + player::{IPlayer, NetTransform}, +}; + +#[derive(GodotClass)] +#[class(base=RigidBody3D, init)] +pub struct Telegrenade { + pub player: Option>, + + base: Base, +} + +#[godot_api] +impl IRigidBody3D for Telegrenade { + fn ready(&mut self) {} +} + +impl Telegrenade { + pub fn teleport(&mut self) {} + + pub fn get_transform(&self) -> NetTransform { + NetTransform { + location: self.base().get_global_position().into(), + rotation: self.base().get_global_rotation().into(), + } + } + + pub fn get_velocity(&self) -> NetVector3 { + self.base().get_linear_velocity().into() + } +}