Add game options properly

This commit is contained in:
Sofia 2026-07-22 04:06:56 +03:00
parent b729c7bbcd
commit 0a330b9d89
6 changed files with 224 additions and 24 deletions

View File

@ -5,12 +5,17 @@
[sub_resource type="LabelSettings" id="LabelSettings_1cgct"] [sub_resource type="LabelSettings" id="LabelSettings_1cgct"]
font_size = 32 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") players_list = NodePath("v_box_container/players_list")
name_field = NodePath("v_box_container/h_box_container/text_edit") name_field = NodePath("v_box_container/h_box_container/text_edit")
player_listing_prefab = ExtResource("1_o1atq") player_listing_prefab = ExtResource("1_o1atq")
ready_button = NodePath("ready_button") ready_button = NodePath("ready_button")
map_selection = NodePath("v_box_container2/option_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 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 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] [node name="option_button" type="OptionButton" parent="v_box_container2" unique_id=426342653]
layout_mode = 2 layout_mode = 2
metadata/_edit_lock_ = true 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"]

View File

@ -22,12 +22,6 @@ use crate::{
pub const GAME_MANAGER_GLOBAL: &str = "GameManagerGlobal"; pub const GAME_MANAGER_GLOBAL: &str = "GameManagerGlobal";
pub struct Team {
name: String,
color: String,
id: u8,
}
#[derive(GodotClass)] #[derive(GodotClass)]
#[class(base=Node, init)] #[class(base=Node, init)]
pub struct GameManager { pub struct GameManager {
@ -89,22 +83,55 @@ impl GameManager {
} }
pub struct GameOptions { pub struct GameOptions {
/// Whether anyone is allowed to change anyone's team, or just the host and pub values: HashMap<GameOption, GameOptionValue>,
/// 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,
} }
impl Default for GameOptions { impl Default for GameOptions {
fn default() -> Self { fn default() -> Self {
Self { let mut opts = HashMap::new();
allow_any_team: false, opts.insert(GameOption::AllowAnyMap, GameOptionValue::Boolean(false));
allow_any_map: 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)] #[derive(GodotClass)]
#[class(base=Node, init)] #[class(base=Node, init)]
pub struct Game { pub struct Game {
@ -217,6 +244,8 @@ impl Game {
pub fn on_player_team_changed(id: u16, team: u8); pub fn on_player_team_changed(id: u16, team: u8);
#[signal] #[signal]
pub fn on_map_changed(id: u8); pub fn on_map_changed(id: u8);
#[signal]
pub fn options_changed();
pub fn singleton() -> Option<Gd<Game>> { pub fn singleton() -> Option<Gd<Game>> {
GameManager::singleton() GameManager::singleton()
@ -224,6 +253,18 @@ impl Game {
.map(|g| g.cast::<Game>()) .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) { pub fn change_map_selection(&mut self, map_idx: u8, emit: bool) {
self.selected_map_idx = map_idx; self.selected_map_idx = map_idx;
if let Some(map) = self.maps.get(map_idx as usize) { if let Some(map) = self.maps.get(map_idx as usize) {

View File

@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use teanet::{Peer, PeerConfig, PeerMessage}; use teanet::{Peer, PeerConfig, PeerMessage};
use crate::{ use crate::{
game_manager::{Game, GameManager, NetPlayer}, game_manager::{Game, GameManager, GameOption, GameOptionValue, NetPlayer},
net::{net_stats::Stats, util::NetVector3}, net::{net_stats::Stats, util::NetVector3},
player::{DamageSource, NetTransform}, player::{DamageSource, NetTransform},
ui::cli::{CliColor, CommandLinePanel}, ui::cli::{CliColor, CommandLinePanel},
@ -256,6 +256,7 @@ pub enum Package {
NewPlayer(NetPlayer), NewPlayer(NetPlayer),
PlayerLeft(NetPlayer), PlayerLeft(NetPlayer),
SetTeam(u16, u8), SetTeam(u16, u8),
GameOptionChanged(GameOption, GameOptionValue),
// Lobby packages // Lobby packages
SetReady(u16, bool), SetReady(u16, bool),

View File

@ -3,7 +3,7 @@ use std::net::SocketAddr;
use teanet::PeerMessage; use teanet::PeerMessage;
use crate::{ use crate::{
game_manager::{Game, GameManager}, game_manager::{Game, GameManager, GameOption},
net::network_manager::{NetworkManager, Package, PeerKind}, net::network_manager::{NetworkManager, Package, PeerKind},
ui::cli::{CliColor, CommandLinePanel}, ui::cli::{CliColor, CommandLinePanel},
}; };
@ -166,6 +166,11 @@ impl NetworkManager {
game.bind_mut().handle_player_team_change(player_id, team); 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) .find_player_by_addr(&conn.address)
.map(|p| p.id()); .map(|p| p.id());
if let Some(self_id) = self_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); game.bind_mut().try_set_player_team(player_id, team, true);
} }
} }
@ -326,7 +333,7 @@ impl NetworkManager {
} }
Package::SelectMap(map_idx) => { Package::SelectMap(map_idx) => {
if let Some(mut game) = Game::singleton() { 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); game.bind_mut().change_map_selection(map_idx, true);
} }
} }

View File

@ -1,10 +1,13 @@
use godot::{ use godot::{
classes::{Button, IPanel, OptionButton, Panel, TextEdit, VBoxContainer}, classes::{
Button, CheckBox, Control, GridContainer, IPanel, Label, OptionButton, Panel, TextEdit,
VBoxContainer,
},
prelude::*, prelude::*,
}; };
use crate::{ use crate::{
game_manager::{Game, GameManager, GameOptions}, game_manager::{Game, GameManager, GameOption, GameOptionValue, GameOptions},
net::network_manager::{NetworkManager, Package, PeerKind}, net::network_manager::{NetworkManager, Package, PeerKind},
ui::player_listing::PlayerListing, ui::player_listing::PlayerListing,
}; };
@ -22,6 +25,10 @@ pub struct LobbyPanel {
ready_button: Option<Gd<Button>>, ready_button: Option<Gd<Button>>,
#[export] #[export]
map_selection: Option<Gd<OptionButton>>, map_selection: Option<Gd<OptionButton>>,
#[export]
options_panel: Option<Gd<Panel>>,
#[export]
options_grid: Option<Gd<GridContainer>>,
is_ready: bool, is_ready: bool,
@ -64,6 +71,17 @@ impl IPanel for LobbyPanel {
} }
if let Some(game) = Game::singleton() { 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 { if let Some(map_selection) = &mut self.map_selection {
for map in game.bind().maps.iter_shared() { for map in game.bind().maps.iter_shared() {
map_selection.add_item(&map.bind().name); map_selection.add_item(&map.bind().name);
@ -113,13 +131,69 @@ impl IPanel for LobbyPanel {
} }
} }
self.update_ready_button(); 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::<Control>()
}
};
nodes.push(value_node);
}
}
if let Some(grid) = &mut self.options_grid {
for node in nodes {
grid.add_child(&node);
}
}
} }
} }
#[godot_api]
impl LobbyPanel { 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) { fn on_game_opts_update(&mut self, opts: &GameOptions, is_host: bool) {
if let Some(selection) = &mut self.map_selection { 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));
} }
} }

View File

@ -4,7 +4,7 @@ use godot::{
}; };
use crate::{ use crate::{
game_manager::{Game, GameOptions}, game_manager::{Game, GameOption, GameOptions},
net::network_manager::NetworkManager, net::network_manager::NetworkManager,
team_resource::TeamResource, team_resource::TeamResource,
}; };
@ -52,6 +52,14 @@ impl IGridContainer for PlayerListing {
} }
if let Some(game) = &Game::singleton() { 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); 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(); let is_host = NetworkManager::singleton().bind().is_host();
if let Some(dropdown) = &mut self.team_dropdown { 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,
);
} }
} }