Skip to content

Commit

Permalink
Add misc vote support.
Browse files Browse the repository at this point in the history
Add `add_vote` & `rem_vote` commands.
  • Loading branch information
Jupeyy committed Jan 12, 2025
1 parent a281e27 commit 7c426f3
Show file tree
Hide file tree
Showing 12 changed files with 486 additions and 27 deletions.
4 changes: 3 additions & 1 deletion game/client-ui/src/ingame_menu/call_vote/main_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe<UserData>, ui_state: &m
"Map" => super::map::render(ui, pipe, ui_state),
"Player" => super::players::render(ui, pipe),
// Misc
_ => {}
_ => {
super::misc::render(ui, pipe);
}
}
},
);
Expand Down
240 changes: 240 additions & 0 deletions game/client-ui/src/ingame_menu/call_vote/misc.rs
Original file line number Diff line number Diff line change
@@ -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<UserData>) {
let config = &mut *pipe.user_data.browser_menu.config;

let sort_dir = config.storage::<TableSort>(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<MiscSorting> = 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);
}
1 change: 1 addition & 0 deletions game/client-ui/src/ingame_menu/call_vote/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod main_frame;
pub mod map;
pub mod misc;
pub mod players;
29 changes: 28 additions & 1 deletion game/client-ui/src/ingame_menu/votes.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -12,6 +12,9 @@ pub struct Votes {
has_unfinished_map_votes: bool,
need_map_votes: bool,
thumbnail_server_resource_download_url: Option<Url>,

misc_votes: BTreeMap<NetworkString<MAX_CATEGORY_NAME_LEN>, BTreeMap<MiscVoteKey, MiscVote>>,
need_misc_votes: bool,
}

#[hiarc_safer_rc_refcell]
Expand Down Expand Up @@ -53,4 +56,28 @@ impl Votes {
pub fn thumbnail_server_resource_download_url(&self) -> Option<Url> {
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<NetworkString<MAX_CATEGORY_NAME_LEN>, BTreeMap<MiscVoteKey, MiscVote>>,
) {
self.misc_votes = misc_votes;
}

pub fn collect_misc_votes(
&self,
) -> BTreeMap<NetworkString<MAX_CATEGORY_NAME_LEN>, BTreeMap<MiscVoteKey, MiscVote>> {
self.misc_votes.clone()
}
}
3 changes: 3 additions & 0 deletions game/game-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 13 additions & 5 deletions game/game-network/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -127,7 +134,8 @@ pub enum ServerToClientMessage<'a> {
/// A value of `None` must be interpreted as no vote active.
StartVoteRes(MsgSvStartVoteResult),
Vote(Option<VoteState>),
LoadVote(MsgSvLoadVotes),
LoadVotes(MsgSvLoadVotes),
ResetVotes(MsgSvResetVotes),
RconCommands(RconCommands),
RconExecResult {
/// Since multiple commands could have been executed,
Expand Down
8 changes: 6 additions & 2 deletions game/game-server/src/map_votes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{
collections::{BTreeMap, HashMap},
path::Path,
sync::Arc,
};

Expand Down Expand Up @@ -30,9 +31,12 @@ pub struct MapVotes {
}

impl MapVotes {
pub async fn new(fs: &Arc<dyn FileSystemInterface>) -> anyhow::Result<Self> {
pub async fn new(
fs: &Arc<dyn FileSystemInterface>,
map_votes_file_path: &Path,
) -> anyhow::Result<Self> {
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
Expand Down
2 changes: 2 additions & 0 deletions game/game-server/src/rcon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,7 @@ pub enum ServerRconCommand {
Exec,
/// Loads server config from a specific path
Load,
AddMiscVote,
RemoveMiscVote,
RecordDemo,
}
Loading

0 comments on commit 7c426f3

Please sign in to comment.