Add necessary boilerplate for triggers
This commit is contained in:
parent
c5d386b755
commit
56e4875a7b
@ -3,6 +3,10 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://dpqqe7hjktit8" path="res://maps/two_towers/two_towers_static.scn" id="2_l5vmt"]
|
[ext_resource type="PackedScene" uid="uid://dpqqe7hjktit8" path="res://maps/two_towers/two_towers_static.scn" id="2_l5vmt"]
|
||||||
[ext_resource type="Script" uid="uid://diepa0rcedh5f" path="res://maps/meta/map_rebuilder.gd" id="4_y3aqj"]
|
[ext_resource type="Script" uid="uid://diepa0rcedh5f" path="res://maps/meta/map_rebuilder.gd" id="4_y3aqj"]
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="BoxMesh_1cgct"]
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="BoxShape3D_1cgct"]
|
||||||
|
|
||||||
[node name="two_towers" type="Map" unique_id=590524284 node_paths=PackedStringArray("spawners", "ball_spawner")]
|
[node name="two_towers" type="Map" unique_id=590524284 node_paths=PackedStringArray("spawners", "ball_spawner")]
|
||||||
spawners = [NodePath("two_towers_static/func_godot_map/entity_11_info_spawn_point"), NodePath("two_towers_static/func_godot_map/entity_5_info_spawn_point")]
|
spawners = [NodePath("two_towers_static/func_godot_map/entity_11_info_spawn_point"), NodePath("two_towers_static/func_godot_map/entity_5_info_spawn_point")]
|
||||||
ball_spawner = NodePath("two_towers_static/func_godot_map/entity_1_info_spawn_point")
|
ball_spawner = NodePath("two_towers_static/func_godot_map/entity_1_info_spawn_point")
|
||||||
@ -11,3 +15,12 @@ ball_spawner = NodePath("two_towers_static/func_godot_map/entity_1_info_spawn_po
|
|||||||
script = ExtResource("4_y3aqj")
|
script = ExtResource("4_y3aqj")
|
||||||
|
|
||||||
[node name="two_towers_static" parent="." unique_id=86902040 instance=ExtResource("2_l5vmt")]
|
[node name="two_towers_static" parent="." unique_id=86902040 instance=ExtResource("2_l5vmt")]
|
||||||
|
|
||||||
|
[node name="shootable_trigger" type="ShootableTrigger" parent="." unique_id=306909670]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 42.184772, 10.981467, 7.8725615)
|
||||||
|
|
||||||
|
[node name="mesh_instance_3d" type="MeshInstance3D" parent="shootable_trigger" unique_id=174590790]
|
||||||
|
mesh = SubResource("BoxMesh_1cgct")
|
||||||
|
|
||||||
|
[node name="collision_shape_3d" type="CollisionShape3D" parent="shootable_trigger" unique_id=298869370]
|
||||||
|
shape = SubResource("BoxShape3D_1cgct")
|
||||||
|
|||||||
@ -1350,6 +1350,50 @@ impl Game {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_trigger_activate(&mut self, id: u8, player_id: u16) {
|
||||||
|
self.run_deferred(move |s| {
|
||||||
|
if let Some(map) = &mut s.current_map
|
||||||
|
&& let Some(triggerable) = map.bind_mut().triggerables.get_mut(&id)
|
||||||
|
{
|
||||||
|
triggerable.bind_mut().activate_visual(player_id, 0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(recorder) = &mut s.replay_recorder {
|
||||||
|
recorder
|
||||||
|
.bind_mut()
|
||||||
|
.record_event(ReplayEvent::TriggerActivate(id, player_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
|
||||||
|
&& let PeerKind::Server(peer, _) = peer
|
||||||
|
{
|
||||||
|
peer.broadcast_reliable(Package::TriggerActivate(id, player_id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_trigger_deactivate(&mut self, id: u8) {
|
||||||
|
self.run_deferred(move |s| {
|
||||||
|
if let Some(map) = &mut s.current_map
|
||||||
|
&& let Some(triggerable) = map.bind_mut().triggerables.get_mut(&id)
|
||||||
|
{
|
||||||
|
triggerable.bind_mut().deactivate_visual();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(recorder) = &mut s.replay_recorder {
|
||||||
|
recorder
|
||||||
|
.bind_mut()
|
||||||
|
.record_event(ReplayEvent::TriggerDeactivate(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
|
||||||
|
&& let PeerKind::Server(peer, _) = peer
|
||||||
|
{
|
||||||
|
peer.broadcast_reliable(Package::TriggerDeactivate(id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn_telegrenade(
|
pub fn spawn_telegrenade(
|
||||||
&mut self,
|
&mut self,
|
||||||
player_id: u16,
|
player_id: u16,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use godot::{classes::CharacterBody3D, prelude::*};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
game::{Game, GameManager},
|
game::{Game, GameManager},
|
||||||
map::pickup::Pickup,
|
map::{pickup::Pickup, triggers::Triggerable},
|
||||||
net::util::NetVector3,
|
net::util::NetVector3,
|
||||||
player::{
|
player::{
|
||||||
IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, remote_player::RemotePlayer,
|
IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer, remote_player::RemotePlayer,
|
||||||
@ -18,6 +18,7 @@ pub mod killbox;
|
|||||||
pub mod map_resource;
|
pub mod map_resource;
|
||||||
pub mod map_static;
|
pub mod map_static;
|
||||||
pub mod pickup;
|
pub mod pickup;
|
||||||
|
pub mod triggers;
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
#[class(base=Node3D, init)]
|
#[class(base=Node3D, init)]
|
||||||
@ -30,6 +31,9 @@ pub struct Map {
|
|||||||
pickup_counter: u8,
|
pickup_counter: u8,
|
||||||
pub pickups: HashMap<u8, DynGd<Node3D, dyn Pickup>>,
|
pub pickups: HashMap<u8, DynGd<Node3D, dyn Pickup>>,
|
||||||
|
|
||||||
|
triggerable_counter: u8,
|
||||||
|
pub triggerables: HashMap<u8, Gd<Triggerable>>,
|
||||||
|
|
||||||
base: Base<Node3D>,
|
base: Base<Node3D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +213,13 @@ impl Map {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_triggerable(&mut self, triggerable: Gd<Triggerable>) -> u8 {
|
||||||
|
let id = self.triggerable_counter;
|
||||||
|
self.triggerable_counter += 1;
|
||||||
|
self.triggerables.insert(id, triggerable);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn_telegrenade(
|
pub fn spawn_telegrenade(
|
||||||
&mut self,
|
&mut self,
|
||||||
location: NetVector3,
|
location: NetVector3,
|
||||||
|
|||||||
141
rust/src/map/triggers.rs
Normal file
141
rust/src/map/triggers.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
use godot::{
|
||||||
|
classes::{Area3D, IArea3D},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
game::Game,
|
||||||
|
map::Map,
|
||||||
|
net::network_manager::NetworkManager,
|
||||||
|
player::{DamageSource, shootable::Shootable},
|
||||||
|
replay::StandaloneReplayManager,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(GodotClass)]
|
||||||
|
#[class(base=Node3D, init)]
|
||||||
|
pub struct Triggerable {
|
||||||
|
triggerable_id: Option<u8>,
|
||||||
|
/// Contains the activator player's ID if active.
|
||||||
|
active: Option<u16>,
|
||||||
|
pub active_for: f64,
|
||||||
|
|
||||||
|
base: Base<Node3D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[godot_api]
|
||||||
|
impl INode3D for Triggerable {
|
||||||
|
fn ready(&mut self) {
|
||||||
|
if let Some(game) = &Game::singleton() {
|
||||||
|
game.signals()
|
||||||
|
.on_map_loaded()
|
||||||
|
.connect_other(self, Self::register_triggerable);
|
||||||
|
} else {
|
||||||
|
StandaloneReplayManager::singleton()
|
||||||
|
.signals()
|
||||||
|
.on_map_loaded()
|
||||||
|
.connect_other(self, Self::register_triggerable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(&mut self, delta: f64) {
|
||||||
|
if self.active.is_some() {
|
||||||
|
self.active_for += delta;
|
||||||
|
} else {
|
||||||
|
self.active_for = 0.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[godot_api]
|
||||||
|
impl Triggerable {
|
||||||
|
/// Called whenever this triggerable's visuals should be immediately
|
||||||
|
/// activated.
|
||||||
|
#[func(virtual)]
|
||||||
|
pub fn activate_visual(&mut self, player_id: u16, time_since_activation: f64) {}
|
||||||
|
/// Called whenever this triggerable's visuals should be immediately
|
||||||
|
/// deactivated.
|
||||||
|
#[func(virtual)]
|
||||||
|
pub fn deactivate_visual(&mut self) {}
|
||||||
|
|
||||||
|
/// Called whenever this triggerable is activated on the server-side.
|
||||||
|
/// Returns a bool indicating if this triggerable should be immediately
|
||||||
|
/// deactivated. Defaults to true.
|
||||||
|
#[func(virtual)]
|
||||||
|
pub fn on_activate(&mut self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
/// Called whenever this triggerable is deactivated on the server-side.
|
||||||
|
#[func(virtual)]
|
||||||
|
pub fn on_deactivate(&mut self) {}
|
||||||
|
|
||||||
|
/// Activates this trigger. Active triggerables can not be re-activated
|
||||||
|
/// without being deactivated first. Only executes on the server-side.
|
||||||
|
#[func]
|
||||||
|
pub fn activate(&mut self, player_id: u16) {
|
||||||
|
if NetworkManager::singleton().bind().is_host() && !self.active.is_some() {
|
||||||
|
self.active = Some(player_id);
|
||||||
|
if let Some(game) = &mut Game::singleton() {
|
||||||
|
game.bind_mut()
|
||||||
|
.on_trigger_activate(self.triggerable_id.unwrap_or(0), player_id);
|
||||||
|
}
|
||||||
|
if self.on_activate() {
|
||||||
|
self.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deactivates this trigger. Only executes on the server-side.
|
||||||
|
#[func]
|
||||||
|
pub fn deactivate(&mut self) {
|
||||||
|
if NetworkManager::singleton().bind().is_host() {
|
||||||
|
self.active = None;
|
||||||
|
if let Some(game) = &mut Game::singleton() {
|
||||||
|
game.bind_mut()
|
||||||
|
.on_trigger_deactivate(self.triggerable_id.unwrap_or(0));
|
||||||
|
}
|
||||||
|
self.on_deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[func]
|
||||||
|
pub fn is_active(&self) -> bool {
|
||||||
|
self.active.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[func]
|
||||||
|
pub fn active_for(&self) -> f64 {
|
||||||
|
self.active_for
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_active(&self) -> Option<u16> {
|
||||||
|
self.active
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_triggerable(&mut self, mut map: Gd<Map>) {
|
||||||
|
self.run_deferred(move |s| {
|
||||||
|
let id = map.bind_mut().register_triggerable(s.to_gd());
|
||||||
|
s.triggerable_id = Some(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GodotClass)]
|
||||||
|
#[class(base=Area3D, init)]
|
||||||
|
pub struct ShootableTrigger {
|
||||||
|
base: Base<Area3D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[godot_api]
|
||||||
|
impl ShootableTrigger {
|
||||||
|
#[signal]
|
||||||
|
pub fn on_shot(player: u16, damage: i32, dir: Vector3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[godot_dyn]
|
||||||
|
impl Shootable for ShootableTrigger {
|
||||||
|
fn on_shoot(&mut self, source: DamageSource, damage: i32, dir: Vector3) {
|
||||||
|
if let DamageSource::Player(id) = source {
|
||||||
|
self.signals().on_shot().emit(id, damage, dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -362,6 +362,8 @@ pub enum Package {
|
|||||||
Goal(u16, Vec<u8>),
|
Goal(u16, Vec<u8>),
|
||||||
Announcement(u16, Announcement),
|
Announcement(u16, Announcement),
|
||||||
Pickup(u8, u16),
|
Pickup(u8, u16),
|
||||||
|
TriggerActivate(u8, u16),
|
||||||
|
TriggerDeactivate(u8),
|
||||||
ThrowTelegrenade(u16, NetVector3),
|
ThrowTelegrenade(u16, NetVector3),
|
||||||
TelegrenadeTeleport(u16, NetVector3),
|
TelegrenadeTeleport(u16, NetVector3),
|
||||||
TelegrenadeActivate,
|
TelegrenadeActivate,
|
||||||
|
|||||||
@ -39,6 +39,7 @@ pub fn cast_ray(
|
|||||||
ray.set_hit_back_faces(false);
|
ray.set_hit_back_faces(false);
|
||||||
ray.set_hit_from_inside(false);
|
ray.set_hit_from_inside(false);
|
||||||
ray.set_collide_with_bodies(true);
|
ray.set_collide_with_bodies(true);
|
||||||
|
ray.set_collide_with_areas(true);
|
||||||
ray.set_exclude(&exclude);
|
ray.set_exclude(&exclude);
|
||||||
if let Some(world) = world
|
if let Some(world) = world
|
||||||
&& let Some(mut space) = world.get_direct_space_state()
|
&& let Some(mut space) = world.get_direct_space_state()
|
||||||
|
|||||||
@ -368,7 +368,7 @@ impl DamageSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, GodotConvert, Clone)]
|
#[derive(Debug, GodotConvert, Clone)]
|
||||||
#[godot(via = u8)]
|
#[godot(via = GString)]
|
||||||
pub enum DamageSourceSignal {
|
pub enum DamageSourceSignal {
|
||||||
Player,
|
Player,
|
||||||
Killbox,
|
Killbox,
|
||||||
|
|||||||
@ -265,6 +265,8 @@ pub struct ReplayPlayback {
|
|||||||
pub spectated_player_id: u16,
|
pub spectated_player_id: u16,
|
||||||
|
|
||||||
pub fast_forwarding: bool,
|
pub fast_forwarding: bool,
|
||||||
|
/// True for one frame after fast-forwarding is done
|
||||||
|
pub was_fast_forwarding: bool,
|
||||||
|
|
||||||
pub finished: bool,
|
pub finished: bool,
|
||||||
|
|
||||||
@ -353,6 +355,20 @@ impl INode for ReplayPlayback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.was_fast_forwarding {
|
||||||
|
for (id, triggerable) in &mut map.bind_mut().triggerables {
|
||||||
|
if let Some(state) = frame.triggerables.get(id) {
|
||||||
|
if let Some(player_id) = state.active {
|
||||||
|
triggerable
|
||||||
|
.bind_mut()
|
||||||
|
.activate_visual(player_id, state.active_for);
|
||||||
|
} else {
|
||||||
|
triggerable.bind_mut().deactivate_visual();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for event in &frame.events {
|
for event in &frame.events {
|
||||||
@ -411,6 +427,20 @@ impl INode for ReplayPlayback {
|
|||||||
pickup.dyn_bind_mut().on_pickup();
|
pickup.dyn_bind_mut().on_pickup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ReplayEvent::TriggerActivate(id, player_id) => {
|
||||||
|
if let Some(map) = &mut self.current_map
|
||||||
|
&& let Some(triggerable) = map.bind_mut().triggerables.get_mut(id)
|
||||||
|
{
|
||||||
|
triggerable.bind_mut().activate_visual(*player_id, 0.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReplayEvent::TriggerDeactivate(id) => {
|
||||||
|
if let Some(map) = &mut self.current_map
|
||||||
|
&& let Some(triggerable) = map.bind_mut().triggerables.get_mut(id)
|
||||||
|
{
|
||||||
|
triggerable.bind_mut().deactivate_visual();
|
||||||
|
}
|
||||||
|
}
|
||||||
ReplayEvent::TelegrenadeSpawn(player_id, location, velocity) => {
|
ReplayEvent::TelegrenadeSpawn(player_id, location, velocity) => {
|
||||||
if let Some(prev_grenade) = self.telegrenades.get_mut(player_id) {
|
if let Some(prev_grenade) = self.telegrenades.get_mut(player_id) {
|
||||||
prev_grenade.queue_free();
|
prev_grenade.queue_free();
|
||||||
@ -463,6 +493,7 @@ impl INode for ReplayPlayback {
|
|||||||
self.signals().on_data_changed().emit();
|
self.signals().on_data_changed().emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.was_fast_forwarding = self.fast_forwarding;
|
||||||
self.fast_forwarding = false;
|
self.fast_forwarding = false;
|
||||||
|
|
||||||
if !self.finished && self.current_frame_idx >= self.replay.frames.len() {
|
if !self.finished && self.current_frame_idx >= self.replay.frames.len() {
|
||||||
@ -660,6 +691,8 @@ impl ReplayRecorder {
|
|||||||
|
|
||||||
let mut pickups_visible = HashMap::new();
|
let mut pickups_visible = HashMap::new();
|
||||||
|
|
||||||
|
let mut triggerables = HashMap::new();
|
||||||
|
|
||||||
if let Some(game) = Game::singleton() {
|
if let Some(game) = Game::singleton() {
|
||||||
for player in &game.bind().players {
|
for player in &game.bind().players {
|
||||||
if let Some(state) = player.get_state(player.clone()) {
|
if let Some(state) = player.get_state(player.clone()) {
|
||||||
@ -682,6 +715,15 @@ impl ReplayRecorder {
|
|||||||
for (id, pickup) in &map.bind().pickups {
|
for (id, pickup) in &map.bind().pickups {
|
||||||
pickups_visible.insert(*id, pickup.dyn_bind().is_active());
|
pickups_visible.insert(*id, pickup.dyn_bind().is_active());
|
||||||
}
|
}
|
||||||
|
for (id, triggerable) in &map.bind().triggerables {
|
||||||
|
triggerables.insert(
|
||||||
|
*id,
|
||||||
|
ReplayTriggerableState {
|
||||||
|
active: triggerable.bind().get_active(),
|
||||||
|
active_for: triggerable.bind().active_for(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
goals = game.bind().goals.clone();
|
goals = game.bind().goals.clone();
|
||||||
@ -693,6 +735,7 @@ impl ReplayRecorder {
|
|||||||
time: self.time_elapsed,
|
time: self.time_elapsed,
|
||||||
goals,
|
goals,
|
||||||
pickups_visible,
|
pickups_visible,
|
||||||
|
triggerables,
|
||||||
player_data,
|
player_data,
|
||||||
ball_data,
|
ball_data,
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
@ -807,6 +850,7 @@ pub struct ReplayFrame {
|
|||||||
player_data: HashMap<u16, ReplayPlayerState>,
|
player_data: HashMap<u16, ReplayPlayerState>,
|
||||||
ball_data: ReplayBall,
|
ball_data: ReplayBall,
|
||||||
pickups_visible: HashMap<u8, bool>,
|
pickups_visible: HashMap<u8, bool>,
|
||||||
|
triggerables: HashMap<u8, ReplayTriggerableState>,
|
||||||
events: Vec<ReplayEvent>,
|
events: Vec<ReplayEvent>,
|
||||||
goals: HashMap<u8, u16>,
|
goals: HashMap<u8, u16>,
|
||||||
}
|
}
|
||||||
@ -820,6 +864,8 @@ pub enum ReplayEvent {
|
|||||||
Punch(u16, NetVector3),
|
Punch(u16, NetVector3),
|
||||||
TakeDamage(u16, DamageSource, i32),
|
TakeDamage(u16, DamageSource, i32),
|
||||||
Pickup(PickupKind, u8, u16),
|
Pickup(PickupKind, u8, u16),
|
||||||
|
TriggerActivate(u8, u16),
|
||||||
|
TriggerDeactivate(u8),
|
||||||
TelegrenadeSpawn(u16, NetVector3, NetVector3),
|
TelegrenadeSpawn(u16, NetVector3, NetVector3),
|
||||||
TelegrenadeDespawn(u16),
|
TelegrenadeDespawn(u16),
|
||||||
}
|
}
|
||||||
@ -873,3 +919,9 @@ pub struct ReplayBallData {
|
|||||||
transform: NetTransform,
|
transform: NetTransform,
|
||||||
velocity: NetVector3,
|
velocity: NetVector3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ReplayTriggerableState {
|
||||||
|
active: Option<u16>,
|
||||||
|
active_for: f64,
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user