diff --git a/godot/scenes/lobby.tscn b/godot/scenes/lobby.tscn index 3401d64..e41e548 100644 --- a/godot/scenes/lobby.tscn +++ b/godot/scenes/lobby.tscn @@ -5,12 +5,17 @@ [sub_resource type="LabelSettings" id="LabelSettings_1cgct"] font_size = 32 -[node name="lobby" type="LobbyPanel" unique_id=915192272 node_paths=PackedStringArray("players_list", "name_field", "ready_button", "map_selection")] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jbm04"] +bg_color = Color(0.30692267, 0.3069227, 0.30692264, 1) + +[node name="lobby" type="LobbyPanel" unique_id=915192272 node_paths=PackedStringArray("players_list", "name_field", "ready_button", "map_selection", "options_panel", "options_grid")] players_list = NodePath("v_box_container/players_list") name_field = NodePath("v_box_container/h_box_container/text_edit") player_listing_prefab = ExtResource("1_o1atq") ready_button = NodePath("ready_button") map_selection = NodePath("v_box_container2/option_button") +options_panel = NodePath("options_panel") +options_grid = NodePath("options_panel/h_box_container/options_grid") anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 @@ -95,3 +100,65 @@ grow_horizontal = 0 [node name="option_button" type="OptionButton" parent="v_box_container2" unique_id=426342653] layout_mode = 2 metadata/_edit_lock_ = true + +[node name="options_panel" type="Panel" parent="." unique_id=1194589850] +visible = false +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -291.5 +offset_top = -213.5 +offset_right = 291.5 +offset_bottom = 213.5 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_jbm04") + +[node name="options_close" type="Button" parent="options_panel" unique_id=2115857774] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -33.5 +offset_bottom = 31.0 +grow_horizontal = 0 +text = "X" + +[node name="h_box_container" type="VBoxContainer" parent="options_panel" unique_id=1163233032] +layout_mode = 0 +offset_left = 0.5 +offset_top = 3.5 +offset_right = 115.5 +offset_bottom = 43.5 + +[node name="label" type="Label" parent="options_panel/h_box_container" unique_id=1002581329] +layout_mode = 2 +text = "Game Options" + +[node name="v_box_container" type="HBoxContainer" parent="options_panel/h_box_container" unique_id=1445409921] +layout_mode = 2 + +[node name="options_grid" type="GridContainer" parent="options_panel/h_box_container" unique_id=504671462] +layout_mode = 2 +columns = 2 + +[node name="options_button" type="Button" parent="." unique_id=2109111628] +layout_mode = 1 +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -309.0 +offset_top = -166.0 +offset_right = -239.0 +offset_bottom = -135.0 +grow_horizontal = 0 +grow_vertical = 0 +text = "Options" + +[connection signal="pressed" from="options_panel/options_close" to="." method="close_options"] +[connection signal="pressed" from="options_button" to="." method="open_options"] diff --git a/rust/src/game_manager.rs b/rust/src/game_manager.rs index 660abe8..568ad8f 100644 --- a/rust/src/game_manager.rs +++ b/rust/src/game_manager.rs @@ -22,12 +22,6 @@ use crate::{ pub const GAME_MANAGER_GLOBAL: &str = "GameManagerGlobal"; -pub struct Team { - name: String, - color: String, - id: u8, -} - #[derive(GodotClass)] #[class(base=Node, init)] pub struct GameManager { @@ -89,22 +83,55 @@ impl GameManager { } pub struct GameOptions { - /// Whether anyone is allowed to change anyone's team, or just the host and - /// the player itself. - pub allow_any_team: bool, - // Whether anyone is allowed to change the map, or just the host. - pub allow_any_map: bool, + pub values: HashMap, } impl Default for GameOptions { fn default() -> Self { - Self { - allow_any_team: false, - allow_any_map: false, + 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 { @@ -217,6 +244,8 @@ impl Game { 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> { GameManager::singleton() @@ -224,6 +253,18 @@ impl Game { .map(|g| g.cast::()) } + 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) { diff --git a/rust/src/net/network_manager.rs b/rust/src/net/network_manager.rs index 019a39f..425239c 100644 --- a/rust/src/net/network_manager.rs +++ b/rust/src/net/network_manager.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use teanet::{Peer, PeerConfig, PeerMessage}; use crate::{ - game_manager::{Game, GameManager, NetPlayer}, + game_manager::{Game, GameManager, GameOption, GameOptionValue, NetPlayer}, net::{net_stats::Stats, util::NetVector3}, player::{DamageSource, NetTransform}, ui::cli::{CliColor, CommandLinePanel}, @@ -256,6 +256,7 @@ pub enum Package { NewPlayer(NetPlayer), PlayerLeft(NetPlayer), SetTeam(u16, u8), + GameOptionChanged(GameOption, GameOptionValue), // Lobby packages SetReady(u16, bool), diff --git a/rust/src/net/protocol.rs b/rust/src/net/protocol.rs index 1388e66..81e9c6d 100644 --- a/rust/src/net/protocol.rs +++ b/rust/src/net/protocol.rs @@ -3,7 +3,7 @@ use std::net::SocketAddr; use teanet::PeerMessage; use crate::{ - game_manager::{Game, GameManager}, + game_manager::{Game, GameManager, GameOption}, net::network_manager::{NetworkManager, Package, PeerKind}, ui::cli::{CliColor, CommandLinePanel}, }; @@ -166,6 +166,11 @@ impl NetworkManager { game.bind_mut().handle_player_team_change(player_id, team); } } + Package::GameOptionChanged(opt, value) => { + if let Some(game) = &mut Game::singleton() { + game.bind_mut().change_option(opt, value); + } + } _ => {} }, }, @@ -318,7 +323,9 @@ impl NetworkManager { .find_player_by_addr(&conn.address) .map(|p| p.id()); if let Some(self_id) = self_id { - if game.bind().opts.allow_any_team || self_id == player_id { + if game.bind().opts.is_true(GameOption::AllowAnyTeam) + || self_id == player_id + { game.bind_mut().try_set_player_team(player_id, team, true); } } @@ -326,7 +333,7 @@ impl NetworkManager { } Package::SelectMap(map_idx) => { if let Some(mut game) = Game::singleton() { - if game.bind().opts.allow_any_map { + if game.bind().opts.is_true(GameOption::AllowAnyMap) { game.bind_mut().change_map_selection(map_idx, true); } } diff --git a/rust/src/ui/lobby.rs b/rust/src/ui/lobby.rs index 00920df..e4792ec 100644 --- a/rust/src/ui/lobby.rs +++ b/rust/src/ui/lobby.rs @@ -1,10 +1,13 @@ use godot::{ - classes::{Button, IPanel, OptionButton, Panel, TextEdit, VBoxContainer}, + classes::{ + Button, CheckBox, Control, GridContainer, IPanel, Label, OptionButton, Panel, TextEdit, + VBoxContainer, + }, prelude::*, }; use crate::{ - game_manager::{Game, GameManager, GameOptions}, + game_manager::{Game, GameManager, GameOption, GameOptionValue, GameOptions}, net::network_manager::{NetworkManager, Package, PeerKind}, ui::player_listing::PlayerListing, }; @@ -22,6 +25,10 @@ pub struct LobbyPanel { ready_button: Option>, #[export] map_selection: Option>, + #[export] + options_panel: Option>, + #[export] + options_grid: Option>, is_ready: bool, @@ -64,6 +71,17 @@ impl IPanel for LobbyPanel { } if let Some(game) = Game::singleton() { + game.signals().options_changed().connect_other(self, |s| { + s.run_deferred(|s| { + if let Some(game) = Game::singleton() { + s.on_game_opts_update( + &game.bind().opts, + NetworkManager::singleton().bind().is_host(), + ); + } + }); + }); + if let Some(map_selection) = &mut self.map_selection { for map in game.bind().maps.iter_shared() { map_selection.add_item(&map.bind().name); @@ -113,13 +131,69 @@ impl IPanel for LobbyPanel { } } self.update_ready_button(); + + let mut nodes = Vec::new(); + if let Some(game) = Game::singleton() { + for (option, value) in &game.bind().opts.values { + let mut label = Label::new_alloc(); + label.set_text(&format!("{}:", option.to_string())); + nodes.push(label.upcast()); + + let value_node = match value { + GameOptionValue::Boolean(value) => { + let mut checkbox = CheckBox::new_alloc(); + checkbox.set_pressed(*value); + checkbox.set_disabled(!NetworkManager::singleton().bind().is_host()); + + let checkbox_clone = checkbox.clone(); + let option = option.clone(); + checkbox.signals().pressed().connect_other(self, move |s| { + s.option_changed( + option, + GameOptionValue::Boolean(checkbox_clone.is_pressed()), + ); + }); + + checkbox.upcast::() + } + }; + nodes.push(value_node); + } + } + + if let Some(grid) = &mut self.options_grid { + for node in nodes { + grid.add_child(&node); + } + } } } +#[godot_api] impl LobbyPanel { + #[func] + fn open_options(&mut self) { + if let Some(panel) = &mut self.options_panel { + panel.set_visible(true); + } + } + + #[func] + fn close_options(&mut self) { + if let Some(panel) = &mut self.options_panel { + panel.set_visible(false); + } + } + + fn option_changed(&mut self, key: GameOption, value: GameOptionValue) { + if let Some(game) = &mut Game::singleton() { + game.bind_mut().change_option(key, value); + } + } + fn on_game_opts_update(&mut self, opts: &GameOptions, is_host: bool) { if let Some(selection) = &mut self.map_selection { - selection.set_disabled(!is_host && !opts.allow_any_map); + selection.set_disabled(!is_host && !opts.is_true(GameOption::AllowAnyMap)); } } diff --git a/rust/src/ui/player_listing.rs b/rust/src/ui/player_listing.rs index 09f1331..49c56ba 100644 --- a/rust/src/ui/player_listing.rs +++ b/rust/src/ui/player_listing.rs @@ -4,7 +4,7 @@ use godot::{ }; use crate::{ - game_manager::{Game, GameOptions}, + game_manager::{Game, GameOption, GameOptions}, net::network_manager::NetworkManager, team_resource::TeamResource, }; @@ -52,6 +52,14 @@ impl IGridContainer for PlayerListing { } if let Some(game) = &Game::singleton() { + game.signals().options_changed().connect_other(self, |s| { + s.run_deferred(|s| { + if let Some(game) = Game::singleton() { + s.update_game_options(&game.bind().opts, game.bind().self_id); + } + }); + }); + self.update_game_options(&game.bind().opts, game.bind().self_id); } } @@ -62,7 +70,9 @@ impl PlayerListing { let is_host = NetworkManager::singleton().bind().is_host(); if let Some(dropdown) = &mut self.team_dropdown { - dropdown.set_disabled(!is_host && !opts.allow_any_team && self.player_id != self_id); + dropdown.set_disabled( + !is_host && !opts.is_true(GameOption::AllowAnyTeam) && self.player_id != self_id, + ); } }