729 lines
21 KiB
Rust
729 lines
21 KiB
Rust
use std::{f32::consts::PI, ops::Neg};
|
|
|
|
use godot::{
|
|
classes::{
|
|
Camera3D, CharacterBody3D, CollisionShape3D, GpuParticles3D, ICharacterBody3D, Input,
|
|
InputEvent, InputEventMouseMotion, Label, input::MouseMode,
|
|
},
|
|
obj::WithBaseField,
|
|
prelude::*,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
game_manager::Game,
|
|
net::{
|
|
network_manager::{
|
|
NetworkManager,
|
|
Package::{self, Jump, Shoot},
|
|
PeerKind,
|
|
},
|
|
util::{NetVector3, cast_ray},
|
|
},
|
|
soldier_mesh::SoldierMesh,
|
|
ui::cli::CommandLinePanel,
|
|
weapon::{BallWeapon, Weapon, WeaponType},
|
|
};
|
|
|
|
#[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);
|
|
fn get_weapon(&self) -> Option<DynGd<Node3D, dyn Weapon>>;
|
|
fn drop_ball(&mut self, with_velocity: bool) -> bool;
|
|
fn get_ball_origin(&self) -> Option<Gd<Node3D>>;
|
|
}
|
|
|
|
pub trait PlayerMovement {
|
|
fn player_update(&mut self, delta: f64);
|
|
fn drop_ball_common(&mut self, with_velocity: bool) -> bool;
|
|
fn get_look_dir(&self) -> Vector3;
|
|
}
|
|
|
|
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();
|
|
|
|
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 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(peer) = &mut NetworkManager::singleton().bind_mut().peer
|
|
&& let PeerKind::Server(peer, _) = peer
|
|
{
|
|
s.swap_weapon(WeaponType::Raygun);
|
|
peer.broadcast_reliable(Package::SwapWeapon(
|
|
s.get_player_id().unwrap_or(0),
|
|
WeaponType::Raygun,
|
|
));
|
|
if let Some(mut game) = Game::singleton() {
|
|
if let Some(ball_origin) = &s.get_ball_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(ball_origin) = self.get_ball_origin() {
|
|
ball_origin.get_global_basis().col_c().neg()
|
|
} else {
|
|
Vector3::ZERO
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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]
|
|
splorch: Option<Gd<GpuParticles3D>>,
|
|
#[export]
|
|
dead_cam_target: Option<Gd<Node3D>>,
|
|
#[export]
|
|
respawn_label: Option<Gd<Label>>,
|
|
#[export]
|
|
ball_origin: Option<Gd<Node3D>>,
|
|
#[export]
|
|
health: i32,
|
|
|
|
pub player_id: Option<u16>,
|
|
|
|
movement_dir: Vector3,
|
|
y_speed: f32,
|
|
look_up: f32,
|
|
|
|
shoot_cd: f64,
|
|
|
|
is_dead: bool,
|
|
dead_for: f64,
|
|
killer_id: Option<u16>,
|
|
|
|
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) {
|
|
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.is_dead {
|
|
return false;
|
|
}
|
|
|
|
if self.drop_ball(true) {
|
|
return true;
|
|
}
|
|
|
|
if self.shoot_cd <= 0. {
|
|
let rid = self.base().get_rid();
|
|
if let Some(mut weapon) = self.get_weapon() {
|
|
weapon
|
|
.dyn_bind_mut()
|
|
.shoot(rid, self.player_id.unwrap_or(0), to, deal_damage);
|
|
}
|
|
self.shoot_cd = 0.2;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn take_damage(&mut self, source: DamageSource, damage: i32, kill: bool) {
|
|
let (signal, player) = source.into_signal();
|
|
self.run_deferred(move |s| s.signals().on_take_damage().emit(signal, player, damage));
|
|
self.health -= damage;
|
|
if kill && self.health <= 0 {
|
|
self.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(splorch) = &mut self.splorch {
|
|
splorch.set_emitting(true);
|
|
}
|
|
self.is_dead = true;
|
|
if let DamageSource::Player(player_id) = source {
|
|
self.killer_id = Some(player_id);
|
|
}
|
|
}
|
|
|
|
fn swap_weapon(&mut self, weapon: WeaponType) {
|
|
if let Some(mesh) = &mut self.soldier_mesh {
|
|
match weapon {
|
|
WeaponType::Raygun => mesh.bind_mut().spawn_raygun(),
|
|
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 drop_ball(&mut self, with_velocity: bool) -> bool {
|
|
self.drop_ball_common(with_velocity)
|
|
}
|
|
|
|
fn get_ball_origin(&self) -> Option<Gd<Node3D>> {
|
|
self.ball_origin.clone()
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
});
|
|
|
|
self.swap_weapon(WeaponType::Raygun);
|
|
|
|
if let Some(camera) = &mut self.camera {
|
|
camera.set_current(true);
|
|
}
|
|
}
|
|
|
|
fn process(&mut self, delta: f64) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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();
|
|
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 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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 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>>,
|
|
#[export]
|
|
collider: Option<Gd<CollisionShape3D>>,
|
|
#[export]
|
|
splorch: Option<Gd<GpuParticles3D>>,
|
|
#[export]
|
|
health: i32,
|
|
#[export]
|
|
spectator_camera: Option<Gd<Camera3D>>,
|
|
#[export]
|
|
ball_origin: Option<Gd<Node3D>>,
|
|
|
|
pub player_id: Option<u16>,
|
|
|
|
movement_dir: Vector3,
|
|
y_speed: f32,
|
|
look_up: f32,
|
|
|
|
is_dead: bool,
|
|
dead_for: f64,
|
|
|
|
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: 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.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.is_dead {
|
|
return false;
|
|
}
|
|
|
|
if self.drop_ball(true) {
|
|
return true;
|
|
}
|
|
|
|
if self.shoot_cd <= 0. {
|
|
let rid = self.base().get_rid();
|
|
if let Some(mesh) = &mut self.soldier_mesh
|
|
&& let Some(mut weapon) = mesh.bind().get_weapon()
|
|
{
|
|
weapon
|
|
.dyn_bind_mut()
|
|
.shoot(rid, self.player_id.unwrap_or(0), to, deal_damage);
|
|
}
|
|
self.shoot_cd = 0.2;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn take_damage(&mut self, source: DamageSource, damage: i32, kill: bool) {
|
|
let (signal, player) = source.into_signal();
|
|
self.run_deferred(move |s| s.signals().on_take_damage().emit(signal, player, damage));
|
|
self.health -= damage;
|
|
if kill && self.health <= 0 {
|
|
self.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(splorch) = &mut self.splorch {
|
|
splorch.set_emitting(true);
|
|
}
|
|
self.is_dead = true;
|
|
}
|
|
|
|
fn swap_weapon(&mut self, weapon: WeaponType) {
|
|
if let Some(mesh) = &mut self.soldier_mesh {
|
|
match weapon {
|
|
WeaponType::Raygun => mesh.bind_mut().spawn_raygun(),
|
|
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 drop_ball(&mut self, with_velocity: bool) -> bool {
|
|
self.drop_ball_common(with_velocity)
|
|
}
|
|
|
|
fn get_ball_origin(&self) -> Option<Gd<Node3D>> {
|
|
self.ball_origin.clone()
|
|
}
|
|
}
|
|
|
|
#[godot_api]
|
|
impl ICharacterBody3D for RemotePlayer {
|
|
fn ready(&mut self) {
|
|
self.swap_weapon(WeaponType::Raygun);
|
|
}
|
|
|
|
fn process(&mut self, delta: f64) {
|
|
if self.is_dead {
|
|
self.dead_for += delta;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if let Some(ball_origin) = &mut self.ball_origin {
|
|
ball_origin.set_rotation(Vector3::RIGHT * self.look_up);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
|
|
#[derive(Clone, 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,
|
|
}
|