use godot::{ classes::{ Area3D, AudioStreamPlayer3D, Camera3D, CharacterBody3D, CollisionShape3D, ICharacterBody3D, Label3D, Sprite3D, TextureProgressBar, }, obj::WithBaseField, prelude::*, }; use crate::{ game::{Game, opts::GameOption}, killbox::KillboxKind, net::network_manager::NetworkManager, pickup::PickupKind, player::{ DamageSource, DamageSourceSignal, IPlayer, NetTransform, PlayerCommon, buffs::{Buff, Buffs}, effects::PlayerEffects, shootable::Shootable, soldier_mesh::SoldierMesh, telegrenade::Telegrenade, weapon::{Weapon, WeaponType}, }, }; #[derive(GodotClass)] #[class(base=CharacterBody3D, init)] pub struct RemotePlayer { #[export] move_speed: f32, #[export] soldier_mesh: Option>, #[export] collider: Option>, #[export] effects: Option>, #[export] health: i32, #[export] punch_cooldown: f64, #[export] pub spectator_camera: Option>, #[export] camera_origin: Option>, #[export] name_label: Option>, #[export] health_bar: Option>, #[export] double_health_bar: Option>, #[export] health_bar_sprite: Option>, #[export] double_health_bar_sprite: Option>, #[export] ball_pickup_sfx: Option>, #[export] ball_throw_sfx: Option>, #[export] arrow: Option>, #[export] telefrag_area_prefab: Option>, pub player_id: Option, pub player_name: String, pub player_color: Color, pub replay_mode: bool, movement_dir: Vector3, y_speed: f32, look_up: f32, is_dead: bool, dead_for: f64, punch_cd_remaining: f64, #[export] telegrenade_cd: f64, telegrenade: Option>, telegrenade_cd_remaining: f64, telegrenades_allowed: bool, telefrag_area: Option>, telefrag_frames: u8, buffs: Buffs, base: Base, } #[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 { 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 { if self.buffs.can_shoot() { self.try_shoot_common(to, deal_damage) } else { false } } fn try_throw_telegrenade(&mut self, to: Vector3) -> bool { if !self.telegrenades_allowed { return false; } if self.telegrenade_cd_remaining > 0. { return false; } if self.throw_telegrenade_common(to) { self.telegrenade_cd_remaining = self.telegrenade_cd; true } else { false } } 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); } else { if let Some(effects) = &mut s.effects { effects.bind_mut().hit(); } } } s.update_health_bar(); }); } fn is_dead(&self) -> bool { self.is_dead } fn dead_for(&self) -> f64 { self.dead_for } fn kill(&mut self, source: DamageSource) { if !self.replay_mode { 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_sprite) = &mut self.health_bar_sprite { healthbar_sprite.set_visible(false); } if let Some(double_health_bar_sprite) = &mut self.double_health_bar_sprite { double_health_bar_sprite.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(arrow) = &mut self.arrow { arrow.set_visible(false); } 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(); if let Some(ball_pickup_sfx) = &mut self.ball_pickup_sfx { ball_pickup_sfx.play(); } if let Some(arrow) = &mut self.arrow { arrow.set_visible(true); } } }; } } fn get_weapon(&self) -> Option> { self.soldier_mesh .as_ref() .and_then(|m| m.bind().get_weapon()) } fn take_weapon(&mut self) -> Option> { self.soldier_mesh .as_mut() .and_then(|m| m.bind_mut().take_weapon().cloned()) } fn drop_ball(&mut self, with_velocity: bool) -> bool { if self.drop_ball_common(with_velocity) && with_velocity && let Some(sfx) = &mut self.ball_throw_sfx { sfx.play(); true } else { false } } fn get_camera_origin(&self) -> Option> { self.camera_origin.clone() } fn is_replay_mode(&self) -> bool { self.replay_mode } fn get_health(&self) -> i32 { self.health } fn heal(&mut self, amount: i32) { self.run_deferred(move |s| { let delta = (s.health + amount).min(200) - s.health; s.run_deferred(move |s| s.signals().on_heal().emit(delta)); s.health += delta; s.update_health_bar(); }); } fn on_pickup(&mut self, kind: PickupKind) { match kind { PickupKind::Health => self.heal(100), } } fn set_telegrenade(&mut self, grenade: Option>) { self.telegrenade = grenade; } fn get_telegrenade(&self) -> Option> { self.telegrenade.clone() } fn telegrenade_teleport(&mut self, to: Vector3) -> bool { self.telegrenade_teleport_common(to) } fn as_gd(&self) -> DynGd { self.to_gd().into_dyn().upcast() } fn set_bubble_color(&mut self, color: Color) { if let Some(mesh) = &mut self.soldier_mesh { mesh.bind_mut().set_bubble_color(color); } } fn get_buffs(&self) -> &Buffs { &self.buffs } fn get_buffs_mut(&mut self) -> &mut Buffs { &mut self.buffs } fn new_buff(&mut self, buff: Buff) { self.buffs.new_buff(buff); self.buffs.clone().update_effects(self); } fn set_buffs(&mut self, buffs: Buffs) { self.buffs = buffs.clone(); buffs.update_effects(self); } fn set_telefrag_frame(&mut self) { if let Some(prefab) = &self.telefrag_area_prefab { let area = prefab.instantiate_as::(); area.signals() .body_entered() .connect_other(self, |s, other| s.on_body_entered(other)); self.base_mut().add_child(&area); self.telefrag_frames = 1; self.telefrag_area = Some(area); } } } #[godot_api] impl ICharacterBody3D for RemotePlayer { fn ready(&mut self) { self.run_deferred(|s| { if let Some(name_label) = &mut s.name_label { name_label.set_text(&s.player_name); name_label.set_modulate(s.player_color); } if let Some(mesh) = &mut s.soldier_mesh { mesh.bind_mut().set_color(s.player_color); } }); self.swap_weapon(WeaponType::Raygun, false); self.update_health_bar(); self.run_deferred(|s| { s.telegrenades_allowed = if let Some(game) = Game::singleton() { game.bind().opts.is_team_true( game.bind().get_player_team(s.player_id.unwrap_or(0)), GameOption::TelegrenadesAllowed, ) } else { false }; }); if let Some(arrow) = &mut self.arrow { arrow.set_visible(false); } self.buffs.clone().update_effects(self); } fn process(&mut self, delta: f64) { self.punch_cd_remaining -= delta; self.telegrenade_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(); } if self.buffs.update(delta) { self.buffs.clone().update_effects(self); } } fn physics_process(&mut self, delta: f64) { self.player_update(delta); if self.telefrag_frames == 0 { if let Some(area) = &mut self.telefrag_area.take() { godot_print!("removed area"); area.queue_free(); } } else { self.telefrag_frames -= 1; } } } #[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, ); #[signal] pub fn on_heal(amount: i32); pub fn update_health_bar(&mut self) { if let Some(health_bar) = &mut self.health_bar { let value = (self.health as f64 / 100.).clamp(0., 1.) * 100.; health_bar.set_value(value); } if let Some(double_health_bar) = &mut self.double_health_bar { let double_value = ((self.health as f64 - 100.) / 100.).clamp(0., 1.) * 100.; double_health_bar.set_value(double_value); } } pub fn set_hp(&mut self, health: i32) { self.health = health; self.update_health_bar(); } } #[godot_dyn] impl Shootable for RemotePlayer { fn on_shoot(&mut self, source: DamageSource, damage: i32, _: Vector3) { self.take_damage(source, damage, true); } }