Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add map cooldown and min/max player configuration, saving of map cooldown state #288

Merged
merged 12 commits into from
Nov 20, 2024
Merged
2 changes: 1 addition & 1 deletion cfg/cs2fixes/cs2fixes.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +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 // Number of maps to wait until a map can be voted / nominated again i.e. cooldown.
Vauff marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
8 changes: 8 additions & 0 deletions configs/maplist.cfg.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@
"de_dust2"
{
"enabled" "1"
"cooldown" "1"
}
"ze_my_first_ze_map"
{
"workshop_id" "123"
"enabled" "1"
"min_players" "30"
"cooldown" "2"
}
"ze_my_second_ze_map"
{
"workshop_id" "456"
"enabled" "1"
"min_players" "5"
"max_players" "10"
"cooldown" "3"
}
"ze_my_third_ze_map"
{
"workshop_id" "789"
"enabled" "1"
"max_players" "20"
"cooldown" "1"
}
}
178 changes: 146 additions & 32 deletions src/map_votes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -70,18 +72,18 @@ 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)
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->GetMapCooldown());
Message("%s %d\n", args[0], g_pMapVoteSystem->GetDefaultMapCooldown());
else {
int iCurrentCooldown = g_pMapVoteSystem->GetMapCooldown();
g_pMapVoteSystem->SetMapCooldown(V_StringToInt32(args[1], iCurrentCooldown));
int iCurrentCooldown = g_pMapVoteSystem->GetDefaultMapCooldown();
g_pMapVoteSystem->SetDefaultMapCooldown(V_StringToInt32(args[1], iCurrentCooldown));
}
}

Expand Down Expand Up @@ -115,12 +117,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 MapIndexPair *a, const MapIndexPair *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;
Expand Down Expand Up @@ -154,16 +156,37 @@ 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<const char*> vecMapNames;
CUtlVector<MapIndexPair> vecMapNames;

for (int i = 0; i < g_pMapVoteSystem->GetMapListSize(); i++)
vecMapNames.AddToTail(g_pMapVoteSystem->GetMapName(i));
{
MapIndexPair map;
map.name = g_pMapVoteSystem->GetMapName(i);
map.index = i;
vecMapNames.AddToTail(map);
}

vecMapNames.Sort(OrderStringsLexicographically);

// TODO: print cooldown time here too (after rewrite)
FOR_EACH_VEC(vecMapNames, i)
ClientPrint(player, HUD_PRINTCONSOLE, "- %s", vecMapNames[i]);
{
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", name);
}

break;
}
Expand Down Expand Up @@ -200,19 +223,21 @@ 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);
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);
}
}
}

Expand All @@ -228,11 +253,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 (m_vecLastPlayedMapIndexes.HasElement(iMapIndex)) return false;
return m_vecMapList[iMapIndex].IsEnabled();
if (GetCooldownMap(iMapIndex) > 0 || GetCurrentMapIndex() == iMapIndex) return false;
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)
Expand All @@ -251,11 +296,7 @@ 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);
}
SetCurrentMapIndex(GetMapIndexFromSubstring(pMapName));
}

void CMapVoteSystem::StartVote()
Expand Down Expand Up @@ -393,11 +434,15 @@ 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);
}
// 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++)
Expand Down Expand Up @@ -654,16 +699,28 @@ bool CMapVoteSystem::LoadMapList()
return false;
}

// Load map cooldowns from file
KeyValues* pKVcooldowns = new KeyValues("cooldowns");
KeyValues::AutoDelete autoDeleteKVcooldowns(pKVcooldowns);
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);
}

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 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);

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, iMinPlayers, iMaxPlayers, iBaseCooldown, iCurrentCooldown));
}

new CTimer(0.f, true, true, []()
Expand All @@ -684,9 +741,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. 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.\n", i, map.GetName(), map.GetWorkshopId(), map.IsEnabled()? "enabled" : "disabled");
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;
Expand Down Expand Up @@ -716,4 +773,61 @@ CUtlStringList CMapVoteSystem::CreateWorkshopMapGroup()
mapList.CopyAndAddToTail(GetMapName(i));

return mapList;
}

void CMapVoteSystem::DecrementAllMapCooldowns()
{
FOR_EACH_VEC(m_vecMapList, i)
{
CMapInfo * pMap = &m_vecMapList[i];
pMap->DecrementCooldown();
}
}

bool CMapVoteSystem::WriteMapCooldownsToFile()
{
KeyValues* pKV = new KeyValues("cooldowns");
KeyValues::AutoDelete autoDelete(pKV);

const char *pszPath = "addons/cs2fixes/data/cooldowns.txt";

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;
}

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));
}
}
}
Loading