From 154c5fa74389786ee05928cd30384bf798b56ab0 Mon Sep 17 00:00:00 2001 From: Sofia Date: Thu, 16 Jul 2026 07:17:07 +0300 Subject: [PATCH] Add GameManager and Game --- godot/project.godot | 1 + godot/scenes/game_manager.tscn | 3 + godot/scenes/lobby.tscn | 52 ++++++++++++++ godot/scenes/network_manager.tscn | 3 + rust/src/game_manager.rs | 113 ++++++++++++++++++++++++++++++ rust/src/lib.rs | 1 + rust/src/net/network_manager.rs | 85 +++++++++++++++++++--- rust/src/ui/cli.rs | 6 +- rust/src/ui/lobby.rs | 19 +++++ rust/src/ui/mod.rs | 1 + teanet-rust | 2 +- 11 files changed, 272 insertions(+), 14 deletions(-) create mode 100644 godot/scenes/game_manager.tscn create mode 100644 godot/scenes/lobby.tscn create mode 100644 rust/src/game_manager.rs create mode 100644 rust/src/ui/lobby.rs diff --git a/godot/project.godot b/godot/project.godot index aae87a9..4e9e218 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -19,6 +19,7 @@ config/icon="res://icon.svg" NetworkManagerGlob="*uid://cxuy526twid8s" CommandLinePanelGlobal="*uid://bsb6asp5o35w3" +GameManagerGlobal="*uid://bii6018k3ip3t" [display] diff --git a/godot/scenes/game_manager.tscn b/godot/scenes/game_manager.tscn new file mode 100644 index 0000000..82652f0 --- /dev/null +++ b/godot/scenes/game_manager.tscn @@ -0,0 +1,3 @@ +[gd_scene format=3 uid="uid://bii6018k3ip3t"] + +[node name="game_manager" type="GameManager" unique_id=1442044769] diff --git a/godot/scenes/lobby.tscn b/godot/scenes/lobby.tscn new file mode 100644 index 0000000..e25ef4e --- /dev/null +++ b/godot/scenes/lobby.tscn @@ -0,0 +1,52 @@ +[gd_scene format=3 uid="uid://bwd6bj21x310s"] + +[sub_resource type="LabelSettings" id="LabelSettings_1cgct"] +font_size = 32 + +[node name="lobby" type="LobbyPanel" unique_id=915192272 node_paths=PackedStringArray("players_list")] +players_list = NodePath("v_box_container/players_list") +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="label" type="Label" parent="." unique_id=1445541926] +layout_mode = 1 +anchors_preset = 10 +anchor_right = 1.0 +offset_top = 69.0 +offset_bottom = 92.0 +grow_horizontal = 2 +text = "Lobby" +label_settings = SubResource("LabelSettings_1cgct") +horizontal_alignment = 1 + +[node name="v_box_container" type="VBoxContainer" parent="." unique_id=336946640] +layout_mode = 0 +offset_left = 178.0 +offset_top = 189.0 +offset_right = 239.0 +offset_bottom = 229.0 + +[node name="players_label" type="Label" parent="v_box_container" unique_id=1537542117] +layout_mode = 2 +text = "Players:" + +[node name="players_list" type="VBoxContainer" parent="v_box_container" unique_id=1834737379] +layout_mode = 2 + +[node name="h_box_container" type="HBoxContainer" parent="." unique_id=1789302658] +layout_mode = 0 +offset_left = 221.0 +offset_top = 512.0 +offset_right = 261.0 +offset_bottom = 552.0 + +[node name="label" type="Label" parent="h_box_container" unique_id=626633696] +layout_mode = 2 +text = "Name:" + +[node name="text_edit" type="TextEdit" parent="h_box_container" unique_id=683714912] +custom_minimum_size = Vector2(150, 0) +layout_mode = 2 diff --git a/godot/scenes/network_manager.tscn b/godot/scenes/network_manager.tscn index e9dc72a..a985f4c 100644 --- a/godot/scenes/network_manager.tscn +++ b/godot/scenes/network_manager.tscn @@ -1,3 +1,6 @@ [gd_scene format=3 uid="uid://cxuy526twid8s"] +[ext_resource type="PackedScene" uid="uid://bwd6bj21x310s" path="res://scenes/lobby.tscn" id="1_cemok"] + [node name="network_manager" type="NetworkManager" unique_id=1177599919] +lobby_scene = ExtResource("1_cemok") diff --git a/rust/src/game_manager.rs b/rust/src/game_manager.rs new file mode 100644 index 0000000..f368c15 --- /dev/null +++ b/rust/src/game_manager.rs @@ -0,0 +1,113 @@ +use std::net::SocketAddr; + +use godot::{prelude::*, tools::get_autoload_by_name}; +use serde::{Deserialize, Serialize}; + +pub const GAME_MANAGER_GLOBAL: &str = "GameManagerGlobal"; + +#[derive(GodotClass)] +#[class(base=Node, init)] +pub struct GameManager { + pub game: Option>, + base: Base, +} + +#[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); + + pub fn singleton() -> Gd { + get_autoload_by_name::(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; + self.base_mut().add_child(&game); + + game.signals() + .on_new_player() + .connect_other(self, |s, id| s.signals().on_new_player().emit(id)); + game.signals() + .on_player_disconnected() + .connect_other(self, |s, id| s.signals().on_player_disconnected().emit(id)); + game.signals() + .on_players_updated() + .connect_other(self, |s| s.signals().on_players_updated().emit()); + + self.game = Some(game); + } +} + +#[derive(GodotClass)] +#[class(base=Node, init)] +pub struct Game { + is_host: bool, + player_counter: u16, + self_id: Option, + pub players: Vec, + + base: Base, +} + +#[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); + + pub fn set_self(&mut self, id: u16) { + self.self_id = Some(id); + } + + pub fn new_player(&mut self, addr: SocketAddr, name: String) -> u16 { + let id = self.player_counter; + self.player_counter += 1; + self.players.push(Player { + id, + connection_addr: addr, + name, + }); + self.signals().on_new_player().emit(id); + id + } + + pub fn set_players(&mut self, players: Vec) { + self.players = players; + self.signals().on_players_updated().emit(); + } + + pub fn find_player_by_addr(&self, addr: &SocketAddr) -> Option<&Player> { + self.players.iter().find(|p| p.connection_addr == *addr) + } + + pub fn remove_player(&mut self, id: u16) { + self.signals().on_new_player().emit(id); + self.players.retain(|p| p.id != 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.signals().on_players_updated().emit(); + } +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct Player { + pub id: u16, + pub connection_addr: SocketAddr, + pub name: String, +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 5c5b6bc..efec1d3 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2,6 +2,7 @@ use std::net::SocketAddr; use godot::prelude::*; +pub mod game_manager; pub mod net; pub mod ui; diff --git a/rust/src/net/network_manager.rs b/rust/src/net/network_manager.rs index 644ae1f..9eb07de 100644 --- a/rust/src/net/network_manager.rs +++ b/rust/src/net/network_manager.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use teanet::{Peer, PeerConfig}; use crate::{ + game_manager::{Game, GameManager, Player}, net::net_stats::Stats, ui::cli::{CLI_GLOBAL_NAME, CliColor, CommandLinePanel}, }; @@ -15,7 +16,10 @@ use crate::{ #[derive(GodotClass)] #[class(base=Node)] pub struct NetworkManager { - peer: Option, + #[export] + lobby_scene: Option>, + + pub peer: Option, last_hello: Instant, last_netstat_update: Instant, @@ -29,6 +33,8 @@ pub const NETWORK_SINGLETON_NAME: &str = "NetworkManagerGlob"; impl INode for NetworkManager { fn init(base: Base) -> Self { NetworkManager { + lobby_scene: None, + peer: None, last_hello: Instant::now(), @@ -55,9 +61,9 @@ impl INode for NetworkManager { .connect_other(self, |s| s.disconnect()); } - fn process(&mut self, delta: f64) { + fn process(&mut self, _delta: f64) { if let Some(peer) = &mut self.peer { - let mut cli = get_autoload_by_name::(CLI_GLOBAL_NAME); + let mut cli = CommandLinePanel::singleton(); let now = Instant::now(); if now - self.last_netstat_update > Duration::from_millis(1000) { @@ -88,6 +94,7 @@ impl INode for NetworkManager { format!("Connected to: {}", connection.address), CliColor::Info, ); + self.swap_to_lobby(false); } teanet::PeerMessage::Disconnected(connection, connection_error) => { cli.bind_mut().publish_message( @@ -104,6 +111,7 @@ impl INode for NetworkManager { } teanet::PeerMessage::Closed => { self.peer = None; + GameManager::singleton().bind_mut().game = None; } teanet::PeerMessage::Message(msg) => match msg { Package::Hello => { @@ -112,6 +120,21 @@ impl INode for NetworkManager { CliColor::Info, ); } + Package::Players(players, self_id) => { + if let Some(game) = + &mut GameManager::singleton().bind_mut().game + { + game.bind_mut().set_players(players); + game.bind_mut().set_self(self_id); + } + } + Package::SetNick(id, nick) => { + if let Some(game) = + &mut GameManager::singleton().bind_mut().game + { + game.bind_mut().update_player_nick(id, nick); + } + } }, } } @@ -130,6 +153,17 @@ impl INode for NetworkManager { format!("Client connected from: {}", connection.address), CliColor::Info, ); + if let Some(game) = + &mut GameManager::singleton().bind_mut().game + { + let self_id = game + .bind_mut() + .new_player(connection.address, String::new()); + peer.send_reliable( + &connection.address, + Package::Players(game.bind().players.clone(), self_id), + ); + } } teanet::PeerMessage::Disconnected(connection, connection_error) => { cli.bind_mut().publish_message( @@ -142,19 +176,26 @@ impl INode for NetworkManager { CliColor::Error, ); } + if let Some(game) = + &mut GameManager::singleton().bind_mut().game + { + let player_id = game + .bind() + .find_player_by_addr(&connection.address) + .map(|p| p.id); + if let Some(player_id) = player_id { + game.bind_mut().remove_player(player_id); + } + } } teanet::PeerMessage::Closed => { cli.bind_mut() .publish_message(format!("Server closed"), CliColor::Info); self.peer = None; + GameManager::singleton().bind_mut().game = None; } teanet::PeerMessage::Message(msg) => match msg { - Package::Hello => { - cli.bind_mut().publish_message( - format!("Got \"Hello\"!"), - CliColor::Info, - ); - } + _ => {} }, } } @@ -170,9 +211,13 @@ impl INode for NetworkManager { } impl NetworkManager { + pub fn singleton() -> Gd { + get_autoload_by_name::(NETWORK_SINGLETON_NAME) + } + pub fn host(&mut self, port: u16) { if self.peer.is_none() { - let mut cli = get_autoload_by_name::(CLI_GLOBAL_NAME); + let mut cli = CommandLinePanel::singleton(); cli.bind_mut() .publish_message(format!("Hosting server at {}", port), CliColor::Info); let peer = Peer::listen(Some(port), PeerConfig::default()); @@ -182,6 +227,15 @@ impl NetworkManager { cli.bind_mut() .publish_message(format!("Server hosted"), CliColor::Info); + + self.swap_to_lobby(true); + + if let Some(game) = &mut GameManager::singleton().bind_mut().game { + let id = game + .bind_mut() + .new_player(SocketAddr::from(([0, 0, 0, 0], 0)), "Host".to_owned()); + game.bind_mut().set_self(id); + } } Err(err) => { cli.bind_mut() @@ -193,7 +247,7 @@ impl NetworkManager { pub fn join(&mut self, addr: SocketAddr) { if self.peer.is_none() { - let mut cli = get_autoload_by_name::(CLI_GLOBAL_NAME); + let mut cli = CommandLinePanel::singleton(); cli.bind_mut() .publish_message(format!("Joining server at {}", addr), CliColor::Info); let peer = Peer::listen(None, PeerConfig::default()); @@ -218,11 +272,20 @@ impl NetworkManager { } } } + + pub fn swap_to_lobby(&self, is_host: bool) { + if let Some(scene) = &self.lobby_scene { + self.base().get_tree().change_scene_to_packed(scene); + GameManager::singleton().bind_mut().init_game(is_host); + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Package { Hello, + Players(Vec, u16), + SetNick(u16, String), } pub enum PeerKind { diff --git a/rust/src/ui/cli.rs b/rust/src/ui/cli.rs index 40ca474..01be6d1 100644 --- a/rust/src/ui/cli.rs +++ b/rust/src/ui/cli.rs @@ -7,8 +7,6 @@ use godot::classes::{Engine, IPanel, Panel, RichTextLabel, ScrollContainer, Text use godot::prelude::*; use godot::tools::get_autoload_by_name; -use crate::net::network_manager::{NETWORK_SINGLETON_NAME, NetworkManager}; - pub enum CliColor { Command, Info, @@ -95,6 +93,10 @@ impl CommandLinePanel { #[signal] pub fn on_disconnect(); + pub fn singleton() -> Gd { + get_autoload_by_name::(CLI_GLOBAL_NAME) + } + pub fn input_command(&mut self, command: String) { self.publish_message(format!("> {}", command), CliColor::Command); if let Some(input) = &mut self.input_field { diff --git a/rust/src/ui/lobby.rs b/rust/src/ui/lobby.rs new file mode 100644 index 0000000..d96e16b --- /dev/null +++ b/rust/src/ui/lobby.rs @@ -0,0 +1,19 @@ +use godot::{ + classes::{IPanel, Panel, VBoxContainer}, + prelude::*, +}; + +#[derive(GodotClass)] +#[class(base=Panel, init)] +pub struct LobbyPanel { + #[export] + players_list: Option>, + + base: Base, +} + +#[godot_api] +impl IPanel for LobbyPanel { + fn ready(&mut self) {} + fn process(&mut self, delta: f64) {} +} diff --git a/rust/src/ui/mod.rs b/rust/src/ui/mod.rs index 68b43db..324513a 100644 --- a/rust/src/ui/mod.rs +++ b/rust/src/ui/mod.rs @@ -1,2 +1,3 @@ pub mod cli; +pub mod lobby; pub mod net_stats; diff --git a/teanet-rust b/teanet-rust index 3551d0e..a348dde 160000 --- a/teanet-rust +++ b/teanet-rust @@ -1 +1 @@ -Subproject commit 3551d0e50b598e96fb3ffbb5cce5ad5792f891ae +Subproject commit a348dde42e9973488f89d1d89e4f0f24138654a6