quakeball-rust/rust/src/game_manager.rs
2026-07-22 04:06:56 +03:00

856 lines
28 KiB
Rust

use std::{collections::HashMap, net::SocketAddr};
use godot::{classes::CharacterBody3D, prelude::*, tools::get_autoload_by_name};
use serde::{Deserialize, Serialize};
use crate::{
ball::Ball,
map::Map,
map_resource::MapResource,
net::{
network_manager::{
BallSync, NetworkManager,
Package::{self, Kill, SelfSync, SetTeam, SpawnPlayer, TakeDamage},
PeerKind, SyncPackage,
},
util::NetVector3,
},
player::{DamageSource, IPlayer, LocalPlayer, NetTransform, RemotePlayer},
team_resource::TeamResource,
weapon::WeaponType,
};
pub const GAME_MANAGER_GLOBAL: &str = "GameManagerGlobal";
#[derive(GodotClass)]
#[class(base=Node, init)]
pub struct GameManager {
#[export]
pub respawn_timer: f64,
pub game: Option<Gd<Game>>,
base: Base<Node>,
#[export]
pub maps: Array<Gd<MapResource>>,
}
#[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) {
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));
}
});
self.base_mut().add_child(&game);
self.game = Some(game);
}
}
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));
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,
}
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",
}
.to_owned()
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum GameOptionValue {
Boolean(bool),
}
#[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>,
game_started: bool,
pub current_map: Option<Gd<Map>>,
pub world_ball: Option<Gd<Ball>>,
since_last_sync: f64,
base: Base<Node>,
}
#[godot_api]
impl INode for Game {
fn physics_process(&mut self, delta: f64) {
self.since_last_sync += delta;
if self.since_last_sync > (1. / 60.) {
self.since_last_sync = 0.;
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(),
) {
sync_data.insert(
player.id(),
SyncPackage {
movement_direction: dir,
look_up,
transform,
},
);
}
}
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(),
));
}
}
}
}
}
}
}
}
#[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();
pub fn singleton() -> Option<Gd<Game>> {
GameManager::singleton()
.get_child(0)
.map(|g| g.cast::<Game>())
}
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 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);
}
}
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);
}
}
});
}
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,
character: None,
});
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) {
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.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);
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);
});
}
}
}
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 ball = map.bind_mut().spawn_ball(transform, velocity, player_id);
if let Some(ball) = &ball {
self.world_ball = Some(ball.clone());
ball.signals()
.on_ball_pickup()
.connect_other(self, |s, player_id| s.handle_ball_pickup(player_id));
}
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(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_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));
}
}
// Handle a swap-weapon command package
pub fn handle_swap_weapon(
&mut self,
player_id: u16,
weapon_type: WeaponType,
allow_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
{
character.dyn_bind_mut().swap_weapon(weapon_type);
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 let Some(player) = self.find_player_mut(id)
&& let Some(character) = &mut player.character
{
// This is a respawn, remove old character
character.queue_free();
}
if let Some(mut map) = self.current_map.clone() {
let mut character =
map.bind_mut()
.spawn_player(id, id == self.self_id.unwrap_or(0), None);
if let Some(character) = &mut character {
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| {
s.on_death(DamageSource::from_signal(signal, spid), id)
},
);
local_player
.bind_mut()
.signals()
.on_take_damage()
.connect_other(self, move |s, signal, spid, damage| {
s.on_take_damage(DamageSource::from_signal(signal, spid), 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| {
s.on_death(DamageSource::from_signal(signal, spid), id)
},
);
remote_player
.bind_mut()
.signals()
.on_take_damage()
.connect_other(self, move |s, signal, spid, damage| {
s.on_take_damage(DamageSource::from_signal(signal, spid), 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 let Some(map) = &mut self.current_map {
if let Some(player) = self.players.iter_mut().find(|p| p.id() == id) {
if let Some(character) = &mut player.character {
// This is a respawn, delete old character
character.queue_free();
}
let character = map.bind_mut().spawn_player(
player.id(),
player.id() == self.self_id.unwrap_or(0),
Some(transform),
);
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,
},
) in sync_data
{
if let Some(player) = self.players.iter_mut().find(|p| p.id() == player_id) {
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(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 let Some(player) = self.find_player_mut(player_id) {
if player.id() != self_id {
player.try_shoot(to, transform, look_up, deal_damage)
} else {
false
}
} else {
false
}
}
// Handle taking damage, from server to client
pub fn handle_take_damage(&mut self, source: DamageSource, player_id: u16, damage: i32) {
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);
}
}
}
// 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(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, target_player_id));
}
}
}
}
#[derive(Clone, Debug)]
pub struct Player {
pub connection_addr: SocketAddr,
pub data: NetPlayer,
pub ready: bool,
pub character: Option<DynGd<CharacterBody3D, dyn IPlayer + 'static>>,
}
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(),
})
} else {
None
}
}
pub fn apply_self_sync(&mut self, data: SyncPackage) {
if let Some(character) = &mut self.character {
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 id(&self) -> u16 {
self.data.id
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NetPlayer {
pub id: u16,
pub name: String,
pub ping: u128,
pub team: u8,
}
impl From<NetPlayer> for Player {
fn from(value: NetPlayer) -> Self {
Player {
connection_addr: SocketAddr::from(([0, 0, 0, 0], 0)),
data: value,
ready: false,
character: None,
}
}
}
impl From<Player> for NetPlayer {
fn from(value: Player) -> Self {
value.data
}
}