295 lines
7.8 KiB
Rust
295 lines
7.8 KiB
Rust
use godot::{
|
|
classes::{
|
|
Camera3D, CharacterBody3D, CollisionShape3D, ICharacterBody3D, Label3D, Sprite3D,
|
|
TextureProgressBar,
|
|
},
|
|
obj::WithBaseField,
|
|
prelude::*,
|
|
};
|
|
|
|
use crate::{
|
|
game_manager::Game,
|
|
killbox::KillboxKind,
|
|
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<Gd<SoldierMesh>>,
|
|
#[export]
|
|
collider: Option<Gd<CollisionShape3D>>,
|
|
#[export]
|
|
effects: Option<Gd<PlayerEffects>>,
|
|
#[export]
|
|
health: i32,
|
|
#[export]
|
|
punch_cooldown: f64,
|
|
#[export]
|
|
pub spectator_camera: Option<Gd<Camera3D>>,
|
|
#[export]
|
|
camera_origin: Option<Gd<Node3D>>,
|
|
#[export]
|
|
name_label: Option<Gd<Label3D>>,
|
|
#[export]
|
|
health_bar: Option<Gd<TextureProgressBar>>,
|
|
#[export]
|
|
health_bar_sprite: Option<Gd<Sprite3D>>,
|
|
|
|
pub player_id: Option<u16>,
|
|
|
|
movement_dir: Vector3,
|
|
y_speed: f32,
|
|
look_up: f32,
|
|
|
|
is_dead: bool,
|
|
dead_for: f64,
|
|
|
|
punch_cd_remaining: 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: 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) {
|
|
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 {
|
|
self.try_shoot_common(to, deal_damage)
|
|
}
|
|
|
|
fn try_punch(&mut self, to: Vector3, deal_damage: bool) -> bool {
|
|
if self.punch_cd_remaining <= 0. {
|
|
self.punch_cd_remaining = self.punch_cooldown;
|
|
self.try_punch_common(to, 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, 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);
|
|
}
|
|
}
|
|
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, 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 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<DynGd<Node3D, dyn Weapon>> {
|
|
self.soldier_mesh
|
|
.as_ref()
|
|
.and_then(|m| m.bind().get_weapon())
|
|
}
|
|
|
|
fn take_weapon(&mut self) -> Option<DynGd<Node3D, dyn Weapon>> {
|
|
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<Gd<Node3D>> {
|
|
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) {
|
|
self.punch_cd_remaining -= delta;
|
|
|
|
if self.is_dead {
|
|
self.dead_for += 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()
|
|
&& !self.is_dead
|
|
&& let Some(effects) = &mut self.effects
|
|
{
|
|
effects.bind_mut().walk();
|
|
}
|
|
}
|
|
|
|
fn physics_process(&mut self, delta: f64) {
|
|
self.player_update(delta);
|
|
}
|
|
}
|
|
|
|
#[godot_api]
|
|
impl RemotePlayer {
|
|
#[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,
|
|
);
|
|
}
|
|
|
|
#[godot_dyn]
|
|
impl Shootable for RemotePlayer {
|
|
fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) {
|
|
self.take_damage(source, damage, true);
|
|
}
|
|
}
|