Compare commits

...

3 Commits

Author SHA1 Message Date
a0ce60c238 Allow activating telegrenade in-flight 2026-07-27 22:54:00 +03:00
a6e6e66d28 Propagate teleport when teleport grenade collides 2026-07-27 22:43:57 +03:00
3a2b2d609d Sync telegrenades 2026-07-27 22:28:55 +03:00
10 changed files with 190 additions and 10 deletions

View File

@ -15,6 +15,8 @@ size = Vector3(0.1, 0.1, 0.1)
[node name="teleport_grenade" type="Telegrenade" unique_id=1670808846] [node name="teleport_grenade" type="Telegrenade" unique_id=1670808846]
collision_layer = 0 collision_layer = 0
collision_mask = 4 collision_mask = 4
contact_monitor = true
max_contacts_reported = 1
[node name="collision_shape_3d" type="CollisionShape3D" parent="." unique_id=1124839205] [node name="collision_shape_3d" type="CollisionShape3D" parent="." unique_id=1124839205]
shape = SubResource("SphereShape3D_1cgct") shape = SubResource("SphereShape3D_1cgct")

View File

@ -23,6 +23,7 @@ hud = NodePath("hud")
hit_sound = NodePath("hit_sound") hit_sound = NodePath("hit_sound")
health = 100 health = 100
punch_cooldown = 1.0 punch_cooldown = 1.0
telegrenade_cd = 10.0
collision_mask = 4 collision_mask = 4
slide_on_ceiling = false slide_on_ceiling = false
floor_constant_speed = true floor_constant_speed = true

View File

@ -49,6 +49,7 @@ health_bar = NodePath("health_bar_sprite/health_bar_viewport/health_bar")
double_health_bar = NodePath("double_health_bar_sprite/health_bar_viewport/double_health_bar") double_health_bar = NodePath("double_health_bar_sprite/health_bar_viewport/double_health_bar")
health_bar_sprite = NodePath("health_bar_sprite") health_bar_sprite = NodePath("health_bar_sprite")
double_health_bar_sprite = NodePath("double_health_bar_sprite") double_health_bar_sprite = NodePath("double_health_bar_sprite")
telegrenade_cd = 10.0
collision_layer = 2 collision_layer = 2
collision_mask = 4 collision_mask = 4
slide_on_ceiling = false slide_on_ceiling = false

View File

