Add initial support for telegrenades

This commit is contained in:
Sofia 2026-07-27 22:17:05 +03:00
parent d32b3cade0
commit 81402f2cf4
11 changed files with 214 additions and 3 deletions

View File

@ -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]

View File

@ -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")])

View File

@ -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")

View File

@ -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<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub telegrenade: Option<Gd<PackedScene>>,
pub game: Option<Gd<Game>>,
base: Base<Node>,
@ -1251,6 +1254,26 @@ impl Game {
}
});
}
pub fn spawn_telegrenade(
&mut self,
location: NetVector3,
velocity: NetVector3,
) -> Option<Gd<Telegrenade>> {
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)]

View File

@ -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<Gd<Telegrenade>> {
if let Some(prefab) = &GameManager::singleton().bind().telegrenade {
let mut telegrenade = prefab.instantiate_as::<Telegrenade>();
self.base_mut().add_child(&telegrenade);
telegrenade.set_global_position(location.into());
telegrenade.set_linear_velocity(velocity.into());
Some(telegrenade)
} else {
None
}
}
}

View File

@ -315,6 +315,7 @@ pub enum Package {
Goal(Vec<u8>),
Announcement(u16, Announcement),
Pickup(u8, u16),
ThrowTelegrenade(u16, NetVector3),
// End-of-Game packages
FinishGame,

View File

@ -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);
}
}
}
_ => {}
},
},

View File

@ -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<Gd<Telegrenade>>,
base: Base<CharacterBody3D>,
}
@ -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<Gd<Telegrenade>>) {
self.telegrenade = grenade;
}
fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer> {
self.to_gd().into_dyn().upcast()
}
}
#[godot_api]
@ -463,6 +478,28 @@ impl ICharacterBody3D for LocalPlayer {
fn input(&mut self, event: Gd<InputEvent>) {
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 {

View File

@ -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<Gd<Telegrenade>>);
fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer>;
}
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<T: IPlayer + ICharacterBody3D + WithBaseField> PlayerCommon for T {
@ -256,6 +266,29 @@ impl<T: IPlayer + ICharacterBody3D + WithBaseField> 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)]

View File

@ -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<Gd<Telegrenade>>,
base: Base<CharacterBody3D>,
}
@ -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<Gd<Telegrenade>>) {
self.telegrenade = grenade;
}
fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer> {
self.to_gd().into_dyn().upcast()
}
}
#[godot_api]

View File

@ -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<DynGd<CharacterBody3D, dyn IPlayer>>,
base: Base<RigidBody3D>,
}
#[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()
}
}