From da84390ce501626311bbb37000b429d6e254c3e7 Mon Sep 17 00:00:00 2001 From: otta8634 Date: Wed, 12 Mar 2025 16:05:43 +0800 Subject: [PATCH] Allow displaying the winning map/gametype on the voting hud Also renamed g_maplist_votable_nodetail to g_maplist_votable_detail, and added sv_vote_gametype_detail. When 1, the hud displays the "(2 votes)" text in light blue for all maps in the tied winning place, provided they've received at least one vote. When 2, the hud displays only which map will win the tie break (randomly chosen by the server) in light blue. This can help prevent annoying situations where for example 4 players vote for different maps and 1 player votes for a map that the 4 players all don't like, previously there'd be a 20% chance it'd be selected despite the 4 players all disliking it -- now the players will know if it'll be picked or not, if this cvar is set to 2. This does however remove most of the randomness from the voting, so this is disabled by default (cvars both 1). Also cleaned up associated code. --- qcsrc/client/mapvoting.qc | 58 ++++++++++++++++++++++----------------- qcsrc/server/mapvoting.qc | 24 +++++++++------- qcsrc/server/mapvoting.qh | 3 +- xonotic-server.cfg | 3 +- 4 files changed, 51 insertions(+), 37 deletions(-) diff --git a/qcsrc/client/mapvoting.qc b/qcsrc/client/mapvoting.qc index 7be6523853..ea6673e715 100644 --- a/qcsrc/client/mapvoting.qc +++ b/qcsrc/client/mapvoting.qc @@ -21,13 +21,14 @@ string mv_pics[MAPVOTE_COUNT]; string mv_pk3[MAPVOTE_COUNT]; // map pk3 name or gametype human readable name string mv_desc[MAPVOTE_COUNT]; float mv_preview[MAPVOTE_COUNT]; -float mv_votes[MAPVOTE_COUNT]; -float mv_flags[MAPVOTE_COUNT]; -float mv_flags_start[MAPVOTE_COUNT]; +int mv_votes[MAPVOTE_COUNT]; +int mv_flags[MAPVOTE_COUNT]; +int mv_flags_start[MAPVOTE_COUNT]; entity mv_pk3list; -float mv_abstain; -float mv_ownvote; -float mv_detail; +bool mv_abstain; +int mv_detail; +int mv_ownvote; +int mv_tie_winner; float mv_timeout; float mv_top2_time; float mv_top2_alpha; @@ -61,7 +62,7 @@ bool PreviewExists(string name) return false; } -string MapVote_FormatMapItem(int id, string map, float _count, float maxwidth, vector fontsize) +string MapVote_FormatMapItem(int id, string map, int _count, float maxwidth, vector fontsize, int most_votes) { TC(int, id); string pre, post; @@ -74,10 +75,13 @@ string MapVote_FormatMapItem(int id, string map, float _count, float maxwidth, v post = sprintf(_(" (%d votes)"), _count); else post = ""; + if(post != "" && (mv_flags[id] & GTV_AVAILABLE)) + if(mv_tie_winner == id || (mv_tie_winner == -2 && _count == most_votes)) + post = strcat("^5", post); } else post = ""; - maxwidth -= stringwidth(pre, false, fontsize) + stringwidth(post, false, fontsize); + maxwidth -= stringwidth(pre, false, fontsize) + stringwidth(post, true, fontsize); map = textShortenToWidth(map, maxwidth, fontsize, stringwidth_nocolors); return strcat(pre, map, post); } @@ -95,7 +99,7 @@ vector MapVote_RGB(int id) return '1 1 1'; } -void GameTypeVote_DrawGameTypeItem(vector pos, float maxh, float tsize, string gtype, string pic, float _count, int id) +void GameTypeVote_DrawGameTypeItem(vector pos, float maxh, float tsize, string gtype, string pic, int _count, int id, int most_votes) { TC(int, id); // Find the correct alpha @@ -153,7 +157,7 @@ void GameTypeVote_DrawGameTypeItem(vector pos, float maxh, float tsize, string g // Split the description into lines entity title; title = spawn(); - title.message = MapVote_FormatMapItem(id, mv_pk3[id], _count, tsize, gtv_text_size); + title.message = strcat(rgb_to_hexcolor(rgb), MapVote_FormatMapItem(id, mv_pk3[id], _count, tsize, gtv_text_size, most_votes)); string thelabel = mv_desc[id], ts; entity last = title; @@ -188,7 +192,7 @@ void GameTypeVote_DrawGameTypeItem(vector pos, float maxh, float tsize, string g offset.y += maxh/2; // Draw the title - drawstring(offset, title.message, gtv_text_size, rgb, alpha, DRAWFLAG_NORMAL); + drawcolorcodedstring(offset, title.message, gtv_text_size, alpha, DRAWFLAG_NORMAL); // Draw the icon if(pic != "") @@ -220,7 +224,7 @@ void MapVote_DrawMapPicture(string pic, vector pos, vector img_size, float theAl } } -void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, string pic, float _count, int id) +void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, string pic, int _count, int id, int most_votes) { TC(int, id); vector img_size = '0 0 0'; @@ -247,9 +251,10 @@ void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, strin pos.y += (isize - img_size.y - hud_fontsize.y) / 2; - label = MapVote_FormatMapItem(id, map, _count, tsize, hud_fontsize); + vector rgb = MapVote_RGB(id); + label = strcat(rgb_to_hexcolor(rgb), MapVote_FormatMapItem(id, map, _count, tsize, hud_fontsize, most_votes)); - text_size = stringwidth(label, false, hud_fontsize); + text_size = stringwidth(label, true, hud_fontsize); float save_rect_sizex = rect_size.x; rect_size.x = max(img_size.x, text_size) + rect_margin; @@ -270,8 +275,6 @@ void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, strin theAlpha = 1; theAlpha *= panel_fg_alpha; - vector rgb = MapVote_RGB(id); - // Highlight selected item if (!mv_winner) { @@ -286,12 +289,12 @@ void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, strin } } - drawstring(text_pos, label, hud_fontsize, rgb, theAlpha, DRAWFLAG_NORMAL); + drawcolorcodedstring(text_pos, label, hud_fontsize, theAlpha, DRAWFLAG_NORMAL); MapVote_DrawMapPicture(pic, pos, img_size, theAlpha); } -void MapVote_DrawAbstain(vector pos, float isize, float tsize, float _count, int id) +void MapVote_DrawAbstain(vector pos, float isize, float tsize, int _count, int id) { TC(int, id); vector rgb; @@ -300,7 +303,7 @@ void MapVote_DrawAbstain(vector pos, float isize, float tsize, float _count, int rgb = MapVote_RGB(id); - label = MapVote_FormatMapItem(id, _("Don't care"), _count, tsize, hud_fontsize); + label = MapVote_FormatMapItem(id, _("Don't care"), _count, tsize, hud_fontsize, -1); text_size = stringwidth(label, false, hud_fontsize); @@ -496,8 +499,13 @@ void MapVote_Draw() if (mv_winner_time) mv_winner_alpha = max(0.2, 1 - sqrt(max(0, time - mv_winner_time))); - void (vector, float, float, string, string, float, float) DrawItem; + int most_votes = -1; + if (mv_tie_winner == -2) + for (i = 0; i < mv_num_maps; ++i) + if (mv_votes[i] > most_votes) + most_votes = mv_votes[i]; + void (vector, float, float, string, string, float, int, int) DrawItem; if(gametypevote) DrawItem = GameTypeVote_DrawGameTypeItem; else @@ -508,9 +516,9 @@ void MapVote_Draw() tmp = mv_votes[i]; // FTEQCC bug: too many array accesses in the function call screw it up map = mv_maps[i]; if(mv_preview[i]) - DrawItem(pos + MapVote_GridVec(dist, i, mv_columns), dist.y, dist.x, map, mv_pics[i], tmp, i); + DrawItem(pos + MapVote_GridVec(dist, i, mv_columns), dist.y, dist.x, map, mv_pics[i], tmp, i, most_votes); else - DrawItem(pos + MapVote_GridVec(dist, i, mv_columns), dist.y, dist.x, map, "", tmp, i); + DrawItem(pos + MapVote_GridVec(dist, i, mv_columns), dist.y, dist.x, map, "", tmp, i, most_votes); } if(mv_abstain) @@ -725,9 +733,7 @@ void MapVote_Init() n_ssdirs = min(n_ssdirs, NUM_SSDIRS); mv_num_maps = min(MAPVOTE_COUNT, ReadByte()); - mv_abstain = ReadByte(); - if(mv_abstain) - mv_abstain = 1; // must be 1 for bool-true, makes stuff easier + mv_abstain = boolean(ReadByte()); mv_detail = ReadByte(); mv_ownvote = -1; @@ -982,6 +988,8 @@ void MapVote_UpdateVotes() else mv_votes[i] = -1; } + if(mv_detail) + mv_tie_winner = ReadChar(); mv_ownvote = ReadByte()-1; } diff --git a/qcsrc/server/mapvoting.qc b/qcsrc/server/mapvoting.qc index 523491f91d..a4b565fedf 100644 --- a/qcsrc/server/mapvoting.qc +++ b/qcsrc/server/mapvoting.qc @@ -42,7 +42,7 @@ float mapvote_rng[MAPVOTE_COUNT]; // random() value for each map to determine ti * This would use less storage but isn't truly random and can sometimes be predictable. */ bool mapvote_run; -bool mapvote_detail; +int mapvote_detail; bool mapvote_abstain; .int mapvote; @@ -240,8 +240,8 @@ void MapVote_Init() MapVote_UnzoneStrings(); mapvote_count = 0; - mapvote_detail = !autocvar_g_maplist_votable_nodetail; - mapvote_abstain = boolean(autocvar_g_maplist_votable_abstain); + mapvote_detail = autocvar_g_maplist_votable_detail; + mapvote_abstain = autocvar_g_maplist_votable_abstain; current_gametype_index = -1; if(mapvote_abstain) @@ -425,26 +425,30 @@ bool MapVote_SendEntity(entity this, entity to, int sf) } } - if(sf & 2) - { - // flag 2 == update of mask + if(sf & 2) // flag 2 == update of mask MapVote_WriteMask(); - } if(sf & 4) { if(mapvote_detail) + { for(i = 0; i < mapvote_count; ++i) if ( mapvote_maps_flags[i] & GTV_AVAILABLE ) WriteByte(MSG_ENTITY, mapvote_selections[i]); + if(mapvote_detail == 2) // tell the client who the tie winner will be + WriteChar(MSG_ENTITY, mapvote_ranked[0]); + else if(mapvote_selections[mapvote_ranked[0]] == 0) // no votes yet, don't draw a winner (-1) + WriteChar(MSG_ENTITY, -1); + else // figure out winners yourself (-2) + WriteChar(MSG_ENTITY, -2); + } + WriteByte(MSG_ENTITY, to.mapvote); } if(sf & 8) - { WriteByte(MSG_ENTITY, mapvote_winner + 1); - } return true; } @@ -903,7 +907,7 @@ bool GameTypeVote_Start() mapvote_count = 0; mapvote_timeout = time + autocvar_sv_vote_gametype_timeout; mapvote_abstain = false; - mapvote_detail = !autocvar_g_maplist_votable_nodetail; + mapvote_detail = autocvar_sv_vote_gametype_detail; int n = tokenizebyseparator(autocvar_sv_vote_gametype_options, " "); n = min(MAPVOTE_COUNT, n); diff --git a/qcsrc/server/mapvoting.qh b/qcsrc/server/mapvoting.qh index 25ffa60daa..c504bc9cbb 100644 --- a/qcsrc/server/mapvoting.qh +++ b/qcsrc/server/mapvoting.qh @@ -11,7 +11,7 @@ float autocvar_g_maplist_shuffle; bool autocvar_g_maplist_votable_abstain; float autocvar_g_maplist_votable_reduce_time; int autocvar_g_maplist_votable_reduce_count; -bool autocvar_g_maplist_votable_nodetail; +int autocvar_g_maplist_votable_detail; string autocvar_g_maplist_votable_screenshot_dir; bool autocvar_g_maplist_votable_suggestions; bool autocvar_g_maplist_votable_suggestions_override_mostrecent; @@ -25,6 +25,7 @@ float autocvar_sv_vote_gametype_timeout; string autocvar_sv_vote_gametype_options; float autocvar_sv_vote_gametype_reduce_time; int autocvar_sv_vote_gametype_reduce_count; +int autocvar_sv_vote_gametype_detail; bool autocvar_sv_vote_gametype_default_current; bool autocvar_sv_vote_gametype_maplist_reset = true; diff --git a/xonotic-server.cfg b/xonotic-server.cfg index 23f17ec5ba..4d848c0dd9 100644 --- a/xonotic-server.cfg +++ b/xonotic-server.cfg @@ -367,7 +367,7 @@ set g_maplist_votable_reduce_count 2 "number of options shown after being reduce set g_maplist_votable_timeout 30 "timeout for the map voting; must be below 50 seconds!" set g_maplist_votable_suggestions 2 "number of maps a player is allowed to suggest for the map voting screen using 'suggestmap'" set g_maplist_votable_suggestions_override_mostrecent 0 "allow players to suggest maps that have been played recently" -set g_maplist_votable_nodetail 0 "hide per-map vote counts (to avoid influential first votes)" +set g_maplist_votable_detail 1 "\"0\" = hide per-map vote counts (to avoid influential first votes), \"1\" = show the counts, \"2\" = also show which map will win in the event of a tie" set g_maplist_votable_abstain 0 "offer a 'don't care' option on the voting screen" set g_maplist_votable_screenshot_dir "maps levelshots" "where to look for map screenshots" @@ -376,6 +376,7 @@ set sv_vote_gametype_reduce_time 10 "reduce the number of options shown after th set sv_vote_gametype_reduce_count 2 "number of options shown after being reduced, during gametype vote screen; if < 2, keep all gametypes that received any votes, if at least 2 did" set sv_vote_gametype_options "dm tdm ca ctf" "identifiers of game modes on the voting screen, can be custom (max 9 chars). see example in server/server.cfg" set sv_vote_gametype_timeout 20 "how long the gametype vote screen lasts" +set sv_vote_gametype_detail 1 "\"0\" = hide per-gametype vote counts (to avoid influential first votes), \"1\" = show the counts, \"2\" = also show which gametype will win in the event of a tie" set sv_vote_gametype_default_current 1 "keep the current gametype if no one votes" set sv_vote_gametype_maplist_reset 1 "reset g_maplist when switching to a new gametype" -- 2.39.5