@ -14,15 +14,21 @@ use crate::{
net::{ net::{
network_manager::{ network_manager::{
BallSync, NetworkManager, BallSync, NetworkManager,
Package::{self, Kill, SelfSync, SetTeam, SpawnPlayer, TakeDamage}, Package::{
PeerKind, SyncPackage, self, Kill, SelfSync, SetTeam, SpawnPlayer, TakeDamage, TelegrenadeTeleport,
},
PeerKind, SyncPackage, TelegrenadeSync,
}, },
util::NetVector3, util::NetVector3,
}, },
pickup::PickupKind, pickup::PickupKind,
player::{ player::{
DamageSource, IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, DamageSource, IPlayer, NetTransform,
remote_player::RemotePlayer, telegrenade::Telegrenade, weapon::WeaponType, ball::Ball,
local_player::LocalPlayer,
remote_player::RemotePlayer,
telegrenade::{self, Telegrenade},
weapon::WeaponType,
}, },
replay::{ReplayEvent, ReplayPlayback, ReplayRecorder}, replay::{ReplayEvent, ReplayPlayback, ReplayRecorder},
team_resource::TeamResource, team_resource::TeamResource,
@ -292,12 +298,24 @@ impl INode for Game {
player.get_look_up(), player.get_look_up(),
player.get_transform(), player.get_transform(),
) { ) {
let telegrenade_sync = if let Some(character) = &player.character
&& let Some(telegrenade) =
character.dyn_bind().get_telegrenade()
{
Some(TelegrenadeSync {
transform: telegrenade.bind().get_transform(),
velocity: telegrenade.bind().get_velocity(),
})
} else {
None
};
sync_data.insert( sync_data.insert(
player.id(), player.id(),
SyncPackage { SyncPackage {
movement_direction: dir, movement_direction: dir,
look_up, look_up,
transform, transform,
telegrenade: telegrenade_sync,
}, },
); );
} }
@ -977,10 +995,20 @@ impl Game {
movement_direction, movement_direction,
look_up, look_up,
transform, transform,
telegrenade,
}, },
) in sync_data ) in sync_data
{ {
if let Some(player) = self.players.iter_mut().find(|p| p.id() == player_id) { if let Some(player) = self.players.iter_mut().find(|p| p.id() == player_id) {
if let Some(telegrenade_sync) = telegrenade
&& let Some(character) = &mut player.character
&& let Some(mut telegrenade) = character.dyn_bind().get_telegrenade()
{
telegrenade.set_global_position(telegrenade_sync.transform.location.into());
telegrenade.set_global_rotation(telegrenade_sync.transform.rotation.into());
telegrenade.set_linear_velocity(telegrenade_sync.velocity.into());
}
if player.id() != self.self_id.unwrap_or(0) { if player.id() != self.self_id.unwrap_or(0) {
// Sync remote players always // Sync remote players always
player.set_transform(transform); player.set_transform(transform);
@ -1274,6 +1302,36 @@ impl Game {
character.dyn_bind_mut().try_throw_telegrenade(to.into()); character.dyn_bind_mut().try_throw_telegrenade(to.into());
} }
} }
// Handle telegrenade activate from server to client
pub fn handle_telegrenade_teleport(&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().telegrenade_teleport(to.into());
}
}
// Handle telegrenade activation from client to server
pub fn handle_telegrenade_activate(&mut self, player_id: u16) {
if let Some(player) = self.find_player_mut(player_id)
&& let Some(character) = &mut player.character
{
let pos = if let Some(telegrenade) = character.dyn_bind().get_telegrenade() {
telegrenade.get_global_position()
} else {
Vector3::ZERO
};
character.dyn_bind_mut().telegrenade_teleport(pos);
self.run_deferred(move |_| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
peer.broadcast_reliable(TelegrenadeTeleport(player_id, pos.into()));
}
});
}
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -1329,6 +1387,7 @@ impl Player {
movement_direction: character.dyn_bind().get_movement_dir().into(), movement_direction: character.dyn_bind().get_movement_dir().into(),
look_up: character.dyn_bind().get_look_up(), look_up: character.dyn_bind().get_look_up(),
transform: character.dyn_bind().get_transform(), transform: character.dyn_bind().get_transform(),
telegrenade: None,
}) })
} else { } else {
None None

View File

@ -316,6 +316,8 @@ pub enum Package {
Announcement(u16, Announcement), Announcement(u16, Announcement),
Pickup(u8, u16), Pickup(u8, u16),
ThrowTelegrenade(u16, NetVector3), ThrowTelegrenade(u16, NetVector3),
TelegrenadeTeleport(u16, NetVector3),
TelegrenadeActivate,
// End-of-Game packages // End-of-Game packages
FinishGame, FinishGame,
@ -326,6 +328,13 @@ pub struct SyncPackage {
pub movement_direction: NetVector3, pub movement_direction: NetVector3,
pub look_up: f32, pub look_up: f32,
pub transform: NetTransform, pub transform: NetTransform,
pub telegrenade: Option<TelegrenadeSync>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelegrenadeSync {
pub transform: NetTransform,
pub velocity: NetVector3,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -238,6 +238,11 @@ impl NetworkManager {
} }
} }
} }
Package::TelegrenadeTeleport(player_id, to) => {
if let Some(game) = &mut Game::singleton() {
game.bind_mut().handle_telegrenade_teleport(player_id, to);
}
}
_ => {} _ => {}
}, },
}, },
@ -459,6 +464,15 @@ impl NetworkManager {
} }
} }
} }
Package::TelegrenadeActivate => {
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().handle_telegrenade_activate(player.id());
}
}
}
_ => {} _ => {}
}, },
}, },

