skullball/rust/src/game_manager.rs
2026-07-28 21:13:07 +03:00

1518 lines
52 KiB
Rust

use std::{collections::HashMap, net::SocketAddr};
use godot::{classes::CharacterBody3D, prelude::*, tools::get_autoload_by_name};
use serde::{Deserialize, Serialize};
use crate::{
announcer::{
Announcement::{self, FirstBlood},
Announcer,
},
bgm::{Music, MusicManager},
chat::{Chat, ChatMessage},
map::Map,
map_resource::MapResource,
net::{
network_manager::{
BallSync, NetworkManager,
Package::{
self, Kill, SelfSync, SetTeam, SpawnPlayer, TakeDamage, TelegrenadeTeleport,
},
PeerKind, SyncPackage, TelegrenadeSync,
},
util::NetVector3,
},
pickup::PickupKind,
player::{
DamageSource, IPlayer, NetTransform, ball::Ball, local_player::LocalPlayer,
remote_player::RemotePlayer, telegrenade::Telegrenade, weapon::WeaponType,
},
replay::{ReplayEvent, ReplayPlayback, ReplayRecorder},
team_resource::TeamResource,
ui::cli::{Command, CommandLinePanel},
};
pub const GAME_MANAGER_GLOBAL: &str = "GameManagerGlobal";
#[derive(GodotClass)]
#[class(base=Node, init)]
pub struct GameManager {
#[export]
pub respawn_timer: f64,
#[export]
pub lobby_screen: Option<Gd<PackedScene>>,
#[export]
pub game_finished_screen: Option<Gd<PackedScene>>,
#[export]
pub main_menu: Option<Gd<PackedScene>>,
#[export]
pub maps: Array<Gd<MapResource>>,
#[export]
#[export_group(name = "Prefabs")]
pub local_player: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub remote_player: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub replay_player: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub ball: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub replay_ball: Option<Gd<PackedScene>>,
#[export]
#[export_group(name = "Prefabs")]
pub telegrenade: Option<Gd<PackedScene>>,
pub game: Option<Gd<Game>>,
base: Base<Node>,
exiting: bool,
}
#[godot_api]
impl GameManager {
#[signal]
pub fn on_players_updated();
#[signal]
pub fn on_new_player(id: u16);
#[signal]
pub fn on_player_disconnected(id: u16);
#[signal]
pub fn on_map_changed(id: u8);
pub fn singleton() -> Gd<GameManager> {
get_autoload_by_name::<GameManager>(GAME_MANAGER_GLOBAL)
}
pub fn init_game(&mut self, is_host: bool) {
self.exiting = false;
if let Some(mut game) = self.game.take() {
game.queue_free();
}
let mut game = Game::new_alloc();
game.bind_mut().is_host = is_host;
game.bind_mut().maps = self.maps.clone();
game.bind_mut().respawn_timer = self.respawn_timer;
self.run_deferred(|s| {
if let Some(game) = &s.game {
game.signals()
.on_new_player()
.connect_other(s, |s, id| s.signals().on_new_player().emit(id));
game.signals()
.on_player_disconnected()
.connect_other(s, |s, id| s.signals().on_player_disconnected().emit(id));
game.signals()
.on_players_updated()
.connect_other(s, |s| s.signals().on_players_updated().emit());
game.signals()
.on_map_changed()
.connect_other(s, |s, id| s.signals().on_map_changed().emit(id));
game.signals().on_game_finished().connect_other(s, |s| {
if !s.exiting {
s.go_to_finish_screen()
}
});
}
});
self.base_mut().add_child(&game);
self.game = Some(game);
}
pub fn exit_game(&mut self) {
self.exiting = true;
self.run_deferred(|s| {
if let Some(mut game) = s.game.take() {
game.bind_mut().finish_game();
game.queue_free();
s.go_to_main_menu();
}
});
}
pub fn go_to_finish_screen(&self) {
if let Some(scene) = &self.game_finished_screen {
self.base().get_tree().change_scene_to_packed(scene);
}
}
pub fn go_to_lobby(&self) {
if let Some(lobby) = &self.lobby_screen {
self.base().get_tree().change_scene_to_packed(lobby);
}
}
pub fn go_to_main_menu(&self) {
if let Some(scene) = &self.main_menu {
self.base().get_tree().change_scene_to_packed(scene);
}
}
}
#[derive(Debug)]
pub struct GameOptions {
pub values: HashMap<GameOption, GameOptionValue>,
}
impl Default for GameOptions {
fn default() -> Self {
let mut opts = HashMap::new();
opts.insert(GameOption::AllowAnyMap, GameOptionValue::Boolean(false));
opts.insert(GameOption::AllowAnyTeam, GameOptionValue::Boolean(false));
opts.insert(GameOption::FriendlyFire, GameOptionValue::Boolean(true));
opts.insert(GameOption::GoalsNeededToWin, GameOptionValue::Number(10.));
opts.insert(
GameOption::TelegrenadesAllowed,
GameOptionValue::Boolean(true),
);
Self { values: opts }
}
}
impl GameOptions {
pub fn is_true(&self, opt: GameOption) -> bool {
let opt = self
.values
.get(&opt)
.copied()
.unwrap_or(GameOptionValue::Boolean(false));
match opt {
GameOptionValue::Boolean(value) => value,
_ => false,
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Copy, Clone, Serialize, Deserialize)]
pub enum GameOption {
/// Allow anyone to select a map, not just the host
AllowAnyMap,
/// Allow anyone to select anyone's team, not just themselves and the host
AllowAnyTeam,
/// Allow anyone to shoot anyone, not just other team's members
FriendlyFire,
/// The number of goals needed to win a match
GoalsNeededToWin,
/// Whether telegrenades are allowed or not.
TelegrenadesAllowed,
}
impl ToString for GameOption {
fn to_string(&self) -> String {
match self {
GameOption::AllowAnyMap => "Allow anyone to select the map",
GameOption::AllowAnyTeam => "Allow anyone to select any teams",
GameOption::FriendlyFire => "Friendly fire",
GameOption::GoalsNeededToWin => "Goals needed to win",
GameOption::TelegrenadesAllowed => "Telegrenades allowed",
}
.to_owned()
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum GameOptionValue {
Boolean(bool),
Number(f64),
}
pub struct RespawnBallCommand;
impl Command for RespawnBallCommand {
fn execute(&self, _: &mut CommandLinePanel, _: &[&str]) {
if let Some(game) = &mut Game::singleton() {
game.bind_mut().respawn_ball();
}
}
fn help(&self) -> &str {
"respawns the ball"
}
}
#[derive(GodotClass)]
#[class(base=Node, init)]
pub struct Game {
#[export]
pub respawn_timer: f64,
pub selected_map_idx: u8,
pub selected_map: Option<Gd<MapResource>>,
pub maps: Array<Gd<MapResource>>,
pub opts: GameOptions,
is_host: bool,
player_counter: u16,
pub self_id: Option<u16>,
pub players: Vec<Player>,
pub game_started: bool,
pub current_map: Option<Gd<Map>>,
pub world_ball: Option<Gd<Ball>>,
pub ball_holder: Option<u16>,
pub goals: HashMap<u8, u16>,
pub replay_recorder: Option<Gd<ReplayRecorder>>,
pub current_replay_frame_start: usize,
pub current_replay_playback: Option<Gd<ReplayPlayback>>,
pub last_replay_clone: f64,
base: Base<Node>,
}
#[godot_api]
impl INode for Game {
fn ready(&mut self) {
let chat = Chat::new_alloc();
self.base_mut().add_child(&chat);
}
fn physics_process(&mut self, delta: f64) {
if let Some(_) = self.current_map {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
match peer {
PeerKind::Client(peer, socket_addr) => {
if let Some(player) = self
.players
.iter()
.find(|p| p.id() == self.self_id.unwrap_or(0))
{
if let Some(data) = &player.get_self_sync() {
peer.send_unreliable(socket_addr, SelfSync(data.clone()));
}
}
}
PeerKind::Server(peer, _) => {
let mut sync_data = HashMap::new();
for player in &self.players {
if let Some(character) = &player.character {
if character.dyn_bind().is_dead() {
continue;
}
}
if let (Some(dir), Some(look_up), Some(transform)) = (
player.get_move_dir(),
player.get_look_up(),
player.get_transform(),
) {
let telegrenade_sync = if let Some(character) = &player.character
&& let Some(telegrenade) =
character.dyn_bind().get_telegrenade()
{
Some(TelegrenadeSync {
transform: telegrenade.bind().get_transform(),
velocity: telegrenade.bind().get_velocity(),
})
} else {
None
};
sync_data.insert(
player.id(),
SyncPackage {
movement_direction: dir,
look_up,
transform,
telegrenade: telegrenade_sync,
},
);
}
}
peer.broadcast_unreliable(Package::Sync(
sync_data,
self.world_ball.as_ref().map(|b| b.bind().get_sync()),
));
}
}
}
}
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Server(peer, _) = peer {
for player in self.players.clone() {
let player_id = player.id();
if let Some(character) = player.character {
if character.dyn_bind().dead_for() > self.respawn_timer {
let character = self.spawn_player_server(player_id);
if let Some(character) = character {
peer.broadcast_reliable(SpawnPlayer(
player_id,
character.dyn_bind().get_transform(),
));
}
}
}
}
}
}
for player in &mut self.players {
player.killing_spree_cd -= delta;
if player.killing_spree_cd <= 0. {
player.killing_spree = 0;
}
}
if let Some(playback) = &mut self.current_replay_playback
&& let Some(recorder) = &self.replay_recorder
{
self.last_replay_clone += delta;
if self.last_replay_clone >= 1. {
playback.bind_mut().replay = recorder
.bind()
.replay
.clone_since(self.current_replay_frame_start);
self.last_replay_clone = 0.
}
}
}
}
#[godot_api]
impl Game {
#[signal]
pub fn on_players_updated();
#[signal]
pub fn on_new_player(id: u16);
#[signal]
pub fn on_player_disconnected(id: u16);
#[signal]
pub fn on_player_team_changed(id: u16, team: u8);
#[signal]
pub fn on_map_changed(id: u8);
#[signal]
pub fn options_changed();
#[signal]
pub fn on_goal();
#[signal]
pub fn on_game_finished();
#[signal]
pub fn on_player_killed(player: u16);
#[signal]
pub fn on_map_loaded(map: Gd<Map>);
pub fn singleton() -> Option<Gd<Game>> {
if GameManager::singleton().get_children().len() > 0 {
GameManager::singleton()
.get_child(0)
.map(|g| g.cast::<Game>())
} else {
None
}
}
pub fn start_playback(&mut self, time_delta: f64, spectated_id: u16) {
if let Some(recorder) = &self.replay_recorder {
let mut playback = ReplayPlayback::new_alloc();
let frame = recorder
.bind()
.replay
.find_frame(recorder.bind().time_elapsed + time_delta);
self.current_replay_frame_start = frame;
playback.bind_mut().replay = recorder.bind().replay.clone_since(frame);
playback.bind_mut().current_map = self.current_map.clone();
playback.bind_mut().current_time = (recorder.bind().time_elapsed + time_delta).max(0.);
playback.bind_mut().fast_forwarding = true;
playback.bind_mut().spectated_player_id = spectated_id;
self.current_replay_playback = Some(playback.clone());
self.base_mut().add_child(&playback);
}
for player in &mut self.players {
if let Some(character) = &mut player.character {
character.set_visible(false);
}
}
if let Some(ball) = &mut self.world_ball {
ball.set_visible(false);
}
}
pub fn stop_playback(&mut self) {
if let Some(mut playback) = self.current_replay_playback.take() {
playback.queue_free();
}
for player in &mut self.players {
if let Some(character) = &mut player.character {
character.set_visible(true);
}
}
if let Some(ball) = &mut self.world_ball {
ball.set_visible(true);
}
}
pub fn start_game(&mut self) {
self.game_started = true;
if let Some(map) = &self.selected_map {
if let Some(scene) = &map.bind().scene {
self.base().get_tree().change_scene_to_packed(scene);
}
}
MusicManager::singleton()
.bind_mut()
.play(Some(Music::Map(self.selected_map_idx)), false);
self.run_deferred(|_| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Server(peer, _) = peer {
peer.broadcast_reliable(Package::GameStarted);
peer.set_accepting_connections(false);
let mut cli = CommandLinePanel::singleton();
cli.bind_mut()
.register_command("respawn_ball".to_string(), Box::new(RespawnBallCommand));
}
}
});
let recorder = ReplayRecorder::new_alloc();
self.replay_recorder = Some(recorder.clone());
self.base_mut().add_child(&recorder);
}
pub fn reset_game(&mut self) {
if let Some(mut recorder) = self.replay_recorder.take() {
recorder.queue_free();
}
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
&& let Some(player) = self.find_player_mut(self.self_id.unwrap_or(0))
{
player.data.ready = true;
peer.broadcast_reliable(Package::SetReady(player.id(), true));
}
for player in &mut self.players {
player.data.kills = 0;
player.data.deaths = 0;
}
self.goals.clear();
}
pub fn finish_game(&mut self) {
if let Some(recorder) = &mut self.replay_recorder {
recorder.bind_mut().stop_recording();
}
self.game_started = false;
for player in &mut self.players {
if let Some(mut character) = player.character.take() {
character.queue_free();
}
player.data.ready = false;
}
self.run_deferred(|s| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
for player in &s.players {
peer.broadcast_reliable(Package::SetReady(player.id(), false));
}
}
});
if let Some(mut ball) = self.world_ball.take() {
ball.queue_free();
}
self.current_map.take();
self.run_deferred(|s| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
peer.set_accepting_connections(true);
peer.broadcast_reliable(Package::FinishGame);
}
s.signals().on_game_finished().emit()
});
let mut cli = CommandLinePanel::singleton();
cli.bind_mut()
.unregister_command("respawn_ball".to_string());
}
pub fn change_option(&mut self, opt: GameOption, value: GameOptionValue) {
self.opts.values.insert(opt, value);
self.run_deferred(move |s| {
s.signals().options_changed().emit();
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Server(peer, _) = peer {
peer.broadcast_reliable(Package::GameOptionChanged(opt, value));
}
}
});
}
pub fn change_map_selection(&mut self, map_idx: u8, emit: bool) {
self.selected_map_idx = map_idx;
if let Some(map) = self.maps.get(map_idx as usize) {
self.selected_map = Some(map);
let teams_max = (self.get_teams().len() - 1) as u8;
self.run_deferred(move |s| {
if NetworkManager::singleton().bind().is_host() {
for player in s.players.clone() {
s.run_deferred(move |s| {
s.try_set_player_team(
player.id(),
player.data.team.clamp(0, teams_max),
true,
)
});
}
}
});
if emit {
self.run_deferred(move |s| s.signals().on_map_changed().emit(map_idx));
}
}
}
pub fn get_teams(&self) -> Array<Gd<TeamResource>> {
if let Some(map) = &self.selected_map {
map.bind().teams.clone()
} else {
Array::new()
}
}
pub fn get_team(&self, team: u8) -> Option<Gd<TeamResource>> {
self.get_teams().get(team as usize)
}
pub fn try_set_player_team(&mut self, player_id: u16, team: u8, emit: bool) {
if let Some(player) = self.find_player_mut(player_id) {
player.data.team = team;
self.run_deferred(move |_| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
match peer {
PeerKind::Client(peer, socket_addr) => {
peer.send_reliable(socket_addr, SetTeam(player_id, team));
}
PeerKind::Server(peer, _) => {
peer.broadcast_reliable(SetTeam(player_id, team));
}
}
}
});
if emit {
self.run_deferred(move |s| {
s.signals().on_player_team_changed().emit(player_id, team)
});
}
}
}
pub fn handle_player_team_change(&mut self, player_id: u16, team: u8) {
if let Some(player) = self.find_player_mut(player_id) {
player.data.team = team;
self.run_deferred(move |s| s.signals().on_player_team_changed().emit(player_id, team));
}
}
pub fn set_self(&mut self, id: u16) {
self.self_id = Some(id);
}
pub fn new_player(&mut self, addr: SocketAddr, name: Option<String>, id: Option<u16>) -> u16 {
let id = if let Some(id) = id {
id
} else {
let id = self.player_counter;
self.player_counter += 1;
id
};
self.players.push(Player {
connection_addr: addr,
data: NetPlayer {
id,
name: name.unwrap_or(format!("Player {}", id)),
ping: 0,
team: 0,
ready: false,
kills: 0,
deaths: 0,
},
killing_spree: 0,
killing_spree_cd: 0.,
character: None,
last_held_weapon: WeaponType::Raygun,
});
self.run_deferred(move |s| s.signals().on_new_player().emit(id));
let team_with_least = self
.get_teams()
.iter_shared()
.enumerate()
.min_by(|(team_idx_a, _), (team_idx_b, _)| {
let a = self
.players
.iter()
.filter(|p| p.data.team == *team_idx_a as u8)
.count();
let b = self
.players
.iter()
.filter(|p| p.data.team == *team_idx_b as u8)
.count();
a.cmp(&b)
})
.map(|(team_idx, _)| team_idx)
.unwrap_or(0);
self.try_set_player_team(id, team_with_least as u8, true);
id
}
pub fn set_players(&mut self, players: Vec<Player>) {
self.players = players;
for player in self.players.clone() {
self.run_deferred(move |s| s.signals().on_new_player().emit(player.id()));
}
}
pub fn find_player_by_addr(&self, addr: &SocketAddr) -> Option<&Player> {
self.players.iter().find(|p| p.connection_addr == *addr)
}
pub fn find_player_by_addr_mut(&mut self, addr: &SocketAddr) -> Option<&mut Player> {
self.players.iter_mut().find(|p| p.connection_addr == *addr)
}
pub fn find_player(&self, player_id: u16) -> Option<&Player> {
self.players.iter().find(|p| p.id() == player_id)
}
pub fn find_player_mut(&mut self, player_id: u16) -> Option<&mut Player> {
self.players.iter_mut().find(|p| p.id() == player_id)
}
pub fn remove_player(&mut self, id: u16) {
if let Some(player) = self.find_player_mut(id)
&& let Some(mut character) = player.character.take()
{
character.dyn_bind_mut().drop_ball(false);
self.run_deferred(move |_| {
character.queue_free();
});
}
self.players.retain(|p| p.id() != id);
self.run_deferred(move |s| s.signals().on_player_disconnected().emit(id));
}
pub fn update_player_nick(&mut self, player_id: u16, nick: String) {
if let Some(player) = self.players.iter_mut().find(|p| p.id() == player_id) {
player.data.name = nick;
}
self.run_deferred(|s| s.signals().on_players_updated().emit());
}
pub fn update_player_ping(&mut self, player_id: u16, ping: u128) {
if let Some(player) = self.players.iter_mut().find(|p| p.id() == player_id) {
player.data.ping = ping;
}
self.run_deferred(|s| s.signals().on_players_updated().emit());
}
pub fn update_player_ready(&mut self, player_id: u16, ready: bool) {
if let Some(player) = self.players.iter_mut().find(|p| p.id() == player_id) {
player.data.ready = ready;
}
self.run_deferred(|s| s.signals().on_players_updated().emit());
}
pub fn on_map_loaded(&mut self, map: Gd<Map>) {
self.current_map = Some(map.clone());
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Client(peer, to) = peer {
peer.send_reliable(to, Package::MapLoaded);
} else {
self.run_deferred(|s| {
for player in s.players.clone() {
s.spawn_player_server(player.id());
}
s.spawn_ball(None, None, None);
});
}
}
self.signals().on_map_loaded().emit(&map);
}
pub fn spawn_ball(
&mut self,
transform: Option<NetTransform>,
velocity: Option<NetVector3>,
player_id: Option<u16>,
) {
if let Some(map) = &mut self.current_map {
let mut ball = map.bind_mut().spawn_ball(transform, velocity, player_id);
if let Some(ball) = &mut ball {
self.world_ball = Some(ball.clone());
ball.signals()
.on_ball_pickup()
.connect_other(self, |s, player_id| s.handle_ball_pickup(player_id));
if self.current_replay_playback.is_some() {
ball.set_visible(false);
}
}
self.run_deferred(move |_| {
if let Some(ball) = ball
&& let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
peer.broadcast_reliable(Package::SpawnBall(
ball.bind().get_transform(),
ball.bind().get_velocity(),
player_id,
));
}
});
}
}
pub fn despawn_ball(&mut self) {
if let Some(ball_holder) = self.ball_holder.take()
&& let Some(player) = self.find_player_mut(ball_holder).cloned()
{
self.handle_swap_weapon(ball_holder, player.last_held_weapon, false, false);
}
if let Some(mut ball) = self.world_ball.take() {
ball.queue_free();
self.run_deferred(|_| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
peer.broadcast_reliable(Package::DespawnBall);
}
});
}
}
pub fn respawn_ball(&mut self) {
self.despawn_ball();
self.spawn_ball(None, None, None);
}
pub fn handle_goal(&mut self, teams: Vec<u8>) {
for team in &teams {
self.goals
.insert(*team, self.goals.get(&team).copied().unwrap_or(0) + 1);
}
self.run_deferred(move |s| {
s.signals().on_goal().emit();
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Server(peer, _) = peer {
peer.broadcast_reliable(Package::Goal(teams));
}
}
if NetworkManager::singleton().bind().is_host() {
let max_goals = s.goals.values().max().copied().unwrap_or(0);
let needed_goals = match s
.opts
.values
.get(&GameOption::GoalsNeededToWin)
.unwrap_or(&&GameOptionValue::Number(10.))
{
GameOptionValue::Boolean(_) => 10,
GameOptionValue::Number(value) => *value as u16,
};
if max_goals >= needed_goals {
s.finish_game();
}
}
});
}
pub fn handle_ball_pickup(&mut self, player_id: u16) {
if NetworkManager::singleton().bind_mut().is_host() {
self.despawn_ball();
self.run_deferred(move |s| {
s.handle_swap_weapon(player_id, WeaponType::Ball, true, false)
});
self.ball_holder = Some(player_id);
}
}
// Handle a swap-weapon command package
pub fn handle_swap_weapon(
&mut self,
player_id: u16,
weapon_type: WeaponType,
allow_ball: bool,
drop_ball: bool,
) {
if weapon_type == WeaponType::Ball && !allow_ball {
return;
}
if let Some(player) = self.find_player_mut(player_id)
&& let Some(character) = &mut player.character
{
if weapon_type != WeaponType::Ball {
player.last_held_weapon = weapon_type;
}
character.dyn_bind_mut().swap_weapon(weapon_type, drop_ball);
let player_id = player.id();
self.run_deferred(move |_| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
peer.broadcast_reliable(Package::SwapWeapon(player_id, weapon_type));
}
});
}
}
pub fn spawn_player_server(&mut self, id: u16) -> Option<DynGd<CharacterBody3D, dyn IPlayer>> {
if id == self.self_id.unwrap_or(0) {
self.stop_playback();
}
if let Some(player) = self.find_player(id)
&& let Some(mut character) = player.character.clone()
{
// This is a respawn, remove old character
if let Some(mut recorder) = self.replay_recorder.clone()
&& let Some(state) = player.get_state(player.clone())
{
recorder
.bind_mut()
.record_event(ReplayEvent::Respawn(id, state));
}
character.queue_free();
}
if let Some(mut map) = self.current_map.clone() {
let team = self.find_player(id).map(|p| p.data.team).unwrap_or(0);
let mut character =
map.bind_mut()
.spawn_player(id, team, id == self.self_id.unwrap_or(0), None, true);
if let Some(character) = &mut character {
if self.current_replay_playback.is_some() {
character.set_visible(false);
}
if let Some(player) = self.find_player(id) {
character
.dyn_bind_mut()
.swap_weapon(player.last_held_weapon, true);
}
if let Ok(mut local_player) = character.clone().try_cast::<LocalPlayer>() {
local_player.bind_mut().signals().on_die().connect_other(
self,
move |s, signal, spid, kind| {
s.on_death(DamageSource::from_signal(signal, spid, kind), id)
},
);
local_player
.bind_mut()
.signals()
.on_take_damage()
.connect_other(self, move |s, signal, spid, kind, damage| {
s.on_take_damage(
DamageSource::from_signal(signal, spid, kind),
id,
damage,
)
});
} else if let Ok(mut remote_player) = character.clone().try_cast::<RemotePlayer>() {
remote_player.bind_mut().signals().on_die().connect_other(
self,
move |s, signal, spid, kind| {
s.on_death(DamageSource::from_signal(signal, spid, kind), id)
},
);
remote_player
.bind_mut()
.signals()
.on_take_damage()
.connect_other(self, move |s, signal, spid, kind, damage| {
s.on_take_damage(
DamageSource::from_signal(signal, spid, kind),
id,
damage,
)
});
}
}
if let Some(player) = self.find_player_mut(id) {
player.character = character.clone();
}
character
} else {
None
}
}
// Method called on the client-side whenever a player has spawned
pub fn spawn_player_client(&mut self, id: u16, transform: NetTransform) {
if id == self.self_id.unwrap_or(0) {
self.stop_playback();
}
if let Some(map) = &mut self.current_map {
if let Some(player) = self.players.iter_mut().find(|p| p.id() == id) {
if let Some(mut character) = player.character.clone() {
// This is a respawn, delete old character
if let Some(mut recorder) = self.replay_recorder.clone()
&& let Some(state) = player.get_state(player.clone())
{
recorder
.bind_mut()
.record_event(ReplayEvent::Respawn(id, state));
}
character.queue_free();
}
let mut character = map.bind_mut().spawn_player(
player.id(),
player.data.team,
player.id() == self.self_id.unwrap_or(0),
Some(transform),
true,
);
if let Some(character) = &mut character {
if self.current_replay_playback.is_some() {
character.set_visible(false);
}
character
.dyn_bind_mut()
.swap_weapon(player.last_held_weapon, true);
}
player.character = character;
}
}
}
// Handle a sync package from the server
pub fn handle_sync(
&mut self,
sync_data: HashMap<u16, SyncPackage>,
ball_sync: Option<BallSync>,
) {
for (
player_id,
SyncPackage {
movement_direction,
look_up,
transform,
telegrenade,
},
) in sync_data
{
if let Some(player) = self.players.iter_mut().find(|p| p.id() == player_id) {
if let Some(telegrenade_sync) = telegrenade
&& let Some(character) = &mut player.character
&& let Some(mut telegrenade) = character.dyn_bind().get_telegrenade()
{
telegrenade.set_global_position(telegrenade_sync.transform.location.into());
telegrenade.set_global_rotation(telegrenade_sync.transform.rotation.into());
telegrenade.set_linear_velocity(telegrenade_sync.velocity.into());
}
if player.id() != self.self_id.unwrap_or(0) {
// Sync remote players always
player.set_transform(transform);
player.set_move_dir(movement_direction);
player.apply_look_up(look_up);
} else {
// Sync local player only if it's far enough
if let Some(local_net_transform) = player.get_transform() {
let local_location: Vector3 = local_net_transform.location.into();
let remote_location: Vector3 = transform.location.into();
if local_location.distance_squared_to(remote_location) > 2. {
player.set_transform(transform);
}
}
}
}
}
if let Some(ball_sync) = ball_sync
&& let Some(ball) = &mut self.world_ball
{
ball.bind_mut().apply_sync(ball_sync);
}
}
// Handle a jump, either from the server or from a client
pub fn handle_jump(&mut self, player_id: u16) {
if player_id != self.self_id.unwrap_or(0) {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Jump(player_id));
}
if let Some(player) = self.find_player_mut(player_id) {
player.apply_jump();
}
}
}
// Handle shooting, either from the server of from a client
pub fn handle_shoot(
&mut self,
player_id: u16,
to: Vector3,
transform: NetTransform,
look_up: f32,
deal_damage: bool,
) -> bool {
let self_id = self.self_id.clone().unwrap_or(0);
if player_id != self_id {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Shoot(player_id, to.into()));
}
if let Some(player) = self.find_player_mut(player_id) {
player.try_shoot(to, transform, look_up, deal_damage)
} else {
false
}
} else {
false
}
}
// Handle shooting, either from the server of from a client
pub fn handle_punch(
&mut self,
player_id: u16,
to: Vector3,
transform: NetTransform,
look_up: f32,
deal_damage: bool,
) -> bool {
let self_id = self.self_id.clone().unwrap_or(0);
if player_id != self_id {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Punch(player_id, to.into()));
}
if let Some(player) = self.find_player_mut(player_id) {
player.try_punch(to, transform, look_up, deal_damage)
} else {
false
}
} else {
false
}
}
// Handle for when a player is killed, run both on client and server
pub fn on_kill(&mut self, source: DamageSource, player_id: u16) {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Death(source, player_id));
}
if let DamageSource::Player(source_player_id) = source
&& let Some(player) = self.find_player(source_player_id)
&& let Some(character) = player.character.clone()
&& let Ok(mut local_player) = character.try_cast::<LocalPlayer>()
{
local_player.bind_mut().play_local_hit_sound();
}
let total_prev_kills: u16 = self.players.iter().map(|p| p.data.kills).sum();
if let Some(chat) = &mut Chat::singleton() {
chat.bind_mut()
.new_message(ChatMessage::Death(source, player_id));
}
let killing_spree = if let DamageSource::Player(source_id) = source
&& let Some(source_player) = self.find_player_mut(source_id)
{
source_player.data.kills += 1;
source_player.killing_spree += 1;
source_player.killing_spree_cd = 10.;
source_player.killing_spree
} else {
0
};
if let Some(player) = self.find_player_mut(player_id) {
player.killing_spree = 0;
player.killing_spree_cd = 0.;
player.data.deaths += 1;
}
self.run_deferred(move |s| {
s.signals().on_player_killed().emit(player_id);
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
if total_prev_kills == 0 {
if let DamageSource::Player(source_id) = source {
s.on_announcement(source_id, Announcement::FirstBlood);
peer.broadcast_reliable(Package::Announcement(source_id, FirstBlood));
}
}
let announcement = match killing_spree {
2 => Some(Announcement::DoubleKill),
3 => Some(Announcement::TripleKill),
4 => Some(Announcement::MegaKill),
_ => None,
};
if let Some(announcement) = announcement {
if let DamageSource::Player(source_id) = source {
s.on_announcement(source_id, announcement);
peer.broadcast_reliable(Package::Announcement(source_id, announcement));
}
}
}
});
}
pub fn on_chat_message(&mut self, player_id: u16, message: String) {
if let Some(chat) = &mut Chat::singleton() {
if let Some(player) = self.find_player(player_id)
&& let Some(team) = self.get_team(player.data.team)
{
chat.bind_mut().new_message(ChatMessage::ChatMessage(
player.data.name.clone(),
team.bind().color,
message.clone(),
));
}
}
self.run_deferred(move |_| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
peer.broadcast_reliable(Package::ChatMessage(player_id, message.clone()));
}
});
}
pub fn on_announcement(&mut self, player_id: u16, announcement: Announcement) {
if let Some(chat) = &mut Chat::singleton() {
chat.bind_mut()
.new_message(ChatMessage::Announcement(player_id, announcement));
}
Announcer::singleton().bind_mut().announce(announcement);
}
// Handle taking damage, from server to client
pub fn handle_take_damage(&mut self, source: DamageSource, player_id: u16, damage: i32) {
if let Some(recorder) = &mut self.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::TakeDamage(player_id, source, damage));
}
if let Some(player) = self.find_player_mut(player_id) {
if let Some(character) = &mut player.character {
character.dyn_bind_mut().take_damage(source, damage, false);
}
}
}
// Handle dying, from server to client
pub fn handle_kill(&mut self, source: DamageSource, player_id: u16) {
if let Some(player) = self.find_player_mut(player_id) {
if let Some(character) = &mut player.character {
character.dyn_bind_mut().kill(source.clone());
self.on_kill(source, player_id);
}
}
}
// Called whenever a player takes damage on the server-side
pub fn on_take_damage(&mut self, source: DamageSource, target_player_id: u16, damage: i32) {
if let Some(recorder) = &mut self.replay_recorder {
recorder.bind_mut().record_event(ReplayEvent::TakeDamage(
target_player_id,
source,
damage,
));
}
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Server(peer, _) = peer {
peer.broadcast_reliable(TakeDamage(source, target_player_id, damage));
}
}
}
// Called whenever a player dies on the server-side
pub fn on_death(&mut self, source: DamageSource, target_player_id: u16) {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
if let PeerKind::Server(peer, _) = peer {
peer.broadcast_reliable(Kill(source.clone(), target_player_id));
self.on_kill(source, target_player_id);
}
}
}
pub fn recorder(&mut self) -> &mut Option<Gd<ReplayRecorder>> {
&mut self.replay_recorder
}
pub fn on_pickup(&mut self, id: u8, player_id: u16) {
self.run_deferred(move |s| {
let kind = if let Some(map) = &mut s.current_map
&& let Some(pickup) = map.bind_mut().pickups.get_mut(&id)
{
pickup.dyn_bind_mut().on_pickup();
pickup.dyn_bind().kind()
} else {
PickupKind::Health
};
if let Some(player) = s.find_player_mut(player_id)
&& let Some(character) = &mut player.character
{
character.dyn_bind_mut().on_pickup(kind);
}
if let Some(recorder) = &mut s.replay_recorder {
recorder
.bind_mut()
.record_event(ReplayEvent::Pickup(kind, player_id));
}
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
peer.broadcast_reliable(Package::Pickup(id, player_id));
}
});
}
pub fn spawn_telegrenade(
&mut self,
location: NetVector3,
velocity: NetVector3,
) -> Option<Gd<Telegrenade>> {
if let Some(map) = &mut self.current_map {
map.bind_mut().spawn_telegrenade(location, velocity)
} else {
None
}
}
pub fn on_throw_telegrenade(&mut self, player_id: u16, to: NetVector3) {
if let Some(player) = self.find_player_mut(player_id)
&& let Some(character) = &mut player.character
{
character.dyn_bind_mut().try_throw_telegrenade(to.into());
}
}
// Handle telegrenade activate from server to client
pub fn handle_telegrenade_teleport(&mut self, player_id: u16, to: NetVector3) {
if let Some(player) = self.find_player_mut(player_id)
&& let Some(character) = &mut player.character
{
character.dyn_bind_mut().telegrenade_teleport(to.into());
}
}
// Handle telegrenade activation from client to server
pub fn handle_telegrenade_activate(&mut self, player_id: u16) {
if let Some(player) = self.find_player_mut(player_id)
&& let Some(character) = &mut player.character
{
let pos = if let Some(telegrenade) = character.dyn_bind().get_telegrenade() {
telegrenade.get_global_position()
} else {
Vector3::ZERO
};
character.dyn_bind_mut().telegrenade_teleport(pos);
self.run_deferred(move |_| {
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
peer.broadcast_reliable(TelegrenadeTeleport(player_id, pos.into()));
}
});
}
}
}
#[derive(Clone, Debug)]
pub struct Player {
pub connection_addr: SocketAddr,
pub data: NetPlayer,
pub character: Option<DynGd<CharacterBody3D, dyn IPlayer + 'static>>,
pub killing_spree: u16,
pub killing_spree_cd: f64,
pub last_held_weapon: WeaponType,
}
impl Player {
pub fn get_transform(&self) -> Option<NetTransform> {
if let Some(character) = &self.character {
Some(character.dyn_bind().get_transform())
} else {
None
}
}
pub fn set_transform(&mut self, transform: NetTransform) {
if let Some(character) = &mut self.character {
character.dyn_bind_mut().apply_transform(transform);
}
}
pub fn get_move_dir(&self) -> Option<NetVector3> {
if let Some(character) = &self.character {
Some(character.dyn_bind().get_movement_dir().into())
} else {
None
}
}
pub fn get_look_up(&self) -> Option<f32> {
if let Some(character) = &self.character {
Some(character.dyn_bind().get_look_up())
} else {
None
}
}
pub fn set_move_dir(&mut self, dir: NetVector3) {
if let Some(character) = &mut self.character {
character.dyn_bind_mut().set_movement_dir(dir.into());
}
}
pub fn get_self_sync(&self) -> Option<SyncPackage> {
if let Some(character) = &self.character {
Some(SyncPackage {
movement_direction: character.dyn_bind().get_movement_dir().into(),
look_up: character.dyn_bind().get_look_up(),
transform: character.dyn_bind().get_transform(),
telegrenade: None,
})
} else {
None
}
}
pub fn apply_self_sync(&mut self, data: SyncPackage) {
if let Some(character) = &mut self.character {
let server_pos = character.get_global_position();
let client_pos: Vector3 = data.transform.location.into();
let max_distance = (self.data.ping as f32 / 1000.)
* (character.dyn_bind().get_move_speed() + character.dyn_bind().get_y_speed());
if client_pos.distance_to(server_pos) <= max_distance {
character.dyn_bind_mut().apply_position(client_pos);
}
character
.dyn_bind_mut()
.set_movement_dir(data.movement_direction.into());
character
.dyn_bind_mut()
.apply_rotation(data.transform.rotation.into());
character.dyn_bind_mut().apply_look_up(data.look_up);
}
}
pub fn apply_jump(&mut self) {
if let Some(character) = &mut self.character {
character.dyn_bind_mut().jump();
}
}
pub fn apply_look_up(&mut self, rot: f32) {
if let Some(character) = &mut self.character {
character.dyn_bind_mut().apply_look_up(rot);
}
}
pub fn try_shoot(
&mut self,
to: Vector3,
transform: NetTransform,
look_up: f32,
deal_damage: bool,
) -> bool {
if let Some(character) = &mut self.character {
character
.dyn_bind_mut()
.apply_rotation(transform.rotation.into());
character.dyn_bind_mut().apply_look_up(look_up);
character.dyn_bind_mut().try_shoot(to, deal_damage)
} else {
false
}
}
pub fn try_punch(
&mut self,
to: Vector3,
transform: NetTransform,
look_up: f32,
deal_damage: bool,
) -> bool {
if let Some(character) = &mut self.character {
character
.dyn_bind_mut()
.apply_rotation(transform.rotation.into());
character.dyn_bind_mut().apply_look_up(look_up);
character.dyn_bind_mut().try_punch(to, deal_damage)
} else {
false
}
}
pub fn id(&self) -> u16 {
self.data.id
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NetPlayer {
pub id: u16,
pub ready: bool,
pub name: String,
pub ping: u128,
pub team: u8,
pub kills: u16,
pub deaths: u16,
}
impl From<NetPlayer> for Player {
fn from(value: NetPlayer) -> Self {
Player {
connection_addr: SocketAddr::from(([0, 0, 0, 0], 0)),
data: value,
character: None,
killing_spree: 0,
killing_spree_cd: 0.,
last_held_weapon: WeaponType::Raygun,
}
}
}
impl From<Player> for NetPlayer {
fn from(value: Player) -> Self {
value.data
}
}