quakeball-rust/rust/src/player.rs

420 lines
12 KiB
Rust

use std::{f32::consts::PI, ops::Neg};
use godot::{
classes::{
AudioStreamPlayer3D, Camera3D, CharacterBody3D, ICharacterBody3D, Input, InputEvent,
InputEventMouseMotion, PhysicsRayQueryParameters3D, input::MouseMode,
},
obj::WithBaseField,
prelude::*,
};
use serde::{Deserialize, Serialize};
use crate::{
net::{
network_manager::{
NetworkManager,
Package::{Jump, Shoot},
PeerKind,
},
util::NetVector3,
},
soldier_mesh::SoldierMesh,
ui::cli::CommandLinePanel,
weapon::{Raygun, Weapon},
};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct NetPlayerTransform {
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: 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);
fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool;
}
pub trait PlayerMovement {
fn player_update(&mut self, delta: f64);
}
impl<T: IPlayer + ICharacterBody3D + WithBaseField> 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<Gd<Camera3D>>,
#[export]
move_speed: f32,
#[export]
soldier_mesh: Option<Gd<SoldierMesh>>,
pub player_id: Option<u16>,
pub weapon: Option<DynGd<Node3D, dyn Weapon>>,
movement_dir: Vector3,
y_speed: f32,
look_up: f32,
shoot_cd: f64,
locked: bool,
base: Base<CharacterBody3D>,
}
#[godot_dyn]
impl IPlayer for LocalPlayer {
fn get_player_id(&self) -> Option<u16> {
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) {}
fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool {
if self.shoot_cd <= 0. {
if let Some(weapon) = &mut self.weapon {
weapon.dyn_bind_mut().shoot(to, deal_damage);
}
self.shoot_cd = 0.2;
true
} else {
false
}
}
}
#[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 {
self.weapon = mesh.bind_mut().spawn_raygun();
}
}
fn process(&mut self, delta: f64) {
self.shoot_cd -= delta;
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<InputEvent>) {
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();
let ray = PhysicsRayQueryParameters3D::create(
camera.get_global_position(),
camera.get_global_position() + forward * 10000.,
);
if let Some(mut ray) = ray {
ray.set_collision_mask(0b110);
ray.set_hit_back_faces(false);
ray.set_hit_from_inside(false);
ray.set_collide_with_bodies(true);
if let Some(world) = self.base().get_world_3d()
&& let Some(mut space) = world.get_direct_space_state()
{
let results = space.intersect_ray(&ray);
if let Some(position) = results.get("position") {
Some(Vector3::from_variant(&position))
} else {
Some(camera.get_global_position() + forward * 100.)
}
} else {
None
}
} else {
None
}
} else {
None
};
godot_print!("{:?}", pos);
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 Ok(event) = event.try_cast::<InputEventMouseMotion>() {
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<Gd<SoldierMesh>>,
pub player_id: Option<u16>,
pub weapon: Option<DynGd<Node3D, dyn Weapon>>,
movement_dir: Vector3,
y_speed: f32,
look_up: f32,
shoot_cd: f64,
base: Base<CharacterBody3D>,
}
#[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<u16> {
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;
}
fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool {
if self.shoot_cd <= 0. {
if let Some(weapon) = &mut self.weapon {
weapon.dyn_bind_mut().shoot(to, deal_damage);
}
self.shoot_cd = 0.2;
true
} else {
false
}
}
}
#[godot_api]
impl ICharacterBody3D for RemotePlayer {
fn ready(&mut self) {
if let Some(mesh) = &mut self.soldier_mesh {
self.weapon = mesh.bind_mut().spawn_raygun();
}
}
fn process(&mut self, delta: f64) {
self.player_update(delta);
self.shoot_cd -= 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);
}
}
}