View File

@ -88,7 +88,10 @@ pub struct LocalPlayer {
punch_cd_remaining: f64, punch_cd_remaining: f64,
#[export]
telegrenade_cd: f64,
telegrenade: Option<Gd<Telegrenade>>, telegrenade: Option<Gd<Telegrenade>>,
telegrenade_cd_remaining: f64,
base: Base<CharacterBody3D>, base: Base<CharacterBody3D>,
} }
@ -167,7 +170,16 @@ impl IPlayer for LocalPlayer {
fn apply_look_up(&mut self, _look: f32) {} fn apply_look_up(&mut self, _look: f32) {}
fn try_throw_telegrenade(&mut self, to: Vector3) -> bool { fn try_throw_telegrenade(&mut self, to: Vector3) -> bool {
self.throw_telegrenade_common(to) if self.telegrenade_cd_remaining > 0. {
return false;
}
if self.throw_telegrenade_common(to) {
self.telegrenade_cd_remaining = self.telegrenade_cd;
true
} else {
false
}
} }
fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool {
@ -325,6 +337,14 @@ impl IPlayer for LocalPlayer {
self.telegrenade = grenade; self.telegrenade = grenade;
} }
fn get_telegrenade(&self) -> Option<Gd<Telegrenade>> {
self.telegrenade.clone()
}
fn telegrenade_teleport(&mut self, to: Vector3) -> bool {
self.telegrenade_teleport_common(to)
}
fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer> { fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer> {
self.to_gd().into_dyn().upcast() self.to_gd().into_dyn().upcast()
} }
@ -370,6 +390,7 @@ impl ICharacterBody3D for LocalPlayer {
fn process(&mut self, delta: f64) { fn process(&mut self, delta: f64) {
self.time_passed += delta; self.time_passed += delta;
self.punch_cd_remaining -= delta; self.punch_cd_remaining -= delta;
self.telegrenade_cd_remaining -= delta;
if self.is_dead { if self.is_dead {
self.dead_for += delta; self.dead_for += delta;
@ -479,6 +500,24 @@ impl ICharacterBody3D for LocalPlayer {
fn input(&mut self, event: Gd<InputEvent>) { fn input(&mut self, event: Gd<InputEvent>) {
if self.locked { if self.locked {
if event.is_action_pressed("throw_telegrenade") { if event.is_action_pressed("throw_telegrenade") {
if let Some(telegrenade) = &mut self.telegrenade {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
match peer {
PeerKind::Client(peer, socket_addr) => {
peer.send_reliable(socket_addr, Package::TelegrenadeActivate);
}
PeerKind::Server(peer, _) => {
let position = telegrenade.get_global_position();
self.telegrenade_teleport(position);
peer.broadcast_reliable(Package::TelegrenadeTeleport(
self.player_id.unwrap_or(0),
position.into(),
));
}
}
}
}
if let Some(pos) = self.raycast_from_camera() { if let Some(pos) = self.raycast_from_camera() {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
match peer { match peer {

View File

@ -69,6 +69,8 @@ pub trait IPlayer {
fn on_pickup(&mut self, kind: PickupKind); fn on_pickup(&mut self, kind: PickupKind);
fn try_throw_telegrenade(&mut self, to: Vector3) -> bool; fn try_throw_telegrenade(&mut self, to: Vector3) -> bool;
fn set_telegrenade(&mut self, telegrenade: Option<Gd<Telegrenade>>); fn set_telegrenade(&mut self, telegrenade: Option<Gd<Telegrenade>>);
fn get_telegrenade(&self) -> Option<Gd<Telegrenade>>;
fn telegrenade_teleport(&mut self, to: Vector3) -> bool;
fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer>; fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer>;
} }
@ -81,6 +83,7 @@ pub trait PlayerCommon {
fn try_shoot_common(&mut self, to: Vector3, deal_damage: bool) -> 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 try_punch_common(&mut self, to: Vector3, deal_damage: bool) -> bool;
fn throw_telegrenade_common(&mut self, to: Vector3) -> bool; fn throw_telegrenade_common(&mut self, to: Vector3) -> bool;
fn telegrenade_teleport_common(&mut self, to: Vector3) -> bool;
} }
impl<T: IPlayer + ICharacterBody3D + WithBaseField> PlayerCommon for T { impl<T: IPlayer + ICharacterBody3D + WithBaseField> PlayerCommon for T {
@ -289,6 +292,17 @@ impl<T: IPlayer + ICharacterBody3D + WithBaseField> PlayerCommon for T {
true true
} }
fn telegrenade_teleport_common(&mut self, to: Vector3) -> bool {
if let Some(mut telegrenade) = self.get_telegrenade() {
telegrenade.queue_free();
} else {
return false;
}
self.set_telegrenade(None);
self.base_mut().set_global_position(to);
true
}
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]

View File

@ -64,7 +64,10 @@ pub struct RemotePlayer {
punch_cd_remaining: f64, punch_cd_remaining: f64,
pub telegrenade: Option<Gd<Telegrenade>>, #[export]
telegrenade_cd: f64,
telegrenade: Option<Gd<Telegrenade>>,
telegrenade_cd_remaining: f64,
base: Base<CharacterBody3D>, base: Base<CharacterBody3D>,
} }
@ -138,7 +141,16 @@ impl IPlayer for RemotePlayer {
} }
fn try_throw_telegrenade(&mut self, to: Vector3) -> bool { fn try_throw_telegrenade(&mut self, to: Vector3) -> bool {
self.throw_telegrenade_common(to) if self.telegrenade_cd_remaining > 0. {
return false;
}
if self.throw_telegrenade_common(to) {
self.telegrenade_cd_remaining = self.telegrenade_cd;
true
} else {
false
}
} }
fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool { fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool {
@ -265,6 +277,14 @@ impl IPlayer for RemotePlayer {
self.telegrenade = grenade; self.telegrenade = grenade;
} }
fn get_telegrenade(&self) -> Option<Gd<Telegrenade>> {
self.telegrenade.clone()
}
fn telegrenade_teleport(&mut self, to: Vector3) -> bool {
self.telegrenade_teleport_common(to)
}
fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer> { fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer> {
self.to_gd().into_dyn().upcast() self.to_gd().into_dyn().upcast()
} }
@ -286,6 +306,7 @@ impl ICharacterBody3D for RemotePlayer {
fn process(&mut self, delta: f64) { fn process(&mut self, delta: f64) {
self.punch_cd_remaining -= delta; self.punch_cd_remaining -= delta;
self.telegrenade_cd_remaining -= delta;
if self.is_dead { if self.is_dead {
self.dead_for += delta; self.dead_for += delta;

View File

@ -4,7 +4,10 @@ use godot::{
}; };
use crate::{ use crate::{
net::util::NetVector3, net::{
network_manager::{NetworkManager, Package, PeerKind},
util::NetVector3,
},
player::{IPlayer, NetTransform}, player::{IPlayer, NetTransform},
}; };
@ -18,11 +21,28 @@ pub struct Telegrenade {
#[godot_api] #[godot_api]
impl IRigidBody3D for Telegrenade { impl IRigidBody3D for Telegrenade {
fn ready(&mut self) {} fn ready(&mut self) {
self.base()
.signals()
.body_entered()
.connect_other(self, |s, _| s.on_collision());
}
} }
impl Telegrenade { impl Telegrenade {
pub fn teleport(&mut self) {} pub fn on_collision(&mut self) {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
&& let Some(mut player) = self.player.clone()
{
let pos = self.base().get_global_position();
player.dyn_bind_mut().telegrenade_teleport(pos);
peer.broadcast_reliable(Package::TelegrenadeTeleport(
player.dyn_bind().get_player_id().unwrap_or(0),
pos.into(),
));
}
}
pub fn get_transform(&self) -> NetTransform { pub fn get_transform(&self) -> NetTransform {
NetTransform { NetTransform {