361 lines
12 KiB
Rust
361 lines
12 KiB
Rust
use std::{collections::HashSet, f32::consts::PI, ops::Neg};
|
|
|
|
use godot::{
|
|
classes::{CharacterBody3D, ICharacterBody3D},
|
|
obj::WithBaseField,
|
|
prelude::*,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
game::{Game, opts::GameOption},
|
|
killbox::KillboxKind,
|
|
net::{
|
|
network_manager::NetworkManager,
|
|
util::{NetVector3, shotgun_ray},
|
|
},
|
|
pickup::PickupKind,
|
|
player::{
|
|
buffs::{Buff, Buffs},
|
|
shootable::Shootable,
|
|
telegrenade::Telegrenade,
|
|
weapon::{BallWeapon, Weapon, WeaponType},
|
|
},
|
|
};
|
|
|
|
pub mod ball;
|
|
pub mod buffs;
|
|
pub mod effects;
|
|
pub mod local_player;
|
|
pub mod menu_player;
|
|
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)]
|
|
pub struct NetTransform {
|
|
pub location: NetVector3,
|
|
pub rotation: NetVector3,
|
|
}
|
|
|
|
pub trait IPlayer {
|
|
fn get_player_id(&self) -> Option<u16>;
|
|
fn get_y_speed(&self) -> f32;
|
|
fn set_y_speed(&mut self, speed: f32);
|
|
fn get_movement_dir(&self) -> Vector3;
|
|
fn set_movement_dir(&mut self, dir: Vector3);
|
|
fn get_move_speed(&self) -> f32;
|
|
fn apply_transform(&mut self, transform: NetTransform);
|
|
fn apply_position(&mut self, pos: Vector3);
|
|
fn apply_rotation(&mut self, rot: Vector3);
|
|
fn apply_look_up(&mut self, rot: f32);
|
|
fn get_look_up(&self) -> f32;
|
|
fn get_transform(&self) -> NetTransform;
|
|
fn jump(&mut self);
|
|
fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool;
|
|
fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool;
|
|
fn take_damage(&mut self, source_player: DamageSource, damage: i32, kill: bool);
|
|
fn is_dead(&self) -> bool;
|
|
fn kill(&mut self, source_player: DamageSource);
|
|
fn dead_for(&self) -> f64;
|
|
fn swap_weapon(&mut self, weapon: WeaponType, drop_ball: bool);
|
|
fn get_weapon(&self) -> Option<DynGd<Node3D, dyn Weapon>>;
|
|
fn take_weapon(&mut self) -> Option<DynGd<Node3D, dyn Weapon>>;
|
|
fn drop_ball(&mut self, with_velocity: bool) -> bool;
|
|
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);
|
|
fn try_throw_telegrenade(&mut self, to: Vector3) -> bool;
|
|
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 set_bubble_color(&mut self, color: Color);
|
|
fn get_buffs(&self) -> &Buffs;
|
|
fn get_buffs_mut(&mut self) -> &mut Buffs;
|
|
fn new_buff(&mut self, buff: Buff);
|
|
fn set_buffs(&mut self, buffs: Buffs);
|
|
fn set_telefrag_frame(&mut self);
|
|
}
|
|
|
|
pub trait PlayerCommon {
|
|
fn player_update(&mut self, delta: f64);
|
|
fn try_jump(&mut self) -> bool;
|
|
fn drop_ball_common(&mut self, with_velocity: bool) -> bool;
|
|
fn get_look_dir(&self) -> Vector3;
|
|
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;
|
|
fn telegrenade_teleport_common(&mut self, to: Vector3) -> bool;
|
|
}
|
|
|
|
impl<T: IPlayer + ICharacterBody3D + WithBaseField> PlayerCommon for T {
|
|
fn player_update(&mut self, delta: f64) {
|
|
self.set_y_speed(self.get_y_speed() - 9.8 * delta as f32);
|
|
if self.base().is_on_floor() && self.get_y_speed() < 0. {
|
|
self.set_y_speed(0.);
|
|
}
|
|
|
|
let speed = Vector3::UP * self.get_y_speed()
|
|
+ self
|
|
.get_movement_dir()
|
|
.try_normalized()
|
|
.unwrap_or_default()
|
|
.rotated(Vector3::UP, self.base().get_rotation().y)
|
|
* self.get_move_speed();
|
|
|
|
if self.is_dead() {
|
|
self.base_mut().set_velocity(Vector3::ZERO);
|
|
} else {
|
|
self.base_mut().set_velocity(speed);
|
|
self.base_mut().move_and_slide();
|
|
}
|
|
|
|
// Check if hitting a ceiling; if so, stop upwards velocity (done here instead of
|
|
// at the start of the next frame to get the right-after-move_and_slide state
|
|
// rather than the simulated one).
|
|
if self.base().get_velocity().y < 0.001 && self.get_y_speed() > 0.001 {
|
|
self.set_y_speed(0.0);
|
|
}
|
|
}
|
|
|
|
fn try_jump(&mut self) -> bool {
|
|
if self.base().is_on_floor() {
|
|
self.set_y_speed(5.);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn drop_ball_common(&mut self, with_velocity: bool) -> bool {
|
|
if let Some(weapon) = self.get_weapon()
|
|
&& let Ok(_) = weapon.try_cast::<BallWeapon>()
|
|
{
|
|
self.run_deferred(move |s| {
|
|
if let Some(game) = Game::singleton()
|
|
&& let Some(player) = game.bind().find_player(s.get_player_id().unwrap_or(0))
|
|
{
|
|
s.swap_weapon(player.last_held_weapon, false);
|
|
}
|
|
if NetworkManager::singleton().bind().is_host() {
|
|
if let Some(mut game) = Game::singleton() {
|
|
if let Some(ball_origin) = &s.get_camera_origin() {
|
|
let velocity = if with_velocity {
|
|
s.get_look_dir() * 20.
|
|
} else {
|
|
Vector3::ZERO
|
|
};
|
|
|
|
game.bind_mut().spawn_ball(
|
|
Some(NetTransform {
|
|
location: ball_origin.get_global_position().into(),
|
|
rotation: ball_origin.get_global_rotation().into(),
|
|
}),
|
|
Some(velocity.into()),
|
|
s.get_player_id(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn get_look_dir(&self) -> Vector3 {
|
|
if let Some(camera_origin) = self.get_camera_origin() {
|
|
camera_origin.get_global_basis().col_c().neg()
|
|
} else {
|
|
Vector3::ZERO
|
|
}
|
|
}
|
|
|
|
fn can_take_damage(&self, source: &DamageSource) -> bool {
|
|
match source {
|
|
DamageSource::Player(source_player_id) => {
|
|
if !self.get_buffs().can_take_damage() {
|
|
return false;
|
|
}
|
|
|
|
if let Some(game) = Game::singleton() {
|
|
let (self_team, source_team) = if let Some(self_player) =
|
|
game.bind().find_player(self.get_player_id().unwrap_or(0))
|
|
&& let Some(source_player) = game.bind().find_player(*source_player_id)
|
|
{
|
|
(self_player.data.team, source_player.data.team)
|
|
} else {
|
|
(0, 0)
|
|
};
|
|
if self_team == source_team
|
|
&& !game.bind().opts.is_true(GameOption::FriendlyFire)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn try_shoot_common(&mut self, to: Vector3, deal_damage: bool) -> bool {
|
|
if self.is_dead() {
|
|
return false;
|
|
}
|
|
|
|
if !self.is_replay_mode() && self.drop_ball(true) {
|
|
return true;
|
|
}
|
|
|
|
let rid = self.base().get_rid();
|
|
if let Some(mut weapon) = self.get_weapon()
|
|
&& let Some(camera_origin) = &self.get_camera_origin()
|
|
{
|
|
weapon.dyn_bind_mut().shoot(
|
|
rid,
|
|
self.get_player_id().unwrap_or(0),
|
|
camera_origin.get_global_position(),
|
|
to,
|
|
camera_origin.get_basis().col_b(),
|
|
deal_damage,
|
|
)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn try_punch_common(&mut self, to: Vector3, deal_damage: bool) -> bool {
|
|
if self.is_dead() {
|
|
return false;
|
|
}
|
|
if !deal_damage {
|
|
return true;
|
|
}
|
|
|
|
if let Some(origin) = self.get_camera_origin() {
|
|
let rid = self.base().get_rid();
|
|
let up = origin.get_basis().col_b();
|
|
let results = shotgun_ray(
|
|
origin.get_global_position(),
|
|
to,
|
|
up,
|
|
&self.base().get_world_3d(),
|
|
&Array::from_iter(vec![rid]),
|
|
10,
|
|
PI / 2.,
|
|
2.,
|
|
);
|
|
|
|
let mut colliders = HashSet::new();
|
|
for result in results {
|
|
colliders.insert(result.collider);
|
|
}
|
|
|
|
for collider in colliders {
|
|
let collider_pos = if let Ok(node) = collider.clone().try_cast::<Node3D>() {
|
|
node.get_global_position()
|
|
} else {
|
|
Vector3::ZERO
|
|
};
|
|
|
|
let delta = collider_pos - origin.get_global_position();
|
|
|
|
if let Ok(mut shootable) = collider.clone().try_dynify::<dyn Shootable>() {
|
|
self.run_deferred(move |s| {
|
|
shootable.dyn_bind_mut().on_shoot(
|
|
DamageSource::Player(s.get_player_id().unwrap_or(0)),
|
|
100,
|
|
delta.normalized(),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn throw_telegrenade_common(&mut self, to: Vector3) -> bool {
|
|
if self.is_dead() {
|
|
return false;
|
|
}
|
|
|
|
if let Some(weapon) = self.get_weapon()
|
|
&& weapon.dyn_bind().get_type() == WeaponType::Ball
|
|
{
|
|
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(
|
|
s.get_player_id().unwrap_or(0),
|
|
origin.get_global_position().into(),
|
|
(to.normalized() * 50.).into(),
|
|
);
|
|
if let Some(telegrenade) = &mut telegrenade {
|
|
telegrenade.bind_mut().player = Some(s.as_gd());
|
|
s.set_telegrenade(Some(telegrenade.clone()));
|
|
}
|
|
}
|
|
});
|
|
|
|
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);
|
|
self.set_telefrag_frame();
|
|
true
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
|
pub enum DamageSource {
|
|
Player(u16),
|
|
Killbox(KillboxKind),
|
|
God,
|
|
}
|
|
|
|
impl DamageSource {
|
|
pub fn into_signal(&self) -> (DamageSourceSignal, u16, KillboxKind) {
|
|
match self {
|
|
DamageSource::Player(id) => (DamageSourceSignal::Player, *id, KillboxKind::default()),
|
|
DamageSource::Killbox(kind) => (DamageSourceSignal::Killbox, 0, *kind),
|
|
DamageSource::God => (DamageSourceSignal::God, 0, KillboxKind::default()),
|
|
}
|
|
}
|
|
|
|
pub fn from_signal(signal: DamageSourceSignal, player: u16, kind: KillboxKind) -> DamageSource {
|
|
match signal {
|
|
DamageSourceSignal::Player => DamageSource::Player(player),
|
|
DamageSourceSignal::Killbox => DamageSource::Killbox(kind),
|
|
DamageSourceSignal::God => DamageSource::God,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, GodotConvert, Clone)]
|
|
#[godot(via = u8)]
|
|
pub enum DamageSourceSignal {
|
|
Player,
|
|
Killbox,
|
|
God,
|
|
}
|