Register pickups

This commit is contained in:
Sofia 2026-07-27 19:07:47 +03:00
parent e64bcc0427
commit 9187e6abff
16 changed files with 260 additions and 22 deletions

Binary file not shown.

View File

@ -0,0 +1,46 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dgav57iyh6nxg"
path="res://.godot/imported/HealthPickup.fbx-608a017b8743e952888462d93dec636c.scn"
[deps]
source_file="res://assets/HealthPickup.fbx"
dest_files=["res://.godot/imported/HealthPickup.fbx-608a017b8743e952888462d93dec636c.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View File

@ -1,11 +1,7 @@
[gd_scene format=3 uid="uid://7iooamjot1tg"]
[ext_resource type="PackedScene" uid="uid://ct70bpbca8one" path="res://scenes/player/local_player.tscn" id="1_yjvui"]
[ext_resource type="PackedScene" uid="uid://kfxc0migw80o" path="res://scenes/player/remote_player.tscn" id="2_kcncr"]
[ext_resource type="PackedScene" uid="uid://hmh2lrrlo2wy" path="res://scenes/player/replay_player.tscn" id="3_8af6u"]
[ext_resource type="PackedScene" uid="uid://dfnbc8sbufa4j" path="res://scenes/misc/ball.tscn" id="3_e0nj3"]
[ext_resource type="PackedScene" uid="uid://dov2eh5u4ypnx" path="res://scenes/misc/health_pickup.tscn" id="2_yffox"]
[ext_resource type="PackedScene" uid="uid://c1r8nk58cci3a" path="res://scenes/misc/goal.tscn" id="4_w7til"]
[ext_resource type="PackedScene" uid="uid://dc17w5n6y1an" path="res://scenes/misc/replay_ball.tscn" id="5_oqkce"]
[sub_resource type="ImageTexture" id="ImageTexture_3vyb7"]
@ -15,32 +11,20 @@ albedo_texture = SubResource("ImageTexture_3vyb7")
[sub_resource type="PlaneMesh" id="PlaneMesh_1cgct"]
size = Vector2(100, 100)
[sub_resource type="BoxShape3D" id="BoxShape3D_1cgct"]
size = Vector3(100, 0.01, 100)
[sub_resource type="BoxShape3D" id="BoxShape3D_3g557"]
size = Vector3(1000, 1, 1000)
[sub_resource type="BoxShape3D" id="BoxShape3D_1cgct"]
size = Vector3(100, 0.01, 100)
[node name="map" type="Map" unique_id=914577955 node_paths=PackedStringArray("spawners", "ball_spawner")]
spawners = [NodePath("team1_spawner"), NodePath("team2_spawner")]
ball_spawner = NodePath("ball_spawner")
local_player = ExtResource("1_yjvui")
remote_player = ExtResource("2_kcncr")
replay_player = ExtResource("3_8af6u")
ball = ExtResource("3_e0nj3")
replay_ball = ExtResource("5_oqkce")
[node name="terrain" type="MeshInstance3D" parent="." unique_id=1474127252]
material_override = SubResource("StandardMaterial3D_u8vuu")
mesh = SubResource("PlaneMesh_1cgct")
[node name="static_body_3d" type="StaticBody3D" parent="terrain" unique_id=203785193]
collision_layer = 7
collision_mask = 7
[node name="collision_shape_3d" type="CollisionShape3D" parent="terrain/static_body_3d" unique_id=1466526153]
shape = SubResource("BoxShape3D_1cgct")
[node name="directional_light_3d" type="DirectionalLight3D" parent="." unique_id=793524416]
transform = Transform3D(1, 0, 0, 0, 0.7274598, 0.6861503, 0, -0.6861503, 0.7274598, -5.1267624, 3.3997262, 3.715582)
@ -67,3 +51,13 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -12.468555, 0.0050001144, 4.4
[node name="goal2" parent="." unique_id=1997661586 instance=ExtResource("4_w7til")]
team = 1
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.417626, 0.0050001144, -3.8839006)
[node name="static_body_3d" type="StaticBody3D" parent="." unique_id=203785193]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.526536, 0, 10.695821)
collision_layer = 7
collision_mask = 7
[node name="collision_shape_3d" type="CollisionShape3D" parent="static_body_3d" unique_id=1466526153]
shape = SubResource("BoxShape3D_1cgct")
[node name="health_pickup" parent="static_body_3d/collision_shape_3d" unique_id=1860192333 instance=ExtResource("2_yffox")]

View File

