skullball/rust/src/player/mod.rs

342 lines
11 KiB
Rust

use std::{collections::HashSet, f32::consts::PI, ops::Neg};
use godot::{
classes::{CharacterBody3D, ICharacterBody3D},
obj::WithBaseField,
prelude::*,
};
use serde::{Deserialize, Serialize};
use crate::{
game_manager::{Game, GameOption},
killbox::KillboxKind,
net::{
network_manager::NetworkManager,
util::{NetVector3, shotgun_ray},
},
pickup::PickupKind,
player::{
shootable::Shootable,
telegrenade::Telegrenade,
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 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<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: 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<DynGd<Node3D, dyn Weapon>>;
fn take_weapon(&mut self) -> Option<DynGd<Node3D, dyn Weapon>>;
fn drop_ball(&mut self, with_velocity: bool) -> bool;
fn get_camera_origin(&self) -> Option<Gd<Node3D>>;
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<Gd<Telegrenade>>);
fn get_telegrenade(&self) -> Option<Gd<Telegrenade>>;
fn telegrenade_teleport(&mut self, to: Vector3) -> bool;
fn as_gd(&self) -> DynGd<CharacterBody3D, dyn IPlayer>;
}
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<T: IPlayer + ICharacterBody3D + WithBaseField> 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::<BallWeapon>()
{
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 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::<Node3D>() {
node.get_global_position()
} else {
Vector3::ZERO
};
let delta = collider_pos - origin.get_global_position();
if let Ok(mut shootable) = collider.clone().try_dynify::<dyn Shootable>() {
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(
origin.get_global_position().into(),
(to.normalized() * 10.).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);
true
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum DamageSource {
Player(u16),
Killbox(KillboxKind),
}
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),
}
}
pub fn from_signal(signal: DamageSourceSignal, player: u16, kind: KillboxKind) -> DamageSource {
match signal {
DamageSourceSignal::Player => DamageSource::Player(player),
DamageSourceSignal::Killbox => DamageSource::Killbox(kind),
}
}
}
#[derive(Debug, GodotConvert, Clone)]
#[godot(via = u8)]
pub enum DamageSourceSignal {
Player,
Killbox,
}