From 04fb7110b37fe0adf6b011a97982081423411d19 Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:36:21 -0400 Subject: [PATCH 01/24] Add individual map cooldown configuration List cooldown of all maps when running !nominate without having set a nomination --- cfg/cs2fixes/cs2fixes.cfg | 1 - configs/maplist.cfg.example | 4 ++ src/map_votes.cpp | 91 ++++++++++++++++--------------------- src/map_votes.h | 27 +++++++---- 4 files changed, 62 insertions(+), 61 deletions(-) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 2c23756d0..87c615315 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -78,7 +78,6 @@ cs2f_rtv_success_ratio 0.6 // Ratio needed to pass RTV cs2f_rtv_endround 0 // Whether to immediately end the round when RTV succeeds // Map vote settings -cs2f_vote_maps_cooldown 10 // Number of maps to wait until a map can be voted / nominated again i.e. cooldown. cs2f_vote_max_nominations 10 // Number of nominations to include per vote, out of a maximum of 10. // User preferences settings diff --git a/configs/maplist.cfg.example b/configs/maplist.cfg.example index b4632436c..e1d85f90e 100644 --- a/configs/maplist.cfg.example +++ b/configs/maplist.cfg.example @@ -3,20 +3,24 @@ "de_dust2" { "enabled" "1" + "cooldown" "1" } "ze_my_first_ze_map" { "workshop_id" "123" "enabled" "1" + "cooldown" "2" } "ze_my_second_ze_map" { "workshop_id" "456" "enabled" "1" + "cooldown" "3" } "ze_my_third_ze_map" { "workshop_id" "789" "enabled" "1" + "cooldown" "1" } } \ No newline at end of file diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 3ce88c1cc..ada91b1e1 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -70,21 +70,6 @@ CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current Message("Map list reloaded\n"); } -CON_COMMAND_F(cs2f_vote_maps_cooldown, "Number of maps to wait until a map can be voted / nominated again i.e. cooldown.", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) -{ - if (!g_pMapVoteSystem) { - Message("The map vote subsystem is not enabled.\n"); - return; - } - - if (args.ArgC() < 2) - Message("%s %d\n", args[0], g_pMapVoteSystem->GetMapCooldown()); - else { - int iCurrentCooldown = g_pMapVoteSystem->GetMapCooldown(); - g_pMapVoteSystem->SetMapCooldown(V_StringToInt32(args[1], iCurrentCooldown)); - } -} - CON_COMMAND_F(cs2f_vote_max_nominations, "Number of nominations to include per vote, out of a maximum of 10.", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) { if (!g_pMapVoteSystem) { @@ -115,12 +100,12 @@ CON_COMMAND_CHAT_FLAGS(setnextmap, "[mapname] - Force next map (empty to clear f } } -static int __cdecl OrderStringsLexicographically(const char* const* a, const char* const* b) +static int __cdecl OrderStringsLexicographically(const MapCooldownPair *a, const MapCooldownPair *b) { - return V_strcasecmp(*a, *b); + return V_strcasecmp(a->name, b->name); } -CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nomination)", ADMFLAG_NONE) +CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nomination or list all maps)", ADMFLAG_NONE) { if (!g_bVoteManagerEnable || !player) return; @@ -154,16 +139,25 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom { ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The list of all maps will be shown in console."); ClientPrint(player, HUD_PRINTCONSOLE, "The list of all maps is:"); - CUtlVector vecMapNames; + CUtlVector vecMapNames; for (int i = 0; i < g_pMapVoteSystem->GetMapListSize(); i++) - vecMapNames.AddToTail(g_pMapVoteSystem->GetMapName(i)); + { + MapCooldownPair currentMap; + currentMap.name = g_pMapVoteSystem->GetMapName(i); + currentMap.cooldown = g_pMapVoteSystem->GetCooldownMap(i); + vecMapNames.AddToTail(currentMap); + } vecMapNames.Sort(OrderStringsLexicographically); - // TODO: print cooldown time here too (after rewrite) FOR_EACH_VEC(vecMapNames, i) - ClientPrint(player, HUD_PRINTCONSOLE, "- %s", vecMapNames[i]); + { + if (vecMapNames[i].cooldown > 0) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Cooldown: %d", vecMapNames[i].name, vecMapNames[i].cooldown); + else + ClientPrint(player, HUD_PRINTCONSOLE, "- %s", vecMapNames[i].name); + } break; } @@ -200,22 +194,6 @@ CON_COMMAND_CHAT(nomlist, "- List the list of nominations") } } -// TODO: also merge this into nominate after cooldown system is rewritten -CON_COMMAND_CHAT(mapcooldowns, "- List the maps currently in cooldown") -{ - if (!g_bVoteManagerEnable) - return; - - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The list of maps in cooldown will be shown in console."); - ClientPrint(player, HUD_PRINTCONSOLE, "The list of maps in cooldown is:"); - int iMapsInCooldown = g_pMapVoteSystem->GetMapsInCooldown(); - for (int i = iMapsInCooldown - 1; i >= 0; i--) { - int iMapIndex = g_pMapVoteSystem->GetCooldownMap(i); - const char* sMapName = g_pMapVoteSystem->GetMapName(iMapIndex); - ClientPrint(player, HUD_PRINTCONSOLE, "- %s (%d maps ago)", sMapName, iMapsInCooldown - i); - } -} - GAME_EVENT_F(cs_win_panel_match) { if (g_bVoteManagerEnable && !g_pMapVoteSystem->IsVoteOngoing()) @@ -231,7 +209,7 @@ GAME_EVENT_F(endmatch_mapvote_selecting_map) bool CMapVoteSystem::IsMapIndexEnabled(int iMapIndex) { if (iMapIndex >= m_vecMapList.Count() || iMapIndex < 0) return false; - if (m_vecLastPlayedMapIndexes.HasElement(iMapIndex)) return false; + if (GetCooldownMap(iMapIndex) > 0) return false; return m_vecMapList[iMapIndex].IsEnabled(); } @@ -251,10 +229,11 @@ void CMapVoteSystem::OnLevelInit(const char* pMapName) return -1.0f; }); - int iLastCooldownIndex = GetMapsInCooldown() - 1; - int iInitMapIndex = GetMapIndexFromSubstring(pMapName); - if (iLastCooldownIndex >= 0 && iInitMapIndex >= 0 && GetCooldownMap(iLastCooldownIndex) != iInitMapIndex) { - PushMapIndexInCooldown(iInitMapIndex); + // Put the loaded map on cooldown + int iMapIndex = GetMapIndexFromSubstring(pMapName); + if (iMapIndex > 0 && iMapIndex < GetMapListSize()) + { + PutMapOnCooldownAndDecrement(iMapIndex); } } @@ -393,12 +372,6 @@ void CMapVoteSystem::FinishVote() ClientPrintAll(HUD_PRINTCONSOLE, "- %s got %d votes\n", GetMapName(iMapIndex), arrMapVotes[i]); } - // Store the winning map in the vector of played maps and pop until desired cooldown - PushMapIndexInCooldown(iWinningMap); - while (m_vecLastPlayedMapIndexes.Count() > m_iMapCooldown) { - m_vecLastPlayedMapIndexes.Remove(0); - } - // Do the final clean-up for (int i = 0; i < gpGlobals->maxClients; i++) ClearPlayerInfo(i); @@ -658,12 +631,13 @@ bool CMapVoteSystem::LoadMapList() const char *pszName = pKey->GetName(); uint64 iWorkshopId = pKey->GetUint64("workshop_id"); bool bIsEnabled = pKey->GetBool("enabled", true); + int iCooldown = pKey->GetInt("cooldown"); if (iWorkshopId != 0) QueueMapDownload(iWorkshopId); // We just append the maps to the map list - m_vecMapList.AddToTail(CMapInfo(pszName, iWorkshopId, bIsEnabled)); + m_vecMapList.AddToTail(CMapInfo(pszName, iWorkshopId, bIsEnabled, iCooldown)); } new CTimer(0.f, true, true, []() @@ -684,9 +658,9 @@ bool CMapVoteSystem::LoadMapList() CMapInfo map = m_vecMapList[i]; if (map.GetWorkshopId() == 0) - ConMsg("Map %d is %s, which is %s.\n", i, map.GetName(), map.IsEnabled() ? "enabled" : "disabled"); + ConMsg("Map %d is %s, which is %s with a cooldown of %d.\n", i, map.GetName(), map.IsEnabled() ? "enabled" : "disabled", map.GetBaseCooldown()); else - ConMsg("Map %d is %s with workshop id %llu, which is %s.\n", i, map.GetName(), map.GetWorkshopId(), map.IsEnabled()? "enabled" : "disabled"); + ConMsg("Map %d is %s with workshop id %llu, which is %s with a cooldown of %d.\n", i, map.GetName(), map.GetWorkshopId(), map.IsEnabled()? "enabled" : "disabled", map.GetBaseCooldown()); } m_bMapListLoaded = true; @@ -716,4 +690,17 @@ CUtlStringList CMapVoteSystem::CreateWorkshopMapGroup() mapList.CopyAndAddToTail(GetMapName(i)); return mapList; +} + +void CMapVoteSystem::PutMapOnCooldownAndDecrement(int iMapIndex) +{ + // Decrement the cooldown of all maps in the map list + + FOR_EACH_VEC(m_vecMapList, i) + { + CMapInfo * pMap = &m_vecMapList[i]; + pMap->DecrementCooldown(); + } + + PutMapOnCooldown(iMapIndex); } \ No newline at end of file diff --git a/src/map_votes.h b/src/map_votes.h index ef68a2089..469e1f4f3 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -47,24 +47,39 @@ namespace NominationReturnCodes class CMapInfo { public: - CMapInfo(const char* pszName, uint64 iWorkshopId, bool bIsEnabled) + CMapInfo(const char* pszName, uint64 iWorkshopId, bool bIsEnabled, int iBaseCooldown) { V_strcpy(m_pszName, pszName); m_iWorkshopId = iWorkshopId; m_bIsEnabled = bIsEnabled; + m_iBaseCooldown = iBaseCooldown; + m_iCurrentCooldown = 0; } const char* GetName() { return (const char*)m_pszName; }; uint64 GetWorkshopId() const { return m_iWorkshopId; }; bool IsEnabled() { return m_bIsEnabled; }; + int GetBaseCooldown() { return m_iBaseCooldown; }; + int GetCooldown() { return m_iCurrentCooldown; }; + void ResetCooldownToBase() { m_iCurrentCooldown = m_iBaseCooldown; }; + void DecrementCooldown() { m_iCurrentCooldown = MAX(0, (m_iCurrentCooldown - 1)); } private: char m_pszName[64]; uint64 m_iWorkshopId; bool m_bIsEnabled; + int m_iBaseCooldown; + int m_iCurrentCooldown; }; +typedef struct +{ + const char* name; + int cooldown; +} MapCooldownPair; + + class CMapVoteSystem { public: @@ -81,12 +96,10 @@ class CMapVoteSystem void StartVote(); void FinishVote(); bool RegisterPlayerVote(CPlayerSlot iPlayerSlot, int iVoteOption); - void SetMapCooldown(int iMapCooldown) { m_iMapCooldown = iMapCooldown; }; int GetMapIndexFromSubstring(const char* sMapSubstring); - int GetMapCooldown() { return m_iMapCooldown; }; - int GetMapsInCooldown() { return m_vecLastPlayedMapIndexes.Count(); } - int GetCooldownMap(int iCooldownIndex) { return m_vecLastPlayedMapIndexes[iCooldownIndex]; }; - void PushMapIndexInCooldown(int iMapIndex) { m_vecLastPlayedMapIndexes.AddToTail(iMapIndex); }; + int GetCooldownMap(int iMapIndex) { return m_vecMapList[iMapIndex].GetCooldown(); }; + void PutMapOnCooldown(int iMapIndex) { m_vecMapList[iMapIndex].ResetCooldownToBase(); }; + void PutMapOnCooldownAndDecrement(int iMapIndex); void SetMaxNominatedMaps(int iMaxNominatedMaps) { m_iMaxNominatedMaps = iMaxNominatedMaps; }; int GetMaxNominatedMaps() { return m_iMaxNominatedMaps; }; int AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring); @@ -118,10 +131,8 @@ class CMapVoteSystem CUtlQueue m_DownloadQueue; CUtlVector m_vecMapList; - CUtlVector m_vecLastPlayedMapIndexes; int m_arrPlayerNominations[MAXPLAYERS]; int m_iForcedNextMapIndex = -1; - int m_iMapCooldown = 10; int m_iMaxNominatedMaps = 10; int m_iRandomWinnerShift = 0; int m_arrPlayerVotes[MAXPLAYERS]; From 40a7ff3101456694c5a97f3166a7d271b53ef5fa Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:52:42 -0400 Subject: [PATCH 02/24] Add map cooldown saving and loading from file --- src/map_votes.cpp | 41 +++++++++++++++++++++++++++++++++++++---- src/map_votes.h | 5 +++-- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index ada91b1e1..3e3de9dc6 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -229,11 +229,12 @@ void CMapVoteSystem::OnLevelInit(const char* pMapName) return -1.0f; }); - // Put the loaded map on cooldown + // Put the loaded map on cooldown and decrease the cooldown counter of others if loaded map is present in maplist int iMapIndex = GetMapIndexFromSubstring(pMapName); - if (iMapIndex > 0 && iMapIndex < GetMapListSize()) + if (iMapIndex >= 0 && iMapIndex < GetMapListSize()) { PutMapOnCooldownAndDecrement(iMapIndex); + WriteMapCooldownsToFile(); } } @@ -627,17 +628,26 @@ bool CMapVoteSystem::LoadMapList() return false; } + // Load map cooldowns from file + KeyValues* pKVcooldowns = new KeyValues("cooldowns"); + KeyValues::AutoDelete autoDeleteKVcooldowns(pKVcooldowns); + const char *pszCooldownFilePath = "addons/cs2fixes/configs/cooldowns.cfg"; + if (!pKVcooldowns->LoadFromFile(g_pFullFileSystem, pszCooldownFilePath)) { + Message("Failed to load cooldown file at %s - resetting all cooldowns to 0\n", pszCooldownFilePath); + } + for (KeyValues* pKey = pKV->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey()) { const char *pszName = pKey->GetName(); uint64 iWorkshopId = pKey->GetUint64("workshop_id"); bool bIsEnabled = pKey->GetBool("enabled", true); - int iCooldown = pKey->GetInt("cooldown"); + int iBaseCooldown = pKey->GetInt("cooldown"); + int iCurrentCooldown = pKVcooldowns->GetInt(pszName, 0); if (iWorkshopId != 0) QueueMapDownload(iWorkshopId); // We just append the maps to the map list - m_vecMapList.AddToTail(CMapInfo(pszName, iWorkshopId, bIsEnabled, iCooldown)); + m_vecMapList.AddToTail(CMapInfo(pszName, iWorkshopId, bIsEnabled, iBaseCooldown, iCurrentCooldown)); } new CTimer(0.f, true, true, []() @@ -703,4 +713,27 @@ void CMapVoteSystem::PutMapOnCooldownAndDecrement(int iMapIndex) } PutMapOnCooldown(iMapIndex); +} + +bool CMapVoteSystem::WriteMapCooldownsToFile() +{ + KeyValues* pKV = new KeyValues("cooldowns"); + KeyValues::AutoDelete autoDelete(pKV); + + const char *pszPath = "addons/cs2fixes/configs/cooldowns.cfg"; + + FOR_EACH_VEC(m_vecMapList, i) + { + const char* mapName = m_vecMapList[i].GetName(); + const int mapCooldown = m_vecMapList[i].GetCooldown(); + pKV->AddInt(mapName, mapCooldown); + } + + if (!pKV->SaveToFile(g_pFullFileSystem, pszPath)) + { + Panic("Failed to write cooldowns to file: %s\n", pszPath); + return false; + } + + return true; } \ No newline at end of file diff --git a/src/map_votes.h b/src/map_votes.h index 469e1f4f3..a5ac25e24 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -47,13 +47,13 @@ namespace NominationReturnCodes class CMapInfo { public: - CMapInfo(const char* pszName, uint64 iWorkshopId, bool bIsEnabled, int iBaseCooldown) + CMapInfo(const char* pszName, uint64 iWorkshopId, bool bIsEnabled, int iBaseCooldown, int iCurrentCooldown) { V_strcpy(m_pszName, pszName); m_iWorkshopId = iWorkshopId; m_bIsEnabled = bIsEnabled; m_iBaseCooldown = iBaseCooldown; - m_iCurrentCooldown = 0; + m_iCurrentCooldown = iCurrentCooldown; } const char* GetName() { return (const char*)m_pszName; }; @@ -126,6 +126,7 @@ class CMapVoteSystem int WinningMapIndex(); bool UpdateWinningMap(); void GetNominatedMapsForVote(CUtlVector& vecChosenNominatedMaps); + bool WriteMapCooldownsToFile(); STEAM_GAMESERVER_CALLBACK_MANUAL(CMapVoteSystem, OnMapDownloaded, DownloadItemResult_t, m_CallbackDownloadItemResult); CUtlQueue m_DownloadQueue; From 629c8a34a4316aa110e096efcc2c67cb02a8a786 Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Tue, 15 Oct 2024 23:30:39 -0400 Subject: [PATCH 03/24] Move processing of map cooldowns to after vote This is the same behavour as the map vote system in CS:GO, where the current map will not be set on cooldown until after it is voted off --- src/map_votes.cpp | 41 ++++++++++++++++++++++------------------- src/map_votes.h | 8 ++++++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 3e3de9dc6..99ee0168b 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -100,7 +100,7 @@ CON_COMMAND_CHAT_FLAGS(setnextmap, "[mapname] - Force next map (empty to clear f } } -static int __cdecl OrderStringsLexicographically(const MapCooldownPair *a, const MapCooldownPair *b) +static int __cdecl OrderStringsLexicographically(const MapCooldownStruct *a, const MapCooldownStruct *b) { return V_strcasecmp(a->name, b->name); } @@ -139,14 +139,15 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom { ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The list of all maps will be shown in console."); ClientPrint(player, HUD_PRINTCONSOLE, "The list of all maps is:"); - CUtlVector vecMapNames; + CUtlVector vecMapNames; for (int i = 0; i < g_pMapVoteSystem->GetMapListSize(); i++) { - MapCooldownPair currentMap; - currentMap.name = g_pMapVoteSystem->GetMapName(i); - currentMap.cooldown = g_pMapVoteSystem->GetCooldownMap(i); - vecMapNames.AddToTail(currentMap); + MapCooldownStruct mapData; + mapData.name = g_pMapVoteSystem->GetMapName(i); + mapData.cooldown = g_pMapVoteSystem->GetCooldownMap(i); + mapData.mapIndex = i; + vecMapNames.AddToTail(mapData); } vecMapNames.Sort(OrderStringsLexicographically); @@ -155,6 +156,8 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom { if (vecMapNames[i].cooldown > 0) ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Cooldown: %d", vecMapNames[i].name, vecMapNames[i].cooldown); + else if (vecMapNames[i].mapIndex == g_pMapVoteSystem->GetCurrentMapIndex()) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Current Map", vecMapNames[i].name); else ClientPrint(player, HUD_PRINTCONSOLE, "- %s", vecMapNames[i].name); } @@ -209,7 +212,7 @@ GAME_EVENT_F(endmatch_mapvote_selecting_map) bool CMapVoteSystem::IsMapIndexEnabled(int iMapIndex) { if (iMapIndex >= m_vecMapList.Count() || iMapIndex < 0) return false; - if (GetCooldownMap(iMapIndex) > 0) return false; + if (GetCooldownMap(iMapIndex) > 0 || GetCurrentMapIndex() == iMapIndex) return false; return m_vecMapList[iMapIndex].IsEnabled(); } @@ -229,13 +232,7 @@ void CMapVoteSystem::OnLevelInit(const char* pMapName) return -1.0f; }); - // Put the loaded map on cooldown and decrease the cooldown counter of others if loaded map is present in maplist - int iMapIndex = GetMapIndexFromSubstring(pMapName); - if (iMapIndex >= 0 && iMapIndex < GetMapListSize()) - { - PutMapOnCooldownAndDecrement(iMapIndex); - WriteMapCooldownsToFile(); - } + SetCurrentMapIndex(GetMapIndexFromSubstring(pMapName)); } void CMapVoteSystem::StartVote() @@ -373,6 +370,16 @@ void CMapVoteSystem::FinishVote() ClientPrintAll(HUD_PRINTCONSOLE, "- %s got %d votes\n", GetMapName(iMapIndex), arrMapVotes[i]); } + // Put the map on cooldown as we transition to the next map if map index is valid, also decrease cooldown remaining for others + // Map index will be invalid for any map not added to maplist.cfg + DecrementAllMapCooldowns(); + + int iMapIndex = GetCurrentMapIndex(); + if (iMapIndex >= 0 && iMapIndex < GetMapListSize()) + PutMapOnCooldown(iMapIndex); + + WriteMapCooldownsToFile(); + // Do the final clean-up for (int i = 0; i < gpGlobals->maxClients; i++) ClearPlayerInfo(i); @@ -702,17 +709,13 @@ CUtlStringList CMapVoteSystem::CreateWorkshopMapGroup() return mapList; } -void CMapVoteSystem::PutMapOnCooldownAndDecrement(int iMapIndex) +void CMapVoteSystem::DecrementAllMapCooldowns() { - // Decrement the cooldown of all maps in the map list - FOR_EACH_VEC(m_vecMapList, i) { CMapInfo * pMap = &m_vecMapList[i]; pMap->DecrementCooldown(); } - - PutMapOnCooldown(iMapIndex); } bool CMapVoteSystem::WriteMapCooldownsToFile() diff --git a/src/map_votes.h b/src/map_votes.h index a5ac25e24..5d83e7bc0 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -77,7 +77,8 @@ typedef struct { const char* name; int cooldown; -} MapCooldownPair; + int mapIndex; +} MapCooldownStruct; class CMapVoteSystem @@ -99,7 +100,7 @@ class CMapVoteSystem int GetMapIndexFromSubstring(const char* sMapSubstring); int GetCooldownMap(int iMapIndex) { return m_vecMapList[iMapIndex].GetCooldown(); }; void PutMapOnCooldown(int iMapIndex) { m_vecMapList[iMapIndex].ResetCooldownToBase(); }; - void PutMapOnCooldownAndDecrement(int iMapIndex); + void DecrementAllMapCooldowns(); void SetMaxNominatedMaps(int iMaxNominatedMaps) { m_iMaxNominatedMaps = iMaxNominatedMaps; }; int GetMaxNominatedMaps() { return m_iMaxNominatedMaps; }; int AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring); @@ -121,6 +122,8 @@ class CMapVoteSystem uint64 GetCurrentWorkshopMap() { return m_iCurrentWorkshopMap; } const char* GetCurrentMap() { return m_strCurrentMap.c_str(); } int GetDownloadQueueSize() { return m_DownloadQueue.Count(); } + int GetCurrentMapIndex() { return m_iCurrentMapIndex; } + void SetCurrentMapIndex(int iMapIndex) { m_iCurrentMapIndex = iMapIndex; } private: int WinningMapIndex(); @@ -137,6 +140,7 @@ class CMapVoteSystem int m_iMaxNominatedMaps = 10; int m_iRandomWinnerShift = 0; int m_arrPlayerVotes[MAXPLAYERS]; + int m_iCurrentMapIndex; bool m_bIsVoteOngoing = false; bool m_bMapListLoaded = false; bool m_bIntermissionStarted = false; From 875e428d722258af1f68285d860b2315a7edf144 Mon Sep 17 00:00:00 2001 From: Hichatu Date: Wed, 19 Jun 2024 16:16:02 +0100 Subject: [PATCH 04/24] Min and Max player requirements --- src/map_votes.cpp | 30 ++++++++++++++++++++++++++---- src/map_votes.h | 8 +++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 99ee0168b..786eafc61 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -209,11 +209,31 @@ GAME_EVENT_F(endmatch_mapvote_selecting_map) g_pMapVoteSystem->FinishVote(); } +int MapVote_GetOnlinePlayers() +{ + int iOnlinePlayers = 0; + for (int i = 0; i < gpGlobals->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + + if (pPlayer && !pPlayer->IsFakeClient()) + { + iOnlinePlayers++; + } + } + return iOnlinePlayers; +} + bool CMapVoteSystem::IsMapIndexEnabled(int iMapIndex) { if (iMapIndex >= m_vecMapList.Count() || iMapIndex < 0) return false; if (GetCooldownMap(iMapIndex) > 0 || GetCurrentMapIndex() == iMapIndex) return false; - return m_vecMapList[iMapIndex].IsEnabled(); + if (!m_vecMapList[iMapIndex].IsEnabled()) return false; + + int iOnlinePlayers = MapVote_GetOnlinePlayers(); + bool bMeetsMaxPlayers = iOnlinePlayers <= m_vecMapList[iMapIndex].GetMaxPlayers(); + bool bMeetsMinPlayers = iOnlinePlayers >= m_vecMapList[iMapIndex].GetMinPlayers(); + return bMeetsMaxPlayers && bMeetsMinPlayers; } void CMapVoteSystem::OnLevelInit(const char* pMapName) @@ -648,13 +668,15 @@ bool CMapVoteSystem::LoadMapList() uint64 iWorkshopId = pKey->GetUint64("workshop_id"); bool bIsEnabled = pKey->GetBool("enabled", true); int iBaseCooldown = pKey->GetInt("cooldown"); + int iMinPlayers = pKey->GetInt("MinPlayers", 0); + int iMaxPlayers = pKey->GetInt("MaxPlayers", 64); int iCurrentCooldown = pKVcooldowns->GetInt(pszName, 0); if (iWorkshopId != 0) QueueMapDownload(iWorkshopId); // We just append the maps to the map list - m_vecMapList.AddToTail(CMapInfo(pszName, iWorkshopId, bIsEnabled, iBaseCooldown, iCurrentCooldown)); + m_vecMapList.AddToTail(CMapInfo(pszName, iWorkshopId, bIsEnabled, iMinPlayers, iMaxPlayers, iBaseCooldown, iCurrentCooldown)); } new CTimer(0.f, true, true, []() @@ -675,9 +697,9 @@ bool CMapVoteSystem::LoadMapList() CMapInfo map = m_vecMapList[i]; if (map.GetWorkshopId() == 0) - ConMsg("Map %d is %s, which is %s with a cooldown of %d.\n", i, map.GetName(), map.IsEnabled() ? "enabled" : "disabled", map.GetBaseCooldown()); + ConMsg("Map %d is %s, which is %s. MinPlayers: %llu MaxPlayers: %llu Cooldown: %d\n", i, map.GetName(), map.IsEnabled() ? "enabled" : "disabled", map.GetMinPlayers(), map.GetMaxPlayers(), map.GetBaseCooldown()); else - ConMsg("Map %d is %s with workshop id %llu, which is %s with a cooldown of %d.\n", i, map.GetName(), map.GetWorkshopId(), map.IsEnabled()? "enabled" : "disabled", map.GetBaseCooldown()); + ConMsg("Map %d is %s with workshop id %llu, which is %s. MinPlayers: %llu MaxPlayers: %llu Cooldown: %d\n", i, map.GetName(), map.GetWorkshopId(), map.IsEnabled()? "enabled" : "disabled", map.GetMinPlayers(), map.GetMaxPlayers(), map.GetBaseCooldown()); } m_bMapListLoaded = true; diff --git a/src/map_votes.h b/src/map_votes.h index 5d83e7bc0..66f23979e 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -47,13 +47,15 @@ namespace NominationReturnCodes class CMapInfo { public: - CMapInfo(const char* pszName, uint64 iWorkshopId, bool bIsEnabled, int iBaseCooldown, int iCurrentCooldown) + CMapInfo(const char* pszName, uint64 iWorkshopId, bool bIsEnabled, int iMinPlayers, int iMaxPlayers, int iBaseCooldown, int iCurrentCooldown) { V_strcpy(m_pszName, pszName); m_iWorkshopId = iWorkshopId; m_bIsEnabled = bIsEnabled; m_iBaseCooldown = iBaseCooldown; m_iCurrentCooldown = iCurrentCooldown; + m_iMinPlayers = iMinPlayers; + m_iMaxPlayers = iMaxPlayers; } const char* GetName() { return (const char*)m_pszName; }; @@ -63,11 +65,15 @@ class CMapInfo int GetCooldown() { return m_iCurrentCooldown; }; void ResetCooldownToBase() { m_iCurrentCooldown = m_iBaseCooldown; }; void DecrementCooldown() { m_iCurrentCooldown = MAX(0, (m_iCurrentCooldown - 1)); } + int GetMinPlayers() { return m_iMinPlayers; }; + int GetMaxPlayers() { return m_iMaxPlayers; }; private: char m_pszName[64]; uint64 m_iWorkshopId; bool m_bIsEnabled; + int m_iMinPlayers; + int m_iMaxPlayers; int m_iBaseCooldown; int m_iCurrentCooldown; }; From 86993f237f367d0257fe153226c46a932462e2f9 Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Sat, 19 Oct 2024 00:27:47 -0400 Subject: [PATCH 05/24] Update example maplist.cfg --- configs/maplist.cfg.example | 4 ++++ src/map_votes.cpp | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/configs/maplist.cfg.example b/configs/maplist.cfg.example index e1d85f90e..83caea5f6 100644 --- a/configs/maplist.cfg.example +++ b/configs/maplist.cfg.example @@ -9,18 +9,22 @@ { "workshop_id" "123" "enabled" "1" + "minPlayers" "30" "cooldown" "2" } "ze_my_second_ze_map" { "workshop_id" "456" "enabled" "1" + "minPlayers" "5" + "maxPlayers" "10" "cooldown" "3" } "ze_my_third_ze_map" { "workshop_id" "789" "enabled" "1" + "maxPlayers" "20" "cooldown" "1" } } \ No newline at end of file diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 786eafc61..4bb5412cf 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -667,9 +667,9 @@ bool CMapVoteSystem::LoadMapList() const char *pszName = pKey->GetName(); uint64 iWorkshopId = pKey->GetUint64("workshop_id"); bool bIsEnabled = pKey->GetBool("enabled", true); + int iMinPlayers = pKey->GetInt("minPlayers", 0); + int iMaxPlayers = pKey->GetInt("maxPlayers", 64); int iBaseCooldown = pKey->GetInt("cooldown"); - int iMinPlayers = pKey->GetInt("MinPlayers", 0); - int iMaxPlayers = pKey->GetInt("MaxPlayers", 64); int iCurrentCooldown = pKVcooldowns->GetInt(pszName, 0); if (iWorkshopId != 0) From e1a20e55663f3f600130ef5b4d2d4430256cda92 Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:47:33 -0400 Subject: [PATCH 06/24] Add Minimum and Maximum player information to !nominate map list when requirements are not met --- src/map_votes.cpp | 35 +++++++++++++++++++++++------------ src/map_votes.h | 7 ++++--- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 4bb5412cf..5049dec52 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -42,6 +42,8 @@ extern CIdleSystem* g_pIdleSystem; CMapVoteSystem* g_pMapVoteSystem = nullptr; +int MapVote_GetOnlinePlayers(); + CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current map on completion", ADMFLAG_ROOT) { if (!g_bVoteManagerEnable) @@ -100,7 +102,7 @@ CON_COMMAND_CHAT_FLAGS(setnextmap, "[mapname] - Force next map (empty to clear f } } -static int __cdecl OrderStringsLexicographically(const MapCooldownStruct *a, const MapCooldownStruct *b) +static int __cdecl OrderStringsLexicographically(const MapIndexPair *a, const MapIndexPair *b) { return V_strcasecmp(a->name, b->name); } @@ -139,27 +141,36 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom { ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The list of all maps will be shown in console."); ClientPrint(player, HUD_PRINTCONSOLE, "The list of all maps is:"); - CUtlVector vecMapNames; + CUtlVector vecMapNames; for (int i = 0; i < g_pMapVoteSystem->GetMapListSize(); i++) { - MapCooldownStruct mapData; - mapData.name = g_pMapVoteSystem->GetMapName(i); - mapData.cooldown = g_pMapVoteSystem->GetCooldownMap(i); - mapData.mapIndex = i; - vecMapNames.AddToTail(mapData); + MapIndexPair map; + map.name = g_pMapVoteSystem->GetMapName(i); + map.index = i; + vecMapNames.AddToTail(map); } vecMapNames.Sort(OrderStringsLexicographically); FOR_EACH_VEC(vecMapNames, i) { - if (vecMapNames[i].cooldown > 0) - ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Cooldown: %d", vecMapNames[i].name, vecMapNames[i].cooldown); - else if (vecMapNames[i].mapIndex == g_pMapVoteSystem->GetCurrentMapIndex()) - ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Current Map", vecMapNames[i].name); + const char* name = vecMapNames[i].name; + int mapIndex = vecMapNames[i].index; + int cooldown = g_pMapVoteSystem->GetCooldownMap(mapIndex); + int minPlayers = g_pMapVoteSystem->GetMapMinPlayers(mapIndex); + int maxPlayers = g_pMapVoteSystem->GetMapMaxPlayers(mapIndex); + + if (cooldown > 0) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Cooldown: %d", name, cooldown); + else if (mapIndex == g_pMapVoteSystem->GetCurrentMapIndex()) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Current Map", name); + else if (MapVote_GetOnlinePlayers() < minPlayers) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Minimum Players: %d", name, minPlayers); + else if (MapVote_GetOnlinePlayers() > maxPlayers) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Maximum Players: %d", name, maxPlayers); else - ClientPrint(player, HUD_PRINTCONSOLE, "- %s", vecMapNames[i].name); + ClientPrint(player, HUD_PRINTCONSOLE, "- %s", name); } break; diff --git a/src/map_votes.h b/src/map_votes.h index 66f23979e..7d8e3b0c2 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -82,9 +82,8 @@ class CMapInfo typedef struct { const char* name; - int cooldown; - int mapIndex; -} MapCooldownStruct; + int index; +} MapIndexPair; class CMapVoteSystem @@ -130,6 +129,8 @@ class CMapVoteSystem int GetDownloadQueueSize() { return m_DownloadQueue.Count(); } int GetCurrentMapIndex() { return m_iCurrentMapIndex; } void SetCurrentMapIndex(int iMapIndex) { m_iCurrentMapIndex = iMapIndex; } + int GetMapMinPlayers(int iMapIndex) { return m_vecMapList[iMapIndex].GetMinPlayers(); } + int GetMapMaxPlayers(int iMapIndex) { return m_vecMapList[iMapIndex].GetMaxPlayers(); } private: int WinningMapIndex(); From 48b30f667e5c52129aa11c895f114670903f1df9 Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:08:13 -0500 Subject: [PATCH 07/24] Use snake case format for min/max player keyvalues in maplist.cfg --- configs/maplist.cfg.example | 8 ++++---- src/map_votes.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/configs/maplist.cfg.example b/configs/maplist.cfg.example index 83caea5f6..d364f34f2 100644 --- a/configs/maplist.cfg.example +++ b/configs/maplist.cfg.example @@ -9,22 +9,22 @@ { "workshop_id" "123" "enabled" "1" - "minPlayers" "30" + "min_players" "30" "cooldown" "2" } "ze_my_second_ze_map" { "workshop_id" "456" "enabled" "1" - "minPlayers" "5" - "maxPlayers" "10" + "min_players" "5" + "max_players" "10" "cooldown" "3" } "ze_my_third_ze_map" { "workshop_id" "789" "enabled" "1" - "maxPlayers" "20" + "max_players" "20" "cooldown" "1" } } \ No newline at end of file diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 5049dec52..31a2d2f20 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -678,8 +678,8 @@ bool CMapVoteSystem::LoadMapList() const char *pszName = pKey->GetName(); uint64 iWorkshopId = pKey->GetUint64("workshop_id"); bool bIsEnabled = pKey->GetBool("enabled", true); - int iMinPlayers = pKey->GetInt("minPlayers", 0); - int iMaxPlayers = pKey->GetInt("maxPlayers", 64); + int iMinPlayers = pKey->GetInt("min_players", 0); + int iMaxPlayers = pKey->GetInt("max_players", 64); int iBaseCooldown = pKey->GetInt("cooldown"); int iCurrentCooldown = pKVcooldowns->GetInt(pszName, 0); From 1f034a28bb145482c95f2f5ece9c6a6d376bd973 Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:29:31 -0500 Subject: [PATCH 08/24] Re-add !mapcooldowns chat command --- src/map_votes.cpp | 18 ++++++++++++++++++ src/map_votes.h | 1 + 2 files changed, 19 insertions(+) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 31a2d2f20..d4efc3d69 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -208,6 +208,24 @@ CON_COMMAND_CHAT(nomlist, "- List the list of nominations") } } +CON_COMMAND_CHAT(mapcooldowns, "- List the maps currently in cooldown") +{ + if (!g_bVoteManagerEnable) + return; + + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The list of maps in cooldown will be shown in console."); + ClientPrint(player, HUD_PRINTCONSOLE, "The list of maps in cooldown is:"); + int iMapCount = g_pMapVoteSystem->GetMapListSize(); + for (int iMapIndex = 0; iMapIndex < iMapCount; iMapIndex++) { + int iCooldown = g_pMapVoteSystem->GetCooldownMap(iMapIndex); + if (iCooldown > 0 && g_pMapVoteSystem->GetMapEnabledStatus(iMapIndex)) + { + const char* sMapName = g_pMapVoteSystem->GetMapName(iMapIndex); + ClientPrint(player, HUD_PRINTCONSOLE, "- %s (%d maps remaining)", sMapName, iCooldown); + } + } +} + GAME_EVENT_F(cs_win_panel_match) { if (g_bVoteManagerEnable && !g_pMapVoteSystem->IsVoteOngoing()) diff --git a/src/map_votes.h b/src/map_votes.h index 7d8e3b0c2..480ec888e 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -131,6 +131,7 @@ class CMapVoteSystem void SetCurrentMapIndex(int iMapIndex) { m_iCurrentMapIndex = iMapIndex; } int GetMapMinPlayers(int iMapIndex) { return m_vecMapList[iMapIndex].GetMinPlayers(); } int GetMapMaxPlayers(int iMapIndex) { return m_vecMapList[iMapIndex].GetMaxPlayers(); } + bool GetMapEnabledStatus(int iMapIndex) { return m_vecMapList[iMapIndex].IsEnabled(); } private: int WinningMapIndex(); From d4437098235893cb0c1292a850d18d859cd5e47e Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:34:21 -0500 Subject: [PATCH 09/24] Change map cooldown file path --- src/map_votes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index d4efc3d69..d76b623ca 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -687,7 +687,7 @@ bool CMapVoteSystem::LoadMapList() // Load map cooldowns from file KeyValues* pKVcooldowns = new KeyValues("cooldowns"); KeyValues::AutoDelete autoDeleteKVcooldowns(pKVcooldowns); - const char *pszCooldownFilePath = "addons/cs2fixes/configs/cooldowns.cfg"; + const char *pszCooldownFilePath = "addons/cs2fixes/data/cooldowns.txt"; if (!pKVcooldowns->LoadFromFile(g_pFullFileSystem, pszCooldownFilePath)) { Message("Failed to load cooldown file at %s - resetting all cooldowns to 0\n", pszCooldownFilePath); } @@ -774,7 +774,7 @@ bool CMapVoteSystem::WriteMapCooldownsToFile() KeyValues* pKV = new KeyValues("cooldowns"); KeyValues::AutoDelete autoDelete(pKV); - const char *pszPath = "addons/cs2fixes/configs/cooldowns.cfg"; + const char *pszPath = "addons/cs2fixes/data/cooldowns.txt"; FOR_EACH_VEC(m_vecMapList, i) { From c7b4d3b7cd9096815276672b64513e5c70d3c6df Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:00:13 -0500 Subject: [PATCH 10/24] Re-add cs2f_vote_maps_cooldown cvar as the default map cooldown --- cfg/cs2fixes/cs2fixes.cfg | 1 + src/map_votes.cpp | 17 ++++++++++++++++- src/map_votes.h | 3 +++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 87c615315..17d39dfc6 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -78,6 +78,7 @@ cs2f_rtv_success_ratio 0.6 // Ratio needed to pass RTV cs2f_rtv_endround 0 // Whether to immediately end the round when RTV succeeds // Map vote settings +cs2f_vote_maps_cooldown 10 // Default number of maps to wait until a map can be voted / nominated again i.e. cooldown. cs2f_vote_max_nominations 10 // Number of nominations to include per vote, out of a maximum of 10. // User preferences settings diff --git a/src/map_votes.cpp b/src/map_votes.cpp index d76b623ca..2a4418043 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -72,6 +72,21 @@ CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current Message("Map list reloaded\n"); } +CON_COMMAND_F(cs2f_vote_maps_cooldown, "Default number of maps to wait until a map can be voted / nominated again i.e. cooldown.", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) +{ + if (!g_pMapVoteSystem) { + Message("The map vote subsystem is not enabled.\n"); + return; + } + + if (args.ArgC() < 2) + Message("%s %d\n", args[0], g_pMapVoteSystem->GetDefaultMapCooldown()); + else { + int iCurrentCooldown = g_pMapVoteSystem->GetDefaultMapCooldown(); + g_pMapVoteSystem->SetDefaultMapCooldown(V_StringToInt32(args[1], iCurrentCooldown)); + } +} + CON_COMMAND_F(cs2f_vote_max_nominations, "Number of nominations to include per vote, out of a maximum of 10.", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) { if (!g_pMapVoteSystem) { @@ -698,7 +713,7 @@ bool CMapVoteSystem::LoadMapList() bool bIsEnabled = pKey->GetBool("enabled", true); int iMinPlayers = pKey->GetInt("min_players", 0); int iMaxPlayers = pKey->GetInt("max_players", 64); - int iBaseCooldown = pKey->GetInt("cooldown"); + int iBaseCooldown = pKey->GetInt("cooldown", m_iDefaultMapCooldown); int iCurrentCooldown = pKVcooldowns->GetInt(pszName, 0); if (iWorkshopId != 0) diff --git a/src/map_votes.h b/src/map_votes.h index 480ec888e..3a2fdb370 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -132,6 +132,8 @@ class CMapVoteSystem int GetMapMinPlayers(int iMapIndex) { return m_vecMapList[iMapIndex].GetMinPlayers(); } int GetMapMaxPlayers(int iMapIndex) { return m_vecMapList[iMapIndex].GetMaxPlayers(); } bool GetMapEnabledStatus(int iMapIndex) { return m_vecMapList[iMapIndex].IsEnabled(); } + int GetDefaultMapCooldown() { return m_iDefaultMapCooldown; } + void SetDefaultMapCooldown(int iMapCooldown) { m_iDefaultMapCooldown = iMapCooldown; } private: int WinningMapIndex(); @@ -145,6 +147,7 @@ class CMapVoteSystem CUtlVector m_vecMapList; int m_arrPlayerNominations[MAXPLAYERS]; int m_iForcedNextMapIndex = -1; + int m_iDefaultMapCooldown = 10; int m_iMaxNominatedMaps = 10; int m_iRandomWinnerShift = 0; int m_arrPlayerVotes[MAXPLAYERS]; From b876eda75a9669a9cb576060b976c4642d5c0fa1 Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Sun, 17 Nov 2024 11:40:22 -0500 Subject: [PATCH 11/24] Automatically remove nominations when min/max player count requirements not met --- src/map_votes.cpp | 25 +++++++++++++++++++++++++ src/map_votes.h | 1 + src/playermanager.cpp | 2 ++ 3 files changed, 28 insertions(+) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 2a4418043..9457842cc 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -805,4 +805,29 @@ bool CMapVoteSystem::WriteMapCooldownsToFile() } return true; +} + +void CMapVoteSystem::ClearInvalidNominations() +{ + if (!g_bVoteManagerEnable || m_bIsVoteOngoing) + return; + + for (int i = 0; i < gpGlobals->maxClients; i++) { + int iNominatedMapIndex = m_arrPlayerNominations[i]; + + // Ignore unset nominations (negative index) + if (iNominatedMapIndex < 0) + continue; + + // Check if nominated index still meets criteria for nomination + if (!IsMapIndexEnabled(iNominatedMapIndex)) + { + ClearPlayerInfo(i); + CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(i); + if (!pPlayer) + continue; + + ClientPrint(pPlayer, HUD_PRINTTALK, CHAT_PREFIX "Your nomination for \x06%s \x01has been removed because the player count requirements are no longer met.", GetMapName(iNominatedMapIndex)); + } + } } \ No newline at end of file diff --git a/src/map_votes.h b/src/map_votes.h index 3a2fdb370..b9263ce22 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -134,6 +134,7 @@ class CMapVoteSystem bool GetMapEnabledStatus(int iMapIndex) { return m_vecMapList[iMapIndex].IsEnabled(); } int GetDefaultMapCooldown() { return m_iDefaultMapCooldown; } void SetDefaultMapCooldown(int iMapCooldown) { m_iDefaultMapCooldown = iMapCooldown; } + void ClearInvalidNominations(); private: int WinningMapIndex(); diff --git a/src/playermanager.cpp b/src/playermanager.cpp index 9a8c64f72..f57c6f61d 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -610,6 +610,7 @@ bool CPlayerManager::OnClientConnected(CPlayerSlot slot, uint64 xuid, const char ResetPlayerFlags(slot.Get()); g_pMapVoteSystem->ClearPlayerInfo(slot.Get()); + g_pMapVoteSystem->ClearInvalidNominations(); return true; } @@ -627,6 +628,7 @@ void CPlayerManager::OnClientDisconnect(CPlayerSlot slot) ResetPlayerFlags(slot.Get()); g_pMapVoteSystem->ClearPlayerInfo(slot.Get()); + g_pMapVoteSystem->ClearInvalidNominations(); g_pPanoramaVoteHandler->RemovePlayerFromVote(slot.Get()); } From e994f778fa9c6bfb72e0665d09de0416988458dc Mon Sep 17 00:00:00 2001 From: m-arcuri <124469923+m-arcuri@users.noreply.github.com> Date: Sun, 17 Nov 2024 20:52:54 -0500 Subject: [PATCH 12/24] Fix string format of console messages when loading map list --- src/map_votes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 9457842cc..7b8c4dc4e 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -741,9 +741,9 @@ bool CMapVoteSystem::LoadMapList() CMapInfo map = m_vecMapList[i]; if (map.GetWorkshopId() == 0) - ConMsg("Map %d is %s, which is %s. MinPlayers: %llu MaxPlayers: %llu Cooldown: %d\n", i, map.GetName(), map.IsEnabled() ? "enabled" : "disabled", map.GetMinPlayers(), map.GetMaxPlayers(), map.GetBaseCooldown()); + ConMsg("Map %d is %s, which is %s. MinPlayers: %d MaxPlayers: %d Cooldown: %d\n", i, map.GetName(), map.IsEnabled() ? "enabled" : "disabled", map.GetMinPlayers(), map.GetMaxPlayers(), map.GetBaseCooldown()); else - ConMsg("Map %d is %s with workshop id %llu, which is %s. MinPlayers: %llu MaxPlayers: %llu Cooldown: %d\n", i, map.GetName(), map.GetWorkshopId(), map.IsEnabled()? "enabled" : "disabled", map.GetMinPlayers(), map.GetMaxPlayers(), map.GetBaseCooldown()); + ConMsg("Map %d is %s with workshop id %llu, which is %s. MinPlayers: %d MaxPlayers: %d Cooldown: %d\n", i, map.GetName(), map.GetWorkshopId(), map.IsEnabled()? "enabled" : "disabled", map.GetMinPlayers(), map.GetMaxPlayers(), map.GetBaseCooldown()); } m_bMapListLoaded = true; From 41124012065db958d013e4d61411f4d3df6d3e14 Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 20 Nov 2024 13:27:52 -0500 Subject: [PATCH 13/24] Add shared function for player count --- src/leader.cpp | 17 +---------------- src/map_votes.cpp | 23 +++-------------------- src/playermanager.cpp | 16 ++++++++++++++++ src/playermanager.h | 1 + 4 files changed, 21 insertions(+), 36 deletions(-) diff --git a/src/leader.cpp b/src/leader.cpp index 2908d9521..e3ddf4bf5 100644 --- a/src/leader.cpp +++ b/src/leader.cpp @@ -90,21 +90,6 @@ FAKE_INT_CVAR(cs2f_leader_max_glows, "Max amount of glows set by leaders (doesn' FAKE_INT_CVAR(cs2f_leader_max_tracers, "Max amount of tracers set by leaders (doesn't impact admins)", g_iMaxTracers, 3, false) FAKE_INT_CVAR(cs2f_leader_max_beacons, "Max amount of beacons set by leaders (doesn't impact admins)", g_iMaxBeacons, 3, false) -int Leader_GetNeededLeaderVoteCount() -{ - int iOnlinePlayers = 0; - - for (int i = 0; i < gpGlobals->maxClients; i++) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (pPlayer && !pPlayer->IsFakeClient()) - iOnlinePlayers++; - } - - return (int)(iOnlinePlayers * g_flLeaderVoteRatio) + 1; -} - bool Leader_SetNewLeader(ZEPlayer* zpLeader, std::string strColor = "") { CCSPlayerController* pLeader = CCSPlayerController::FromSlot(zpLeader->GetPlayerSlot()); @@ -587,7 +572,7 @@ CON_COMMAND_CHAT(vl, " - Vote for a player to become a leader") } int iLeaderVoteCount = pPlayerTarget->GetLeaderVoteCount(); - int iNeededLeaderVoteCount = Leader_GetNeededLeaderVoteCount(); + int iNeededLeaderVoteCount = (int)(g_playerManager->GetOnlinePlayerCount(false) * g_flLeaderVoteRatio) + 1;; pPlayer->SetLeaderVoteTime(gpGlobals->curtime); diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 7b8c4dc4e..e53a5b586 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -42,8 +42,6 @@ extern CIdleSystem* g_pIdleSystem; CMapVoteSystem* g_pMapVoteSystem = nullptr; -int MapVote_GetOnlinePlayers(); - CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current map on completion", ADMFLAG_ROOT) { if (!g_bVoteManagerEnable) @@ -180,9 +178,9 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Cooldown: %d", name, cooldown); else if (mapIndex == g_pMapVoteSystem->GetCurrentMapIndex()) ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Current Map", name); - else if (MapVote_GetOnlinePlayers() < minPlayers) + else if (g_playerManager->GetOnlinePlayerCount(false) < minPlayers) ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Minimum Players: %d", name, minPlayers); - else if (MapVote_GetOnlinePlayers() > maxPlayers) + else if (g_playerManager->GetOnlinePlayerCount(false) > maxPlayers) ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Maximum Players: %d", name, maxPlayers); else ClientPrint(player, HUD_PRINTCONSOLE, "- %s", name); @@ -253,28 +251,13 @@ GAME_EVENT_F(endmatch_mapvote_selecting_map) g_pMapVoteSystem->FinishVote(); } -int MapVote_GetOnlinePlayers() -{ - int iOnlinePlayers = 0; - for (int i = 0; i < gpGlobals->maxClients; i++) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (pPlayer && !pPlayer->IsFakeClient()) - { - iOnlinePlayers++; - } - } - return iOnlinePlayers; -} - bool CMapVoteSystem::IsMapIndexEnabled(int iMapIndex) { if (iMapIndex >= m_vecMapList.Count() || iMapIndex < 0) return false; if (GetCooldownMap(iMapIndex) > 0 || GetCurrentMapIndex() == iMapIndex) return false; if (!m_vecMapList[iMapIndex].IsEnabled()) return false; - int iOnlinePlayers = MapVote_GetOnlinePlayers(); + int iOnlinePlayers = g_playerManager->GetOnlinePlayerCount(false); bool bMeetsMaxPlayers = iOnlinePlayers <= m_vecMapList[iMapIndex].GetMaxPlayers(); bool bMeetsMinPlayers = iOnlinePlayers >= m_vecMapList[iMapIndex].GetMinPlayers(); return bMeetsMaxPlayers && bMeetsMinPlayers; diff --git a/src/playermanager.cpp b/src/playermanager.cpp index f57c6f61d..57457d24b 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -43,6 +43,7 @@ extern IVEngineServer2 *g_pEngineServer2; extern CGameEntitySystem *g_pEntitySystem; extern CGlobalVars *gpGlobals; extern IGameEventSystem* g_gameEventSystem; +extern CUtlVector* GetClientList(); static int g_iAdminImmunityTargetting = 0; static bool g_bEnableMapSteamIds = false; @@ -1595,4 +1596,19 @@ void CPlayerManager::ResetPlayerFlags(int slot) SetPlayerSilenceSound(slot, false); SetPlayerStopDecals(slot, true); SetPlayerNoShake(slot, false); +} + +int CPlayerManager::GetOnlinePlayerCount(bool bCountBots) +{ + int iOnlinePlayers = 0; + + for (int i = 0; i < GetClientList()->Count(); i++) + { + CServerSideClient* pClient = (*GetClientList())[i]; + + if (pClient && pClient->GetSignonState() >= SIGNONSTATE_CONNECTED && (bCountBots || !pClient->IsFakeClient())) + iOnlinePlayers++; + } + + return iOnlinePlayers; } \ No newline at end of file diff --git a/src/playermanager.h b/src/playermanager.h index a74bf719d..b815dcd65 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -397,6 +397,7 @@ class CPlayerManager bool IsPlayerUsingNoShake(int slot) { return m_nUsingNoShake & ((uint64)1 << slot); } void UpdatePlayerStates(); + int GetOnlinePlayerCount(bool bCountBots); STEAM_GAMESERVER_CALLBACK_MANUAL(CPlayerManager, OnValidateAuthTicket, ValidateAuthTicketResponse_t, m_CallbackValidateAuthTicketResponse); From bd5de19a155cb14554c3bcaac38740035a2845f4 Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 20 Nov 2024 17:35:17 -0500 Subject: [PATCH 14/24] Fix a possible edge case with KV1 capitalization handling Basically, KV1 will cache whichever capitalization gets used first for a string, *globally*. This previously caused issues with CS:GO's mapchooser cooldown saving, because a map could pack a KV1 cfg with the string "ze_Mako". Then, when we go to save "ze_mako", it will instead be auto-converted to "ze_Mako" because that's what the server used first. So we are just re-implementing the checks to avoid this being an issue. --- configs/maplist.cfg.example | 1 - src/map_votes.cpp | 31 +++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/configs/maplist.cfg.example b/configs/maplist.cfg.example index d364f34f2..5df4d3956 100644 --- a/configs/maplist.cfg.example +++ b/configs/maplist.cfg.example @@ -3,7 +3,6 @@ "de_dust2" { "enabled" "1" - "cooldown" "1" } "ze_my_first_ze_map" { diff --git a/src/map_votes.cpp b/src/map_votes.cpp index e53a5b586..f6e6470c5 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -690,14 +690,33 @@ bool CMapVoteSystem::LoadMapList() Message("Failed to load cooldown file at %s - resetting all cooldowns to 0\n", pszCooldownFilePath); } + // KV1 has some funny behaviour with capitalization, to ensure consistency we can't directly lookup case-sensitive key names + std::unordered_map mapCooldowns; + + for (KeyValues* pKey = pKVcooldowns->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey()) + { + std::string sMapName = pKey->GetName(); + int iCooldown = pKey->GetInt(); + + for (int i = 0; sMapName[i]; i++) + sMapName[i] = tolower(sMapName[i]); + + mapCooldowns[sMapName] = iCooldown; + } + for (KeyValues* pKey = pKV->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey()) { - const char *pszName = pKey->GetName(); + const char* pszName = pKey->GetName(); + std::string sName = pszName; + + for (int i = 0; sName[i]; i++) + sName[i] = tolower(sName[i]); + uint64 iWorkshopId = pKey->GetUint64("workshop_id"); bool bIsEnabled = pKey->GetBool("enabled", true); int iMinPlayers = pKey->GetInt("min_players", 0); int iMaxPlayers = pKey->GetInt("max_players", 64); int iBaseCooldown = pKey->GetInt("cooldown", m_iDefaultMapCooldown); - int iCurrentCooldown = pKVcooldowns->GetInt(pszName, 0); + int iCurrentCooldown = mapCooldowns[sName]; if (iWorkshopId != 0) QueueMapDownload(iWorkshopId); @@ -776,9 +795,13 @@ bool CMapVoteSystem::WriteMapCooldownsToFile() FOR_EACH_VEC(m_vecMapList, i) { - const char* mapName = m_vecMapList[i].GetName(); + std::string mapName = m_vecMapList[i].GetName(); const int mapCooldown = m_vecMapList[i].GetCooldown(); - pKV->AddInt(mapName, mapCooldown); + + for (int i = 0; mapName[i]; i++) + mapName[i] = tolower(mapName[i]); + + pKV->AddInt(mapName.c_str(), mapCooldown); } if (!pKV->SaveToFile(g_pFullFileSystem, pszPath)) From 460145917f2f31f5834d5533de46e260b96a849e Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 20 Nov 2024 17:38:45 -0500 Subject: [PATCH 15/24] Fix nomination resetting not working --- src/map_votes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index f6e6470c5..d89d63d96 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -149,6 +149,7 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom break; case NominationReturnCodes::NOMINATION_RESET: ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Your nomination was reset."); + g_pMapVoteSystem->ClearPlayerInfo(player->GetPlayerSlot()); break; case NominationReturnCodes::NOMINATION_RESET_FAILED: { From 8c962f94fc029d27b40b66202612e03a5fb64093 Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 20 Nov 2024 17:52:10 -0500 Subject: [PATCH 16/24] Allow changing map by ID if admin already has console access --- src/adminsystem.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/adminsystem.cpp b/src/adminsystem.cpp index 771fb3a31..441d93558 100644 --- a/src/adminsystem.cpp +++ b/src/adminsystem.cpp @@ -591,7 +591,7 @@ CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP) for (int i = 0; sMapName[i]; i++) { // Injection prevention, because we may pass user input to ServerCommand - if (sMapName[i] == ';') + if (sMapName[i] == ';' || sMapName[i] == '|') return; sMapName[i] = tolower(sMapName[i]); @@ -604,12 +604,12 @@ CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP) std::string sCommand; // Check if input is numeric (workshop ID) - // Not safe to expose until crashing on failed workshop addon downloads is fixed - /*if (V_StringToUint64(pszMapName, 0) != 0) + // Not safe to expose to all admins until crashing on failed workshop addon downloads is fixed + if ((!player || player->GetZEPlayer()->IsAdminFlagSet(ADMFLAG_RCON)) && V_StringToUint64(pszMapName, 0) != 0) { sCommand = "host_workshop_map " + sMapName; - }*/ - if (g_bVoteManagerEnable && g_pMapVoteSystem->GetMapIndexFromSubstring(pszMapName) != -1) + } + else if (g_bVoteManagerEnable && g_pMapVoteSystem->GetMapIndexFromSubstring(pszMapName) != -1) { sCommand = "host_workshop_map " + std::to_string(g_pMapVoteSystem->GetMapWorkshopId(g_pMapVoteSystem->GetMapIndexFromSubstring(pszMapName))); } From ccbc86e6a583e7a85c5429bbeded6e1d6ab13c23 Mon Sep 17 00:00:00 2001 From: Vauff Date: Thu, 21 Nov 2024 10:05:21 -0500 Subject: [PATCH 17/24] Make !mapcooldowns print in order again --- src/map_votes.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index d89d63d96..340ec8966 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -227,17 +227,26 @@ CON_COMMAND_CHAT(mapcooldowns, "- List the maps currently in cooldown") if (!g_bVoteManagerEnable) return; + int iMapCount = g_pMapVoteSystem->GetMapListSize(); + std::vector > vecCooldowns; + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The list of maps in cooldown will be shown in console."); ClientPrint(player, HUD_PRINTCONSOLE, "The list of maps in cooldown is:"); - int iMapCount = g_pMapVoteSystem->GetMapListSize(); - for (int iMapIndex = 0; iMapIndex < iMapCount; iMapIndex++) { + + for (int iMapIndex = 0; iMapIndex < iMapCount; iMapIndex++) + { int iCooldown = g_pMapVoteSystem->GetCooldownMap(iMapIndex); + if (iCooldown > 0 && g_pMapVoteSystem->GetMapEnabledStatus(iMapIndex)) - { - const char* sMapName = g_pMapVoteSystem->GetMapName(iMapIndex); - ClientPrint(player, HUD_PRINTCONSOLE, "- %s (%d maps remaining)", sMapName, iCooldown); - } + vecCooldowns.push_back(std::make_pair(g_pMapVoteSystem->GetMapName(iMapIndex), iCooldown)); } + + std::sort(vecCooldowns.begin(), vecCooldowns.end(), [](auto& left, auto& right) { + return left.second < right.second; + }); + + for (auto pair : vecCooldowns) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s (%d maps remaining)", pair.first.c_str(), pair.second); } GAME_EVENT_F(cs_win_panel_match) @@ -802,7 +811,8 @@ bool CMapVoteSystem::WriteMapCooldownsToFile() for (int i = 0; mapName[i]; i++) mapName[i] = tolower(mapName[i]); - pKV->AddInt(mapName.c_str(), mapCooldown); + if (mapCooldown > 0) + pKV->AddInt(mapName.c_str(), mapCooldown); } if (!pKV->SaveToFile(g_pFullFileSystem, pszPath)) From 0eefc315ce785c04e257a4aa709ac8fe9b437221 Mon Sep 17 00:00:00 2001 From: Vauff Date: Thu, 21 Nov 2024 11:32:32 -0500 Subject: [PATCH 18/24] Don't show disabled maps in !nominate map list --- src/map_votes.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 340ec8966..ef977eeea 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -159,6 +159,9 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom for (int i = 0; i < g_pMapVoteSystem->GetMapListSize(); i++) { + if (!g_pMapVoteSystem->GetMapEnabledStatus(i)) + continue; + MapIndexPair map; map.name = g_pMapVoteSystem->GetMapName(i); map.index = i; From 64a71af89bda3419ae7ec434dbd6ab5a0e42e8c2 Mon Sep 17 00:00:00 2001 From: Vauff Date: Fri, 22 Nov 2024 08:52:35 -0500 Subject: [PATCH 19/24] Fix & update !setnextmap --- src/map_votes.cpp | 74 +++++++++++++++++++++++++++++------------------ src/map_votes.h | 1 + 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index ef977eeea..7d76014d9 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -100,19 +100,21 @@ CON_COMMAND_F(cs2f_vote_max_nominations, "Number of nominations to include per v } } +//TODO: workshop id support for rcon admins? CON_COMMAND_CHAT_FLAGS(setnextmap, "[mapname] - Force next map (empty to clear forced next map)", ADMFLAG_CHANGEMAP) { if (!g_bVoteManagerEnable) return; - bool bIsClearingForceNextMap = args.ArgC() < 2; - int iResponse = g_pMapVoteSystem->ForceNextMap(bIsClearingForceNextMap ? "" : args[1]); - if (bIsClearingForceNextMap) { - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You reset the forced next map."); - } - else { - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have forced the next map to %s.", g_pMapVoteSystem->GetMapName(iResponse)); - } + int iPreviousNextMap = g_pMapVoteSystem->GetForcedNextMap(); + int iResponse = g_pMapVoteSystem->ForceNextMap(args.ArgC() < 2 ? "" : args[1]); + + if (iResponse >= 0 && iPreviousNextMap == iResponse) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "\x06%s\x01 is already the next map!", g_pMapVoteSystem->GetMapName(iResponse)); + else if (iResponse == -1) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Failed to find a map matching \x06%s\x01.", args[1]); + else if (iResponse == -3) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "There is no next map to reset!", args[1]); } static int __cdecl OrderStringsLexicographically(const MapIndexPair *a, const MapIndexPair *b) @@ -283,6 +285,10 @@ void CMapVoteSystem::OnLevelInit(const char* pMapName) m_bIsVoteOngoing = false; m_bIntermissionStarted = false; + m_iForcedNextMapIndex = -1; + + for (int i = 0; i < gpGlobals->maxClients; i++) + ClearPlayerInfo(i); // Delay one tick to override any .cfg's new CTimer(0.02f, false, true, []() @@ -301,22 +307,28 @@ void CMapVoteSystem::StartVote() g_pIdleSystem->PauseIdleChecks(); + // Reset the player vote counts as the vote just started + for (int i = 0; i < gpGlobals->maxClients; i++) + m_arrPlayerVotes[i] = -1; + // If we are forcing a map, just set all vote options to that map - if (m_iForcedNextMapIndex != -1) { - for (int i = 0; i < 10; i++) { + if (m_iForcedNextMapIndex != -1) + { + for (int i = 0; i < 10; i++) g_pGameRules->m_nEndMatchMapGroupVoteOptions[i] = m_iForcedNextMapIndex; - return; - } + + new CTimer(6.0f, false, true, []() + { + g_pMapVoteSystem->FinishVote(); + return -1.0f; + }); + + return; } // Seed the randomness for the event m_iRandomWinnerShift = rand(); - // Reset the player vote counts as the vote just started - for (int i = 0; i < gpGlobals->maxClients; i++) { - m_arrPlayerVotes[i] = -1; - } - // Select random maps not in cooldown, not disabled, and not nominated CUtlVector vecPossibleMaps; CUtlVector vecIncludedMaps; @@ -386,7 +398,6 @@ void CMapVoteSystem::FinishVote() // Clean up the ongoing voting state and variables m_bIsVoteOngoing = false; - m_iForcedNextMapIndex = -1; // Get the winning map bool bIsNextMapVoted = UpdateWinningMap(); @@ -440,10 +451,6 @@ void CMapVoteSystem::FinishVote() WriteMapCooldownsToFile(); - // Do the final clean-up - for (int i = 0; i < gpGlobals->maxClients; i++) - ClearPlayerInfo(i); - // Wait a second and force-change the map new CTimer(1.0, false, true, [iWinningMap]() { char sChangeMapCmd[128]; @@ -609,18 +616,29 @@ int CMapVoteSystem::AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSu int CMapVoteSystem::ForceNextMap(const char* sMapSubstring) { - if (sMapSubstring[0] == '\0') { - ClientPrintAll(HUD_PRINTTALK, " \x06%s \x01 is no longer the forced next map.\n", m_vecMapList[m_iForcedNextMapIndex].GetName()); - m_iForcedNextMapIndex = -1; - return 0; + if (sMapSubstring[0] == '\0') + { + if (m_iForcedNextMapIndex != -1) + { + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01is no longer the forced next map.\n", m_vecMapList[m_iForcedNextMapIndex].GetName()); + m_iForcedNextMapIndex = -1; + return -2; + } + + return -3; } int iFoundIndex = GetMapIndexFromSubstring(sMapSubstring); - if (iFoundIndex == -1) return iFoundIndex; + + if (iFoundIndex == -1) + return iFoundIndex; + + if (m_iForcedNextMapIndex == iFoundIndex) + return iFoundIndex; // When found, print the map and store the forced map m_iForcedNextMapIndex = iFoundIndex; - ClientPrintAll(HUD_PRINTTALK, " \x06%s \x01 has been forced as next map.\n", m_vecMapList[iFoundIndex].GetName()); + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01has been forced as the next map.\n", m_vecMapList[iFoundIndex].GetName()); return iFoundIndex; } diff --git a/src/map_votes.h b/src/map_votes.h index b9263ce22..c8192543c 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -135,6 +135,7 @@ class CMapVoteSystem int GetDefaultMapCooldown() { return m_iDefaultMapCooldown; } void SetDefaultMapCooldown(int iMapCooldown) { m_iDefaultMapCooldown = iMapCooldown; } void ClearInvalidNominations(); + int GetForcedNextMap() { return m_iForcedNextMapIndex; } private: int WinningMapIndex(); From 3bcaccce5f47264ca6df3de4207577f4016da889 Mon Sep 17 00:00:00 2001 From: Vauff Date: Fri, 22 Nov 2024 08:53:12 -0500 Subject: [PATCH 20/24] Allow immediate RTV's when enabled manually at map start --- src/playermanager.h | 2 +- src/votemanager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/playermanager.h b/src/playermanager.h index b815dcd65..91f86d5ad 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -159,7 +159,7 @@ class ZEPlayer m_bVotedRTV = false; m_bVotedExtend = false; m_bIsInfected = false; - m_flRTVVoteTime = 0; + m_flRTVVoteTime = -60.0f; m_flExtendVoteTime = 0; m_iFloodTokens = 0; m_flLastTalkTime = 0; diff --git a/src/votemanager.cpp b/src/votemanager.cpp index 2bfeb38d9..66e9e4344 100644 --- a/src/votemanager.cpp +++ b/src/votemanager.cpp @@ -279,7 +279,7 @@ CON_COMMAND_CHAT(rtv, "- Vote to end the current map sooner") g_RTVState = ERTVState::POST_RTV_SUCCESSFULL; g_ExtendState = EExtendState::POST_RTV; // CONVAR_TODO - g_pEngineServer2->ServerCommand("mp_timelimit 1"); + g_pEngineServer2->ServerCommand("mp_timelimit 0.01"); if (g_bRTVEndRound) { From 9643cb14f9854fed82e83ed56119e111a94425a9 Mon Sep 17 00:00:00 2001 From: Vauff Date: Fri, 22 Nov 2024 09:04:33 -0500 Subject: [PATCH 21/24] Update GetNeededRTVCount to use GetOnlinePlayerCount --- src/votemanager.cpp | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/votemanager.cpp b/src/votemanager.cpp index 66e9e4344..335814ee6 100644 --- a/src/votemanager.cpp +++ b/src/votemanager.cpp @@ -167,22 +167,7 @@ int GetCurrentRTVCount() int GetNeededRTVCount() { - int iOnlinePlayers = 0.0f; - int iVoteCount = 0; - - for (int i = 0; i < gpGlobals->maxClients; i++) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (pPlayer && !pPlayer->IsFakeClient()) - { - iOnlinePlayers++; - if (pPlayer->GetRTVVote()) - iVoteCount++; - } - } - - return (int)(iOnlinePlayers * g_flRTVSucceedRatio) + 1; + return (int)(g_playerManager->GetOnlinePlayerCount(false) * g_flRTVSucceedRatio) + 1; } int GetCurrentExtendCount() From c9a837d2ce6d21443070a9895bd0225a52fab5a2 Mon Sep 17 00:00:00 2001 From: Vauff Date: Fri, 22 Nov 2024 14:22:20 -0500 Subject: [PATCH 22/24] Add more detailed error messages to !nominate --- src/map_votes.cpp | 88 +++++++++++++++++++++++++++++++---------------- src/map_votes.h | 18 ++++++---- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 7d76014d9..855b847e5 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -127,27 +127,36 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom if (!g_bVoteManagerEnable || !player) return; - int iResponse = g_pMapVoteSystem->AddMapNomination(player->GetPlayerSlot(), args.ArgC() < 2 ? "" : args[1]); + std::pair response = g_pMapVoteSystem->AddMapNomination(player->GetPlayerSlot(), args.ArgC() < 2 ? "" : args[1]); ZEPlayer* pPlayer = g_playerManager->GetPlayer(player->GetPlayerSlot()); if (!pPlayer) return; - switch (iResponse) { + switch (response.first) { case NominationReturnCodes::VOTE_STARTED: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Could not nominate as the vote has already started."); - break; - case NominationReturnCodes::INVALID_INPUT: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Could not nominate as the input is invalid. Usage: !nominate "); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Nominations are currently disabled because the vote has already started."); break; case NominationReturnCodes::MAP_NOT_FOUND: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Could not nominate as no map matched '%s'.", args[1]); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because no map matched.", args[1]); + break; + case NominationReturnCodes::MAP_DISABLED: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's disabled.", g_pMapVoteSystem->GetMapName(response.second)); + break; + case NominationReturnCodes::MAP_CURRENT: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's already the current map!", g_pMapVoteSystem->GetMapName(response.second)); break; - case NominationReturnCodes::INVALID_MAP: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The map matching '%s' is not available for nomination.", args[1]); + case NominationReturnCodes::MAP_COOLDOWN: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's on a %i map cooldown.", g_pMapVoteSystem->GetMapName(response.second), g_pMapVoteSystem->GetCooldownMap(response.second)); + break; + case NominationReturnCodes::MAP_MINPLAYERS: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i more players.", g_pMapVoteSystem->GetMapName(response.second), g_pMapVoteSystem->GetMapMinPlayers(response.second) - g_playerManager->GetOnlinePlayerCount(false)); + break; + case NominationReturnCodes::MAP_MAXPLAYERS: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i less players.", g_pMapVoteSystem->GetMapName(response.second), g_playerManager->GetOnlinePlayerCount(false) - g_pMapVoteSystem->GetMapMaxPlayers(response.second)); break; case NominationReturnCodes::NOMINATION_DISABLED: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Nomination is currently disabled."); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Nominations are currently disabled."); break; case NominationReturnCodes::NOMINATION_RESET: ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Your nomination was reset."); @@ -179,22 +188,24 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom int cooldown = g_pMapVoteSystem->GetCooldownMap(mapIndex); int minPlayers = g_pMapVoteSystem->GetMapMinPlayers(mapIndex); int maxPlayers = g_pMapVoteSystem->GetMapMaxPlayers(mapIndex); + int playerCount = g_playerManager->GetOnlinePlayerCount(false); if (cooldown > 0) ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Cooldown: %d", name, cooldown); else if (mapIndex == g_pMapVoteSystem->GetCurrentMapIndex()) ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Current Map", name); - else if (g_playerManager->GetOnlinePlayerCount(false) < minPlayers) - ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Minimum Players: %d", name, minPlayers); - else if (g_playerManager->GetOnlinePlayerCount(false) > maxPlayers) - ClientPrint(player, HUD_PRINTCONSOLE, "- %s - Maximum Players: %d", name, maxPlayers); + else if (playerCount < minPlayers) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s - +%d Players", name, minPlayers - playerCount); + else if (playerCount > maxPlayers) + ClientPrint(player, HUD_PRINTCONSOLE, "- %s - -%d Players", name, playerCount - maxPlayers); else ClientPrint(player, HUD_PRINTCONSOLE, "- %s", name); } break; } - default: + case NominationReturnCodes::MAP_NOMINATED: + { if (pPlayer->GetNominateTime() + 60.0f > gpGlobals->curtime) { int iRemainingTime = (int)(pPlayer->GetNominateTime() + 60.0f - gpGlobals->curtime); @@ -204,11 +215,14 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom else { const char* sPlayerName = player->GetPlayerName(); - const char* sMapName = g_pMapVoteSystem->GetMapName(iResponse); - int iNumNominations = g_pMapVoteSystem->GetTotalNominations(iResponse); - ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX " \x06%s \x01was nominated by %s. It now has %d nominations.", sMapName, sPlayerName, iNumNominations); + const char* sMapName = g_pMapVoteSystem->GetMapName(response.second); + int iNumNominations = g_pMapVoteSystem->GetTotalNominations(response.second); + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01was nominated by %s. It now has %d nominations.", sMapName, sPlayerName, iNumNominations); pPlayer->SetNominateTime(gpGlobals->curtime); } + + break; + } } } @@ -583,14 +597,12 @@ void CMapVoteSystem::ClearPlayerInfo(int iSlot) m_arrPlayerVotes[iSlot] = -1; } -int CMapVoteSystem::AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring) +std::pair CMapVoteSystem::AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring) { - if (m_bIsVoteOngoing) return NominationReturnCodes::VOTE_STARTED; - if (m_iForcedNextMapIndex != -1 || m_iMaxNominatedMaps == 0) return NominationReturnCodes::NOMINATION_DISABLED; + if (m_bIsVoteOngoing) return std::make_pair(NominationReturnCodes::VOTE_STARTED, 0); + if (m_iForcedNextMapIndex != -1 || m_iMaxNominatedMaps == 0) return std::make_pair(NominationReturnCodes::NOMINATION_DISABLED, 0); - CCSPlayerController* pController = CCSPlayerController::FromSlot(iPlayerSlot); - if (!pController) return NominationReturnCodes::INVALID_INPUT; - int iSlot = pController->GetPlayerSlot(); + int iSlot = iPlayerSlot.Get(); if (sMapSubstring[0] == '\0') { @@ -598,20 +610,38 @@ int CMapVoteSystem::AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSu if (m_arrPlayerNominations[iSlot] != -1) { m_arrPlayerNominations[iSlot] = -1; - return NominationReturnCodes::NOMINATION_RESET; + return std::make_pair(NominationReturnCodes::NOMINATION_RESET, 0); } else { - return NominationReturnCodes::NOMINATION_RESET_FAILED; + return std::make_pair(NominationReturnCodes::NOMINATION_RESET_FAILED, 0); } } // We are not reseting the nomination: is the map found? is it valid? int iFoundIndex = GetMapIndexFromSubstring(sMapSubstring); - if (iFoundIndex == -1) return NominationReturnCodes::MAP_NOT_FOUND; - if (!IsMapIndexEnabled(iFoundIndex)) return NominationReturnCodes::INVALID_MAP; + int iOnlinePlayers = g_playerManager->GetOnlinePlayerCount(false); + + if (iFoundIndex == -1) + return std::make_pair(NominationReturnCodes::MAP_NOT_FOUND, 0); + + if (!GetMapEnabledStatus(iFoundIndex)) + return std::make_pair(NominationReturnCodes::MAP_DISABLED, iFoundIndex); + + if (GetCurrentMapIndex() == iFoundIndex) + return std::make_pair(NominationReturnCodes::MAP_CURRENT, iFoundIndex); + + if (GetCooldownMap(iFoundIndex) > 0) + return std::make_pair(NominationReturnCodes::MAP_COOLDOWN, iFoundIndex); + + if (iOnlinePlayers < m_vecMapList[iFoundIndex].GetMinPlayers()) + return std::make_pair(NominationReturnCodes::MAP_MINPLAYERS, iFoundIndex); + + if (iOnlinePlayers > m_vecMapList[iFoundIndex].GetMaxPlayers()) + return std::make_pair(NominationReturnCodes::MAP_MAXPLAYERS, iFoundIndex); + m_arrPlayerNominations[iSlot] = iFoundIndex; - return iFoundIndex; + return std::make_pair(NominationReturnCodes::MAP_NOMINATED, iFoundIndex); } int CMapVoteSystem::ForceNextMap(const char* sMapSubstring) diff --git a/src/map_votes.h b/src/map_votes.h index c8192543c..35ec2d5ad 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -34,12 +34,16 @@ namespace NominationReturnCodes { static const int VOTE_STARTED = -100; - static const int INVALID_INPUT = -101; - static const int MAP_NOT_FOUND = -102; - static const int INVALID_MAP = -103; - static const int NOMINATION_DISABLED = -104; - static const int NOMINATION_RESET = -105; - static const int NOMINATION_RESET_FAILED = -106; + static const int NOMINATION_DISABLED = -101; + static const int NOMINATION_RESET = -102; + static const int NOMINATION_RESET_FAILED = -103; + static const int MAP_NOT_FOUND = -104; + static const int MAP_DISABLED = -105; + static const int MAP_CURRENT = -106; + static const int MAP_COOLDOWN = -107; + static const int MAP_MINPLAYERS = -108; + static const int MAP_MAXPLAYERS = -109; + static const int MAP_NOMINATED = -110; } #endif @@ -108,7 +112,7 @@ class CMapVoteSystem void DecrementAllMapCooldowns(); void SetMaxNominatedMaps(int iMaxNominatedMaps) { m_iMaxNominatedMaps = iMaxNominatedMaps; }; int GetMaxNominatedMaps() { return m_iMaxNominatedMaps; }; - int AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring); + std::pair AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring); bool IsMapIndexEnabled(int iMapIndex); int GetTotalNominations(int iMapIndex); int ForceNextMap(const char* sMapSubstring); From 3dc9155bcf3cd056721edea6a8b6d729a9cb50f7 Mon Sep 17 00:00:00 2001 From: Vauff Date: Fri, 22 Nov 2024 19:22:28 -0500 Subject: [PATCH 23/24] Added warning on several map-related commands for non-unique queries --- src/adminsystem.cpp | 15 ++++- src/map_votes.cpp | 141 +++++++++++++++++++++++++++++--------------- src/map_votes.h | 21 ++++--- 3 files changed, 120 insertions(+), 57 deletions(-) diff --git a/src/adminsystem.cpp b/src/adminsystem.cpp index 441d93558..0865455ae 100644 --- a/src/adminsystem.cpp +++ b/src/adminsystem.cpp @@ -602,6 +602,7 @@ CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP) if (!g_pEngineServer2->IsMapValid(pszMapName)) { std::string sCommand; + std::vector foundIndexes = g_pMapVoteSystem->GetMapIndexesFromSubstring(pszMapName); // Check if input is numeric (workshop ID) // Not safe to expose to all admins until crashing on failed workshop addon downloads is fixed @@ -609,9 +610,19 @@ CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP) { sCommand = "host_workshop_map " + sMapName; } - else if (g_bVoteManagerEnable && g_pMapVoteSystem->GetMapIndexFromSubstring(pszMapName) != -1) + else if (g_bVoteManagerEnable && foundIndexes.size() > 0) { - sCommand = "host_workshop_map " + std::to_string(g_pMapVoteSystem->GetMapWorkshopId(g_pMapVoteSystem->GetMapIndexFromSubstring(pszMapName))); + if (foundIndexes.size() > 1) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Multiple maps matched \x06%s\x01, try being more specific:", pszMapName); + + for (int i = 0; i < foundIndexes.size() && i < 5; i++) + ClientPrint(player, HUD_PRINTTALK, "- %s", g_pMapVoteSystem->GetMapName(foundIndexes[i])); + + return; + } + + sCommand = "host_workshop_map " + std::to_string(g_pMapVoteSystem->GetMapWorkshopId(foundIndexes[0])); } else { diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 855b847e5..d236497c5 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -107,14 +107,30 @@ CON_COMMAND_CHAT_FLAGS(setnextmap, "[mapname] - Force next map (empty to clear f return; int iPreviousNextMap = g_pMapVoteSystem->GetForcedNextMap(); - int iResponse = g_pMapVoteSystem->ForceNextMap(args.ArgC() < 2 ? "" : args[1]); + std::pair> response = g_pMapVoteSystem->ForceNextMap(args.ArgC() < 2 ? "" : args[1]); - if (iResponse >= 0 && iPreviousNextMap == iResponse) - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "\x06%s\x01 is already the next map!", g_pMapVoteSystem->GetMapName(iResponse)); - else if (iResponse == -1) - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Failed to find a map matching \x06%s\x01.", args[1]); - else if (iResponse == -3) - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "There is no next map to reset!", args[1]); + if (response.first == 0 && iPreviousNextMap == response.second[0]) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "\x06%s\x01 is already the next map!", g_pMapVoteSystem->GetMapName(iPreviousNextMap)); + return; + } + + switch (response.first) + { + case -1: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Failed to find a map matching \x06%s\x01.", args[1]); + break; + case -3: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "There is no next map to reset!"); + break; + case -4: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Multiple maps matched \x06%s\x01, try being more specific:", args[1]); + + for (int i = 0; i < response.second.size() && i < 5; i++) + ClientPrint(player, HUD_PRINTTALK, "- %s", g_pMapVoteSystem->GetMapName(response.second[i])); + + break; + } } static int __cdecl OrderStringsLexicographically(const MapIndexPair *a, const MapIndexPair *b) @@ -122,12 +138,12 @@ static int __cdecl OrderStringsLexicographically(const MapIndexPair *a, const Ma return V_strcasecmp(a->name, b->name); } -CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nomination or list all maps)", ADMFLAG_NONE) +CON_COMMAND_CHAT(nominate, "[mapname] - Nominate a map (empty to clear nomination or list all maps)") { if (!g_bVoteManagerEnable || !player) return; - std::pair response = g_pMapVoteSystem->AddMapNomination(player->GetPlayerSlot(), args.ArgC() < 2 ? "" : args[1]); + std::pair> response = g_pMapVoteSystem->AddMapNomination(player->GetPlayerSlot(), args.ArgC() < 2 ? "" : args[1]); ZEPlayer* pPlayer = g_playerManager->GetPlayer(player->GetPlayerSlot()); if (!pPlayer) @@ -141,19 +157,19 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because no map matched.", args[1]); break; case NominationReturnCodes::MAP_DISABLED: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's disabled.", g_pMapVoteSystem->GetMapName(response.second)); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's disabled.", g_pMapVoteSystem->GetMapName(response.second[0])); break; case NominationReturnCodes::MAP_CURRENT: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's already the current map!", g_pMapVoteSystem->GetMapName(response.second)); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's already the current map!", g_pMapVoteSystem->GetMapName(response.second[0])); break; case NominationReturnCodes::MAP_COOLDOWN: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's on a %i map cooldown.", g_pMapVoteSystem->GetMapName(response.second), g_pMapVoteSystem->GetCooldownMap(response.second)); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's on a %i map cooldown.", g_pMapVoteSystem->GetMapName(response.second[0]), g_pMapVoteSystem->GetCooldownMap(response.second[0])); break; case NominationReturnCodes::MAP_MINPLAYERS: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i more players.", g_pMapVoteSystem->GetMapName(response.second), g_pMapVoteSystem->GetMapMinPlayers(response.second) - g_playerManager->GetOnlinePlayerCount(false)); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i more players.", g_pMapVoteSystem->GetMapName(response.second[0]), g_pMapVoteSystem->GetMapMinPlayers(response.second[0]) - g_playerManager->GetOnlinePlayerCount(false)); break; case NominationReturnCodes::MAP_MAXPLAYERS: - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i less players.", g_pMapVoteSystem->GetMapName(response.second), g_playerManager->GetOnlinePlayerCount(false) - g_pMapVoteSystem->GetMapMaxPlayers(response.second)); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i less players.", g_pMapVoteSystem->GetMapName(response.second[0]), g_playerManager->GetOnlinePlayerCount(false) - g_pMapVoteSystem->GetMapMaxPlayers(response.second[0])); break; case NominationReturnCodes::NOMINATION_DISABLED: ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Nominations are currently disabled."); @@ -162,6 +178,15 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Your nomination was reset."); g_pMapVoteSystem->ClearPlayerInfo(player->GetPlayerSlot()); break; + case NominationReturnCodes::MAP_MULTIPLE: + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Multiple maps matched \x06%s\x01, try being more specific:", args[1]); + + for (int i = 0; i < response.second.size() && i < 5; i++) + ClientPrint(player, HUD_PRINTTALK, "- %s", g_pMapVoteSystem->GetMapName(response.second[i])); + + break; + } case NominationReturnCodes::NOMINATION_RESET_FAILED: { ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The list of all maps will be shown in console."); @@ -215,8 +240,8 @@ CON_COMMAND_CHAT_FLAGS(nominate, "[mapname] - Nominate a map (empty to clear nom else { const char* sPlayerName = player->GetPlayerName(); - const char* sMapName = g_pMapVoteSystem->GetMapName(response.second); - int iNumNominations = g_pMapVoteSystem->GetTotalNominations(response.second); + const char* sMapName = g_pMapVoteSystem->GetMapName(response.second[0]); + int iNumNominations = g_pMapVoteSystem->GetTotalNominations(response.second[0]); ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01was nominated by %s. It now has %d nominations.", sMapName, sPlayerName, iNumNominations); pPlayer->SetNominateTime(gpGlobals->curtime); } @@ -312,7 +337,7 @@ void CMapVoteSystem::OnLevelInit(const char* pMapName) return -1.0f; }); - SetCurrentMapIndex(GetMapIndexFromSubstring(pMapName)); + SetCurrentMapIndex(GetMapIndexFromString(pMapName)); } void CMapVoteSystem::StartVote() @@ -578,14 +603,28 @@ void CMapVoteSystem::GetNominatedMapsForVote(CUtlVector& vecChosenNominated } } -int CMapVoteSystem::GetMapIndexFromSubstring(const char* sMapSubstring) +std::vector CMapVoteSystem::GetMapIndexesFromSubstring(const char* sMapSubstring) { - FOR_EACH_VEC(m_vecMapList, i) { - if (V_stristr(m_vecMapList[i].GetName(), sMapSubstring)) { - return i; - } - } - return -1; + std::vector vecMaps; + + FOR_EACH_VEC(m_vecMapList, i) + { + if (V_stristr(m_vecMapList[i].GetName(), sMapSubstring)) + vecMaps.push_back(i); + } + + return vecMaps; +} + +int CMapVoteSystem::GetMapIndexFromString(const char* sMapString) +{ + FOR_EACH_VEC(m_vecMapList, i) + { + if (!V_strcasecmp(m_vecMapList[i].GetName(), sMapString)) + return i; + } + + return -1; } void CMapVoteSystem::ClearPlayerInfo(int iSlot) @@ -597,10 +636,10 @@ void CMapVoteSystem::ClearPlayerInfo(int iSlot) m_arrPlayerVotes[iSlot] = -1; } -std::pair CMapVoteSystem::AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring) +std::pair> CMapVoteSystem::AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring) { - if (m_bIsVoteOngoing) return std::make_pair(NominationReturnCodes::VOTE_STARTED, 0); - if (m_iForcedNextMapIndex != -1 || m_iMaxNominatedMaps == 0) return std::make_pair(NominationReturnCodes::NOMINATION_DISABLED, 0); + if (m_bIsVoteOngoing) return std::make_pair(NominationReturnCodes::VOTE_STARTED, std::vector()); + if (m_iForcedNextMapIndex != -1 || m_iMaxNominatedMaps == 0) return std::make_pair(NominationReturnCodes::NOMINATION_DISABLED, std::vector()); int iSlot = iPlayerSlot.Get(); @@ -610,41 +649,46 @@ std::pair CMapVoteSystem::AddMapNomination(CPlayerSlot iPlayerSlot, co if (m_arrPlayerNominations[iSlot] != -1) { m_arrPlayerNominations[iSlot] = -1; - return std::make_pair(NominationReturnCodes::NOMINATION_RESET, 0); + return std::make_pair(NominationReturnCodes::NOMINATION_RESET, std::vector()); } else { - return std::make_pair(NominationReturnCodes::NOMINATION_RESET_FAILED, 0); + return std::make_pair(NominationReturnCodes::NOMINATION_RESET_FAILED, std::vector()); } } // We are not reseting the nomination: is the map found? is it valid? - int iFoundIndex = GetMapIndexFromSubstring(sMapSubstring); + std::vector foundIndexes = GetMapIndexesFromSubstring(sMapSubstring); int iOnlinePlayers = g_playerManager->GetOnlinePlayerCount(false); - if (iFoundIndex == -1) - return std::make_pair(NominationReturnCodes::MAP_NOT_FOUND, 0); + if (foundIndexes.size() == 0) + return std::make_pair(NominationReturnCodes::MAP_NOT_FOUND, std::vector()); + + if (foundIndexes.size() > 1) + return std::make_pair(NominationReturnCodes::MAP_MULTIPLE, foundIndexes); + + int iFoundIndex = foundIndexes[0]; if (!GetMapEnabledStatus(iFoundIndex)) - return std::make_pair(NominationReturnCodes::MAP_DISABLED, iFoundIndex); + return std::make_pair(NominationReturnCodes::MAP_DISABLED, foundIndexes); if (GetCurrentMapIndex() == iFoundIndex) - return std::make_pair(NominationReturnCodes::MAP_CURRENT, iFoundIndex); + return std::make_pair(NominationReturnCodes::MAP_CURRENT, foundIndexes); if (GetCooldownMap(iFoundIndex) > 0) - return std::make_pair(NominationReturnCodes::MAP_COOLDOWN, iFoundIndex); + return std::make_pair(NominationReturnCodes::MAP_COOLDOWN, foundIndexes); if (iOnlinePlayers < m_vecMapList[iFoundIndex].GetMinPlayers()) - return std::make_pair(NominationReturnCodes::MAP_MINPLAYERS, iFoundIndex); + return std::make_pair(NominationReturnCodes::MAP_MINPLAYERS, foundIndexes); if (iOnlinePlayers > m_vecMapList[iFoundIndex].GetMaxPlayers()) - return std::make_pair(NominationReturnCodes::MAP_MAXPLAYERS, iFoundIndex); + return std::make_pair(NominationReturnCodes::MAP_MAXPLAYERS, foundIndexes); m_arrPlayerNominations[iSlot] = iFoundIndex; - return std::make_pair(NominationReturnCodes::MAP_NOMINATED, iFoundIndex); + return std::make_pair(NominationReturnCodes::MAP_NOMINATED, foundIndexes); } -int CMapVoteSystem::ForceNextMap(const char* sMapSubstring) +std::pair> CMapVoteSystem::ForceNextMap(const char* sMapSubstring) { if (sMapSubstring[0] == '\0') { @@ -652,24 +696,29 @@ int CMapVoteSystem::ForceNextMap(const char* sMapSubstring) { ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01is no longer the forced next map.\n", m_vecMapList[m_iForcedNextMapIndex].GetName()); m_iForcedNextMapIndex = -1; - return -2; + return std::make_pair(-2, std::vector()); } - return -3; + return std::make_pair(-3, std::vector()); } - int iFoundIndex = GetMapIndexFromSubstring(sMapSubstring); + std::vector foundIndexes = GetMapIndexesFromSubstring(sMapSubstring); + + if (foundIndexes.size() == 0) + return std::make_pair(-1, foundIndexes); + + if (foundIndexes.size() > 1) + return std::make_pair(-4, foundIndexes); - if (iFoundIndex == -1) - return iFoundIndex; + int iFoundIndex = foundIndexes[0]; if (m_iForcedNextMapIndex == iFoundIndex) - return iFoundIndex; + return std::make_pair(0, foundIndexes); // When found, print the map and store the forced map m_iForcedNextMapIndex = iFoundIndex; ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01has been forced as the next map.\n", m_vecMapList[iFoundIndex].GetName()); - return iFoundIndex; + return std::make_pair(0, foundIndexes); } static int __cdecl OrderMapsByWorkshopId(const CMapInfo* a, const CMapInfo* b) diff --git a/src/map_votes.h b/src/map_votes.h index 35ec2d5ad..81075cde0 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -26,6 +26,7 @@ #include "steam/steam_api_common.h" #include "steam/isteamugc.h" #include +#include // Nomination constants, used as return codes for nomination commands @@ -38,12 +39,13 @@ namespace NominationReturnCodes static const int NOMINATION_RESET = -102; static const int NOMINATION_RESET_FAILED = -103; static const int MAP_NOT_FOUND = -104; - static const int MAP_DISABLED = -105; - static const int MAP_CURRENT = -106; - static const int MAP_COOLDOWN = -107; - static const int MAP_MINPLAYERS = -108; - static const int MAP_MAXPLAYERS = -109; - static const int MAP_NOMINATED = -110; + static const int MAP_MULTIPLE = -105; + static const int MAP_DISABLED = -106; + static const int MAP_CURRENT = -107; + static const int MAP_COOLDOWN = -108; + static const int MAP_MINPLAYERS = -109; + static const int MAP_MAXPLAYERS = -110; + static const int MAP_NOMINATED = -111; } #endif @@ -106,16 +108,17 @@ class CMapVoteSystem void StartVote(); void FinishVote(); bool RegisterPlayerVote(CPlayerSlot iPlayerSlot, int iVoteOption); - int GetMapIndexFromSubstring(const char* sMapSubstring); + std::vector GetMapIndexesFromSubstring(const char* sMapSubstring); + int GetMapIndexFromString(const char* sMapString); int GetCooldownMap(int iMapIndex) { return m_vecMapList[iMapIndex].GetCooldown(); }; void PutMapOnCooldown(int iMapIndex) { m_vecMapList[iMapIndex].ResetCooldownToBase(); }; void DecrementAllMapCooldowns(); void SetMaxNominatedMaps(int iMaxNominatedMaps) { m_iMaxNominatedMaps = iMaxNominatedMaps; }; int GetMaxNominatedMaps() { return m_iMaxNominatedMaps; }; - std::pair AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring); + std::pair> AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring); bool IsMapIndexEnabled(int iMapIndex); int GetTotalNominations(int iMapIndex); - int ForceNextMap(const char* sMapSubstring); + std::pair> ForceNextMap(const char* sMapSubstring); int GetMapListSize() { return m_vecMapList.Count(); }; const char* GetMapName(int iMapIndex) { return m_vecMapList[iMapIndex].GetName(); }; uint64 GetMapWorkshopId(int iMapIndex) { return m_vecMapList[iMapIndex].GetWorkshopId(); }; From 9517de2a7e114190fec531e08c16307fe766f818 Mon Sep 17 00:00:00 2001 From: Vauff Date: Fri, 22 Nov 2024 20:03:33 -0500 Subject: [PATCH 24/24] Adjustments for logging Improves behaviour with a logging backend.. still need to upstream that :) --- src/map_votes.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index d236497c5..0afa51ac6 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -49,7 +49,7 @@ CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current if (g_pMapVoteSystem->GetDownloadQueueSize() != 0) { - Message("Please wait for current map downloads to finish before loading map list again\n"); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Please wait for current map downloads to finish before loading map list again."); return; } @@ -67,7 +67,7 @@ CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current V_snprintf(sChangeMapCmd, sizeof(sChangeMapCmd), "map %s", g_pMapVoteSystem->GetCurrentMap()); g_pEngineServer2->ServerCommand(sChangeMapCmd); - Message("Map list reloaded\n"); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Map list reloaded!"); } CON_COMMAND_F(cs2f_vote_maps_cooldown, "Default number of maps to wait until a map can be voted / nominated again i.e. cooldown.", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) @@ -454,19 +454,24 @@ void CMapVoteSystem::FinishVote() if (iNextMapVoteIndex < 0) iNextMapVoteIndex = -1; g_pGameRules->m_nEndMatchMapVoteWinner = iNextMapVoteIndex; int iWinningMap = g_pGameRules->m_nEndMatchMapGroupVoteOptions[iNextMapVoteIndex]; + char buffer[256]; + if (bIsNextMapVoted) { - ClientPrintAll(HUD_PRINTTALK, "The vote has ended. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap)); + V_snprintf(buffer, sizeof(buffer), "The vote has ended. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap)); } else if (bIsNextMapForced) { - ClientPrintAll(HUD_PRINTTALK, "The vote was overriden. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap)); + V_snprintf(buffer, sizeof(buffer), "The vote was overriden. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap)); } else { - ClientPrintAll(HUD_PRINTTALK, "No map was chosen. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap)); + V_snprintf(buffer, sizeof(buffer), "No map was chosen. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap)); } + ClientPrintAll(HUD_PRINTTALK, buffer); + Message(buffer); + // Print vote result information: how many votes did each map get? int arrMapVotes[10] = { 0 }; - ClientPrintAll(HUD_PRINTCONSOLE, "Map vote result --- total votes per map:\n"); + Message("Map vote result --- total votes per map:\n"); for (int i = 0; i < gpGlobals->maxClients; i++) { auto pController = CCSPlayerController::FromSlot(i); int iPlayerVotedIndex = m_arrPlayerVotes[i]; @@ -477,7 +482,7 @@ void CMapVoteSystem::FinishVote() for (int i = 0; i < 10; i++) { int iMapIndex = g_pGameRules->m_nEndMatchMapGroupVoteOptions[i]; const char* sIsWinner = (i == iNextMapVoteIndex) ? "(WINNER)" : ""; - ClientPrintAll(HUD_PRINTCONSOLE, "- %s got %d votes\n", GetMapName(iMapIndex), arrMapVotes[i]); + Message("- %s got %d votes\n", GetMapName(iMapIndex), arrMapVotes[i]); } // Put the map on cooldown as we transition to the next map if map index is valid, also decrease cooldown remaining for others