242 lines
6.9 KiB
Rust
242 lines
6.9 KiB
Rust
use std::collections::HashSet;
|
|
|
|
use godot::{
|
|
classes::{Area3D, IArea3D, StaticBody3D},
|
|
prelude::*,
|
|
};
|
|
|
|
use crate::{
|
|
game::Game,
|
|
map::Map,
|
|
net::network_manager::NetworkManager,
|
|
player::{
|
|
DamageSource, local_player::LocalPlayer, remote_player::RemotePlayer, 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>,
|
|
active_for: f64,
|
|
|
|
visual_active: Option<u16>,
|
|
visual_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 physics_process(&mut self, delta: f64) {
|
|
if self.active.is_some() {
|
|
self.active_for += delta;
|
|
} else {
|
|
self.active_for = 0.;
|
|
}
|
|
|
|
if self.visual_active.is_some() {
|
|
self.visual_active_for += delta;
|
|
} else {
|
|
self.visual_active_for = 0.;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[godot_api]
|
|
impl Triggerable {
|
|
/// Called whenever this triggerable's visuals should be immediately
|
|
/// activated.
|
|
#[func(virtual)]
|
|
pub fn on_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 on_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) {}
|
|
|
|
pub fn activate_visual(&mut self, player_id: u16, time_since_activation: f64) {
|
|
self.visual_active = Some(player_id);
|
|
self.visual_active_for = time_since_activation;
|
|
self.on_activate_visual(player_id, time_since_activation);
|
|
}
|
|
|
|
pub fn deactivate_visual(&mut self) {
|
|
self.visual_active = None;
|
|
self.visual_active_for = 0.;
|
|
self.on_deactivate_visual();
|
|
}
|
|
|
|
/// 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();
|
|
}
|
|
}
|
|
|
|
/// Returns true if this triggerable is active currently on the real running
|
|
/// version of the game.
|
|
#[func]
|
|
pub fn is_server_active(&self) -> bool {
|
|
self.active.is_some()
|
|
}
|
|
|
|
/// Returns the amount of time this triggerable has been active on the real
|
|
/// running version of the game.
|
|
#[func]
|
|
pub fn server_active_for(&self) -> f64 {
|
|
self.active_for
|
|
}
|
|
|
|
/// Returns true if this triggerable should be visibly active, whether it's
|
|
/// happening in a replay or the real game.
|
|
#[func]
|
|
pub fn is_active(&self) -> bool {
|
|
self.visual_active.is_some()
|
|
}
|
|
|
|
/// Returns the amount of time this triggerable has been visually active,
|
|
/// whether it's happening in replay or in the real game.
|
|
#[func]
|
|
pub fn active_for(&self) -> f64 {
|
|
self.visual_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=StaticBody3D, init)]
|
|
pub struct ShootableTrigger {
|
|
base: Base<StaticBody3D>,
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(GodotClass)]
|
|
#[class(base=Area3D, init)]
|
|
pub struct ProximityTrigger {
|
|
colliders_inside: HashSet<Gd<Node3D>>,
|
|
base: Base<Area3D>,
|
|
}
|
|
|
|
#[godot_api]
|
|
impl IArea3D for ProximityTrigger {
|
|
fn ready(&mut self) {
|
|
let base = self.base_mut().clone();
|
|
base.signals()
|
|
.body_entered()
|
|
.connect_other(self, Self::on_enter);
|
|
base.signals()
|
|
.body_exited()
|
|
.connect_other(self, Self::on_leave);
|
|
}
|
|
}
|
|
|
|
#[godot_api]
|
|
impl ProximityTrigger {
|
|
#[signal]
|
|
pub fn on_trigger(player: u16);
|
|
#[signal]
|
|
pub fn on_all_left();
|
|
|
|
fn on_enter(&mut self, node: Gd<Node3D>) {
|
|
if NetworkManager::singleton().bind().is_host() {
|
|
let was_empty = self.colliders_inside.is_empty();
|
|
|
|
let player_id = if let Ok(local_player) = node.clone().try_cast::<LocalPlayer>() {
|
|
self.colliders_inside.insert(node);
|
|
Some(local_player.bind().player_id.unwrap_or(0))
|
|
} else if let Ok(remote_player) = node.clone().try_cast::<RemotePlayer>() {
|
|
if !remote_player.bind().replay_mode {
|
|
self.colliders_inside.insert(node);
|
|
}
|
|
Some(remote_player.bind().player_id.unwrap_or(0))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if was_empty && let Some(player_id) = player_id {
|
|
self.signals().on_trigger().emit(player_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_leave(&mut self, node: Gd<Node3D>) {
|
|
self.colliders_inside.remove(&node);
|
|
if self.colliders_inside.is_empty() {
|
|
self.signals().on_all_left().emit();
|
|
}
|
|
}
|
|
}
|