use std::{f32::consts::PI, ops::Neg}; use godot::{ classes::{ Area3D, AudioStreamPlayer, Camera3D, CharacterBody3D, CollisionShape3D, ColorRect, ICharacterBody3D, Input, InputEvent, InputEventMouseMotion, Label, input::MouseMode, }, obj::WithBaseField, prelude::*, }; use crate::{ game::{Game, GameManager, opts::GameOption}, game_settings::{GameSettings, GameSettingsManager}, killbox::KillboxKind, net::{ network_manager::{ NetworkManager, Package::{self, Jump, Punch, Shoot, ThrowTelegrenade}, PeerKind, }, util::cast_ray, }, pickup::PickupKind, player::{ DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon, buffs::{Buff, Buffs}, effects::PlayerEffects, remote_player::RemotePlayer, shootable::Shootable, soldier_mesh::SoldierMesh, telegrenade::Telegrenade, weapon::{BallWeapon, Raygun, Shotgun, Weapon, WeaponType}, }, replay::ReplayEvent, ui::{cli::CommandLinePanel, cooldown_icon::CooldownIcon, hud::Hud}, }; #[derive(GodotClass)] #[class(base=CharacterBody3D, init)] pub struct LocalPlayer { #[export] look_sensitivity: f32, #[export] camera: Option>, #[export] vignette: Option>, #[export] move_speed: f32, #[export] soldier_mesh: Option>, #[export] collider: Option>, #[export] effects: Option>, #[export] dead_cam_target: Option>, #[export] respawn_label: Option>, #[export] camera_origin: Option>, #[export] weapon_node: Option>, #[export] hud: Option>, #[export] hit_sound: Option>, #[export] goal_sound_good: Option>, #[export] goal_sound_bad: Option>, #[export] pickup_ball_sfx: Option>, #[export] throw_ball_sfx: Option>, #[export] health: i32, #[export] punch_cooldown: f64, #[export] telegrenade_cooldown: Option>, #[export] telefrag_area_prefab: Option>, pub player_id: Option, pub weapon: Option>, movement_dir: Vector3, y_speed: f32, y_rot: f32, look_up: f32, is_dead: bool, dead_for: f64, killer_id: Option, time_passed: f64, locked: bool, punch_cd_remaining: f64, #[export] telegrenade_cd: f64, telegrenade: Option>, telegrenade_cd_remaining: f64, telegrenades_allowed: bool, telefrag_area: Option>, telefrag_frames: u8, buffs: Buffs, base: Base, } #[godot_dyn] impl IPlayer for LocalPlayer { fn get_player_id(&self) -> Option { self.player_id } fn get_y_speed(&self) -> f32 { self.y_speed } fn set_y_speed(&mut self, speed: f32) { self.y_speed = speed; } fn get_movement_dir(&self) -> Vector3 { self.movement_dir } fn set_movement_dir(&mut self, _dir: Vector3) {} fn get_move_speed(&self) -> f32 { self.move_speed } fn apply_transform(&mut self, transform: NetTransform) { self.base_mut() .set_global_position(transform.location.into()); self.base_mut() .set_global_rotation(transform.rotation.into()); } fn apply_position(&mut self, pos: Vector3) { self.base_mut().set_global_position(pos); } fn apply_rotation(&mut self, _rot: Vector3) {} fn get_transform(&self) -> NetTransform { NetTransform { location: self.base().get_global_position().into(), rotation: self.base().get_global_rotation().into(), } } fn jump(&mut self) { if self.try_jump() { if let Some(game) = &mut Game::singleton() && let Some(recorder) = game.bind_mut().recorder() { recorder .bind_mut() .record_event(ReplayEvent::Jump(self.player_id.unwrap_or(0))); } if let Some(effects) = &mut self.effects { effects.bind_mut().jump(); } if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { match peer { PeerKind::Client(peer, addr) => peer.send_reliable(&addr, Jump(0)), PeerKind::Server(peer, _) => { peer.broadcast_reliable(Jump(self.player_id.unwrap_or(0))) } } } } } fn get_look_up(&self) -> f32 { self.look_up } fn apply_look_up(&mut self, _look: f32) {} fn try_throw_telegrenade(&mut self, to: Vector3) -> bool { if !self.telegrenades_allowed { return false; } if self.telegrenade_cd_remaining > 0. { return false; } if self.throw_telegrenade_common(to) { self.telegrenade_cd_remaining = self.telegrenade_cd; true } else { false } } fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { if self.buffs.can_shoot() && self.try_shoot_common(to, deal_damage) { if let Some(weapon) = &mut self.weapon { weapon.set_position(-Vector3::FORWARD); weapon.set_rotation(Vector3::RIGHT * 0.5); } if let Some(game) = &mut Game::singleton() && let Some(recorder) = game.bind_mut().recorder() { recorder .bind_mut() .record_event(ReplayEvent::Shoot(self.player_id.unwrap_or(0), to.into())); } true } else { false } } fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool { if self.punch_cd_remaining <= 0. { if let Some(weapon) = &mut self.weapon { weapon.set_position(Vector3::FORWARD); weapon.set_rotation(Vector3::UP); } self.punch_cd_remaining = self.punch_cooldown; if self.try_punch_common(to, deal_damage) { if let Some(game) = &mut Game::singleton() && let Some(recorder) = game.bind_mut().recorder() { recorder .bind_mut() .record_event(ReplayEvent::Punch(self.player_id.unwrap_or(0), to.into())); } true } else { false } } else { false } } fn take_damage(&mut self, source: DamageSource, damage: i32, kill: bool) { self.run_deferred(move |s| { if s.can_take_damage(&source) { let (signal, player, kind) = source.into_signal(); s.run_deferred(move |s| { s.signals() .on_take_damage() .emit(signal, player, kind, damage) }); s.health -= damage; if kill && s.health <= 0 { s.kill(source); } else { if let Some(effects) = &mut s.effects { effects.bind_mut().hit(); } } } }); } fn is_dead(&self) -> bool { self.is_dead } fn dead_for(&self) -> f64 { self.dead_for } fn kill(&mut self, source: DamageSource) { self.drop_ball(false); let (signal, player, kind) = source.into_signal(); self.run_deferred(move |s| s.signals().on_die().emit(signal, player, kind)); if let Some(mut mesh) = self.soldier_mesh.take() { mesh.queue_free(); } if let Some(mut collider) = self.collider.take() { collider.queue_free(); } if let Some(effects) = &mut self.effects { effects.bind_mut().death(); } if let Some(mut weapon) = self.weapon.take() { weapon.queue_free(); } self.is_dead = true; if let DamageSource::Player(player_id) = source { self.killer_id = Some(player_id); } } fn swap_weapon(&mut self, weapon: WeaponType, drop_ball: bool) { if drop_ball { self.drop_ball(true); } if let Some(mesh) = self.soldier_mesh.clone() { match weapon { WeaponType::Raygun => { if let Some(raygun) = &mesh.bind().raygun { self.spawn_weapon(raygun.instantiate_as::().into_dyn().upcast()); } } WeaponType::Shotgun => { if let Some(raygun) = &mesh.bind().shotgun { self.spawn_weapon(raygun.instantiate_as::().into_dyn().upcast()); } } WeaponType::Ball => { if let Some(ball) = &mesh.bind().ball { self.spawn_weapon(ball.instantiate_as::().into_dyn().upcast()); } if let Some(ball_pickup_sfx) = &mut self.pickup_ball_sfx { ball_pickup_sfx.play(); } } }; } } fn get_weapon(&self) -> Option> { self.weapon.clone() } fn take_weapon(&mut self) -> Option> { self.weapon.take() } fn drop_ball(&mut self, with_velocity: bool) -> bool { if self.drop_ball_common(with_velocity) && with_velocity && let Some(sfx) = &mut self.throw_ball_sfx { sfx.play(); true } else { false } } fn get_camera_origin(&self) -> Option> { self.camera_origin.clone() } fn is_replay_mode(&self) -> bool { false } 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), } } fn set_telegrenade(&mut self, grenade: Option>) { self.telegrenade = grenade; } fn get_telegrenade(&self) -> Option> { self.telegrenade.clone() } fn telegrenade_teleport(&mut self, to: Vector3) -> bool { self.telegrenade_teleport_common(to) } fn as_gd(&self) -> DynGd { self.to_gd().into_dyn().upcast() } fn set_bubble_color(&mut self, color: Color) { self.set_vignette_color(color); } fn get_buffs(&self) -> &Buffs { &self.buffs } fn get_buffs_mut(&mut self) -> &mut Buffs { &mut self.buffs } fn new_buff(&mut self, buff: Buff) { self.buffs.new_buff(buff); self.buffs.clone().update_effects(self); } fn set_buffs(&mut self, buffs: Buffs) { self.buffs = buffs.clone(); buffs.update_effects(self); } fn set_telefrag_frame(&mut self) { if let Some(prefab) = &self.telefrag_area_prefab { let area = prefab.instantiate_as::(); area.signals() .body_entered() .connect_other(self, |s, other| s.on_body_entered(other)); self.base_mut().add_child(&area); self.telefrag_frames = 1; self.telefrag_area = Some(area); } } } #[godot_api] impl ICharacterBody3D for LocalPlayer { fn ready(&mut self) { self.update_settings(&GameSettingsManager::singleton().bind().settings); GameSettingsManager::singleton() .bind_mut() .signals() .on_settings_changed() .connect_other(self, |s| { s.update_settings(&GameSettingsManager::singleton().bind().settings) }); if !CommandLinePanel::singleton().bind().opened { self.set_lock(true); } self.swap_weapon(WeaponType::Raygun, false); if let Some(camera) = &mut self.camera { camera.set_current(true); } if let Some(mesh) = &mut self.soldier_mesh && let Some(remover) = &mut mesh.bind_mut().hand_remover { remover.bind_mut().active = true; } if let Some(mut hud) = self.hud.clone() { hud.bind_mut().player_id = self.player_id.unwrap_or(0); hud.bind_mut() .signals() .on_lock_changed() .connect_other(self, |s, lock| s.set_lock(lock)); } self.run_deferred(|s| { s.telegrenades_allowed = if let Some(game) = Game::singleton() { game.bind().opts.is_team_true( game.bind().get_player_team(s.player_id.unwrap_or(0)), GameOption::TelegrenadesAllowed, ) } else { false }; if let Some(game) = Game::singleton() && let Some(player) = game.bind().find_player(s.player_id.unwrap_or(0)) && let Some(team) = game.bind().get_team(player.data.team) { if let Some(mesh) = &mut s.soldier_mesh { mesh.bind_mut().set_color(team.bind().color()); } } }); self.buffs.clone().update_effects(self); } fn process(&mut self, delta: f64) { self.time_passed += delta; self.punch_cd_remaining -= delta; self.telegrenade_cd_remaining -= delta; if let Some(telegrenade_cd) = &mut self.telegrenade_cooldown { telegrenade_cd.bind_mut().value = self.telegrenade_cd_remaining; let telegrenade_key = GameSettingsManager::singleton() .bind() .settings .keybinds .telegrenade; telegrenade_cd.bind_mut().add_text = (&telegrenade_key.to_string()).into(); } if self.is_dead { self.dead_for += delta; if let Some(label) = &mut self.respawn_label { let respawn_timer = if let Some(game) = Game::singleton() { game.bind().opts.get_float(GameOption::RespawnTimer) } else { 0. }; let time_remaining = respawn_timer - self.dead_for; label.set_text(&format!("Respawn in\n{:.2}", time_remaining)); } if let Some(camera) = &self.camera { if camera.is_current() && self.dead_for > 3. { if let Some(mut game) = Game::singleton() && let Some(killer_id) = self.killer_id { let mut game = game.bind_mut(); let remote_player = game.find_player(killer_id); if let Some(remote_player) = &remote_player && let Some(remote_character) = remote_player.character.clone() && let Ok(mut remote_character) = remote_character.try_cast::() && let Some(camera) = &mut remote_character.bind_mut().spectator_camera { camera.set_current(true); } game.start_playback(-5., killer_id); } } } } let input_direction = if self.locked { let input = Input::singleton(); if input.is_action_just_pressed("jump") { self.jump(); } Vector3::RIGHT * input.is_action_pressed("right") as u32 as f32 - Vector3::RIGHT * input.is_action_pressed("left") as u32 as f32 + Vector3::FORWARD * input.is_action_pressed("forward") as u32 as f32 - Vector3::FORWARD * input.is_action_pressed("backward") as u32 as f32 } else { Vector3::ZERO }; self.movement_dir = input_direction; if let Some(mesh) = &mut self.soldier_mesh { mesh.bind_mut() .update_movement(Vector2::new(self.movement_dir.x, -self.movement_dir.z)); mesh.bind_mut().update_y_look(self.look_up); } if self.is_dead && let Some(target) = &self.dead_cam_target && let Some(camera) = &mut self.camera { let camera_prev_pos = camera.get_global_position(); let new_pos = camera_prev_pos.lerp(target.get_global_position(), 0.1); camera.set_global_position(new_pos); } if self.movement_dir.length_squared() > 0. && self.base().is_on_floor() && !self.is_dead { if let Some(effects) = &mut self.effects { effects.bind_mut().walk(); } } if let Some(weapon) = &mut self.weapon { let (target_y, target_x) = if self.movement_dir.length_squared() > 0. { ( (self.time_passed * 8.).sin() / 30., (self.time_passed * 4.).sin() / 40., ) } else { (0., 0.) }; let done_cd_percent = if weapon.dyn_bind().get_max_cd() > 0. { 1. - (weapon.dyn_bind().get_remaining_cd() / weapon.dyn_bind().get_max_cd()) } else { 1. }; let new_weapon_rot = weapon .get_rotation() .lerp(Vector3::ZERO, 0.03 * done_cd_percent as f32); weapon.set_rotation(new_weapon_rot); let prev_weapon_pos = weapon.get_position(); let new_y = prev_weapon_pos.y.lerp(target_y as f32, 0.2); let new_x = prev_weapon_pos.x.lerp(target_x as f32, 0.2); let new_z = prev_weapon_pos.z.lerp(0., 0.2); let new_weapon_pos = Vector3 { y: new_y, x: new_x, z: new_z, }; weapon.set_position(new_weapon_pos); } if self.buffs.update(delta) { self.buffs.clone().update_effects(self); } } fn input(&mut self, event: Gd) { if self.locked { if event.is_action_pressed("throw_telegrenade") && self.telegrenades_allowed { if let Some(telegrenade) = &mut self.telegrenade { if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { match peer { PeerKind::Client(peer, socket_addr) => { peer.send_reliable(socket_addr, Package::TelegrenadeActivate); } PeerKind::Server(peer, _) => { let position = telegrenade.get_global_position(); self.telegrenade_teleport(position); peer.broadcast_reliable(Package::TelegrenadeTeleport( self.player_id.unwrap_or(0), position.into(), )); } } } } if let Some(pos) = self.raycast_from_camera() { let dir = if let Some(origin) = &self.camera_origin { pos - origin.get_global_position() } else { Vector3::ZERO }; if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { match peer { PeerKind::Client(peer, addr) => { if self.try_throw_telegrenade(dir) { peer.send_reliable(addr, ThrowTelegrenade(0, dir.into())); } } PeerKind::Server(peer, _) => { if self.try_throw_telegrenade(dir) { peer.broadcast_reliable(ThrowTelegrenade( self.player_id.unwrap_or(0), dir.into(), )); } } } } } } if event.is_action_pressed("shoot") { if let Some(pos) = self.raycast_from_camera() { if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { match peer { PeerKind::Client(peer, addr) => { if self.try_shoot(pos, false) { peer.send_reliable( addr, Shoot(0, pos.into(), self.get_transform(), self.look_up), ); } } PeerKind::Server(peer, _) => { if self.try_shoot(pos, true) { peer.broadcast_reliable(Shoot( self.player_id.unwrap_or(0), pos.into(), self.get_transform(), self.look_up, )); } } } } } } if event.is_action_pressed("punch") { if let Some(pos) = self.raycast_from_camera() { if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { match peer { PeerKind::Client(peer, addr) => { if self.try_punch(pos, false) { peer.send_reliable( addr, Punch(0, pos.into(), self.get_transform(), self.look_up), ); } } PeerKind::Server(peer, _) => { if self.try_punch(pos, true) { peer.broadcast_reliable(Punch( self.player_id.unwrap_or(0), pos.into(), self.get_transform(), self.look_up, )); } } } } } } if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { let weapon_type = if event.is_action_pressed("weapon_1") { Some(WeaponType::Raygun) } else if event.is_action_pressed("weapon_2") { Some(WeaponType::Shotgun) } else if (event.is_action_pressed("swap_weapon_up") || event.is_action_pressed("swap_weapon_down")) && let Some(game) = Game::singleton() && let Some(player) = game.bind().find_player(self.player_id.unwrap_or(0)) { match player.last_held_weapon { WeaponType::Raygun => Some(WeaponType::Shotgun), WeaponType::Shotgun => Some(WeaponType::Raygun), WeaponType::Ball => None, } } else { None }; if let Some(weapon_type) = weapon_type { self.swap_weapon(weapon_type, true); match peer { PeerKind::Client(peer, socket_addr) => { peer.send_reliable(socket_addr, Package::SwapWeapon(0, weapon_type)); } PeerKind::Server(peer, _) => { if let Some(game) = &mut Game::singleton() && let Some(player) = game.bind_mut().find_player_mut(self.player_id.unwrap_or(0)) { player.last_held_weapon = weapon_type; } peer.broadcast_reliable(Package::SwapWeapon( self.player_id.unwrap_or(0), weapon_type, )); } } } } if let Ok(event) = event.try_cast::() { if !self.is_dead() { let x = event.get_relative().x * 0.001 * self.look_sensitivity; let y = event.get_relative().y * 0.001 * self.look_sensitivity; if let Some(camera) = self.camera.as_mut() { let curr_rotation = camera.get_rotation(); let x_rot = (curr_rotation.x - y * self.look_sensitivity).clamp(-PI / 2., PI / 2.); camera.set_rotation(Vector3::RIGHT * x_rot); self.look_up = x_rot; } let y_rot = -x * self.look_sensitivity; self.y_rot = self.base().get_global_rotation().y + y_rot; } } } } fn physics_process(&mut self, delta: f64) { let rot = self.y_rot; let new_rot = Vector3 { y: rot, ..self.base().get_global_rotation() }; self.base_mut().set_global_rotation(new_rot); self.player_update(delta); if self.telefrag_frames == 0 { if let Some(area) = &mut self.telefrag_area.take() { area.queue_free(); } } else { self.telefrag_frames -= 1; } } fn exit_tree(&mut self) { if let Some(game) = Game::singleton() && !game.bind().game_started { self.set_lock(false); } } } #[godot_api] impl LocalPlayer { #[signal] pub fn on_die(source: DamageSourceSignal, player: u16, killbox_kind: KillboxKind); #[signal] pub fn on_take_damage( source: DamageSourceSignal, source_player: u16, killbox_kind: KillboxKind, damage: i32, ); #[signal] pub fn on_heal(amount: i32); fn set_lock(&mut self, locked: bool) { self.locked = locked; if self.locked { Input::singleton().set_mouse_mode(MouseMode::CAPTURED); } else { Input::singleton().set_mouse_mode(MouseMode::VISIBLE); } } fn spawn_weapon(&mut self, mut weapon: DynGd) { let prev_cd = if let Some(mut weapon) = self.weapon.take() { let prev_cd = weapon.dyn_bind().get_remaining_cd(); weapon.queue_free(); prev_cd } else { 0. }; if let Some(weapon_node) = &mut self.weapon_node { weapon.dyn_bind_mut().set_remaining_cd(prev_cd); weapon_node.add_child(&weapon); self.weapon = Some(weapon); } } #[func] pub fn exit_to_lobby(&mut self) { if let Some(game) = &mut Game::singleton() { game.bind_mut().finish_game(); } } #[func] pub fn exit(&mut self) { GameManager::singleton().bind_mut().exit_game(); NetworkManager::singleton().bind_mut().disconnect(); } pub fn update_settings(&mut self, settings: &GameSettings) { if let Some(camera) = &mut self.camera { camera.set_fov(settings.fov as f32); } } pub fn play_local_hit_sound(&mut self) { if let Some(hit_sound) = &mut self.hit_sound { hit_sound.play(); } } pub fn play_local_goal_sound(&mut self) { if let Some(goal_good) = &mut self.goal_sound_good { goal_good.play(); } } pub fn play_local_goal_bad_sound(&mut self) { if let Some(goal_bad) = &mut self.goal_sound_bad { goal_bad.play(); } } fn raycast_from_camera(&self) -> Option { if let Some(camera) = &self.camera { let forward = camera.get_global_basis().col_c().neg(); match cast_ray( camera.get_global_position(), camera.get_global_position() + forward, &self.base().get_world_3d(), &Array::from_iter(vec![self.base().get_rid()]), ) { Some(res) => Some(res.position), None => Some(camera.get_global_position() + forward * 100.), } } else { None } } fn set_vignette_color(&mut self, color: Color) { if let Some(rect) = &mut self.vignette && let Some(mut mat) = rect.get_material() { mat.set("shader_parameter/Color", &color.to_variant()); } } } #[godot_dyn] impl Shootable for LocalPlayer { fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) { self.take_damage(source, damage, true); } }