680 lines
23 KiB
Rust
680 lines
23 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::{
|
|
NetworkManager,
|
|
Package::{self, Kill, SelfSync, SpawnPlayer, TakeDamage},
|
|
PeerKind, SyncPackage,
|
|
},
|
|
util::NetVector3,
|
|
},
|
|
player::{DamageSource, IPlayer, LocalPlayer, NetTransform, RemotePlayer},
|
|
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);
|
|
}
|
|
}
|
|
|
|
#[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>>,
|
|
|
|
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 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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer {
|
|
if let PeerKind::Server(peer, _) = peer {
|
|
for player in self.players.clone() {
|
|
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_map_changed(id: u8);
|
|
|
|
pub fn singleton() -> Option<Gd<Game>> {
|
|
GameManager::singleton()
|
|
.get_child(0)
|
|
.map(|g| g.cast::<Game>())
|
|
}
|
|
|
|
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);
|
|
if emit {
|
|
self.run_deferred(move |s| s.signals().on_map_changed().emit(map_idx));
|
|
}
|
|
}
|
|
}
|
|
|
|
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: 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 {
|
|
id,
|
|
connection_addr: addr,
|
|
name,
|
|
ping: 0,
|
|
ready: false,
|
|
character: None,
|
|
});
|
|
self.run_deferred(move |s| s.signals().on_new_player().emit(id));
|
|
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.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.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);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn spawn_ball(&mut self, transform: Option<NetTransform>) {
|
|
if let Some(map) = &mut self.current_map {
|
|
let ball = map.bind_mut().spawn_ball(transform);
|
|
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(|_| {
|
|
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()));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
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 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 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>) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 id: u16,
|
|
pub connection_addr: SocketAddr,
|
|
pub name: String,
|
|
pub ping: u128,
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct NetPlayer {
|
|
pub id: u16,
|
|
pub name: String,
|
|
pub ping: u128,
|
|
}
|
|
|
|
impl From<NetPlayer> for Player {
|
|
fn from(value: NetPlayer) -> Self {
|
|
Player {
|
|
id: value.id,
|
|
connection_addr: SocketAddr::from(([0, 0, 0, 0], 0)),
|
|
name: value.name,
|
|
ping: value.ping,
|
|
ready: false,
|
|
character: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Player> for NetPlayer {
|
|
fn from(value: Player) -> Self {
|
|
NetPlayer {
|
|
id: value.id,
|
|
name: value.name,
|
|
ping: value.ping,
|
|
}
|
|
}
|
|
}
|