From 32a7e0ab3d36c9e9e53b3851bf874eca01b6c4c9 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 31 Jul 2024 05:01:59 +0000 Subject: [PATCH] Refactor map list code to use buffers --- qcsrc/common/command/generic.qc | 21 +++- qcsrc/common/command/generic.qh | 2 + qcsrc/lib/string.qh | 2 +- qcsrc/menu/xonotic/maplist.qc | 4 +- qcsrc/server/command/getreplies.qc | 25 ++-- qcsrc/server/intermission.qc | 176 +++++++++++++++-------------- qcsrc/server/intermission.qh | 5 +- qcsrc/server/mapvoting.qc | 2 + xonotic-server.cfg | 2 +- 9 files changed, 135 insertions(+), 104 deletions(-) diff --git a/qcsrc/common/command/generic.qc b/qcsrc/common/command/generic.qc index 3ce25e26d..b176cd811 100644 --- a/qcsrc/common/command/generic.qc +++ b/qcsrc/common/command/generic.qc @@ -229,6 +229,25 @@ GENERIC_COMMAND(dumpcommands, "Dump all commands on the program to _cmd } } +string maplist_shuffle(string input) +{ + int buf = buf_create(); + + int _cnt = 0; + FOREACH_WORD(input, true, + { + int _j = floor(random() * (_cnt + 1)); + if(_j != _cnt) + bufstr_set(buf, _cnt, bufstr_get(buf, _j)); + bufstr_set(buf, _j, it); + ++_cnt; + }); + + string output = buf_implode(buf, " "); + buf_del(buf); + return substring(output, 1, -1); +} + void GenericCommand_maplist(int request, int argc) { switch(request) @@ -293,7 +312,7 @@ void GenericCommand_maplist(int request, int argc) case "shuffle": // randomly shuffle the maplist { - cvar_set("g_maplist", shufflewords(cvar_string("g_maplist"))); + cvar_set("g_maplist", maplist_shuffle(cvar_string("g_maplist"))); return; } diff --git a/qcsrc/common/command/generic.qh b/qcsrc/common/command/generic.qh index 68aa0ae88..5117b2c41 100644 --- a/qcsrc/common/command/generic.qh +++ b/qcsrc/common/command/generic.qh @@ -38,3 +38,5 @@ void Curl_URI_Get_Callback(int id, float status, string data); int curl_uri_get_pos; float curl_uri_get_exec[URI_GET_CURL_END - URI_GET_CURL + 1]; string curl_uri_get_cvar[URI_GET_CURL_END - URI_GET_CURL + 1]; + +string maplist_shuffle(string input); diff --git a/qcsrc/lib/string.qh b/qcsrc/lib/string.qh index f3fbc6bc2..6c542c8fc 100644 --- a/qcsrc/lib/string.qh +++ b/qcsrc/lib/string.qh @@ -299,7 +299,7 @@ string swapwords(string str, float i, float j) { string s1, s2, s3, s4, s5; float si, ei, sj, ej, s0, en; - int n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle" + int n = tokenizebyseparator(str, " "); si = argv_start_index(i); sj = argv_start_index(j); ei = argv_end_index(i); diff --git a/qcsrc/menu/xonotic/maplist.qc b/qcsrc/menu/xonotic/maplist.qc index 89748e85c..84e408196 100644 --- a/qcsrc/menu/xonotic/maplist.qc +++ b/qcsrc/menu/xonotic/maplist.qc @@ -261,8 +261,8 @@ void MapList_Add_All(entity btn, entity me) _MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0, MapInfo_ForbiddenFlags(), 0); // all s = ""; for(i = 0; i < MapInfo_count; ++i) - s = strcat(s, " ", MapInfo_BSPName_ByID(i)); - cvar_set("g_maplist", substring(s, 1, strlen(s) - 1)); + s = cons(s, MapInfo_BSPName_ByID(i)); + cvar_set("g_maplist", s); me.refilter(me); } diff --git a/qcsrc/server/command/getreplies.qc b/qcsrc/server/command/getreplies.qc index 1746c107b..3eba391bc 100644 --- a/qcsrc/server/command/getreplies.qc +++ b/qcsrc/server/command/getreplies.qc @@ -232,21 +232,20 @@ string getladder() string getmaplist() { - string maplist = "", col; - int i, argc; + if(autocvar_g_maplist == "") + return "^7Map list is empty"; - argc = tokenize_console(autocvar_g_maplist); - for (i = 0; i < argc; ++i) + string maplist = ""; + int mapcount = 0; + FOREACH_WORD(autocvar_g_maplist, MapInfo_CheckMap(it), { - if (MapInfo_CheckMap(argv(i))) - { - if (i % 2) col = "^2"; else col = "^3"; - maplist = sprintf("%s%s%s ", maplist, col, argv(i)); - } - } + string col = (i % 2) ? "^2" : "^3"; + maplist = cons(maplist, strcat(col, it)); + mapcount += 1; + }); MapInfo_ClearTemps(); - return sprintf("^7Maps in list: %s\n", maplist); + return sprintf("^7Maps in list (%d): %s\n", mapcount, maplist); } const int LSMAPS_MAX = 250; @@ -276,12 +275,12 @@ string getlsmaps() if (i % 2) col = "^2"; else col = "^3"; } - lsmaps = sprintf("%s%s%s ", lsmaps, col, MapInfo_Map_bspname); + lsmaps = cons(lsmaps, strcat(col, MapInfo_Map_bspname)); } } if(added > LSMAPS_MAX) - lsmaps = sprintf("%s^7(%d not listed)", lsmaps, added - LSMAPS_MAX); + lsmaps = sprintf("%s ^7(%d not listed)", lsmaps, added - LSMAPS_MAX); MapInfo_ClearTemps(); return sprintf("^7Maps available (%d)%s: %s\n", added, (newmaps ? " (New maps have asterisks marked in blue)" : ""), lsmaps); diff --git a/qcsrc/server/intermission.qc b/qcsrc/server/intermission.qc index 9749a95d2..7f11112c1 100644 --- a/qcsrc/server/intermission.qc +++ b/qcsrc/server/intermission.qc @@ -32,7 +32,7 @@ int GetMaplistPosition() { if(idx < Map_Count) { - if(map == argv(idx)) + if(map == bufstr_get(maplist_buffer, idx)) { return idx; } @@ -41,7 +41,7 @@ int GetMaplistPosition() for(int pos = 0; pos < Map_Count; ++pos) { - if(map == argv(pos)) + if(map == bufstr_get(maplist_buffer, pos)) return pos; } @@ -99,9 +99,9 @@ bool MapHasRightSize(string map) return true; } -string Map_Filename(int position) +string Map_Filename(string m) { - return strcat("maps/", argv(position), ".bsp"); + return strcat("maps/", m, ".bsp"); } void Map_MarkAsRecent(string m) @@ -111,31 +111,32 @@ void Map_MarkAsRecent(string m) bool Map_IsRecent(string m) { + if(autocvar_g_maplist_mostrecent == "") + return false; return strhasword(autocvar_g_maplist_mostrecent, m); } -bool Map_Check(int position, float pass) +bool Map_Check(string m, int pass) { - string map_next = argv(position); if(pass <= 1) { - if(Map_IsRecent(map_next)) + if(Map_IsRecent(m)) return false; } - if(MapInfo_CheckMap(map_next)) + if(MapInfo_CheckMap(m)) { if(pass == 2) return true; // MapInfo_Map_flags was set by MapInfo_CheckMap() if (MapInfo_Map_flags & MAPINFO_FLAG_DONOTWANT) return false; - if(MapHasRightSize(map_next)) + if(MapHasRightSize(m)) return true; return false; } else { - string filename = Map_Filename(position); + string filename = Map_Filename(m); LOG_DEBUG( "Couldn't select '", filename, "'..." ); } @@ -156,7 +157,7 @@ void Map_Goto_SetIndex(int position) { Map_Current = position; cvar_set("g_maplist_index", ftos(position)); - Map_Goto_SetStr(argv(position)); + Map_Goto_SetStr(bufstr_get(maplist_buffer, position)); } void Map_Goto(float reinit) @@ -169,28 +170,34 @@ void Map_Goto(float reinit) // -2 = permanent failure int MaplistMethod_Iterate(void) // usual method { - int pass, i; + int attempts = 42; // skip advanced checks if this many maps fail LOG_TRACE("Trying MaplistMethod_Iterate"); - for(pass = 1; pass <= 2; ++pass) + for(int i = 1; i < Map_Count; ++i) { - for(i = 1; i < Map_Count; ++i) + int mapindex = (i + Map_Current) % Map_Count; + if(Map_Check(bufstr_get(maplist_buffer, mapindex), 1)) + return mapindex; + + attempts -= 1; + if(attempts <= 0) { - int mapindex; - mapindex = (i + Map_Current) % Map_Count; - if(Map_Check(mapindex, pass)) - return mapindex; + LOG_DEBUG("MaplistMethod_Iterate: Couldn't find a suitable map, falling back to next valid"); + break; } } - return -1; + + // failing that, just accept the next map in the list + int mapindex = (1 + Map_Current) % Map_Count; + return mapindex; } int MaplistMethod_Repeat(void) // fallback method { LOG_TRACE("Trying MaplistMethod_Repeat"); - if(Map_Check(Map_Current, 2)) + if(Map_Check(bufstr_get(maplist_buffer, Map_Current), 2)) return Map_Current; return -2; } @@ -207,93 +214,94 @@ int MaplistMethod_Random(void) // random map selection { int mapindex; mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map - if(Map_Check(mapindex, 1)) + if(Map_Check(bufstr_get(maplist_buffer, mapindex), 1)) return mapindex; } return -1; } -// the exponent sets a bias on the map selection: -// the higher the exponent, the less likely "shortly repeated" same maps are -int MaplistMethod_Shuffle(float exponent) // more clever shuffling +// NOTE: call Maplist_Close when you're done! +int Maplist_Init(void) { - float i, j, imax, insertpos; - - LOG_TRACE("Trying MaplistMethod_Shuffle"); - - imax = 42; - - for(i = 0; i <= imax; ++i) + bool have_maps = false; + if(autocvar_g_maplist != "") { - string newlist; - - // now reinsert this at another position - insertpos = (random() ** (1 / exponent)); // ]0, 1] - insertpos = insertpos * (Map_Count - 1); // ]0, Map_Count - 1] - insertpos = ceil(insertpos) + 1; // {2, 3, 4, ..., Map_Count} - LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos)); - - // insert the current map there - newlist = ""; - for(j = 1; j < insertpos; ) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above + // make sure there is at least one playable map in the list + bool needtrim = false; + FOREACH_WORD(autocvar_g_maplist, true, { - if (j + 2 < insertpos) - newlist = strcat(newlist, " ", argv(j++), " ", argv(j++), " ", argv(j++)); - else - newlist = strcat(newlist, " ", argv(j++)); - } - newlist = strcat(newlist, " ", argv(0)); // now insert the just selected map - for(j = insertpos; j < Map_Count; ) // i == Map_Count: no loop, has just been inserted as last + if(!fexists(Map_Filename(it))) + { + needtrim = true; + if(have_maps) + break; + continue; + } + if(have_maps || !Map_Check(it, 2)) + continue; + have_maps = true; + if(needtrim) + break; + }); + + // additionally trim any non-existent maps + if(needtrim) { - if (j + 2 < Map_Count) - newlist = strcat(newlist, " ", argv(j++), " ", argv(j++), " ", argv(j++)); - else - newlist = strcat(newlist, " ", argv(j++)); + int trimmedmaps = 0; + string newmaplist = ""; + FOREACH_WORD(autocvar_g_maplist, true, + { + if(!fexists(Map_Filename(it))) + { + trimmedmaps += 1; + continue; + } + newmaplist = cons(newmaplist, it); + }); + cvar_set("g_maplist", newmaplist); + LOG_DEBUGF("Maplist_Init: trimmed %d missing maps from the list", trimmedmaps); } - newlist = substring(newlist, 1, strlen(newlist) - 1); - cvar_set("g_maplist", newlist); - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - - // NOTE: the selected map has just been inserted at (insertpos-1)th position - if (Map_Check(insertpos - 1, 1)) - return insertpos - 1; - } - return -1; -} - -int Maplist_Init(void) -{ - int i, available_maps = 0; - Map_Count = 0; - if(autocvar_g_maplist != "") - { - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - for (i = 0; i < Map_Count; ++i) - if (Map_Check(i, 2)) - ++available_maps; } - if (!available_maps) + if (!have_maps) { bprint( "Maplist contains no usable maps! Resetting it to default map list.\n" ); cvar_set("g_maplist", MapInfo_ListAllowedMaps(MapInfo_CurrentGametype(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags())); if(!server_is_dedicated) localcmd("\nmenu_cmd sync\n"); - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - for (i = 0; i < Map_Count; ++i) - if (Map_Check(i, 2)) - ++available_maps; } + maplist_buffer = buf_create(); + + int _cnt = 0; + FOREACH_WORD(autocvar_g_maplist, Map_Check(it, 2), + { + // NOTE: inlined maplist_shuffle function to avoid a second buffer, keep both in sync + if(autocvar_g_maplist_shuffle) + { + int _j = floor(random() * (_cnt + 1)); + if(_j != _cnt) + bufstr_set(maplist_buffer, _cnt, bufstr_get(maplist_buffer, _j)); + bufstr_set(maplist_buffer, _j, it); + ++_cnt; + } + else + bufstr_set(maplist_buffer, i, it); + }); + + Map_Count = buf_getsize(maplist_buffer); + if(Map_Count == 0) error("empty maplist, cannot select a new map"); Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1); - if(autocvar_g_maplist_shuffle) - cvar_set("g_maplist", shufflewords(autocvar_g_maplist)); + return Map_Count; +} - return available_maps; +void Maplist_Close() +{ + buf_del(maplist_buffer); } // NOTE: call Maplist_Init() before making GetNextMap() call(s) @@ -301,9 +309,6 @@ string GetNextMap(void) { int nextMap = -1; - if(nextMap == -1 && autocvar_g_maplist_shuffle > 0) - nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1); - if(nextMap == -1 && autocvar_g_maplist_selectrandom) nextMap = MaplistMethod_Random(); @@ -394,6 +399,7 @@ void GotoNextMap(float reinit) Maplist_Init(); string nextMap = GetNextMap(); + Maplist_Close(); if(nextMap == "") error("Everything is broken - cannot find a next map. Please report this to the developers."); Map_Goto(reinit); diff --git a/qcsrc/server/intermission.qh b/qcsrc/server/intermission.qh index 67413147c..44a5a2da8 100644 --- a/qcsrc/server/intermission.qh +++ b/qcsrc/server/intermission.qh @@ -10,6 +10,8 @@ bool intermission_running; float intermission_exittime; bool alreadychangedlevel; +int maplist_buffer; + string GetGametype(); string GetMapname(); @@ -22,9 +24,10 @@ void GotoNextMap(float reinit); bool Map_IsRecent(string m); -bool Map_Check(int position, float pass); +bool Map_Check(string m, float pass); int Maplist_Init(void); +void Maplist_Close(); string GetNextMap(void); void Map_Goto_SetStr(string nextmapname); diff --git a/qcsrc/server/mapvoting.qc b/qcsrc/server/mapvoting.qc index 8fa3c54f0..b52f4518d 100644 --- a/qcsrc/server/mapvoting.qc +++ b/qcsrc/server/mapvoting.qc @@ -214,6 +214,8 @@ void MapVote_AddVotableMaps(int nmax, int smax) for (int i = 0; i < max_attempts && mapvote_count < nmax; ++i) MapVote_AddVotable(GetNextMap(), false); + + Maplist_Close(); } string voted_gametype_string; diff --git a/xonotic-server.cfg b/xonotic-server.cfg index a4fa5d3b7..d7304eca0 100644 --- a/xonotic-server.cfg +++ b/xonotic-server.cfg @@ -227,7 +227,7 @@ set g_maplist_mostrecent "" "contains the name of the maps that were most recent set g_maplist_mostrecent_count 3 "number of most recent maps that are blocked from being played again" set g_maplist_index 0 "this is used internally for saving position in maplist cycle" set g_maplist_selectrandom 0 "if 1, a random map will be chosen as next map - DEPRECATED in favor of g_maplist_shuffle" -set g_maplist_shuffle 1 "1: shuffling method which avoids playing the same maps in short succession by taking out the first element and inserting it into g_maplist with a bias to the end of the list. -1: a simpler shuffling method which should be adequate if g_maplist_mostrecent_count is large enough." +set g_maplist_shuffle 1 "shuffles the order of maps during selection to ensure random maps are chosen" set g_maplist_check_waypoints 0 "when 1, maps are skipped if there currently are bots, but the map has no waypoints" set g_maplist_ignore_sizes 0 "when 1, all maps are shown in the map list regardless of player count" set g_maplist_sizes_count_maxplayers 1 "check the player limit when getting the player count so forced spectators don't affect the size restrictions" -- 2.39.2