skullball/rust/src/player/mod.rs

1033 lines
32 KiB
Rust

use std::{f32::consts::PI, ops::Neg};
use godot::{
classes::{
Button, Camera3D, CharacterBody3D, CollisionShape3D, ICharacterBody3D, Input, InputEvent,
InputEventMouseMotion, Label, Label3D, Panel, PanelContainer, input::MouseMode,
},
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},
};
pub mod ball;
pub mod effects;
pub mod player_hand_remover;
pub mod shootable;
pub mod soldier_mesh;
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_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 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>>;
}
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;
}
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();
}
}
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
}
}
#[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>>,
#[export]
collider: Option<Gd<CollisionShape3D>>,
#[export]
effects: Option<Gd<PlayerEffects>>,
#[export]
dead_cam_target: Option<Gd<Node3D>>,
#[export]
respawn_label: Option<Gd<Label>>,
#[export]
camera_origin: Option<Gd<Node3D>>,
#[export]
weapon_node: Option<Gd<Node3D>>,
#[export]
pause_panel: Option<Gd<Panel>>,
#[export]
exit_to_lobby: Option<Gd<Button>>,
#[export]
settings_panel: Option<Gd<SettingsPanel>>,
#[export]
scoreboard: Option<Gd<PanelContainer>>,
#[export]
chatbox: Option<Gd<ChatBox>>,
#[export]
health: i32,
pub player_id: Option<u16>,
pub weapon: Option<DynGd<Node3D, dyn Weapon>>,
movement_dir: Vector3,
y_speed: f32,
look_up: f32,
is_dead: bool,
dead_for: f64,
killer_id: Option<u16>,
time_passed: 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: 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(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
{
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,
)
} 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);
}
}
});
}
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::<Raygun>().into_dyn().upcast());
}
}
WeaponType::Shotgun => {
if let Some(raygun) = &mesh.bind().shotgun {
self.spawn_weapon(raygun.instantiate_as::<Shotgun>().into_dyn().upcast());
}
}
WeaponType::Ball => {
if let Some(ball) = &mesh.bind().ball {
self.spawn_weapon(ball.instantiate_as::<BallWeapon>().into_dyn().upcast());
}
}
};
}
}
fn get_weapon(&self) -> Option<DynGd<Node3D, dyn Weapon>> {
self.weapon.clone()
}
fn take_weapon(&mut self) -> Option<DynGd<Node3D, dyn Weapon>> {
self.weapon.take()
}
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 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::<RemotePlayer>()
&& 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. {
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 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<InputEvent>) {
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::<InputEventMouseMotion>() {
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<Node3D, dyn Weapon>) {
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<Gd<SoldierMesh>>,
#[export]
collider: Option<Gd<CollisionShape3D>>,
#[export]
effects: Option<Gd<PlayerEffects>>,
#[export]
health: i32,
#[export]
spectator_camera: Option<Gd<Camera3D>>,
#[export]
camera_origin: Option<Gd<Node3D>>,
#[export]
name_label: Option<Gd<Label3D>>,
pub player_id: Option<u16>,
movement_dir: Vector3,
y_speed: f32,
look_up: f32,
is_dead: bool,
dead_for: 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_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) {
self.try_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(),
camera_origin.get_basis().col_b(),
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) = 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);
}
}
});
}
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();
}
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) {
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.
&& 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),
Killbox,
}
impl DamageSource {
pub fn into_signal(&self) -> (DamageSourceSignal, u16) {
match self {
DamageSource::Player(id) => (DamageSourceSignal::Player, *id),
DamageSource::Killbox => (DamageSourceSignal::Killbox, 0),
}
}
pub fn from_signal(signal: DamageSourceSignal, player: u16) -> DamageSource {
match signal {
DamageSourceSignal::Player => DamageSource::Player(player),
DamageSourceSignal::Killbox => DamageSource::Killbox,
}
}
}
#[derive(Debug, GodotConvert, Clone)]
#[godot(via = u8)]
pub enum DamageSourceSignal {
Player,
Killbox,
}