diff --git a/rust/src/player/local_player.rs b/rust/src/player/local_player.rs new file mode 100644 index 0000000..9311ff4 --- /dev/null +++ b/rust/src/player/local_player.rs @@ -0,0 +1,629 @@ +use std::{f32::consts::PI, ops::Neg}; + +use godot::{ + classes::{ + Button, Camera3D, CharacterBody3D, CollisionShape3D, ICharacterBody3D, Input, InputEvent, + InputEventMouseMotion, Label, Panel, PanelContainer, input::MouseMode, + }, + obj::WithBaseField, + prelude::*, +}; + +use crate::{ + game_manager::{Game, GameManager}, + game_settings::{GameSettings, GameSettingsManager}, + net::{ + network_manager::{ + NetworkManager, + Package::{self, Jump, Shoot}, + PeerKind, + }, + util::cast_ray, + }, + player::{ + DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon, + effects::PlayerEffects, + remote_player::RemotePlayer, + shootable::Shootable, + soldier_mesh::SoldierMesh, + weapon::{BallWeapon, Raygun, Shotgun, Weapon, WeaponType}, + }, + ui::{chatbox::ChatBox, cli::CommandLinePanel, settings::SettingsPanel}, +}; + +#[derive(GodotClass)] +#[class(base=CharacterBody3D, init)] +pub struct LocalPlayer { + #[export] + look_sensitivity: f32, + #[export] + camera: 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] + pause_panel: Option>, + #[export] + exit_to_lobby: Option>, + #[export] + settings_panel: Option>, + #[export] + scoreboard: Option>, + #[export] + chatbox: Option>, + #[export] + health_label: Option>, + #[export] + health: i32, + + pub player_id: Option, + + pub weapon: Option>, + + movement_dir: Vector3, + y_speed: f32, + look_up: f32, + + is_dead: bool, + dead_for: f64, + killer_id: Option, + + time_passed: f64, + + locked: bool, + + 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_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(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_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { + if self.is_dead { + return false; + } + + if 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.camera_origin + { + if weapon.dyn_bind_mut().shoot( + rid, + self.player_id.unwrap_or(0), + camera_origin.get_global_position(), + to, + camera_origin.get_global_basis().col_b(), + deal_damage, + ) { + weapon.set_rotation(Vector3::RIGHT * 0.3); + 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) = source.into_signal(); + s.run_deferred(move |s| s.signals().on_take_damage().emit(signal, player, damage)); + s.health -= damage; + if kill && s.health <= 0 { + s.kill(source); + } + } + if let Some(health_label) = &mut s.health_label { + health_label.set_text(&s.health.max(0).to_string()); + } + }); + } + + 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) = source.into_signal(); + self.run_deferred(move |s| s.signals().on_die().emit(signal, player)); + 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()); + } + } + }; + } + } + + 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 { + self.drop_ball_common(with_velocity) + } + + fn get_camera_origin(&self) -> Option> { + self.camera_origin.clone() + } +} + +#[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); + } + + CommandLinePanel::singleton() + .signals() + .on_open() + .connect_other(self, |s| s.update_lock()); + CommandLinePanel::singleton() + .signals() + .on_close() + .connect_other(self, |s| { + s.update_lock(); + }); + + if let Some(chatbox) = self.chatbox.clone() { + chatbox + .signals() + .on_opened() + .connect_other(self, |s| s.update_lock()); + chatbox + .signals() + .on_closed() + .connect_other(self, |s| s.update_lock()); + } + + 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; + } + + self.run_deferred(|s| { + if let Some(exit_to_lobby) = &mut s.exit_to_lobby { + exit_to_lobby.set_visible(NetworkManager::singleton().bind().is_host()); + } + }); + } + + fn process(&mut self, delta: f64) { + self.time_passed += delta; + + 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().respawn_timer + } 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(game) = Game::singleton() + && let Some(killer_id) = self.killer_id + { + let game = game.bind(); + 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); + } + } + } + } + } + + 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); + } + + self.player_update(delta); + + 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 let Some(scoreboard) = &mut self.scoreboard { + scoreboard.set_visible(Input::singleton().is_action_pressed("scoreboard")); + } + + if self.movement_dir.length_squared() > 0. && self.base().is_on_floor() { + 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_weapon_pos = Vector3 { + y: new_y, + x: new_x, + ..prev_weapon_pos + }; + weapon.set_position(new_weapon_pos); + } + } + + fn input(&mut self, event: Gd) { + if event.is_action_pressed("pause") { + if let Some(pause_panel) = &mut self.pause_panel { + let is_visible = pause_panel.is_visible(); + pause_panel.set_visible(!is_visible); + if !pause_panel.is_visible() { + if let Some(settings_panel) = &mut self.settings_panel { + settings_panel.set_visible(false); + } + } + self.update_lock(); + } + } + + if self.locked { + if event.is_action_pressed("shoot") { + let pos = 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 + }; + + if let Some(pos) = pos { + 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( + 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 { + 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.base_mut().rotate_y(y_rot); + } + } + } + } + + 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); + #[signal] + pub fn on_take_damage(source: DamageSourceSignal, source_player: u16, damage: i32); + + fn update_lock(&mut self) { + self.run_deferred(|s| { + let paused = if let Some(panel) = &s.pause_panel { + panel.is_visible() + } else { + false + }; + let chat_visible = if let Some(chat) = &s.chatbox { + chat.bind().is_editing() + } else { + false + }; + s.set_lock(!CommandLinePanel::singleton().bind().opened && !paused && !chat_visible); + }); + } + + 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); + } + } +} + +#[godot_dyn] +impl Shootable for LocalPlayer { + fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) { + self.take_damage(source, damage, true); + } +} diff --git a/rust/src/player/mod.rs b/rust/src/player/mod.rs index f114044..b6faee3 100644 --- a/rust/src/player/mod.rs +++ b/rust/src/player/mod.rs @@ -1,39 +1,19 @@ -use std::{f32::consts::PI, ops::Neg}; +use std::ops::Neg; -use godot::{ - classes::{ - Button, Camera3D, CharacterBody3D, CollisionShape3D, ICharacterBody3D, Input, InputEvent, - InputEventMouseMotion, Label, Label3D, Panel, PanelContainer, Sprite3D, TextureProgressBar, - input::MouseMode, - }, - obj::WithBaseField, - prelude::*, -}; +use godot::{classes::ICharacterBody3D, obj::WithBaseField, prelude::*}; use serde::{Deserialize, Serialize}; use crate::{ - game_manager::{Game, GameManager, GameOption}, - game_settings::{GameSettings, GameSettingsManager}, - net::{ - network_manager::{ - NetworkManager, - Package::{self, Jump, Shoot}, - PeerKind, - }, - util::{NetVector3, cast_ray}, - }, - player::{ - effects::PlayerEffects, - shootable::Shootable, - soldier_mesh::SoldierMesh, - weapon::{BallWeapon, Raygun, Shotgun, Weapon, WeaponType}, - }, - ui::{chatbox::ChatBox, cli::CommandLinePanel, settings::SettingsPanel}, + game_manager::{Game, GameOption}, + net::{network_manager::NetworkManager, util::NetVector3}, + player::weapon::{BallWeapon, Weapon, WeaponType}, }; pub mod ball; pub mod effects; +pub mod local_player; pub mod player_hand_remover; +pub mod remote_player; pub mod shootable; pub mod soldier_mesh; pub mod weapon; @@ -179,869 +159,6 @@ impl PlayerCommon for T { } } -#[derive(GodotClass)] -#[class(base=CharacterBody3D, init)] -pub struct LocalPlayer { - #[export] - look_sensitivity: f32, - #[export] - camera: 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] - pause_panel: Option>, - #[export] - exit_to_lobby: Option>, - #[export] - settings_panel: Option>, - #[export] - scoreboard: Option>, - #[export] - chatbox: Option>, - #[export] - health_label: Option>, - #[export] - health: i32, - - pub player_id: Option, - - pub weapon: Option>, - - movement_dir: Vector3, - y_speed: f32, - look_up: f32, - - is_dead: bool, - dead_for: f64, - killer_id: Option, - - time_passed: f64, - - locked: bool, - - 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_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(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_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { - if self.is_dead { - return false; - } - - if 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.camera_origin - { - if weapon.dyn_bind_mut().shoot( - rid, - self.player_id.unwrap_or(0), - camera_origin.get_global_position(), - to, - camera_origin.get_global_basis().col_b(), - deal_damage, - ) { - weapon.set_rotation(Vector3::RIGHT * 0.3); - 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) = source.into_signal(); - s.run_deferred(move |s| s.signals().on_take_damage().emit(signal, player, damage)); - s.health -= damage; - if kill && s.health <= 0 { - s.kill(source); - } - } - if let Some(health_label) = &mut s.health_label { - health_label.set_text(&s.health.max(0).to_string()); - } - }); - } - - 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) = source.into_signal(); - self.run_deferred(move |s| s.signals().on_die().emit(signal, player)); - 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()); - } - } - }; - } - } - - 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 { - self.drop_ball_common(with_velocity) - } - - fn get_camera_origin(&self) -> Option> { - self.camera_origin.clone() - } -} - -#[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); - } - - CommandLinePanel::singleton() - .signals() - .on_open() - .connect_other(self, |s| s.update_lock()); - CommandLinePanel::singleton() - .signals() - .on_close() - .connect_other(self, |s| { - s.update_lock(); - }); - - if let Some(chatbox) = self.chatbox.clone() { - chatbox - .signals() - .on_opened() - .connect_other(self, |s| s.update_lock()); - chatbox - .signals() - .on_closed() - .connect_other(self, |s| s.update_lock()); - } - - 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; - } - - self.run_deferred(|s| { - if let Some(exit_to_lobby) = &mut s.exit_to_lobby { - exit_to_lobby.set_visible(NetworkManager::singleton().bind().is_host()); - } - }); - } - - fn process(&mut self, delta: f64) { - self.time_passed += delta; - - 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().respawn_timer - } 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(game) = Game::singleton() - && let Some(killer_id) = self.killer_id - { - let game = game.bind(); - 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); - } - } - } - } - } - - 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); - } - - self.player_update(delta); - - 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 let Some(scoreboard) = &mut self.scoreboard { - scoreboard.set_visible(Input::singleton().is_action_pressed("scoreboard")); - } - - if self.movement_dir.length_squared() > 0. && self.base().is_on_floor() { - 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_weapon_pos = Vector3 { - y: new_y, - x: new_x, - ..prev_weapon_pos - }; - weapon.set_position(new_weapon_pos); - } - } - - fn input(&mut self, event: Gd) { - if event.is_action_pressed("pause") { - if let Some(pause_panel) = &mut self.pause_panel { - let is_visible = pause_panel.is_visible(); - pause_panel.set_visible(!is_visible); - if !pause_panel.is_visible() { - if let Some(settings_panel) = &mut self.settings_panel { - settings_panel.set_visible(false); - } - } - self.update_lock(); - } - } - - if self.locked { - if event.is_action_pressed("shoot") { - let pos = 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 - }; - - if let Some(pos) = pos { - 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( - 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 { - 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.base_mut().rotate_y(y_rot); - } - } - } - } - - 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); - #[signal] - pub fn on_take_damage(source: DamageSourceSignal, source_player: u16, damage: i32); - - fn update_lock(&mut self) { - self.run_deferred(|s| { - let paused = if let Some(panel) = &s.pause_panel { - panel.is_visible() - } else { - false - }; - let chat_visible = if let Some(chat) = &s.chatbox { - chat.bind().is_editing() - } else { - false - }; - s.set_lock(!CommandLinePanel::singleton().bind().opened && !paused && !chat_visible); - }); - } - - 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); - } - } -} - -#[godot_dyn] -impl Shootable for LocalPlayer { - fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) { - self.take_damage(source, damage, true); - } -} - -#[derive(GodotClass)] -#[class(base=CharacterBody3D, init)] -pub struct RemotePlayer { - #[export] - move_speed: f32, - #[export] - soldier_mesh: Option>, - #[export] - collider: Option>, - #[export] - effects: Option>, - #[export] - health: i32, - #[export] - spectator_camera: Option>, - #[export] - camera_origin: Option>, - #[export] - name_label: Option>, - #[export] - health_bar: Option>, - #[export] - health_bar_sprite: Option>, - - pub player_id: Option, - - movement_dir: Vector3, - y_speed: f32, - look_up: f32, - - is_dead: bool, - dead_for: f64, - - base: Base, -} - -#[godot_dyn] -impl IPlayer for RemotePlayer { - 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) { - self.movement_dir = dir.into(); - } - - fn get_move_speed(&self) -> f32 { - self.move_speed - } - - fn get_player_id(&self) -> Option { - self.player_id - } - - 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_rotation(&mut self, rot: Vector3) { - self.base_mut().set_global_rotation(rot); - } - - 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(effects) = &mut self.effects { - effects.bind_mut().jump(); - } - } - } - - fn get_look_up(&self) -> f32 { - self.look_up - } - - fn apply_look_up(&mut self, look: f32) { - self.look_up = look; - } - - fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { - if self.is_dead { - return false; - } - - if self.drop_ball(true) { - return true; - } - - let rid = self.base().get_rid(); - if let Some(mesh) = &mut self.soldier_mesh - && let Some(mut weapon) = mesh.bind().get_weapon() - && let Some(camera_origin) = &self.camera_origin - { - weapon.dyn_bind_mut().shoot( - rid, - self.player_id.unwrap_or(0), - camera_origin.get_global_position(), - to, - camera_origin.get_basis().col_b(), - deal_damage, - ) - } 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) = source.into_signal(); - s.run_deferred(move |s| s.signals().on_take_damage().emit(signal, player, damage)); - s.health -= damage; - if kill && s.health <= 0 { - s.kill(source); - } - } - if let Some(health_bar) = &mut s.health_bar { - let value = (s.health as f64 / 100.).clamp(0., 1.) * 100.; - health_bar.set_value(value); - } - }); - } - - 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) = source.into_signal(); - self.run_deferred(move |s| s.signals().on_die().emit(signal, player)); - 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 name) = self.name_label.take() { - name.queue_free(); - } - if let Some(healthbar_sprit) = &mut self.health_bar_sprite { - healthbar_sprit.set_visible(false); - } - self.is_dead = true; - } - - fn swap_weapon(&mut self, weapon: WeaponType, drop_ball: bool) { - if drop_ball { - self.drop_ball(true); - } - - if let Some(mesh) = &mut self.soldier_mesh { - match weapon { - WeaponType::Raygun => mesh.bind_mut().spawn_raygun(), - WeaponType::Shotgun => mesh.bind_mut().spawn_shotgun(), - WeaponType::Ball => mesh.bind_mut().spawn_ball(), - }; - } - } - - fn get_weapon(&self) -> Option> { - self.soldier_mesh - .as_ref() - .and_then(|m| m.bind().get_weapon()) - } - - fn take_weapon(&mut self) -> Option> { - self.soldier_mesh - .as_mut() - .and_then(|m| m.bind_mut().take_weapon().cloned()) - } - - fn drop_ball(&mut self, with_velocity: bool) -> bool { - self.drop_ball_common(with_velocity) - } - - fn get_camera_origin(&self) -> Option> { - self.camera_origin.clone() - } -} - -#[godot_api] -impl ICharacterBody3D for RemotePlayer { - fn ready(&mut self) { - self.run_deferred(|s| { - if let Some(player_id) = s.player_id - && let Some(game) = Game::singleton() - && let Some(player) = game.bind().find_player(player_id) - && let Some(team) = game.bind().get_team(player.data.team) - && let Some(name_label) = &mut s.name_label - { - name_label.set_text(&player.data.name); - name_label.set_modulate(team.bind().color); - } - }); - - self.swap_weapon(WeaponType::Raygun, false); - } - - fn process(&mut self, delta: f64) { - if self.is_dead { - self.dead_for += delta; - } - - self.player_update(delta); - - 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 let Some(ball_origin) = &mut self.camera_origin { - ball_origin.set_rotation(Vector3::RIGHT * self.look_up); - } - - if self.movement_dir.length_squared() > 0. - && self.base().is_on_floor() - && let Some(effects) = &mut self.effects - { - effects.bind_mut().walk(); - } - } -} - -#[godot_api] -impl RemotePlayer { - #[signal] - pub fn on_die(source: DamageSourceSignal, player: u16); - #[signal] - pub fn on_take_damage(source: DamageSourceSignal, source_player: u16, damage: i32); -} - -#[godot_dyn] -impl Shootable for RemotePlayer { - fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) { - self.take_damage(source, damage, true); - } -} - #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum DamageSource { Player(u16), diff --git a/rust/src/player/remote_player.rs b/rust/src/player/remote_player.rs new file mode 100644 index 0000000..3a31049 --- /dev/null +++ b/rust/src/player/remote_player.rs @@ -0,0 +1,285 @@ +use godot::{ + classes::{ + Camera3D, CharacterBody3D, CollisionShape3D, ICharacterBody3D, Label3D, Sprite3D, + TextureProgressBar, + }, + obj::WithBaseField, + prelude::*, +}; + +use crate::{ + game_manager::Game, + player::{ + DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon, + effects::PlayerEffects, + shootable::Shootable, + soldier_mesh::SoldierMesh, + weapon::{Weapon, WeaponType}, + }, +}; + +#[derive(GodotClass)] +#[class(base=CharacterBody3D, init)] +pub struct RemotePlayer { + #[export] + move_speed: f32, + #[export] + soldier_mesh: Option>, + #[export] + collider: Option>, + #[export] + effects: Option>, + #[export] + health: i32, + #[export] + pub spectator_camera: Option>, + #[export] + camera_origin: Option>, + #[export] + name_label: Option>, + #[export] + health_bar: Option>, + #[export] + health_bar_sprite: Option>, + + pub player_id: Option, + + movement_dir: Vector3, + y_speed: f32, + look_up: f32, + + is_dead: bool, + dead_for: f64, + + base: Base, +} + +#[godot_dyn] +impl IPlayer for RemotePlayer { + 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) { + self.movement_dir = dir.into(); + } + + fn get_move_speed(&self) -> f32 { + self.move_speed + } + + fn get_player_id(&self) -> Option { + self.player_id + } + + 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_rotation(&mut self, rot: Vector3) { + self.base_mut().set_global_rotation(rot); + } + + 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(effects) = &mut self.effects { + effects.bind_mut().jump(); + } + } + } + + fn get_look_up(&self) -> f32 { + self.look_up + } + + fn apply_look_up(&mut self, look: f32) { + self.look_up = look; + } + + fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { + if self.is_dead { + return false; + } + + if self.drop_ball(true) { + return true; + } + + let rid = self.base().get_rid(); + if let Some(mesh) = &mut self.soldier_mesh + && let Some(mut weapon) = mesh.bind().get_weapon() + && let Some(camera_origin) = &self.camera_origin + { + weapon.dyn_bind_mut().shoot( + rid, + self.player_id.unwrap_or(0), + camera_origin.get_global_position(), + to, + camera_origin.get_basis().col_b(), + deal_damage, + ) + } 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) = source.into_signal(); + s.run_deferred(move |s| s.signals().on_take_damage().emit(signal, player, damage)); + s.health -= damage; + if kill && s.health <= 0 { + s.kill(source); + } + } + if let Some(health_bar) = &mut s.health_bar { + let value = (s.health as f64 / 100.).clamp(0., 1.) * 100.; + health_bar.set_value(value); + } + }); + } + + 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) = source.into_signal(); + self.run_deferred(move |s| s.signals().on_die().emit(signal, player)); + 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 name) = self.name_label.take() { + name.queue_free(); + } + if let Some(healthbar_sprit) = &mut self.health_bar_sprite { + healthbar_sprit.set_visible(false); + } + self.is_dead = true; + } + + fn swap_weapon(&mut self, weapon: WeaponType, drop_ball: bool) { + if drop_ball { + self.drop_ball(true); + } + + if let Some(mesh) = &mut self.soldier_mesh { + match weapon { + WeaponType::Raygun => mesh.bind_mut().spawn_raygun(), + WeaponType::Shotgun => mesh.bind_mut().spawn_shotgun(), + WeaponType::Ball => mesh.bind_mut().spawn_ball(), + }; + } + } + + fn get_weapon(&self) -> Option> { + self.soldier_mesh + .as_ref() + .and_then(|m| m.bind().get_weapon()) + } + + fn take_weapon(&mut self) -> Option> { + self.soldier_mesh + .as_mut() + .and_then(|m| m.bind_mut().take_weapon().cloned()) + } + + fn drop_ball(&mut self, with_velocity: bool) -> bool { + self.drop_ball_common(with_velocity) + } + + fn get_camera_origin(&self) -> Option> { + self.camera_origin.clone() + } +} + +#[godot_api] +impl ICharacterBody3D for RemotePlayer { + fn ready(&mut self) { + self.run_deferred(|s| { + if let Some(player_id) = s.player_id + && let Some(game) = Game::singleton() + && let Some(player) = game.bind().find_player(player_id) + && let Some(team) = game.bind().get_team(player.data.team) + && let Some(name_label) = &mut s.name_label + { + name_label.set_text(&player.data.name); + name_label.set_modulate(team.bind().color); + } + }); + + self.swap_weapon(WeaponType::Raygun, false); + } + + fn process(&mut self, delta: f64) { + if self.is_dead { + self.dead_for += delta; + } + + self.player_update(delta); + + 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 let Some(ball_origin) = &mut self.camera_origin { + ball_origin.set_rotation(Vector3::RIGHT * self.look_up); + } + + if self.movement_dir.length_squared() > 0. + && self.base().is_on_floor() + && let Some(effects) = &mut self.effects + { + effects.bind_mut().walk(); + } + } +} + +#[godot_api] +impl RemotePlayer { + #[signal] + pub fn on_die(source: DamageSourceSignal, player: u16); + #[signal] + pub fn on_take_damage(source: DamageSourceSignal, source_player: u16, damage: i32); +} + +#[godot_dyn] +impl Shootable for RemotePlayer { + fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) { + self.take_damage(source, damage, true); + } +}