From 7c426f34a77b94a5f0ef5f3a95c54615315bbc3a Mon Sep 17 00:00:00 2001 From: Jupeyy Date: Fri, 10 Jan 2025 20:26:33 +0100 Subject: [PATCH] Add misc vote support. Add `add_vote` & `rem_vote` commands. --- .../src/ingame_menu/call_vote/main_frame.rs | 4 +- .../src/ingame_menu/call_vote/misc.rs | 240 ++++++++++++++++++ .../src/ingame_menu/call_vote/mod.rs | 1 + game/client-ui/src/ingame_menu/votes.rs | 29 ++- game/game-config/src/config.rs | 3 + game/game-network/src/messages.rs | 18 +- game/game-server/src/map_votes.rs | 8 +- game/game-server/src/rcon.rs | 2 + game/game-server/src/server.rs | 179 +++++++++++-- src/client/client.rs | 11 + src/client/game.rs | 1 + src/client/game/active.rs | 17 +- 12 files changed, 486 insertions(+), 27 deletions(-) create mode 100644 game/client-ui/src/ingame_menu/call_vote/misc.rs diff --git a/game/client-ui/src/ingame_menu/call_vote/main_frame.rs b/game/client-ui/src/ingame_menu/call_vote/main_frame.rs index 8c50b5f..dd44241 100644 --- a/game/client-ui/src/ingame_menu/call_vote/main_frame.rs +++ b/game/client-ui/src/ingame_menu/call_vote/main_frame.rs @@ -50,7 +50,9 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_state: &m "Map" => super::map::render(ui, pipe, ui_state), "Player" => super::players::render(ui, pipe), // Misc - _ => {} + _ => { + super::misc::render(ui, pipe); + } } }, ); diff --git a/game/client-ui/src/ingame_menu/call_vote/misc.rs b/game/client-ui/src/ingame_menu/call_vote/misc.rs new file mode 100644 index 0000000..0a7ee6c --- /dev/null +++ b/game/client-ui/src/ingame_menu/call_vote/misc.rs @@ -0,0 +1,240 @@ +use std::collections::BTreeSet; + +use egui::{Frame, ScrollArea, Sense, Shadow}; +use egui_extras::{Column, Size, StripBuilder, TableBuilder}; +use game_base::server_browser::{SortDir, TableSort}; +use game_config::config::Config; +use game_interface::votes::{MiscVote, MiscVoteCategoryKey, MiscVoteKey}; +use ui_base::{ + components::{ + clearable_edit_field::clearable_edit_field, + menu_top_button::{menu_top_button, MenuTopButtonProps}, + }, + style::{bg_frame_color, topbar_buttons}, + types::UiRenderPipe, + utils::{add_margins, get_margin}, +}; + +use crate::{events::UiEvent, ingame_menu::user_data::UserData, sort::sortable_header}; + +const MISC_VOTE_DIR_STORAGE_NAME: &str = "misc-vote-sort-dir"; + +fn render_table( + ui: &mut egui::Ui, + misc_infos: &[(usize, &(MiscVoteKey, MiscVote))], + index: usize, + config: &mut Config, +) { + let mut table = TableBuilder::new(ui).auto_shrink([false, false]); + table = table.column(Column::auto().at_least(150.0)); + + table + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .striped(true) + .sense(Sense::click()) + .header(30.0, |mut row| { + let names = vec!["Name"]; + sortable_header(&mut row, MISC_VOTE_DIR_STORAGE_NAME, config, &names); + }) + .body(|body| { + body.rows(25.0, misc_infos.len(), |mut row| { + let (original_index, (misc, _)) = &misc_infos[row.index()]; + row.set_selected(index == *original_index); + row.col(|ui| { + ui.label(misc.display_name.as_str()); + }); + if row.response().clicked() { + config + .engine + .ui + .path + .query + .insert("vote-misc-index".to_string(), original_index.to_string()); + } + }) + }); +} + +pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { + let config = &mut *pipe.user_data.browser_menu.config; + + let sort_dir = config.storage::(MISC_VOTE_DIR_STORAGE_NAME); + + let path = &mut config.engine.ui.path; + + let mut misc_search = path + .query + .entry("vote-misc-search".to_string()) + .or_default() + .clone(); + + let mut category = path + .query + .entry("vote-misc-category".to_string()) + .or_default() + .as_str() + .try_into() + .unwrap_or_default(); + + pipe.user_data.votes.request_misc_votes(); + let mut misc_votes = pipe.user_data.votes.collect_misc_votes(); + + let mut categories: Vec<_> = misc_votes.keys().cloned().collect(); + categories.sort(); + let mut vote_category = misc_votes.remove(&category); + + if vote_category.is_none() { + if let Some((name, votes)) = categories.first().and_then(|c| misc_votes.remove_entry(c)) { + category = name; + vote_category = Some(votes); + } + } + + let mut misc_infos: Vec<(_, _)> = vote_category + .map(|votes| votes.into_iter().collect()) + .unwrap_or_default(); + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + enum MiscSorting { + Name, + } + + let mut sortings: BTreeSet = Default::default(); + sortings.insert(MiscSorting::Name); + + let cur_sort = MiscSorting::Name; + + misc_infos.sort_by(|(i1k, _), (i2k, _)| { + let cmp = match cur_sort { + MiscSorting::Name => i1k.display_name.cmp(&i2k.display_name), + }; + if matches!(sort_dir.sort_dir, SortDir::Desc) { + cmp.reverse() + } else { + cmp + } + }); + + let category = category.to_string(); + + let index_entry = path + .query + .entry("vote-misc-index".to_string()) + .or_default() + .clone(); + let index: usize = index_entry.parse().unwrap_or_default(); + + Frame::default() + .fill(bg_frame_color()) + .inner_margin(get_margin(ui)) + .shadow(Shadow::NONE) + .show(ui, |ui| { + let mut builder = StripBuilder::new(ui); + + let has_multi_categories = categories.len() > 1; + if has_multi_categories { + builder = builder.size(Size::exact(20.0)); + builder = builder.size(Size::exact(2.0)); + } + + builder + .size(Size::remainder()) + .size(Size::exact(20.0)) + .vertical(|mut strip| { + if has_multi_categories { + strip.cell(|ui| { + ui.style_mut().wrap_mode = None; + ScrollArea::horizontal().show(ui, |ui| { + ui.horizontal(|ui| { + ui.set_style(topbar_buttons()); + for category_name in categories { + if menu_top_button( + ui, + |_, _| None, + MenuTopButtonProps::new( + &category_name, + &Some(category.clone()), + ), + ) + .clicked() + { + config.engine.ui.path.query.insert( + "vote-misc-category".to_string(), + category_name.to_string(), + ); + } + } + }); + }); + }); + strip.empty(); + } + strip.cell(|ui| { + ui.style_mut().wrap_mode = None; + ui.style_mut().spacing.item_spacing.y = 0.0; + StripBuilder::new(ui) + .size(Size::remainder()) + .size(Size::exact(20.0)) + .vertical(|mut strip| { + strip.cell(|ui| { + ui.style_mut().wrap_mode = None; + ui.painter().rect_filled( + ui.available_rect_before_wrap(), + 0.0, + bg_frame_color(), + ); + ui.set_clip_rect(ui.available_rect_before_wrap()); + add_margins(ui, |ui| { + let misc_infos: Vec<_> = misc_infos + .iter() + .enumerate() + .filter(|(_, (key, _))| { + key.display_name + .as_str() + .to_lowercase() + .contains(&misc_search.to_lowercase()) + }) + .collect(); + render_table(ui, &misc_infos, index, config); + }); + }); + strip.cell(|ui| { + ui.style_mut().wrap_mode = None; + ui.horizontal_centered(|ui| { + // Search + ui.label("\u{1f50d}"); + clearable_edit_field( + ui, + &mut misc_search, + Some(200.0), + None, + ); + }); + }); + }); + }); + strip.cell(|ui| { + ui.style_mut().wrap_mode = None; + ui.horizontal(|ui| { + if ui.button("Vote").clicked() { + if let Some((vote_key, _)) = misc_infos.get(index) { + pipe.user_data.browser_menu.events.push(UiEvent::VoteMisc( + MiscVoteCategoryKey { + category: category.as_str().try_into().unwrap(), + vote_key: vote_key.clone(), + }, + )); + } + } + }); + }); + }); + }); + + config + .engine + .ui + .path + .query + .insert("vote-misc-search".to_string(), misc_search); +} diff --git a/game/client-ui/src/ingame_menu/call_vote/mod.rs b/game/client-ui/src/ingame_menu/call_vote/mod.rs index 10bb8d2..064b848 100644 --- a/game/client-ui/src/ingame_menu/call_vote/mod.rs +++ b/game/client-ui/src/ingame_menu/call_vote/mod.rs @@ -1,3 +1,4 @@ pub mod main_frame; pub mod map; +pub mod misc; pub mod players; diff --git a/game/client-ui/src/ingame_menu/votes.rs b/game/client-ui/src/ingame_menu/votes.rs index 1023902..03fc395 100644 --- a/game/client-ui/src/ingame_menu/votes.rs +++ b/game/client-ui/src/ingame_menu/votes.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use base::network_string::NetworkString; -use game_interface::votes::{MapVote, MapVoteKey, MAX_CATEGORY_NAME_LEN}; +use game_interface::votes::{MapVote, MapVoteKey, MiscVote, MiscVoteKey, MAX_CATEGORY_NAME_LEN}; use hiarc::{hiarc_safer_rc_refcell, Hiarc}; use url::Url; @@ -12,6 +12,9 @@ pub struct Votes { has_unfinished_map_votes: bool, need_map_votes: bool, thumbnail_server_resource_download_url: Option, + + misc_votes: BTreeMap, BTreeMap>, + need_misc_votes: bool, } #[hiarc_safer_rc_refcell] @@ -53,4 +56,28 @@ impl Votes { pub fn thumbnail_server_resource_download_url(&self) -> Option { self.thumbnail_server_resource_download_url.clone() } + + pub fn request_misc_votes(&mut self) { + self.need_misc_votes = true; + } + + /// Automatically resets the "need" state, so + /// another [`Votes::request_misc_votes`] has to + /// be called. + pub fn needs_misc_votes(&mut self) -> bool { + std::mem::replace(&mut self.need_misc_votes, false) + } + + pub fn fill_misc_votes( + &mut self, + misc_votes: BTreeMap, BTreeMap>, + ) { + self.misc_votes = misc_votes; + } + + pub fn collect_misc_votes( + &self, + ) -> BTreeMap, BTreeMap> { + self.misc_votes.clone() + } } diff --git a/game/game-config/src/config.rs b/game/game-config/src/config.rs index f119fab..78ac12e 100644 --- a/game/game-config/src/config.rs +++ b/game/game-config/src/config.rs @@ -668,6 +668,9 @@ pub struct ConfigServer { /// and local servers). #[default = false] pub auto_map_votes: bool, + /// Path to the map votes file. + #[default = "map_votes.json"] + pub map_votes_path: String, /// Whether to allow spatial chat on this server. /// Note that spatial chat causes lot of network /// traffic. diff --git a/game/game-network/src/messages.rs b/game/game-network/src/messages.rs index 5bba397..d8e36a3 100644 --- a/game/game-network/src/messages.rs +++ b/game/game-network/src/messages.rs @@ -5,6 +5,10 @@ use std::{ }; use base::network_string::{NetworkReducedAsciiString, NetworkString}; +use game_base::network::messages::{ + MsgClAddLocalPlayer, MsgClChatMsg, MsgClInputs, MsgClLoadVotes, MsgClReady, MsgClReadyResponse, + MsgClSnapshotAck, MsgSvAddLocalPlayerResponse, MsgSvChatMsg, MsgSvServerInfo, +}; use game_interface::{ account_info::{AccountInfo, MAX_ACCOUNT_NAME_LEN}, client_commands::{ClientCameraMode, JoinStage}, @@ -25,10 +29,6 @@ use game_interface::{ }; use pool::mt_datatypes::PoolCow; use serde::{Deserialize, Serialize}; -use game_base::network::messages::{ - MsgClAddLocalPlayer, MsgClChatMsg, MsgClInputs, MsgClLoadVotes, MsgClReady, MsgClReadyResponse, - MsgClSnapshotAck, MsgSvAddLocalPlayerResponse, MsgSvChatMsg, MsgSvServerInfo, -}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct MsgSvInputAck { @@ -52,6 +52,13 @@ pub enum MsgSvLoadVotes { }, } +/// Type of votes to reset. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum MsgSvResetVotes { + Map, + Misc, +} + /// Vote result of vote started by a client. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MsgSvStartVoteResult { @@ -127,7 +134,8 @@ pub enum ServerToClientMessage<'a> { /// A value of `None` must be interpreted as no vote active. StartVoteRes(MsgSvStartVoteResult), Vote(Option), - LoadVote(MsgSvLoadVotes), + LoadVotes(MsgSvLoadVotes), + ResetVotes(MsgSvResetVotes), RconCommands(RconCommands), RconExecResult { /// Since multiple commands could have been executed, diff --git a/game/game-server/src/map_votes.rs b/game/game-server/src/map_votes.rs index 686c1e8..fd6bf0b 100644 --- a/game/game-server/src/map_votes.rs +++ b/game/game-server/src/map_votes.rs @@ -1,5 +1,6 @@ use std::{ collections::{BTreeMap, HashMap}, + path::Path, sync::Arc, }; @@ -30,9 +31,12 @@ pub struct MapVotes { } impl MapVotes { - pub async fn new(fs: &Arc) -> anyhow::Result { + pub async fn new( + fs: &Arc, + map_votes_file_path: &Path, + ) -> anyhow::Result { let votes_file: MapVotesFile = - serde_json::from_slice(&fs.read_file("map_votes.json".as_ref()).await?)?; + serde_json::from_slice(&fs.read_file(map_votes_file_path).await?)?; Ok(Self { votes: ServerMapVotes { categories: votes_file diff --git a/game/game-server/src/rcon.rs b/game/game-server/src/rcon.rs index cf8a9b6..7852399 100644 --- a/game/game-server/src/rcon.rs +++ b/game/game-server/src/rcon.rs @@ -68,5 +68,7 @@ pub enum ServerRconCommand { Exec, /// Loads server config from a specific path Load, + AddMiscVote, + RemoveMiscVote, RecordDemo, } diff --git a/game/game-server/src/server.rs b/game/game-server/src/server.rs index 6b2504e..afcc331 100644 --- a/game/game-server/src/server.rs +++ b/game/game-server/src/server.rs @@ -128,7 +128,7 @@ use game_network::{ game_event_generator::{GameEventGenerator, GameEvents}, messages::{ ClientToServerMessage, ClientToServerPlayerMessage, MsgSvInputAck, MsgSvLoadVotes, - MsgSvStartVoteResult, ServerToClientMessage, + MsgSvResetVotes, MsgSvStartVoteResult, ServerToClientMessage, }, }; @@ -220,7 +220,7 @@ pub struct Server { map_votes: ServerMapVotes, map_votes_hash: Hash, misc_votes: BTreeMap, BTreeMap>, - misc_votes_hash: Hash, + misc_votes_hash: Option, // database db: Option>, @@ -364,7 +364,10 @@ impl Server { ty: CommandArgType::Number, user_ty: Some("PLAYER_ID".try_into().unwrap()), }], - description: "Ban a user with the given player id".try_into().unwrap(), + description: "Ban a user with \ + the given player id" + .try_into() + .unwrap(), usage: "ban_id ".try_into().unwrap(), }, cmd: ServerRconCommand::BanId, @@ -378,7 +381,10 @@ impl Server { ty: CommandArgType::Number, user_ty: Some("PLAYER_ID".try_into().unwrap()), }], - description: "Kick a user with the given player id".try_into().unwrap(), + description: "Kick a user with \ + the given player id" + .try_into() + .unwrap(), usage: "kick_id ".try_into().unwrap(), }, cmd: ServerRconCommand::KickId, @@ -389,10 +395,10 @@ impl Server { Command { rcon: RconCommand { args: Default::default(), - description: - "List information about this player such as the connected clients" - .try_into() - .unwrap(), + description: "List information about this player \ + such as the connected clients" + .try_into() + .unwrap(), usage: "status".try_into().unwrap(), }, cmd: ServerRconCommand::Status, @@ -437,6 +443,56 @@ impl Server { cmd: ServerRconCommand::Load, }, ), + ( + "add_vote".try_into().unwrap(), + Command { + rcon: RconCommand { + args: vec![ + CommandArg { + ty: CommandArgType::Text, + user_ty: None, + }, + CommandArg { + ty: CommandArgType::Text, + user_ty: None, + }, + CommandArg { + ty: CommandArgType::Text, + user_ty: None, + }, + ], + description: "Adds a vote to the misc votes taking a \ + category & name, aswell as a rcon command." + .try_into() + .unwrap(), + usage: "add_vote ".try_into().unwrap(), + }, + cmd: ServerRconCommand::AddMiscVote, + }, + ), + ( + "rem_vote".try_into().unwrap(), + Command { + rcon: RconCommand { + args: vec![ + CommandArg { + ty: CommandArgType::Text, + user_ty: None, + }, + CommandArg { + ty: CommandArgType::Text, + user_ty: None, + }, + ], + description: "Removes a vote from the misc votes \ + taking a category & name." + .try_into() + .unwrap(), + usage: "rem_vote ".try_into().unwrap(), + }, + cmd: ServerRconCommand::RemoveMiscVote, + }, + ), ]; config::parsing::parse_conf_values_as_str_list( @@ -593,9 +649,12 @@ impl Server { let fs = io.fs.clone(); io.rt.spawn(async move { AutoMapVotes::new(&fs).await }) }); + + let map_votes_file_path = config_game.sv.map_votes_path.clone(); let map_votes_file = { let fs = io.fs.clone(); - io.rt.spawn(async move { MapVotes::new(&fs).await }) + io.rt + .spawn(async move { MapVotes::new(&fs, map_votes_file_path.as_ref()).await }) }; let fs = io.fs.clone(); @@ -875,7 +934,7 @@ impl Server { map_votes, map_votes_hash, misc_votes: Default::default(), - misc_votes_hash: generate_hash_for(&[]), + misc_votes_hash: None, // database db, @@ -1111,14 +1170,27 @@ impl Server { None } + fn broadcast_in_order_filtered( + &self, + packet: ServerToClientMessage<'_>, + channel: NetworkInOrderChannel, + f: impl Fn(&(&NetworkConnectionId, &ServerClient)) -> bool, + ) { + self.clients + .clients + .iter() + .filter(f) + .for_each(|(send_con_id, _)| { + self.network.send_in_order_to(&packet, send_con_id, channel); + }); + } + fn broadcast_in_order( &self, packet: ServerToClientMessage<'_>, channel: NetworkInOrderChannel, ) { - self.clients.clients.keys().for_each(|send_con_id| { - self.network.send_in_order_to(&packet, send_con_id, channel); - }); + self.broadcast_in_order_filtered(packet, channel, |_| true); } fn send_vote(&self, vote_state: Option, start_time: Duration) { @@ -1820,6 +1892,69 @@ impl Server { ServerRconCommand::Load => { Self::handle_load_config(&mut self.config_game, &self.io, cmd) } + ServerRconCommand::AddMiscVote => { + let Syn::Text(category) = &cmd.args[0].0 else { + panic!("Command parser returned a non requested command arg"); + }; + let Syn::Text(name) = &cmd.args[1].0 else { + panic!("Command parser returned a non requested command arg"); + }; + let Syn::Text(cmd) = &cmd.args[1].0 else { + panic!("Command parser returned a non requested command arg"); + }; + let category = category.as_str().try_into()?; + let display_name = name.as_str().try_into()?; + let command = cmd.as_str().try_into()?; + let res = format!("Added vote {name} in {category}"); + self.misc_votes + .entry(category) + .or_default() + .insert(MiscVoteKey { display_name }, MiscVote { command }); + self.misc_votes_hash = None; + + self.broadcast_in_order_filtered( + ServerToClientMessage::ResetVotes(MsgSvResetVotes::Misc), + NetworkInOrderChannel::Custom(7013), // This number reads as "vote". + |(_, client)| client.loaded_misc_votes, + ); + self.clients + .clients + .values_mut() + .for_each(|c| c.loaded_misc_votes = false); + Ok(res) + } + ServerRconCommand::RemoveMiscVote => { + let Syn::Text(category) = &cmd.args[0].0 else { + panic!("Command parser returned a non requested command arg"); + }; + let Syn::Text(name) = &cmd.args[1].0 else { + panic!("Command parser returned a non requested command arg"); + }; + + let display_name = name.as_str().try_into()?; + + let res = format!("Remove vote {name} from {category}"); + if let Some(votes) = self.misc_votes.get_mut(category) { + votes.remove(&MiscVoteKey { display_name }); + + if votes.is_empty() { + self.misc_votes.remove(category); + } + } + + self.misc_votes_hash = None; + + self.broadcast_in_order_filtered( + ServerToClientMessage::ResetVotes(MsgSvResetVotes::Misc), + NetworkInOrderChannel::Custom(7013), // This number reads as "vote". + |(_, client)| client.loaded_misc_votes, + ); + self.clients + .clients + .values_mut() + .for_each(|c| c.loaded_misc_votes = false); + Ok(res) + } ServerRconCommand::RecordDemo => { let had_demo_recorder = self.demo_recorder.is_some(); self.demo_recorder = Some(DemoRecorder::new( @@ -2221,7 +2356,7 @@ impl Server { if cached_votes.is_none_or(|hash| hash != self.map_votes_hash) { self.network.send_unordered_to( - &ServerToClientMessage::LoadVote(MsgSvLoadVotes::Map { + &ServerToClientMessage::LoadVotes(MsgSvLoadVotes::Map { categories: self.map_votes.categories.clone(), has_unfinished_map_votes: self .map_votes @@ -2236,9 +2371,21 @@ impl Server { if !client.loaded_misc_votes { client.loaded_misc_votes = true; - if cached_votes.is_none_or(|hash| hash != self.misc_votes_hash) { + if self.misc_votes_hash.is_none() { + self.misc_votes_hash = Some(generate_hash_for( + &bincode::serde::encode_to_vec( + &self.misc_votes, + bincode::config::standard(), + ) + .unwrap(), + )); + } + + if cached_votes + .is_none_or(|hash| Some(hash) != self.misc_votes_hash) + { self.network.send_unordered_to( - &ServerToClientMessage::LoadVote(MsgSvLoadVotes::Misc { + &ServerToClientMessage::LoadVotes(MsgSvLoadVotes::Misc { votes: self.misc_votes.clone(), }), con_id, diff --git a/src/client/client.rs b/src/client/client.rs index 584746f..d956776 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -2879,6 +2879,17 @@ impl FromNativeImpl for ClientNativeImpl { game.resource_download_server.clone(), ); } + if self.votes.needs_misc_votes() { + if !game.misc_votes_loaded { + game.misc_votes_loaded = true; + game.network + .send_unordered_to_server(&ClientToServerMessage::LoadVotes( + MsgClLoadVotes::Misc { cached_votes: None }, + )); + } + self.votes + .fill_misc_votes(game.game_data.misc_votes.clone()); + } if has_input { let evs = self.inp_manager.handle_player_binds( diff --git a/src/client/game.rs b/src/client/game.rs index b716b24..ed72724 100644 --- a/src/client/game.rs +++ b/src/client/game.rs @@ -677,6 +677,7 @@ impl Game { events: events_pool.new(), map_votes_loaded: Default::default(), + misc_votes_loaded: Default::default(), render_players_pool: Pool::with_capacity(64), render_observers_pool: Pool::with_capacity(2), diff --git a/src/client/game/active.rs b/src/client/game/active.rs index 661816c..251451d 100644 --- a/src/client/game/active.rs +++ b/src/client/game/active.rs @@ -32,7 +32,8 @@ use game_interface::{ }, }; use game_network::messages::{ - ClientToServerMessage, MsgSvLoadVotes, MsgSvStartVoteResult, ServerToClientMessage, + ClientToServerMessage, MsgSvLoadVotes, MsgSvResetVotes, MsgSvStartVoteResult, + ServerToClientMessage, }; use game_server::server::Server; use game_state_wasm::game::state_wasm_manager::GameStateWasmManager; @@ -77,6 +78,7 @@ pub struct ActiveGame { pub events: PoolBTreeMap<(GameTickType, bool), GameEvents>, pub map_votes_loaded: bool, + pub misc_votes_loaded: bool, pub render_players_pool: Pool>, pub render_observers_pool: Pool>, @@ -678,7 +680,7 @@ impl ActiveGame { self.game_data.vote = vote_state.map(|v| (PoolRc::from_item_without_pool(v), voted, *timestamp)); } - ServerToClientMessage::LoadVote(votes) => match votes { + ServerToClientMessage::LoadVotes(votes) => match votes { MsgSvLoadVotes::Map { categories, has_unfinished_map_votes, @@ -690,6 +692,17 @@ impl ActiveGame { self.game_data.misc_votes = votes; } }, + ServerToClientMessage::ResetVotes(votes) => match votes { + MsgSvResetVotes::Map => { + self.game_data.map_votes.clear(); + self.game_data.has_unfinished_map_votes = false; + self.map_votes_loaded = false; + } + MsgSvResetVotes::Misc => { + self.game_data.misc_votes.clear(); + self.misc_votes_loaded = false; + } + }, ServerToClientMessage::RconCommands(cmds) => { self.remote_console.fill_entries(cmds.cmds); }