use std::f32::consts::PI; use godot::{ classes::{ Camera3D, CharacterBody3D, ICharacterBody3D, Input, InputEvent, InputEventMouseMotion, input::MouseMode, }, obj::WithBaseField, prelude::*, }; use serde::{Deserialize, Serialize}; use crate::{ net::{ network_manager::{NetworkManager, Package::Jump, PeerKind}, util::NetVector3, }, soldier_mesh::SoldierMesh, ui::cli::CommandLinePanel, }; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct NetPlayerTransform { 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: NetPlayerTransform); 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) -> NetPlayerTransform; fn jump(&mut self); } pub trait PlayerMovement { fn player_update(&mut self, delta: f64); } impl PlayerMovement 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(); self.base_mut().set_velocity(speed); self.base_mut().move_and_slide(); } } #[derive(GodotClass)] #[class(base=CharacterBody3D, init)] pub struct LocalPlayer { #[export] look_sensitivity: f32, #[export] camera: Option>, #[export] move_speed: f32, #[export] soldier_mesh: Option>, pub player_id: Option, movement_dir: Vector3, y_speed: f32, look_up: f32, 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: NetPlayerTransform) { 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) -> NetPlayerTransform { NetPlayerTransform { location: self.base().get_global_position().into(), rotation: self.base().get_global_rotation().into(), } } fn jump(&mut self) { self.set_y_speed(5.); 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) {} } #[godot_api] impl ICharacterBody3D for LocalPlayer { fn ready(&mut self) { if !CommandLinePanel::singleton().bind().opened { self.set_lock(true); } CommandLinePanel::singleton() .signals() .on_open() .connect_other(self, |s| s.set_lock(false)); CommandLinePanel::singleton() .signals() .on_close() .connect_other(self, |s| { s.set_lock(true); }); if let Some(mesh) = &mut self.soldier_mesh { mesh.bind_mut().spawn_raygun(); } } fn process(&mut self, delta: f64) { let input_direction = if self.locked { let input = Input::singleton(); if self.base().is_on_floor() && 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_y_look(self.look_up); } self.player_update(delta); } fn input(&mut self, event: Gd) { if self.locked { if let Ok(event) = event.try_cast::() { 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); } } } } impl LocalPlayer { 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); } } } #[derive(GodotClass)] #[class(base=CharacterBody3D, init)] pub struct RemotePlayer { #[export] move_speed: f32, #[export] soldier_mesh: Option>, pub player_id: Option, movement_dir: Vector3, y_speed: f32, look_up: f32, 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: NetPlayerTransform) { 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) -> NetPlayerTransform { NetPlayerTransform { location: self.base().get_global_position().into(), rotation: self.base().get_global_rotation().into(), } } fn jump(&mut self) { self.set_y_speed(5.); } fn get_look_up(&self) -> f32 { self.look_up } fn apply_look_up(&mut self, look: f32) { self.look_up = look; } } #[godot_api] impl ICharacterBody3D for RemotePlayer { fn ready(&mut self) { if let Some(mesh) = &mut self.soldier_mesh { mesh.bind_mut().spawn_raygun(); } } fn process(&mut self, delta: f64) { 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); } } }