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; 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>; fn take_weapon(&mut self) -> Option>; fn drop_ball(&mut self, with_velocity: bool) -> bool; fn get_camera_origin(&self) -> Option>; 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>); fn get_telegrenade(&self) -> Option>; fn telegrenade_teleport(&mut self, to: Vector3) -> bool; fn as_gd(&self) -> DynGd; 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 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::() { 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::() { node.get_global_position() } else { Vector3::ZERO }; let delta = collider_pos - origin.get_global_position(); if let Ok(mut shootable) = collider.clone().try_dynify::() { 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, }