@ -0,0 +1,14 @@
[gd_scene format=3 uid="uid://d0hq2ctndreej"]
[ext_resource type="PackedScene" uid="uid://dgav57iyh6nxg" path="res://assets/HealthPickup.fbx" id="1_r21p4"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tue51"]
albedo_color = Color(0, 1, 0, 1)
emission_enabled = true
emission = Color(0, 1, 0, 1)
emission_energy_multiplier = 5.0
[node name="HealthPickup" unique_id=663926357 instance=ExtResource("1_r21p4")]
[node name="Cube" parent="." index="0" unique_id=659090742]
surface_material_override/0 = SubResource("StandardMaterial3D_tue51")

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,19 @@
[gd_scene format=3 uid="uid://dov2eh5u4ypnx"]
[ext_resource type="PackedScene" uid="uid://d0hq2ctndreej" path="res://mesh/health_pickup.tscn" id="1_1mios"]
[sub_resource type="SphereShape3D" id="SphereShape3D_1cgct"]
radius = 0.8577998
[node name="health_pickup" type="HealthPickup" unique_id=1860192333 node_paths=PackedStringArray("pickup_area")]
pickup_area = NodePath("area_3d")
[node name="area_3d" type="Area3D" parent="." unique_id=168425054]
transform = Transform3D(0.5306476, 0, -0.8475926, 0, 1, 0, 0.8475926, 0, 0.5306476, 0, 1.2881019, 0)
collision_layer = 0
collision_mask = 3
[node name="HealthPickup" parent="area_3d" unique_id=663926357 instance=ExtResource("1_1mios")]
[node name="collision_shape_3d" type="CollisionShape3D" parent="area_3d" unique_id=1572818215]
shape = SubResource("SphereShape3D_1cgct")

View File

@ -19,6 +19,7 @@ use crate::{
},
util::NetVector3,
},
pickup::{Pickup, PickupCommon, PickupKind},
player::{
DamageSource, IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer,
remote_player::RemotePlayer, weapon::WeaponType,
@ -366,6 +367,8 @@ impl Game {
pub fn on_game_finished();
#[signal]
pub fn on_player_killed(player: u16);
#[signal]
pub fn on_map_loaded(map: Gd<Map>);
pub fn singleton() -> Option<Gd<Game>> {
if GameManager::singleton().get_children().len() > 0 {
@ -693,7 +696,7 @@ impl Game {
}
pub fn on_map_loaded(&mut self, map: Gd<Map>) {
self.current_map = Some(map);
self.current_map = Some(map.clone());
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Client(peer, to) = peer {
peer.send_reliable(to, Package::MapLoaded);
@ -706,6 +709,7 @@ impl Game {
});
}
}
self.signals().on_map_loaded().emit(&map);
}
pub fn spawn_ball(

View File

@ -11,6 +11,7 @@ pub mod killbox;
pub mod map;
pub mod map_resource;
pub mod net;
pub mod pickup;
pub mod player;
pub mod popup_queue;
pub mod replay;

View File

@ -1,8 +1,11 @@
use std::collections::HashMap;
use godot::{classes::CharacterBody3D, prelude::*};
use crate::{
game_manager::{Game, GameManager},
net::util::NetVector3,
pickup::Pickup,
player::{
IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, remote_player::RemotePlayer,
},
@ -17,6 +20,9 @@ pub struct Map {
#[export]
pub ball_spawner: Option<Gd<Node3D>>,
pickup_counter: u8,
pub pickups: HashMap<u8, DynGd<Node3D, dyn Pickup>>,
base: Base<Node3D>,
}
@ -188,4 +194,13 @@ impl Map {
None
}
}
pub fn register_pickup(&mut self, pickup: DynGd<Node3D, dyn Pickup>) -> u8 {
godot_print!("Registered {:?}", pickup);
let id = self.pickup_counter;
self.pickup_counter += 1;
self.pickups.insert(id, pickup);
id
}
}

View File

@ -13,6 +13,7 @@ use crate::{
game_manager::{Game, GameManager, GameOption, GameOptionValue, NetPlayer},
game_settings::GameSettingsManager,
net::{net_stats::Stats, util::NetVector3},
pickup::PickupKind,
player::{DamageSource, NetTransform, weapon::WeaponType},
popup_queue::{Popup, PopupQueue},
ui::cli::{CliColor, CommandLinePanel},

101
rust/src/pickup.rs Normal file
View File

@ -0,0 +1,101 @@
use godot::{classes::Area3D, prelude::*};
use serde::{Deserialize, Serialize};
use crate::{
game_manager::Game,
map::Map,
net::network_manager::NetworkManager,
player::{IPlayer, local_player::LocalPlayer},
replay::StandaloneReplayManager,
};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PickupKind {
Health,
}
#[derive(GodotClass)]
#[class(base=Node3D, init, tool)]
pub struct HealthPickup {
#[export]
pub pickup_area: Option<Gd<Area3D>>,
time_elapsed: f64,
pickup_id: Option<u8>,
base: Base<Node3D>,
}
#[godot_api]
impl INode3D for HealthPickup {
fn ready(&mut self) {
if let Some(area) = self.pickup_area.clone() {
area.signals()
.body_entered()
.connect_other(self, Self::on_collision);
}
if let Some(game) = &Game::singleton() {
game.signals()
.on_map_loaded()
.connect_other(self, Self::register_pickup);
} else {
StandaloneReplayManager::singleton()
.signals()
.on_map_loaded()
.connect_other(self, Self::register_pickup);
}
}
fn process(&mut self, delta: f64) {
self.time_elapsed += delta;
self.update_common(self.time_elapsed);
}
}
impl HealthPickup {
pub fn register_pickup(&mut self, mut map: Gd<Map>) {
self.run_deferred(move |s| {
let id = map
.bind_mut()
.register_pickup(s.to_gd().into_dyn().upcast());
s.pickup_id = Some(id);
});
}
}
#[godot_dyn]
impl Pickup for HealthPickup {
fn area(&mut self) -> &mut Option<Gd<Area3D>> {
&mut self.pickup_area
}
fn kind(&self) -> PickupKind {
PickupKind::Health
}
}
pub trait Pickup {
fn area(&mut self) -> &mut Option<Gd<Area3D>>;
fn kind(&self) -> PickupKind;
}
pub trait PickupCommon {
fn update_common(&mut self, time_elapsed: f64);
fn on_collision(&mut self, collider: Gd<Node3D>);
}
impl<T: Pickup> PickupCommon for T {
fn update_common(&mut self, time_elapsed: f64) {
if let Some(area) = self.area() {
area.set_position(Vector3::UP * 1.5 + Vector3::UP * (time_elapsed.sin() / 4.) as f32);
area.set_rotation(Vector3::UP * time_elapsed as f32);
}
}
fn on_collision(&mut self, collider: Gd<Node3D>) {
if !NetworkManager::singleton().bind().is_host() {
return;
}
}
}

View File

@ -21,6 +21,7 @@ use crate::{
},
util::cast_ray,
},
pickup::PickupKind,
player::{
DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon,
effects::PlayerEffects,
@ -298,6 +299,21 @@ impl IPlayer for LocalPlayer {
fn get_health(&self) -> i32 {
self.health
}
fn heal(&mut self, amount: i32) {
self.run_deferred(move |s| {
let delta = (s.health + amount).min(200) - s.health;
s.run_deferred(move |s| s.signals().on_heal().emit(delta));
s.health += delta;
});
}
fn on_pickup(&mut self, kind: PickupKind) {
godot_print!("picked up");
match kind {
PickupKind::Health => self.heal(100),
}
}
}
#[godot_api]
@ -592,6 +608,8 @@ impl LocalPlayer {
killbox_kind: KillboxKind,
damage: i32,
);
#[signal]
pub fn on_heal(amount: i32);
fn set_lock(&mut self, locked: bool) {
self.locked = locked;

View File

@ -10,6 +10,7 @@ use crate::{
network_manager::NetworkManager,
util::{NetVector3, shotgun_ray},
},
pickup::PickupKind,
player::{
shootable::Shootable,
weapon::{BallWeapon, Weapon, WeaponType},
@ -58,6 +59,8 @@ pub trait IPlayer {
fn get_camera_origin(&self) -> Option<Gd<Node3D>>;
fn is_replay_mode(&self) -> bool;
fn get_health(&self) -> i32;
fn heal(&mut self, amount: i32);
fn on_pickup(&mut self, kind: PickupKind);
}
pub trait PlayerCommon {

View File

@ -10,6 +10,7 @@ use godot::{
use crate::{
game_manager::Game,
killbox::KillboxKind,
pickup::PickupKind,
player::{
DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon,
effects::PlayerEffects,
@ -234,6 +235,20 @@ impl IPlayer for RemotePlayer {
fn get_health(&self) -> i32 {
self.health
}
fn heal(&mut self, amount: i32) {
self.run_deferred(move |s| {
let delta = (s.health + amount).min(200) - s.health;
s.run_deferred(move |s| s.signals().on_heal().emit(delta));
s.health += delta;
});
}
fn on_pickup(&mut self, kind: PickupKind) {
match kind {
PickupKind::Health => self.heal(100),
}
}
}
#[godot_api]
@ -292,6 +307,8 @@ impl RemotePlayer {
killbox_kind: KillboxKind,
damage: i32,
);
#[signal]
pub fn on_heal(amount: i32);
}
#[godot_dyn]

View File

@ -43,7 +43,11 @@ pub struct StandaloneReplayManager {
base: Base<Node>,
}
#[godot_api]
impl StandaloneReplayManager {
#[signal]
pub fn on_map_loaded(map: Gd<Map>);
pub fn singleton() -> Gd<StandaloneReplayManager> {
get_autoload_by_name::<StandaloneReplayManager>(STANDALONE_REPLAY_GLOBAL)
}
@ -85,7 +89,7 @@ impl StandaloneReplayManager {
.connect_other(self, Self::on_done);
playback.bind_mut().replay = replay;
playback.bind_mut().current_map = Some(map);
playback.bind_mut().current_map = Some(map.clone());
playback.bind_mut().current_time = 0.;
playback.bind_mut().fast_forwarding = false;
playback.bind_mut().spectated_player_id = 0;
@ -109,6 +113,7 @@ impl StandaloneReplayManager {
}
}
}
self.signals().on_map_loaded().emit(&map);
}
pub fn on_done(&mut self) {