diff --git a/AMBuildScript b/AMBuildScript
index 1f902a6e..abe002b1 100644
--- a/AMBuildScript
+++ b/AMBuildScript
@@ -92,7 +92,7 @@ class MMSPluginConfig(object):
if builder.options.targets:
target_archs = builder.options.targets.split(',')
else:
- target_archs = ['x86', 'x86_64']
+ target_archs = ['x86_64']
for arch in target_archs:
try:
diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj
index cd8c2ac9..9d9f8776 100644
--- a/CS2Fixes.vcxproj
+++ b/CS2Fixes.vcxproj
@@ -279,6 +279,7 @@
+
diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters
index e4c1f32d..2d425015 100644
--- a/CS2Fixes.vcxproj.filters
+++ b/CS2Fixes.vcxproj.filters
@@ -364,5 +364,8 @@
Header Files
+
+ Header Files
+
\ No newline at end of file
diff --git a/PackageScript b/PackageScript
index bd8cb922..82844781 100644
--- a/PackageScript
+++ b/PackageScript
@@ -59,7 +59,7 @@ mapcfg_folder = builder.AddFolder(os.path.join('cfg', MMSPlugin.plugin_name, 'ma
gamedata_folder = builder.AddFolder(os.path.join('addons', MMSPlugin.plugin_name, 'gamedata'))
builder.AddCopy(os.path.join(builder.sourcePath, 'configs', 'admins.cfg.example'), configs_folder)
builder.AddCopy(os.path.join(builder.sourcePath, 'configs', 'discordbots.cfg.example'), configs_folder)
-builder.AddCopy(os.path.join(builder.sourcePath, 'configs', 'maplist.cfg.example'), configs_folder)
+builder.AddCopy(os.path.join(builder.sourcePath, 'configs', 'maplist.jsonc.example'), configs_folder)
builder.AddCopy(os.path.join(builder.sourcePath, 'cfg', MMSPlugin.plugin_name, 'cs2fixes.cfg'), cfg_folder)
builder.AddCopy(os.path.join(builder.sourcePath, 'cfg', MMSPlugin.plugin_name, 'maps', 'de_somemap.cfg'), mapcfg_folder)
builder.AddCopy(os.path.join(builder.sourcePath, 'configs', 'zr', 'playerclass.jsonc.example'), zr_folder)
diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg
index 8be5d885..ed07458a 100644
--- a/cfg/cs2fixes/cs2fixes.cfg
+++ b/cfg/cs2fixes/cs2fixes.cfg
@@ -78,8 +78,9 @@ 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.
+cs2f_vote_maps_cooldown 6.0 // Default number of hours until a map can be played again i.e. cooldown
+cs2f_vote_max_nominations 10 // Number of nominations to include per vote, out of a maximum of 10
+cs2f_vote_max_maps 10 // Number of total maps to include per vote, including nominations, out of a maximum of 10
// User preferences settings
cs2f_user_prefs_api "" // User Preferences REST API endpoint
diff --git a/configs/maplist.cfg.example b/configs/maplist.cfg.example
deleted file mode 100644
index 5df4d395..00000000
--- a/configs/maplist.cfg.example
+++ /dev/null
@@ -1,29 +0,0 @@
-"Maplist"
-{
- "de_dust2"
- {
- "enabled" "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"
- }
-}
\ No newline at end of file
diff --git a/configs/maplist.jsonc.example b/configs/maplist.jsonc.example
new file mode 100644
index 00000000..9786c30b
--- /dev/null
+++ b/configs/maplist.jsonc.example
@@ -0,0 +1,52 @@
+{
+ "Groups":
+ {
+ // If any map in a group gets played, all maps in that group will receive the group cooldown
+ // The group cooldown applies immediately, but only starts ticking down *after* the trigger map ends
+ "MyFirstGroup":
+ {
+ "enabled": true,
+ "cooldown": 4 // Cooldown in hours
+ },
+ "MySecondGroup":
+ {
+ "enabled": true,
+ "cooldown": 0.5
+ },
+ "MyThirdGroup":
+ {
+ // Omitting the cooldown option will fall back to cs2f_vote_maps_cooldown cvar
+ "enabled": true
+ }
+ },
+ "Maps":
+ {
+ "de_dust2": // Map name should always match .vpk name to avoid unintended behaviour
+ {
+ "enabled": true
+ },
+ "ze_my_first_ze_map":
+ {
+ "enabled": true,
+ "workshop_id": 123, // Workshop ID must be specified for workshop maps
+ "min_players": 20, // Minimum players required to nominate or appear in vote
+ "cooldown": 2 // Custom cooldown in hours, can override default map cooldown
+ },
+ "ze_my_second_ze_map":
+ {
+ "enabled": true,
+ "workshop_id": 456,
+ "min_players": 5, // Maximum players where map can be nominated or appear in vote
+ "max_players": 10,
+ "cooldown": 3,
+ "groups": [ "MyFirstGroup" ] // Any other maps in this group will receive the group cooldown when this map is played
+ },
+ "ze_my_third_ze_map":
+ {
+ "enabled": true,
+ "workshop_id": 789,
+ "max_players": 20,
+ "groups": [ "MyFirstGroup", "MySecondGroup" ] // A map can be in multiple groups
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/adminsystem.cpp b/src/adminsystem.cpp
index e9882eb6..55e953bc 100644
--- a/src/adminsystem.cpp
+++ b/src/adminsystem.cpp
@@ -38,7 +38,7 @@
extern IVEngineServer2* g_pEngineServer2;
extern CGameEntitySystem* g_pEntitySystem;
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern CCSGameRules* g_pGameRules;
CAdminSystem* g_pAdminSystem = nullptr;
@@ -113,10 +113,10 @@ void PrintMultiAdminAction(ETargetType nType, const char* pszAdminName, const ch
CON_COMMAND_F(c_reload_admins, "Reload admin config", FCVAR_SPONLY | FCVAR_LINKED_CONCOMMAND)
{
- if (!g_pAdminSystem->LoadAdmins())
+ if (!g_pAdminSystem->LoadAdmins() || !GetGlobals())
return;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -131,10 +131,10 @@ CON_COMMAND_F(c_reload_admins, "Reload admin config", FCVAR_SPONLY | FCVAR_LINKE
CON_COMMAND_F(c_reload_infractions, "Reload infractions file", FCVAR_SPONLY | FCVAR_LINKED_CONCOMMAND)
{
- if (!g_pAdminSystem->LoadInfractions())
+ if (!g_pAdminSystem->LoadInfractions() || !GetGlobals())
return;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -577,76 +577,6 @@ CON_COMMAND_CHAT_FLAGS(entfirecontroller, " [parameter] - Fire out
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Input successful on %i player controllers.", iFoundEnts);
}
-CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP)
-{
- if (args.ArgC() < 2)
- {
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Usage: !map ");
- return;
- }
-
- std::string sMapName = args[1];
-
- for (int i = 0; sMapName[i]; i++)
- {
- // Injection prevention, because we may pass user input to ServerCommand
- if (sMapName[i] == ';' || sMapName[i] == '|')
- return;
-
- sMapName[i] = tolower(sMapName[i]);
- }
-
- const char* pszMapName = sMapName.c_str();
-
- 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
- if ((!player || player->GetZEPlayer()->IsAdminFlagSet(ADMFLAG_RCON)) && V_StringToUint64(pszMapName, 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING) != 0)
- {
- sCommand = "host_workshop_map " + sMapName;
- }
- else if (g_bVoteManagerEnable && foundIndexes.size() > 0)
- {
- 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
- {
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Failed to find a map matching %s.", pszMapName);
- return;
- }
-
- ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to %s...", pszMapName);
-
- new CTimer(5.0f, false, true, [sCommand]() {
- g_pEngineServer2->ServerCommand(sCommand.c_str());
- return -1.0f;
- });
-
- return;
- }
-
- ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to %s...", pszMapName);
-
- new CTimer(5.0f, false, true, [sMapName]() {
- g_pEngineServer2->ChangeLevel(sMapName.c_str(), nullptr);
- return -1.0f;
- });
-}
-
CON_COMMAND_CHAT_FLAGS(hsay, " - Say something as a hud hint", ADMFLAG_CHAT)
{
if (args.ArgC() < 2)
@@ -685,8 +615,7 @@ CON_COMMAND_CHAT_FLAGS(extend, " - Extend current map (negative value r
int iExtendTime = V_StringToInt32(args[1], 0);
- // Call the votemanager extend function so the extend vote can be checked
- ExtendMap(iExtendTime);
+ g_pVoteManager->ExtendMap(iExtendTime);
const char* pszCommandPlayerName = player ? player->GetPlayerName() : CONSOLE_NAME;
@@ -698,6 +627,9 @@ CON_COMMAND_CHAT_FLAGS(extend, " - Extend current map (negative value r
CON_COMMAND_CHAT_FLAGS(pm, " - Private message a player. This will also show to all online admins", ADMFLAG_GENERIC)
{
+ if (!GetGlobals())
+ return;
+
if (args.ArgC() < 3)
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Usage: /pm ");
@@ -737,7 +669,7 @@ CON_COMMAND_CHAT_FLAGS(pm, " - Private message a player. This wi
return;
}
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -755,9 +687,12 @@ CON_COMMAND_CHAT_FLAGS(pm, " - Private message a player. This wi
CON_COMMAND_CHAT_FLAGS(who, "- List the flags of all online players", ADMFLAG_GENERIC)
{
+ if (!GetGlobals())
+ return;
+
std::vector> rgNameSlotID;
- for (size_t i = 0; i < gpGlobals->maxClients; i++)
+ for (size_t i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* ccsPly = CCSPlayerController::FromSlot(i);
@@ -961,7 +896,8 @@ CON_COMMAND_CHAT_FLAGS(listdc, "- List recently disconnected players and their S
CON_COMMAND_CHAT_FLAGS(endround, "- Immediately ends the round, client-side variant of endround", ADMFLAG_RCON)
{
- g_pGameRules->TerminateRound(0.0f, CSRoundEndReason::Draw);
+ if (g_pGameRules)
+ g_pGameRules->TerminateRound(0.0f, CSRoundEndReason::Draw);
}
CON_COMMAND_CHAT_FLAGS(money, " - Set a player's amount of money", ADMFLAG_CHEATS)
diff --git a/src/buttonwatch.cpp b/src/buttonwatch.cpp
index 7c6c5213..6cd6664c 100644
--- a/src/buttonwatch.cpp
+++ b/src/buttonwatch.cpp
@@ -96,7 +96,7 @@ bool IsButtonWatchEnabled()
std::map mapRecentEnts;
void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay)
{
- if (!IsButtonWatchEnabled() || V_stricmp(pThis->m_pDesc->m_pName, "OnPressed") || !pActivator || !((CBaseEntity*)pActivator)->IsPawn() || !pCaller || mapRecentEnts.contains(pCaller->GetEntityIndex().Get()))
+ if (!IsButtonWatchEnabled() || !GetGlobals() || V_stricmp(pThis->m_pDesc->m_pName, "OnPressed") || !pActivator || !((CBaseEntity*)pActivator)->IsPawn() || !pCaller || mapRecentEnts.contains(pCaller->GetEntityIndex().Get()))
return;
CCSPlayerController* ccsPlayer = CCSPlayerController::FromPawn(static_cast(pActivator));
@@ -112,7 +112,7 @@ void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEnt
std::string strButton = std::to_string(pCaller->GetEntityIndex().Get()) + " " + std::string(((CBaseEntity*)pCaller)->GetName());
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* ccsPlayer = CCSPlayerController::FromSlot(i);
if (!ccsPlayer)
diff --git a/src/cs2_sdk/entity/ccsweaponbase.h b/src/cs2_sdk/entity/ccsweaponbase.h
index 91e03c9b..32f1378a 100644
--- a/src/cs2_sdk/entity/ccsweaponbase.h
+++ b/src/cs2_sdk/entity/ccsweaponbase.h
@@ -21,7 +21,7 @@
#include "cbaseentity.h"
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
enum gear_slot_t : uint32_t
{
@@ -97,8 +97,11 @@ class CBasePlayerWeapon : public CEconEntity
void Disarm()
{
- m_nNextPrimaryAttackTick(MAX(m_nNextPrimaryAttackTick(), gpGlobals->tickcount + 24));
- m_nNextSecondaryAttackTick(MAX(m_nNextSecondaryAttackTick(), gpGlobals->tickcount + 24));
+ if (!GetGlobals())
+ return;
+
+ m_nNextPrimaryAttackTick(MAX(m_nNextPrimaryAttackTick(), GetGlobals()->tickcount + 24));
+ m_nNextSecondaryAttackTick(MAX(m_nNextSecondaryAttackTick(), GetGlobals()->tickcount + 24));
}
};
diff --git a/src/cs2_sdk/entity/cgamerules.h b/src/cs2_sdk/entity/cgamerules.h
index 6b491038..127ba020 100644
--- a/src/cs2_sdk/entity/cgamerules.h
+++ b/src/cs2_sdk/entity/cgamerules.h
@@ -65,6 +65,7 @@ class CCSGameRules : public CGameRules
SCHEMA_FIELD(GameTime_t, m_fRoundStartTime)
SCHEMA_FIELD(GameTime_t, m_flRestartRoundTime)
SCHEMA_FIELD_POINTER(int, m_nEndMatchMapGroupVoteOptions)
+ SCHEMA_FIELD_POINTER(int, m_nEndMatchMapGroupVoteTypes)
SCHEMA_FIELD(int, m_nEndMatchMapVoteWinner)
SCHEMA_FIELD(int, m_iRoundTime)
SCHEMA_FIELD(bool, m_bFreezePeriod)
diff --git a/src/cs2_sdk/schema.cpp b/src/cs2_sdk/schema.cpp
index efcb343a..18d42009 100644
--- a/src/cs2_sdk/schema.cpp
+++ b/src/cs2_sdk/schema.cpp
@@ -27,7 +27,7 @@
#include "tier0/memdbgon.h"
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
using SchemaKeyValueMap_t = CUtlMap;
using SchemaTableMap_t = CUtlMap;
diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp
index 93c42adb..f884e441 100644
--- a/src/cs2fixes.cpp
+++ b/src/cs2fixes.cpp
@@ -124,10 +124,8 @@ CS2Fixes g_CS2Fixes;
IGameEventSystem* g_gameEventSystem = nullptr;
IGameEventManager2* g_gameEventManager = nullptr;
-INetworkGameServer* g_pNetworkGameServer = nullptr;
CGameEntitySystem* g_pEntitySystem = nullptr;
CEntityListener* g_pEntityListener = nullptr;
-CGlobalVars* gpGlobals = nullptr;
CPlayerManager* g_playerManager = nullptr;
IVEngineServer2* g_pEngineServer2 = nullptr;
CGameConfig* g_GameConfig = nullptr;
@@ -148,6 +146,18 @@ CGameEntitySystem* GameEntitySystem()
return *reinterpret_cast((uintptr_t)(g_pGameResourceServiceServer) + offset);
}
+// Will return null between map end & new map startup, null check if necessary!
+INetworkGameServer* GetNetworkGameServer()
+{
+ return g_pNetworkServerService->GetIGameServer();
+}
+
+// Will return null between map end & new map startup, null check if necessary!
+CGlobalVars* GetGlobals()
+{
+ return g_pEngineServer2->GetServerGlobals();
+}
+
PLUGIN_EXPOSE(CS2Fixes, g_CS2Fixes);
bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool late)
{
@@ -311,17 +321,16 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
RegisterEventListeners();
g_pEntitySystem = GameEntitySystem();
g_pEntitySystem->AddListenerEntity(g_pEntityListener);
- g_pNetworkGameServer = g_pNetworkServerService->GetIGameServer();
- gpGlobals = g_pEngineServer2->GetServerGlobals();
}
g_pAdminSystem = new CAdminSystem();
g_playerManager = new CPlayerManager(late);
g_pDiscordBotManager = new CDiscordBotManager();
- g_pZRPlayerClassManager = new CZRPlayerClassManager();
g_pMapVoteSystem = new CMapVoteSystem();
+ g_pVoteManager = new CVoteManager();
g_pUserPreferencesSystem = new CUserPreferencesSystem();
g_pUserPreferencesStorage = new CUserPreferencesREST();
+ g_pZRPlayerClassManager = new CZRPlayerClassManager();
g_pZRWeaponConfig = new ZRWeaponConfig();
g_pZRHitgroupConfig = new ZRHitgroupConfig();
g_pEntityListener = new CEntityListener();
@@ -384,6 +393,9 @@ bool CS2Fixes::Unload(char* error, size_t maxlen)
if (g_iGoToIntermissionId != -1)
SH_REMOVE_HOOK_ID(g_iGoToIntermissionId);
+ if (g_iCGamePlayerEquipUseId != -1)
+ SH_REMOVE_HOOK_ID(g_iCGamePlayerEquipUseId);
+
ConVar_Unregister();
g_CommandList.Purge();
@@ -393,17 +405,29 @@ bool CS2Fixes::Unload(char* error, size_t maxlen)
RemoveTimers();
UnregisterEventListeners();
- if (g_playerManager)
- delete g_playerManager;
+ if (g_GameConfig)
+ delete g_GameConfig;
if (g_pAdminSystem)
delete g_pAdminSystem;
+ if (g_playerManager)
+ delete g_playerManager;
+
if (g_pDiscordBotManager)
delete g_pDiscordBotManager;
- if (g_GameConfig)
- delete g_GameConfig;
+ if (g_pMapVoteSystem)
+ delete g_pMapVoteSystem;
+
+ if (g_pVoteManager)
+ delete g_pVoteManager;
+
+ if (g_pUserPreferencesSystem)
+ delete g_pUserPreferencesSystem;
+
+ if (g_pUserPreferencesStorage)
+ delete g_pUserPreferencesStorage;
if (g_pZRPlayerClassManager)
delete g_pZRPlayerClassManager;
@@ -414,12 +438,6 @@ bool CS2Fixes::Unload(char* error, size_t maxlen)
if (g_pZRHitgroupConfig)
delete g_pZRHitgroupConfig;
- if (g_pUserPreferencesSystem)
- delete g_pUserPreferencesSystem;
-
- if (g_pUserPreferencesStorage)
- delete g_pUserPreferencesStorage;
-
if (g_pEntityListener)
delete g_pEntityListener;
@@ -429,9 +447,6 @@ bool CS2Fixes::Unload(char* error, size_t maxlen)
if (g_pPanoramaVoteHandler)
delete g_pPanoramaVoteHandler;
- if (g_iCGamePlayerEquipUseId != -1)
- SH_REMOVE_HOOK_ID(g_iCGamePlayerEquipUseId);
-
return true;
}
@@ -484,14 +499,14 @@ void CS2Fixes::Hook_DispatchConCommand(ConCommandHandle cmdHandle, const CComman
if (pController)
ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "You are flooding the server!");
}
- else if (bAdminChat) // Admin chat can be sent by anyone but only seen by admins, use flood protection here too
+ else if (bAdminChat && GetGlobals()) // Admin chat can be sent by anyone but only seen by admins, use flood protection here too
{
// HACK: At this point, we can safely modify the arg buffer as it won't be passed anywhere else
// The string here is originally ("@foo bar"), trim it to be (foo bar)
char* pszMessage = (char*)(args.ArgS() + 2);
pszMessage[V_strlen(pszMessage) - 1] = 0;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -528,10 +543,8 @@ void CS2Fixes::Hook_DispatchConCommand(ConCommandHandle cmdHandle, const CComman
void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession* pSession, const char* pszMapName)
{
- g_pNetworkGameServer = g_pNetworkServerService->GetIGameServer();
g_pEntitySystem = GameEntitySystem();
g_pEntitySystem->AddListenerEntity(g_pEntityListener);
- gpGlobals = g_pEngineServer2->GetServerGlobals();
Message("Hook_StartupServer: %s\n", pszMapName);
@@ -543,7 +556,7 @@ void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISou
RegisterEventListeners();
g_pPanoramaVoteHandler->Reset();
- VoteManager_Init();
+ g_pVoteManager->VoteManager_Init();
g_pIdleSystem->Reset();
}
@@ -649,11 +662,11 @@ void CS2Fixes::AllPluginsLoaded()
CUtlVector* GetClientList()
{
- if (!g_pNetworkGameServer)
+ if (!GetNetworkGameServer())
return nullptr;
static int offset = g_GameConfig->GetOffset("CNetworkGameServer_ClientList");
- return (CUtlVector*)(&g_pNetworkGameServer[offset]);
+ return (CUtlVector*)(&GetNetworkGameServer()[offset]);
}
CServerSideClient* GetClientBySlot(CPlayerSlot slot)
@@ -670,6 +683,9 @@ void FullUpdateAllClients()
{
auto pClients = GetClientList();
+ if (!pClients)
+ return;
+
FOR_EACH_VEC(*pClients, i)
(*pClients)[i]->ForceFullUpdate();
}
@@ -785,10 +801,13 @@ void CS2Fixes::Hook_GameFramePost(bool simulating, bool bFirstTick, bool bLastTi
VPROF_BUDGET("CS2Fixes::Hook_GameFramePost", "CS2FixesPerFrame");
+ if (!GetGlobals())
+ return;
+
if (simulating && g_bHasTicked)
- g_flUniversalTime += gpGlobals->curtime - g_flLastTickedTime;
+ g_flUniversalTime += GetGlobals()->curtime - g_flLastTickedTime;
- g_flLastTickedTime = gpGlobals->curtime;
+ g_flLastTickedTime = GetGlobals()->curtime;
g_bHasTicked = true;
for (int i = g_timers.Tail(); i != g_timers.InvalidIndex();)
@@ -819,7 +838,7 @@ void CS2Fixes::Hook_GameFramePost(bool simulating, bool bFirstTick, bool bLastTi
if (g_bEnableZR)
CZRRegenTimer::Tick();
- EntityHandler_OnGameFramePost(simulating, gpGlobals->tickcount);
+ EntityHandler_OnGameFramePost(simulating, GetGlobals()->tickcount);
}
extern bool g_bFlashLightTransmitOthers;
@@ -827,7 +846,7 @@ extern bool g_bFlashLightTransmitOthers;
void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts,
const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities, bool bEnablePVSBits)
{
- if (!g_pEntitySystem)
+ if (!g_pEntitySystem || !GetGlobals())
return;
VPROF("CS2Fixes::Hook_CheckTransmit");
@@ -851,7 +870,7 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount
if (!pSelfZEPlayer)
continue;
- for (int j = 0; j < gpGlobals->maxClients; j++)
+ for (int j = 0; j < GetGlobals()->maxClients; j++)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(j);
// Always transmit to themselves
@@ -891,13 +910,7 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount
void CS2Fixes::Hook_ApplyGameSettings(KeyValues* pKV)
{
- if (!pKV->FindKey("launchoptions"))
- return;
-
- if (pKV->FindKey("launchoptions")->FindKey("customgamemode"))
- g_pMapVoteSystem->SetCurrentWorkshopMap(pKV->FindKey("launchoptions")->GetUint64("customgamemode"));
- else if (pKV->FindKey("launchoptions")->FindKey("levelname"))
- g_pMapVoteSystem->SetCurrentMap(pKV->FindKey("launchoptions")->GetString("levelname"));
+ g_pMapVoteSystem->ApplyGameSettings(pKV);
}
void CS2Fixes::Hook_CreateWorkshopMapGroup(const char* name, const CUtlStringList& mapList)
@@ -972,7 +985,8 @@ void CS2Fixes::Hook_PhysicsTouchShuffle(CUtlVector* pList, bool u
// [Kxnrl]
// seems it sorted by flags?
- std::srand(gpGlobals->tickcount);
+ if (GetGlobals())
+ std::srand(GetGlobals()->tickcount);
// Fisher-Yates shuffle
@@ -1010,7 +1024,7 @@ void CS2Fixes::Hook_CheckMovingGround(double frametime)
CCSPlayer_MovementServices* pMove = META_IFACEPTR(CCSPlayer_MovementServices);
CCSPlayerPawn* pPawn = pMove->GetPawn();
- if (!pPawn)
+ if (!pPawn || !GetGlobals())
RETURN_META(MRES_IGNORED);
CCSPlayerController* pController = pPawn->GetOriginalController();
@@ -1024,10 +1038,10 @@ void CS2Fixes::Hook_CheckMovingGround(double frametime)
// The point of doing this is to avoid running the function (and applying/resetting basevelocity) multiple times per tick
// This can happen when the client or server lags
- if (aPlayerTicks[iSlot] == gpGlobals->tickcount)
+ if (aPlayerTicks[iSlot] == GetGlobals()->tickcount)
RETURN_META(MRES_SUPERCEDE);
- aPlayerTicks[iSlot] = gpGlobals->tickcount;
+ aPlayerTicks[iSlot] = GetGlobals()->tickcount;
RETURN_META(MRES_IGNORED);
}
@@ -1064,10 +1078,12 @@ void CS2Fixes::OnLevelInit(char const* pMapName,
ZR_OnLevelInit();
}
-// Potentially might not work
void CS2Fixes::OnLevelShutdown()
{
Message("OnLevelShutdown()\n");
+
+ if (g_bVoteManagerEnable)
+ g_pMapVoteSystem->OnLevelShutdown();
}
bool CS2Fixes::Pause(char* error, size_t maxlen)
diff --git a/src/customio.cpp b/src/customio.cpp
index 7aa5b330..e5e7e166 100644
--- a/src/customio.cpp
+++ b/src/customio.cpp
@@ -31,7 +31,7 @@
#include
#include
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
struct AddOutputKey_t
{
@@ -414,13 +414,16 @@ FAKE_FLOAT_CVAR(cs2f_burn_interval, "The interval between burn damage ticks", g_
bool IgnitePawn(CCSPlayerPawn* pPawn, float flDuration, CBaseEntity* pInflictor, CBaseEntity* pAttacker, CBaseEntity* pAbility, DamageTypes_t nDamageType)
{
+ if (!GetGlobals())
+ return false;
+
auto pParticleEnt = reinterpret_cast(pPawn->m_hEffectEntity().Get());
// This guy is already burning, don't ignite again
if (pParticleEnt)
{
// Override the end time instead of just adding to it so players who get a ton of ignite inputs don't burn forever
- pParticleEnt->m_flDissolveStartTime = gpGlobals->curtime + flDuration;
+ pParticleEnt->m_flDissolveStartTime = GetGlobals()->curtime + flDuration;
return true;
}
@@ -431,7 +434,7 @@ bool IgnitePawn(CCSPlayerPawn* pPawn, float flDuration, CBaseEntity* pInflictor,
pParticleEnt->m_bStartActive(true);
pParticleEnt->m_iszEffectName(g_sBurnParticle.c_str());
pParticleEnt->m_hControlPointEnts[0] = pPawn;
- pParticleEnt->m_flDissolveStartTime = gpGlobals->curtime + flDuration; // Store the end time in the particle itself so we can increment if needed
+ pParticleEnt->m_flDissolveStartTime = GetGlobals()->curtime + flDuration; // Store the end time in the particle itself so we can increment if needed
pParticleEnt->Teleport(&vecOrigin, nullptr, nullptr);
pParticleEnt->DispatchSpawn();
@@ -448,7 +451,7 @@ bool IgnitePawn(CCSPlayerPawn* pPawn, float flDuration, CBaseEntity* pInflictor,
new CTimer(0.f, false, false, [hPawn, hInflictor, hAttacker, hAbility, nDamageType]() {
CCSPlayerPawn* pPawn = hPawn.Get();
- if (!pPawn)
+ if (!pPawn || !GetGlobals())
return -1.f;
const auto pParticleEnt = reinterpret_cast(pPawn->m_hEffectEntity().Get());
@@ -463,7 +466,7 @@ bool IgnitePawn(CCSPlayerPawn* pPawn, float flDuration, CBaseEntity* pInflictor,
return -1.f;
}
- if (pParticleEnt->m_flDissolveStartTime() <= gpGlobals->curtime || !pPawn->IsAlive())
+ if (pParticleEnt->m_flDissolveStartTime() <= GetGlobals()->curtime || !pPawn->IsAlive())
{
pParticleEnt->AcceptInput("Stop");
UTIL_AddEntityIOEvent(pParticleEnt, "Kill"); // Kill on the next frame
diff --git a/src/detours.cpp b/src/detours.cpp
index 5370f9a5..83506baa 100644
--- a/src/detours.cpp
+++ b/src/detours.cpp
@@ -53,7 +53,7 @@
#include "tier0/memdbgon.h"
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern CGameEntitySystem* g_pEntitySystem;
extern IGameEventManager2* g_gameEventManager;
extern CCSGameRules* g_pGameRules;
@@ -201,15 +201,15 @@ void FASTCALL Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther)
pOther->Teleport(&origin, nullptr, nullptr);
}
- if (g_bLogPushes)
+ if (g_bLogPushes && GetGlobals())
{
Vector vecEntBaseVelocity = pOther->m_vecBaseVelocity;
Vector vecOrigPush = vecAbsDir * pPush->m_flSpeed();
Message("Pushing entity %i | frame = %i | tick = %i | entity basevelocity %s = %.2f %.2f %.2f | original push velocity = %.2f %.2f %.2f | final push velocity = %.2f %.2f %.2f\n",
pOther->GetEntityIndex(),
- gpGlobals->framecount,
- gpGlobals->tickcount,
+ GetGlobals()->framecount,
+ GetGlobals()->tickcount,
(flags & FL_BASEVELOCITY) ? "WITH FLAG" : "",
vecEntBaseVelocity.x, vecEntBaseVelocity.y, vecEntBaseVelocity.z,
vecOrigPush.x, vecOrigPush.y, vecOrigPush.z,
@@ -235,6 +235,9 @@ void SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPla
{
VPROF("SayChatMessageWithTimer");
+ if (!GetGlobals() || !g_pGameRules)
+ return;
+
char buf[256];
// Filter console message - remove non-alphanumeric chars and convert to lowercase
@@ -303,7 +306,7 @@ void SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPla
}
}
- float fCurrentRoundClock = g_pGameRules->m_iRoundTime - (gpGlobals->curtime - g_pGameRules->m_fRoundStartTime.Get().GetTime());
+ float fCurrentRoundClock = g_pGameRules->m_iRoundTime - (GetGlobals()->curtime - g_pGameRules->m_fRoundStartTime.Get().GetTime());
// Only display trigger time if the timer is greater than 4 seconds, and time expires within the round
if ((uiTriggerTimerLength > 4) && (fCurrentRoundClock > uiTriggerTimerLength))
@@ -480,7 +483,7 @@ void FASTCALL Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pM
{
CCSPlayerPawn* pPawn = pThis->GetPawn();
- if (!pPawn->IsAlive())
+ if (!pPawn->IsAlive() || !GetGlobals())
return ProcessMovement(pThis, pMove);
CCSPlayerController* pController = pPawn->GetOriginalController();
@@ -495,13 +498,13 @@ void FASTCALL Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pM
// Yes, this is what source1 does to scale player speed
// Scale frametime during the entire movement processing step and revert right after
- float flStoreFrametime = gpGlobals->frametime;
+ float flStoreFrametime = GetGlobals()->frametime;
- gpGlobals->frametime *= flSpeedMod;
+ GetGlobals()->frametime *= flSpeedMod;
ProcessMovement(pThis, pMove);
- gpGlobals->frametime = flStoreFrametime;
+ GetGlobals()->frametime = flStoreFrametime;
}
static bool g_bDisableSubtick = false;
@@ -560,8 +563,12 @@ void FASTCALL Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer(CGamePlayer
CServerSideClient* FASTCALL Detour_GetFreeClient(int64_t unk1, const __m128i* unk2, unsigned int unk3, int64_t unk4, char unk5, void* unk6)
{
+ // Not sure if this function can even be called in this state, but if it is, we can't do shit anyways
+ if (!GetClientList() || !GetGlobals())
+ return nullptr;
+
// Check if there is still unused slots, this should never break so just fall back to original behaviour for ease (we don't have a CServerSideClient constructor)
- if (gpGlobals->maxClients != GetClientList()->Count())
+ if (GetGlobals()->maxClients != GetClientList()->Count())
return GetFreeClient(unk1, unk2, unk3, unk4, unk5, unk6);
// Phantom client fix
diff --git a/src/entities.cpp b/src/entities.cpp
index e377627c..33dcc10c 100644
--- a/src/entities.cpp
+++ b/src/entities.cpp
@@ -511,10 +511,10 @@ namespace CPointViewControlHandler
{
const auto key = pEntity->GetHandle().ToInt();
const auto it = s_repository.find(key);
- if (it == s_repository.end())
+ if (it == s_repository.end() || !GetGlobals())
return false;
- for (auto i = 0; i < gpGlobals->maxClients; i++)
+ for (auto i = 0; i < GetGlobals()->maxClients; i++)
{
const auto pController = CCSPlayerController::FromSlot(i);
if (!pController || !pController->IsConnected() || pController->IsBot() || pController->m_bIsHLTV())
diff --git a/src/events.cpp b/src/events.cpp
index 6e85afb9..01980bf2 100644
--- a/src/events.cpp
+++ b/src/events.cpp
@@ -25,7 +25,9 @@
#include "entity/cbaseplayercontroller.h"
#include "entity/cgamerules.h"
#include "eventlistener.h"
+#include "idlemanager.h"
#include "leader.h"
+#include "map_votes.h"
#include "networkstringtabledefs.h"
#include "panoramavote.h"
#include "recipientfilters.h"
@@ -37,7 +39,7 @@
extern IGameEventManager2* g_gameEventManager;
extern IServerGameClients* g_pSource2GameClients;
extern CGameEntitySystem* g_pEntitySystem;
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern CCSGameRules* g_pGameRules;
extern IVEngineServer2* g_pEngineServer2;
@@ -261,10 +263,10 @@ GAME_EVENT_F(round_start)
if (g_bFullAllTalk)
g_pEngineServer2->ServerCommand("sv_full_alltalk 1");
- if (!g_bEnableTopDefender)
+ if (!g_bEnableTopDefender || !GetGlobals())
return;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -280,29 +282,14 @@ GAME_EVENT_F(round_start)
GAME_EVENT_F(round_end)
{
if (g_bVoteManagerEnable)
- {
- ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_timelimit"));
-
- // CONVAR_TODO
- // HACK: values is actually the cvar value itself, hence this ugly cast.
- float flTimelimit = *(float*)&cvar->values;
-
- int iTimeleft = (int)((g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - gpGlobals->curtime);
+ g_pVoteManager->OnRoundEnd();
- // check for end of last round
- if (iTimeleft <= 0)
- {
- g_RTVState = ERTVState::POST_LAST_ROUND_END;
- g_ExtendState = EExtendState::POST_LAST_ROUND_END;
- }
- }
-
- if (!g_bEnableTopDefender)
+ if (!g_bEnableTopDefender || !GetGlobals())
return;
CUtlVector sortedPlayers;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -365,4 +352,12 @@ GAME_EVENT_F(bullet_impact)
GAME_EVENT_F(vote_cast)
{
g_pPanoramaVoteHandler->VoteCast(pEvent);
+}
+
+GAME_EVENT_F(cs_win_panel_match)
+{
+ g_pIdleSystem->PauseIdleChecks();
+
+ if (!g_pMapVoteSystem->IsVoteOngoing())
+ g_pMapVoteSystem->StartVote();
}
\ No newline at end of file
diff --git a/src/gamesystem.cpp b/src/gamesystem.cpp
index 55935778..e0580124 100644
--- a/src/gamesystem.cpp
+++ b/src/gamesystem.cpp
@@ -22,6 +22,7 @@
#include "adminsystem.h"
#include "common.h"
#include "entities.h"
+#include "entity/cgamerules.h"
#include "gameconfig.h"
#include "idlemanager.h"
#include "leader.h"
@@ -31,8 +32,9 @@
#include "tier0/memdbgon.h"
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern CGameConfig* g_GameConfig;
+extern CCSGameRules* g_pGameRules;
CBaseGameSystemFactory** CBaseGameSystemFactory::sm_pFirst = nullptr;
@@ -91,7 +93,9 @@ GS_EVENT_MEMBER(CGameSystem, ServerPreEntityThink)
VPROF_BUDGET("CGameSystem::ServerPreEntityThink", "CS2FixesPerFrame")
g_playerManager->FlashLightThink();
g_pIdleSystem->UpdateIdleTimes();
- EntityHandler_OnGameFramePre(gpGlobals->m_bInSimulation, gpGlobals->tickcount);
+
+ if (GetGlobals())
+ EntityHandler_OnGameFramePre(GetGlobals()->m_bInSimulation, GetGlobals()->tickcount);
}
// Called every frame after entities think
@@ -100,3 +104,8 @@ GS_EVENT_MEMBER(CGameSystem, ServerPostEntityThink)
VPROF_BUDGET("CGameSystem::ServerPostEntityThink", "CS2FixesPerFrame")
g_playerManager->UpdatePlayerStates();
}
+
+GS_EVENT_MEMBER(CGameSystem, GameShutdown)
+{
+ g_pGameRules = nullptr;
+}
diff --git a/src/gamesystem.h b/src/gamesystem.h
index 0a56f33b..9f472b7e 100644
--- a/src/gamesystem.h
+++ b/src/gamesystem.h
@@ -30,6 +30,7 @@ class CGameSystem : public CBaseGameSystem
GS_EVENT(BuildGameSessionManifest);
GS_EVENT(ServerPreEntityThink);
GS_EVENT(ServerPostEntityThink);
+ GS_EVENT(GameShutdown);
void Shutdown() override
{
diff --git a/src/idlemanager.cpp b/src/idlemanager.cpp
index b30eb233..c565f68a 100644
--- a/src/idlemanager.cpp
+++ b/src/idlemanager.cpp
@@ -22,7 +22,7 @@
#include
extern IVEngineServer2* g_pEngineServer2;
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern CPlayerManager* g_playerManager;
CIdleSystem* g_pIdleSystem = nullptr;
@@ -37,11 +37,11 @@ FAKE_BOOL_CVAR(cs2f_idle_kick_admins, "Whether to kick idle players with ADMFLAG
void CIdleSystem::CheckForIdleClients()
{
- if (m_bPaused || g_fIdleKickTime <= 0.0f)
+ if (m_bPaused || g_fIdleKickTime <= 0.0f || !GetGlobals())
return;
int iClientNum = 0;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* zPlayer = g_playerManager->GetPlayer(i);
@@ -51,7 +51,7 @@ void CIdleSystem::CheckForIdleClients()
iClientNum++;
}
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* zPlayer = g_playerManager->GetPlayer(i);
@@ -88,12 +88,12 @@ void CIdleSystem::CheckForIdleClients()
// Logged inputs and time for the logged inputs are updated every time this function is run.
void CIdleSystem::UpdateIdleTimes()
{
- if (g_fIdleKickTime <= 0.0f)
+ if (g_fIdleKickTime <= 0.0f || !GetGlobals())
return;
VPROF("CIdleSystem::UpdateIdleTimes");
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -129,7 +129,10 @@ void CIdleSystem::Reset()
{
m_bPaused = false;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
diff --git a/src/leader.cpp b/src/leader.cpp
index c2583781..bd34f0bf 100644
--- a/src/leader.cpp
+++ b/src/leader.cpp
@@ -28,7 +28,7 @@
extern IVEngineServer2* g_pEngineServer2;
extern CGameEntitySystem* g_pEntitySystem;
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern IGameEventManager2* g_gameEventManager;
// All colors MUST have 255 alpha
@@ -178,10 +178,13 @@ std::pair GetLeaders()
std::pair GetCount(int iType)
{
+ if (!GetGlobals())
+ return std::make_pair(0, "");
+
int iCount = 0;
std::string strPlayerNames = "";
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(CPlayerSlot(i));
if (!pPlayer)
@@ -376,7 +379,13 @@ void Leader_PostEventAbstract_Source1LegacyGameEvent(const uint64* clients, cons
void Leader_OnRoundStart(IGameEvent* pEvent)
{
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ g_bPingWithLeader = true;
+ g_iMarkerCount = 0;
+
+ if (!GetGlobals())
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pLeader = CCSPlayerController::FromSlot((CPlayerSlot)i);
if (!pLeader)
@@ -394,9 +403,6 @@ void Leader_OnRoundStart(IGameEvent* pEvent)
else
Leader_ApplyLeaderVisuals(pawnLeader);
}
-
- g_bPingWithLeader = true;
- g_iMarkerCount = 0;
}
// revisit this later with a TempEnt implementation
@@ -503,7 +509,7 @@ CON_COMMAND_CHAT(glows, "- List all active player glows")
CON_COMMAND_CHAT(vl, " - Vote for a player to become a leader")
{
- if (!g_bEnableLeader)
+ if (!g_bEnableLeader || !GetGlobals())
return;
if (!player)
@@ -518,7 +524,7 @@ CON_COMMAND_CHAT(vl, " - Vote for a player to become a leader")
return;
}
- if (gpGlobals->curtime < 60.0f)
+ if (GetGlobals()->curtime < 60.0f)
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Leader voting is not open yet.");
return;
@@ -540,9 +546,9 @@ CON_COMMAND_CHAT(vl, " - Vote for a player to become a leader")
if (!pPlayer)
return;
- if (pPlayer->GetLeaderVoteTime() + 30.0f > gpGlobals->curtime)
+ if (pPlayer->GetLeaderVoteTime() + 30.0f > GetGlobals()->curtime)
{
- int iRemainingTime = (int)(pPlayer->GetLeaderVoteTime() + 30.0f - gpGlobals->curtime);
+ int iRemainingTime = (int)(pPlayer->GetLeaderVoteTime() + 30.0f - GetGlobals()->curtime);
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Wait %i seconds before you can !vl again.", iRemainingTime);
return;
}
@@ -571,7 +577,7 @@ CON_COMMAND_CHAT(vl, " - Vote for a player to become a leader")
int iLeaderVoteCount = pPlayerTarget->GetLeaderVoteCount();
int iNeededLeaderVoteCount = (int)(g_playerManager->GetOnlinePlayerCount(false) * g_flLeaderVoteRatio) + 1;
- pPlayer->SetLeaderVoteTime(gpGlobals->curtime);
+ pPlayer->SetLeaderVoteTime(GetGlobals()->curtime);
if (iLeaderVoteCount + 1 >= iNeededLeaderVoteCount)
{
diff --git a/src/map_votes.cpp b/src/map_votes.cpp
index b29020e7..2ae60d10 100644
--- a/src/map_votes.cpp
+++ b/src/map_votes.cpp
@@ -23,22 +23,25 @@
#include "ctimer.h"
#include "entity/cgamerules.h"
#include "eventlistener.h"
-#include "idlemanager.h"
+#include "iserver.h"
#include "playermanager.h"
#include "steam/steam_gameserver.h"
#include "strtools.h"
#include "utlstring.h"
#include "utlvector.h"
+#undef snprintf
+#include "vendor/nlohmann/json.hpp"
#include "votemanager.h"
+#include
#include
+#include
#include
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern CCSGameRules* g_pGameRules;
extern IVEngineServer2* g_pEngineServer2;
extern CSteamGameServerAPIContext g_steamAPI;
extern IGameTypes* g_pGameTypes;
-extern CIdleSystem* g_pIdleSystem;
CMapVoteSystem* g_pMapVoteSystem = nullptr;
@@ -53,7 +56,11 @@ CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current
return;
}
- g_pMapVoteSystem->LoadMapList();
+ if (!g_pMapVoteSystem->LoadMapList() || !V_strcmp(g_pMapVoteSystem->GetCurrentMapName(), "MISSING_MAP"))
+ {
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Failed to reload map list!");
+ return;
+ }
// A CUtlStringList param is also expected, but we build it in our CreateWorkshopMapGroup pre-hook anyways
CALL_VIRTUAL(void, g_GameConfig->GetOffset("IGameTypes_CreateWorkshopMapGroup"), g_pGameTypes, "workshop");
@@ -63,197 +70,136 @@ CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current
if (g_pMapVoteSystem->GetCurrentWorkshopMap() != 0)
V_snprintf(sChangeMapCmd, sizeof(sChangeMapCmd), "host_workshop_map %llu", g_pMapVoteSystem->GetCurrentWorkshopMap());
- else if (g_pMapVoteSystem->GetCurrentMap()[0] != '\0')
- V_snprintf(sChangeMapCmd, sizeof(sChangeMapCmd), "map %s", g_pMapVoteSystem->GetCurrentMap());
+ else
+ V_snprintf(sChangeMapCmd, sizeof(sChangeMapCmd), "map %s", g_pMapVoteSystem->GetCurrentMapName());
g_pEngineServer2->ServerCommand(sChangeMapCmd);
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)
+CON_COMMAND_F(cs2f_vote_maps_cooldown, "Default number of hours until a map can be played again i.e. cooldown", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY)
{
- if (!g_pMapVoteSystem)
- {
- Message("The map vote subsystem is not enabled.\n");
- return;
- }
+ float fCurrentCooldown = g_pMapVoteSystem->GetDefaultMapCooldown();
if (args.ArgC() < 2)
- Message("%s %d\n", args[0], g_pMapVoteSystem->GetDefaultMapCooldown());
+ Msg("%s %f\n", args[0], fCurrentCooldown);
else
- {
- int iCurrentCooldown = g_pMapVoteSystem->GetDefaultMapCooldown();
- g_pMapVoteSystem->SetDefaultMapCooldown(V_StringToInt32(args[1], iCurrentCooldown));
- }
+ g_pMapVoteSystem->SetDefaultMapCooldown(V_StringToFloat32(args[1], fCurrentCooldown));
}
-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)
+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)
+ int iMaxNominatedMaps = g_pMapVoteSystem->GetMaxNominatedMaps();
+
+ if (args.ArgC() < 2)
+ Msg("%s %d\n", args[0], iMaxNominatedMaps);
+ else
{
- Message("The map vote subsystem is not enabled.\n");
- return;
+ int iValue = V_StringToInt32(args[1], iMaxNominatedMaps);
+
+ if (iValue < 0 || iValue > 10)
+ Msg("Value must be between 0-10!\n");
+ else
+ g_pMapVoteSystem->SetMaxNominatedMaps(iValue);
}
+}
+
+CON_COMMAND_F(cs2f_vote_max_maps, "Number of total maps to include per vote, including nominations, out of a maximum of 10", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY)
+{
+ int iMaxVoteMaps = g_pMapVoteSystem->GetMaxVoteMaps();
if (args.ArgC() < 2)
- Message("%s %d\n", args[0], g_pMapVoteSystem->GetMaxNominatedMaps());
+ Msg("%s %d\n", args[0], iMaxVoteMaps);
else
{
- int iMaxNominatedMaps = g_pMapVoteSystem->GetMaxNominatedMaps();
- g_pMapVoteSystem->SetMaxNominatedMaps(V_StringToInt32(args[1], iMaxNominatedMaps));
+ int iValue = V_StringToInt32(args[1], iMaxVoteMaps);
+
+ if (iValue < 2 || iValue > 10)
+ Msg("Value must be between 2-10!\n");
+ else
+ g_pMapVoteSystem->SetMaxVoteMaps(iValue);
}
}
-// TODO: workshop id support for rcon admins?
-CON_COMMAND_CHAT_FLAGS(setnextmap, "[mapname] - Force next map (empty to clear forced next map)", ADMFLAG_CHANGEMAP)
+CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP)
{
if (!g_bVoteManagerEnable)
return;
- int iPreviousNextMap = g_pMapVoteSystem->GetForcedNextMap();
- std::pair> response = g_pMapVoteSystem->ForceNextMap(args.ArgC() < 2 ? "" : args[1]);
-
- if (response.first == 0 && iPreviousNextMap == response.second[0])
+ if (args.ArgC() < 2)
{
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "\x06%s\x01 is already the next map!", g_pMapVoteSystem->GetMapName(iPreviousNextMap));
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Usage: !map ");
return;
}
- switch (response.first)
+ std::string sMapInput = g_pMapVoteSystem->StringToLower(args[1]);
+
+ for (int i = 0; sMapInput[i]; i++)
{
- 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]);
+ // Injection prevention, because we may pass user input to ServerCommand
+ if (sMapInput[i] == ';' || sMapInput[i] == '|')
+ return;
+ }
- for (int i = 0; i < response.second.size() && i < 5; i++)
- ClientPrint(player, HUD_PRINTTALK, "- %s", g_pMapVoteSystem->GetMapName(response.second[i]));
+ const char* pszMapInput = sMapInput.c_str();
- break;
- }
-}
+ if (g_pEngineServer2->IsMapValid(pszMapInput))
+ {
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to \x06%s\x01...", pszMapInput);
-static int __cdecl OrderStringsLexicographically(const MapIndexPair* a, const MapIndexPair* b)
-{
- return V_strcasecmp(a->name, b->name);
-}
+ new CTimer(5.0f, false, true, [sMapInput]() {
+ g_pEngineServer2->ChangeLevel(sMapInput.c_str(), nullptr);
+ return -1.0f;
+ });
-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]);
- ZEPlayer* pPlayer = g_playerManager->GetPlayer(player->GetPlayerSlot());
-
- if (!pPlayer)
- return;
-
- switch (response.first)
- {
- case NominationReturnCodes::VOTE_STARTED:
- 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 "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[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[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[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[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[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.");
- break;
- case NominationReturnCodes::NOMINATION_RESET:
- 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]);
+ std::string sCommand;
+ std::string sMapName;
+ uint64 iMap = g_pMapVoteSystem->HandlePlayerMapLookup(player, pszMapInput, true);
- for (int i = 0; i < response.second.size() && i < 5; i++)
- ClientPrint(player, HUD_PRINTTALK, "- %s", g_pMapVoteSystem->GetMapName(response.second[i]));
+ if (iMap == -1)
+ return;
- break;
- }
- case NominationReturnCodes::NOMINATION_RESET_FAILED:
- {
- 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;
+ if (iMap > g_pMapVoteSystem->GetMapListSize())
+ {
+ sCommand = "host_workshop_map " + std::to_string(iMap);
+ sMapName = std::to_string(iMap);
+ }
+ else
+ {
+ uint64 workshopId = g_pMapVoteSystem->GetMapWorkshopId(iMap);
+ sMapName = g_pMapVoteSystem->GetMapName(iMap);
- for (int i = 0; i < g_pMapVoteSystem->GetMapListSize(); i++)
- {
- if (!g_pMapVoteSystem->GetMapEnabledStatus(i))
- continue;
+ if (workshopId == 0)
+ sCommand = "map " + sMapName;
+ else
+ sCommand = "host_workshop_map " + std::to_string(workshopId);
+ }
- MapIndexPair map;
- map.name = g_pMapVoteSystem->GetMapName(i);
- map.index = i;
- vecMapNames.AddToTail(map);
- }
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to \x06%s\x01...", sMapName.c_str());
- vecMapNames.Sort(OrderStringsLexicographically);
+ new CTimer(5.0f, false, true, [sCommand]() {
+ g_pEngineServer2->ServerCommand(sCommand.c_str());
+ return -1.0f;
+ });
+}
- FOR_EACH_VEC(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);
- 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 (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);
- }
+CON_COMMAND_CHAT_FLAGS(setnextmap, "[name/id] - Force next map (empty to clear forced next map)", ADMFLAG_CHANGEMAP)
+{
+ if (!g_bVoteManagerEnable)
+ return;
- break;
- }
- case NominationReturnCodes::MAP_NOMINATED:
- {
- if (pPlayer->GetNominateTime() + 60.0f > gpGlobals->curtime)
- {
- int iRemainingTime = (int)(pPlayer->GetNominateTime() + 60.0f - gpGlobals->curtime);
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Wait %i seconds before you can nominate again.", iRemainingTime);
- return;
- }
- else
- {
- const char* sPlayerName = player->GetPlayerName();
- 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);
- }
+ g_pMapVoteSystem->ForceNextMap(player, args.ArgC() < 2 ? "" : args[1]);
+}
- break;
- }
- }
+CON_COMMAND_CHAT(nominate, "[mapname] - Nominate a map (empty to clear nomination or list all maps)")
+{
+ if (!g_bVoteManagerEnable || !player)
+ return;
+
+ g_pMapVoteSystem->AttemptNomination(player, args.ArgC() < 2 ? "" : args[1]);
}
CON_COMMAND_CHAT(nomlist, "- List the list of nominations")
@@ -261,15 +207,24 @@ CON_COMMAND_CHAT(nomlist, "- List the list of nominations")
if (!g_bVoteManagerEnable)
return;
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Current nominations:");
- for (int i = 0; i < g_pMapVoteSystem->GetMapListSize(); i++)
+ if (g_pMapVoteSystem->GetForcedNextMap() != -1)
{
- if (!g_pMapVoteSystem->IsMapIndexEnabled(i)) continue;
- int iNumNominations = g_pMapVoteSystem->GetTotalNominations(i);
- if (iNumNominations == 0) continue;
- const char* sMapName = g_pMapVoteSystem->GetMapName(i);
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "- %s (%d times)\n", sMapName, iNumNominations);
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Nominations are disabled because the next map has been forced to \x06%s\x01.", g_pMapVoteSystem->GetForcedNextMapName().c_str());
+ return;
}
+
+ std::unordered_map mapNominatedMaps = g_pMapVoteSystem->GetNominatedMaps();
+
+ if (mapNominatedMaps.size() == 0)
+ {
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "No maps have been nominated yet!");
+ return;
+ }
+
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Current nominations:");
+
+ for (auto pair : mapNominatedMaps)
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "- %s (%d times)\n", g_pMapVoteSystem->GetMapName(pair.first), pair.second);
}
CON_COMMAND_CHAT(mapcooldowns, "- List the maps currently in cooldown")
@@ -277,49 +232,61 @@ 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:");
+ // Use a new vector, because we want to sort the command output
+ std::vector> vecCooldowns;
- for (int iMapIndex = 0; iMapIndex < iMapCount; iMapIndex++)
+ for (std::shared_ptr pCooldown : g_pMapVoteSystem->GetMapCooldowns())
{
- int iCooldown = g_pMapVoteSystem->GetCooldownMap(iMapIndex);
+ // Only print maps that are added to maplist.cfg
+ if (pCooldown->IsOnCooldown() && g_pMapVoteSystem->GetMapIndexFromString(pCooldown->GetMapName()) != -1)
+ vecCooldowns.push_back(std::make_pair(pCooldown->GetMapName(), pCooldown->GetCurrentCooldown()));
+ }
- if (iCooldown > 0 && g_pMapVoteSystem->GetMapEnabledStatus(iMapIndex))
- vecCooldowns.push_back(std::make_pair(g_pMapVoteSystem->GetMapName(iMapIndex), iCooldown));
+ if (vecCooldowns.size() == 0)
+ {
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "There are no maps on cooldown!");
+ return;
}
- std::sort(vecCooldowns.begin(), vecCooldowns.end(), [](auto& left, auto& right) {
+ 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:");
+
+ 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);
+ ClientPrint(player, HUD_PRINTCONSOLE, "- %s (%s)", pair.first.c_str(), g_pMapVoteSystem->GetMapCooldownText(pair.first.c_str(), true).c_str());
}
-GAME_EVENT_F(cs_win_panel_match)
+CON_COMMAND_CHAT(nextmap, "- Check the next map if it was forced")
{
- if (g_bVoteManagerEnable && !g_pMapVoteSystem->IsVoteOngoing())
- g_pMapVoteSystem->StartVote();
+ if (!g_bVoteManagerEnable)
+ return;
+
+ if (g_pMapVoteSystem->GetForcedNextMap() == -1)
+ {
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Next map is pending vote, no map has been forced.");
+ return;
+ }
+
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Next map is \x06%s\x01.", g_pMapVoteSystem->GetForcedNextMapName().c_str());
}
-GAME_EVENT_F(endmatch_mapvote_selecting_map)
+CON_COMMAND_CHAT(maplist, "- List the maps in the server")
{
- if (g_bVoteManagerEnable)
- g_pMapVoteSystem->FinishVote();
+ g_pMapVoteSystem->PrintMapList(player);
}
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;
+ if (iMapIndex >= GetMapListSize() || iMapIndex < 0) return false;
+ if (GetMapCooldown(iMapIndex)->IsOnCooldown() || GetCurrentMapIndex() == iMapIndex) return false;
+ if (!m_vecMapList[iMapIndex]->IsEnabled()) return false;
int iOnlinePlayers = g_playerManager->GetOnlinePlayerCount(false);
- bool bMeetsMaxPlayers = iOnlinePlayers <= m_vecMapList[iMapIndex].GetMaxPlayers();
- bool bMeetsMinPlayers = iOnlinePlayers >= m_vecMapList[iMapIndex].GetMinPlayers();
+ bool bMeetsMaxPlayers = iOnlinePlayers <= GetMapMaxPlayers(iMapIndex);
+ bool bMeetsMinPlayers = iOnlinePlayers >= GetMapMinPlayers(iMapIndex);
return bMeetsMaxPlayers && bMeetsMinPlayers;
}
@@ -330,101 +297,135 @@ void CMapVoteSystem::OnLevelInit(const char* pMapName)
m_bIsVoteOngoing = false;
m_bIntermissionStarted = false;
- m_iForcedNextMapIndex = -1;
+ m_iForcedNextMap = -1;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < MAXPLAYERS; i++)
ClearPlayerInfo(i);
// Delay one tick to override any .cfg's
new CTimer(0.02f, false, true, []() {
g_pEngineServer2->ServerCommand("mp_match_end_changelevel 0");
+ g_pEngineServer2->ServerCommand("mp_endmatch_votenextmap 1");
return -1.0f;
});
-
- SetCurrentMapIndex(GetMapIndexFromString(pMapName));
}
void CMapVoteSystem::StartVote()
{
+ if (!g_pGameRules)
+ return;
+
m_bIsVoteOngoing = true;
- g_pIdleSystem->PauseIdleChecks();
+ // Select random maps that meet requirements to appear
+ std::vector vecPossibleMaps;
+ for (int i = 0; i < GetMapListSize(); i++)
+ if (IsMapIndexEnabled(i))
+ vecPossibleMaps.push_back(i);
- // Reset the player vote counts as the vote just started
- for (int i = 0; i < gpGlobals->maxClients; i++)
- m_arrPlayerVotes[i] = -1;
+ m_iVoteSize = std::min((int)vecPossibleMaps.size(), GetMaxVoteMaps());
+ bool bAbort = false;
+ // CONVAR_TODO
+ ConVar* pVoteCvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_endmatch_votenextmap"));
+ // HACK: values is actually the cvar value itself, hence this ugly cast.
+ bool bVoteEnabled = *(bool*)&pVoteCvar->values;
- // If we are forcing a map, just set all vote options to that map
- if (m_iForcedNextMapIndex != -1)
+ if (!bVoteEnabled)
+ {
+ m_bIsVoteOngoing = false;
+ bAbort = true;
+ }
+ else if (m_iForcedNextMap != -1)
{
- for (int i = 0; i < 10; i++)
- g_pGameRules->m_nEndMatchMapGroupVoteOptions[i] = m_iForcedNextMapIndex;
-
new CTimer(6.0f, false, true, []() {
g_pMapVoteSystem->FinishVote();
return -1.0f;
});
- return;
+ bAbort = true;
+ }
+ else if (m_iVoteSize < 2)
+ {
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Not enough maps available for map vote, aborting! Please have an admin loosen map limits.");
+ Message("Not enough maps available for map vote, aborting!\n");
+ g_pEngineServer2->ServerCommand("mp_match_end_changelevel 1"); // Allow game to auto-switch map again
+ m_bIsVoteOngoing = false;
+ bAbort = true;
}
- // Seed the randomness for the event
- m_iRandomWinnerShift = rand();
-
- // Select random maps not in cooldown, not disabled, and not nominated
- CUtlVector vecPossibleMaps;
- CUtlVector vecIncludedMaps;
- GetNominatedMapsForVote(vecIncludedMaps);
- for (int i = 0; i < m_vecMapList.Count(); i++)
+ if (bAbort)
{
- if (!IsMapIndexEnabled(i)) continue;
- if (vecIncludedMaps.HasElement(i)) continue;
- vecPossibleMaps.AddToTail(i);
+ // Disable the map vote
+ for (int i = 0; i < 10; i++)
+ {
+ g_pGameRules->m_nEndMatchMapGroupVoteTypes[i] = -1;
+ g_pGameRules->m_nEndMatchMapGroupVoteOptions[i] = -1;
+ }
+
+ return;
}
+ // We're checking this later, so we can always disable the map vote if mp_endmatch_votenextmap is disabled
+ if (!g_bVoteManagerEnable)
+ return;
+
+ // Reset the player vote counts as the vote just started
+ for (int i = 0; i < MAXPLAYERS; i++)
+ m_arrPlayerVotes[i] = -1;
+
// Print all available maps out to console
- FOR_EACH_VEC(vecPossibleMaps, i)
+ for (int i = 0; i < vecPossibleMaps.size(); i++)
{
int iPossibleMapIndex = vecPossibleMaps[i];
- Message("The %d-th possible map index %d is %s\n", i, iPossibleMapIndex, m_vecMapList[iPossibleMapIndex].GetName());
+ Message("The %d-th possible map index %d is %s\n", i, iPossibleMapIndex, GetMapName(iPossibleMapIndex));
}
- // Set the maps in the vote: merge nominated and possible maps, then randomly sort
- int iNumMapsInVote = vecPossibleMaps.Count() + vecIncludedMaps.Count();
- if (iNumMapsInVote >= 10) iNumMapsInVote = 10;
- while (vecIncludedMaps.Count() < iNumMapsInVote && vecPossibleMaps.Count() > 0)
+ // Seed the randomness for the event
+ m_iRandomWinnerShift = rand();
+
+ // Set the maps in the vote: merge nominated and random possible maps
+ std::vector vecIncludedMaps = GetNominatedMapsForVote();
+
+ while (vecIncludedMaps.size() < m_iVoteSize && vecPossibleMaps.size() > 0)
{
- int iMapToAdd = vecPossibleMaps[rand() % vecPossibleMaps.Count()];
- vecIncludedMaps.AddToTail(iMapToAdd);
- vecPossibleMaps.FindAndRemove(iMapToAdd);
+ int iMapToAdd = vecPossibleMaps[rand() % vecPossibleMaps.size()];
+
+ // Do we need to add this map? It may have already been included via nomination
+ if (std::find(vecIncludedMaps.begin(), vecIncludedMaps.end(), iMapToAdd) == vecIncludedMaps.end())
+ vecIncludedMaps.push_back(iMapToAdd);
+
+ std::erase_if(vecPossibleMaps, [iMapToAdd](int i) { return i == iMapToAdd; });
}
// Randomly sort the chosen maps
for (int i = 0; i < 10; i++)
{
- if (i < iNumMapsInVote)
+ if (i < m_iVoteSize)
{
- int iMapToAdd = vecIncludedMaps[rand() % vecIncludedMaps.Count()];
+ int iMapToAdd = vecIncludedMaps[rand() % vecIncludedMaps.size()];
+ g_pGameRules->m_nEndMatchMapGroupVoteTypes[i] = 0;
g_pGameRules->m_nEndMatchMapGroupVoteOptions[i] = iMapToAdd;
- vecIncludedMaps.FindAndRemove(iMapToAdd);
+ std::erase_if(vecIncludedMaps, [iMapToAdd](int i) { return i == iMapToAdd; });
}
else
{
+ g_pGameRules->m_nEndMatchMapGroupVoteTypes[i] = -1;
g_pGameRules->m_nEndMatchMapGroupVoteOptions[i] = -1;
}
}
// Print the maps chosen in the vote to console
- for (int i = 0; i < iNumMapsInVote; i++)
+ for (int i = 0; i < m_iVoteSize; i++)
{
int iMapIndex = g_pGameRules->m_nEndMatchMapGroupVoteOptions[i];
- Message("The %d-th chosen map index %d is %s\n", i, iMapIndex, m_vecMapList[iMapIndex].GetName());
+ Message("The %d-th chosen map index %d is %s\n", i, iMapIndex, GetMapName(iMapIndex));
}
// Start the end-of-vote timer to finish the vote
- ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_endmatch_votenextleveltime"));
- float flVoteTime = *(float*)&cvar->values;
+ // CONVAR_TODO
+ ConVar* pVoteTimeCvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_endmatch_votenextleveltime"));
+ float flVoteTime = *(float*)&pVoteTimeCvar->values;
new CTimer(flVoteTime, false, true, []() {
g_pMapVoteSystem->FinishVote();
return -1.0;
@@ -434,7 +435,11 @@ void CMapVoteSystem::StartVote()
int CMapVoteSystem::GetTotalNominations(int iMapIndex)
{
int iNumNominations = 0;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+
+ if (!GetGlobals())
+ return iNumNominations;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
auto pController = CCSPlayerController::FromSlot(i);
if (pController && pController->IsConnected() && m_arrPlayerNominations[i] == iMapIndex)
@@ -445,7 +450,8 @@ int CMapVoteSystem::GetTotalNominations(int iMapIndex)
void CMapVoteSystem::FinishVote()
{
- if (!m_bIsVoteOngoing) return;
+ if (!m_bIsVoteOngoing || !g_pGameRules)
+ return;
// Clean up the ongoing voting state and variables
m_bIsVoteOngoing = false;
@@ -453,26 +459,31 @@ void CMapVoteSystem::FinishVote()
// Get the winning map
bool bIsNextMapVoted = UpdateWinningMap();
int iNextMapVoteIndex = WinningMapIndex();
+ bool bIsNextMapForced = m_iForcedNextMap != -1;
+ char buffer[256];
+ uint64 iWinningMap; // Map index OR possibly workshop ID if next map was forced
- // If we are forcing the map, show different text
- bool bIsNextMapForced = m_iForcedNextMapIndex != -1;
if (bIsNextMapForced)
{
- iNextMapVoteIndex = 0;
- g_pGameRules->m_nEndMatchMapGroupVoteOptions[0] = m_iForcedNextMapIndex;
- g_pGameRules->m_nEndMatchMapVoteWinner = iNextMapVoteIndex;
+ iWinningMap = m_iForcedNextMap;
}
+ else
+ {
+ if (iNextMapVoteIndex == -1)
+ {
+ Panic("Failed to count map votes, file a bug\n");
+ iNextMapVoteIndex = 0;
+ }
- // Print out the winning map
- if (iNextMapVoteIndex < 0) iNextMapVoteIndex = -1;
- g_pGameRules->m_nEndMatchMapVoteWinner = iNextMapVoteIndex;
- int iWinningMap = g_pGameRules->m_nEndMatchMapGroupVoteOptions[iNextMapVoteIndex];
- char buffer[256];
+ g_pGameRules->m_nEndMatchMapVoteWinner = iNextMapVoteIndex;
+ iWinningMap = g_pGameRules->m_nEndMatchMapGroupVoteOptions[iNextMapVoteIndex];
+ }
- if (bIsNextMapVoted)
+ // Print out the map we're changing to
+ if (bIsNextMapForced)
+ V_snprintf(buffer, sizeof(buffer), "The vote was overriden. \x06%s\x01 will be the next map!\n", GetForcedNextMapName().c_str());
+ else if (bIsNextMapVoted)
V_snprintf(buffer, sizeof(buffer), "The vote has ended. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap));
- else if (bIsNextMapForced)
- V_snprintf(buffer, sizeof(buffer), "The vote was overriden. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap));
else
V_snprintf(buffer, sizeof(buffer), "No map was chosen. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap));
@@ -480,36 +491,28 @@ void CMapVoteSystem::FinishVote()
Message(buffer);
// Print vote result information: how many votes did each map get?
- int arrMapVotes[10] = {0};
- Message("Map vote result --- total votes per map:\n");
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!bIsNextMapForced && GetGlobals())
{
- auto pController = CCSPlayerController::FromSlot(i);
- int iPlayerVotedIndex = m_arrPlayerVotes[i];
- if (pController && pController->IsConnected() && iPlayerVotedIndex >= 0)
- arrMapVotes[iPlayerVotedIndex]++;
- }
- for (int i = 0; i < 10; i++)
- {
- int iMapIndex = g_pGameRules->m_nEndMatchMapGroupVoteOptions[i];
- const char* sIsWinner = (i == iNextMapVoteIndex) ? "(WINNER)" : "";
- Message("- %s got %d votes\n", GetMapName(iMapIndex), arrMapVotes[i]);
+ int arrMapVotes[10] = {0};
+ Message("Map vote result --- total votes per map:\n");
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
+ {
+ auto pController = CCSPlayerController::FromSlot(i);
+ int iPlayerVotedIndex = m_arrPlayerVotes[i];
+ if (pController && pController->IsConnected() && iPlayerVotedIndex >= 0)
+ arrMapVotes[iPlayerVotedIndex]++;
+ }
+ for (int i = 0; i < m_iVoteSize; i++)
+ {
+ int iMapIndex = g_pGameRules->m_nEndMatchMapGroupVoteOptions[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
- // 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();
-
// Wait a second and force-change the map
new CTimer(1.0, false, true, [iWinningMap]() {
char sChangeMapCmd[128];
- uint64 workshopId = g_pMapVoteSystem->GetMapWorkshopId(iWinningMap);
+ uint64 workshopId = iWinningMap > g_pMapVoteSystem->GetMapListSize() ? iWinningMap : g_pMapVoteSystem->GetMapWorkshopId(iWinningMap);
if (workshopId == 0)
V_snprintf(sChangeMapCmd, sizeof(sChangeMapCmd), "map %s", g_pMapVoteSystem->GetMapName(iWinningMap));
@@ -524,12 +527,12 @@ void CMapVoteSystem::FinishVote()
bool CMapVoteSystem::RegisterPlayerVote(CPlayerSlot iPlayerSlot, int iVoteOption)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(iPlayerSlot);
- if (!pController || !m_bIsVoteOngoing) return false;
- if (iVoteOption < 0 || iVoteOption >= 10) return false;
+ if (!pController || !m_bIsVoteOngoing || !g_pGameRules) return false;
+ if (iVoteOption < 0 || iVoteOption >= m_iVoteSize) return false;
// Filter out votes on invalid maps
int iMapIndexToVote = g_pGameRules->m_nEndMatchMapGroupVoteOptions[iVoteOption];
- if (iMapIndexToVote < 0 || iMapIndexToVote >= m_vecMapList.Count()) return false;
+ if (iMapIndexToVote < 0 || iMapIndexToVote >= GetMapListSize()) return false;
// Set the vote for the player
int iSlot = pController->GetPlayerSlot();
@@ -549,7 +552,7 @@ bool CMapVoteSystem::RegisterPlayerVote(CPlayerSlot iPlayerSlot, int iVoteOption
bool CMapVoteSystem::UpdateWinningMap()
{
int iWinningMapIndex = WinningMapIndex();
- if (iWinningMapIndex >= 0)
+ if (iWinningMapIndex >= 0 && g_pGameRules)
{
g_pGameRules->m_nEndMatchMapVoteWinner = iWinningMapIndex;
return true;
@@ -559,9 +562,12 @@ bool CMapVoteSystem::UpdateWinningMap()
int CMapVoteSystem::WinningMapIndex()
{
+ if (!GetGlobals())
+ return -1;
+
// Count the votes of every player
int arrMapVotes[10] = {0};
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
auto pController = CCSPlayerController::FromSlot(i);
if (pController && pController->IsConnected() && m_arrPlayerVotes[i] >= 0)
@@ -570,18 +576,18 @@ int CMapVoteSystem::WinningMapIndex()
// Identify the max. number of votes
int iMaxVotes = 0;
- for (int i = 0; i < 10; i++)
+ for (int i = 0; i < m_iVoteSize; i++)
iMaxVotes = (arrMapVotes[i] > iMaxVotes) ? arrMapVotes[i] : iMaxVotes;
// Identify how many maps are tied with the max number of votes
int iMapsWithMaxVotes = 0;
- for (int i = 0; i < 10; i++)
+ for (int i = 0; i < m_iVoteSize; i++)
if (arrMapVotes[i] == iMaxVotes) iMapsWithMaxVotes++;
// Break ties: 'random' map with the most votes
int iWinningMapTieBreak = m_iRandomWinnerShift % iMapsWithMaxVotes;
int iWinningMapCount = 0;
- for (int i = 0; i < 10; i++)
+ for (int i = 0; i < m_iVoteSize; i++)
{
if (arrMapVotes[i] == iMaxVotes)
{
@@ -592,54 +598,145 @@ int CMapVoteSystem::WinningMapIndex()
return -1;
}
-void CMapVoteSystem::GetNominatedMapsForVote(CUtlVector& vecChosenNominatedMaps)
+std::unordered_map CMapVoteSystem::GetNominatedMaps()
{
- int iNumDistinctMaps = 0;
- CUtlVector vecAvailableNominatedMaps;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ std::unordered_map mapNominatedMaps;
+
+ if (!GetGlobals())
+ return mapNominatedMaps;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
+ CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
int iNominatedMapIndex = m_arrPlayerNominations[i];
// Introduce nominated map indexes and count the total number
- if (iNominatedMapIndex != -1)
+ if (iNominatedMapIndex != -1 && pController && pController->IsConnected() && IsMapIndexEnabled(iNominatedMapIndex))
+ ++mapNominatedMaps[iNominatedMapIndex];
+ }
+
+ return mapNominatedMaps;
+}
+
+std::vector CMapVoteSystem::GetNominatedMapsForVote()
+{
+ std::unordered_map mapOriginalNominatedMaps = GetNominatedMaps(); // Original nominations map
+ std::unordered_map mapAvailableNominatedMaps(mapOriginalNominatedMaps); // A copy of the map that we can remove from without worry
+ std::vector vecTiedNominations; // Nominations with tied nom counts
+ std::vector vecChosenNominatedMaps; // Final vector of chosen nominations
+ int iMapsToIncludeInNominate = std::min({(int)mapOriginalNominatedMaps.size(), GetMaxNominatedMaps(), GetMaxVoteMaps()});
+ int iMostNominations;
+ auto rng = std::default_random_engine{std::random_device{}()};
+
+ // Select top maps by number of nominations
+ while (vecChosenNominatedMaps.size() < iMapsToIncludeInNominate)
+ {
+ if (vecTiedNominations.size() == 0)
{
- if (!vecAvailableNominatedMaps.HasElement(iNominatedMapIndex))
- iNumDistinctMaps++;
- vecAvailableNominatedMaps.AddToTail(iNominatedMapIndex);
+ // Find highest nomination count
+ iMostNominations = std::max_element(
+ mapAvailableNominatedMaps.begin(), mapAvailableNominatedMaps.end(),
+ [](const std::pair& p1, const std::pair& p2) {
+ return p1.second < p2.second;
+ })
+ ->second;
+
+ // Copy the most nominated maps to a new vector
+ for (auto pair : mapAvailableNominatedMaps)
+ if (pair.second == iMostNominations)
+ vecTiedNominations.push_back(pair.first);
+
+ // Randomize the vector order
+ std::ranges::shuffle(vecTiedNominations, rng);
}
+
+ // Pick map from front of vector, and remove from both sources
+ vecChosenNominatedMaps.push_back(vecTiedNominations.front());
+ mapAvailableNominatedMaps.erase(vecTiedNominations.front());
+ vecTiedNominations.erase(vecTiedNominations.begin());
}
- // Randomly select maps out of the set of nominated maps
- // weighting by number of nominations, and returning a random order
- int iMapsToIncludeInNominate = (iNumDistinctMaps < m_iMaxNominatedMaps) ? iNumDistinctMaps : m_iMaxNominatedMaps;
- while (vecChosenNominatedMaps.Count() < iMapsToIncludeInNominate)
+ if (!GetGlobals())
+ return vecChosenNominatedMaps;
+
+ // Notify nomination owners about the state of their nominations
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
- int iMapToAdd = vecAvailableNominatedMaps[rand() % vecAvailableNominatedMaps.Count()];
- vecChosenNominatedMaps.AddToTail(iMapToAdd);
- while (vecAvailableNominatedMaps.HasElement(iMapToAdd))
- vecAvailableNominatedMaps.FindAndRemove(iMapToAdd);
+ int iNominatedMapIndex = m_arrPlayerNominations[i];
+ CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
+
+ if (!pController || !pController->IsConnected())
+ continue;
+
+ // Ignore unset nominations (negative index)
+ if (iNominatedMapIndex < 0)
+ continue;
+
+ int iNominations = mapOriginalNominatedMaps[iNominatedMapIndex];
+ // At this point, iMostNominations represents nomination count of last map to make the map vote
+ int iNominationsNeeded = iMostNominations - iNominations;
+
+ // Bad RNG, needed 1 more for guaranteed selection then
+ if (iNominationsNeeded == 0)
+ iNominationsNeeded = 1;
+
+ if (std::find(vecChosenNominatedMaps.begin(), vecChosenNominatedMaps.end(), iNominatedMapIndex) != vecChosenNominatedMaps.end())
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Your \x06%s\x01 nomination made it to the map vote with \x06%i nomination%s\x01.", GetMapName(iNominatedMapIndex), iNominations, iNominations > 1 ? "s" : "");
+ else
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Your \x06%s\x01 nomination failed to make the map vote, it needed \x06%i more nomination%s\x01 for a total of \x06%i nominations\x01.",
+ GetMapName(iNominatedMapIndex), iNominationsNeeded, iNominationsNeeded > 1 ? "s" : "", iNominations + iNominationsNeeded);
}
+
+ return vecChosenNominatedMaps;
}
std::vector CMapVoteSystem::GetMapIndexesFromSubstring(const char* sMapSubstring)
{
std::vector vecMaps;
- FOR_EACH_VEC(m_vecMapList, i)
- {
- if (V_stristr(m_vecMapList[i].GetName(), sMapSubstring))
+ for (int i = 0; i < GetMapListSize(); i++)
+ if (V_stristr(GetMapName(i), sMapSubstring))
vecMaps.push_back(i);
- }
return vecMaps;
}
-int CMapVoteSystem::GetMapIndexFromString(const char* sMapString)
+uint64 CMapVoteSystem::HandlePlayerMapLookup(CCSPlayerController* pController, const char* sMapSubstring, bool bAllowWorkshopID)
{
- FOR_EACH_VEC(m_vecMapList, i)
+ if (bAllowWorkshopID)
{
- if (!V_strcasecmp(m_vecMapList[i].GetName(), sMapString))
- return i;
+ uint64 iWorkshopID = V_StringToUint64(sMapSubstring, 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING);
+
+ // Check if input is numeric (workshop ID)
+ // Not safe to expose to all admins until crashing on failed workshop addon downloads is fixed
+ if ((!pController || pController->GetZEPlayer()->IsAdminFlagSet(ADMFLAG_RCON)) && iWorkshopID != 0)
+ {
+ // Try to get a head start on downloading the map if needed
+ g_steamAPI.SteamUGC()->DownloadItem(iWorkshopID, false);
+
+ return iWorkshopID;
+ }
+ }
+
+ std::vector foundIndexes = GetMapIndexesFromSubstring(sMapSubstring);
+
+ if (foundIndexes.size() > 0)
+ {
+ if (foundIndexes.size() > 1)
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Multiple maps matched \x06%s\x01, try being more specific:", sMapSubstring);
+
+ for (int i = 0; i < foundIndexes.size() && i < 5; i++)
+ ClientPrint(pController, HUD_PRINTTALK, "- %s", GetMapName(foundIndexes[i]));
+ }
+ else
+ {
+ return foundIndexes[0];
+ }
+ }
+ else
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to find a map matching \x06%s\x01.", sMapSubstring);
}
return -1;
@@ -654,102 +751,179 @@ void CMapVoteSystem::ClearPlayerInfo(int iSlot)
m_arrPlayerVotes[iSlot] = -1;
}
-std::pair> CMapVoteSystem::AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring)
+int CMapVoteSystem::GetMapIndexFromString(const char* pszMapString)
+{
+ for (int i = 0; i < GetMapListSize(); i++)
+ if (!V_strcasecmp(GetMapName(i), pszMapString))
+ return i;
+
+ return -1;
+}
+
+std::shared_ptr CMapVoteSystem::GetGroupFromString(const char* pszName)
{
- 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());
+ for (int i = 0; i < m_vecGroups.size(); i++)
+ if (!V_strcmp(m_vecGroups[i]->GetName(), pszName))
+ return m_vecGroups[i];
- int iSlot = iPlayerSlot.Get();
+ return nullptr;
+}
+
+void CMapVoteSystem::AttemptNomination(CCSPlayerController* pController, const char* sMapSubstring)
+{
+ int iSlot = pController->GetPlayerSlot();
+ ZEPlayer* pPlayer = g_playerManager->GetPlayer(iSlot);
+
+ if (!pPlayer || !GetGlobals())
+ return;
+
+ if (GetMaxNominatedMaps() == 0)
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Nominations are currently disabled.");
+ return;
+ }
+
+ if (GetForcedNextMap() != -1)
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Nominations are disabled because the next map has been forced to \x06%s\x01.", GetForcedNextMapName().c_str());
+ return;
+ }
+
+ if (IsVoteOngoing())
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Nominations are disabled because the vote has already started.");
+ return;
+ }
if (sMapSubstring[0] == '\0')
{
- // If we are resetting the nomination, return NOMINATION_RESET
if (m_arrPlayerNominations[iSlot] != -1)
{
- m_arrPlayerNominations[iSlot] = -1;
- return std::make_pair(NominationReturnCodes::NOMINATION_RESET, std::vector());
+ ClearPlayerInfo(iSlot);
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Your nomination was reset.");
}
else
{
- return std::make_pair(NominationReturnCodes::NOMINATION_RESET_FAILED, std::vector());
+ PrintMapList(pController);
}
- }
-
- // We are not reseting the nomination: is the map found? is it valid?
- std::vector foundIndexes = GetMapIndexesFromSubstring(sMapSubstring);
- int iOnlinePlayers = g_playerManager->GetOnlinePlayerCount(false);
- if (foundIndexes.size() == 0)
- return std::make_pair(NominationReturnCodes::MAP_NOT_FOUND, std::vector());
+ return;
+ }
- if (foundIndexes.size() > 1)
- return std::make_pair(NominationReturnCodes::MAP_MULTIPLE, foundIndexes);
+ int iFoundIndex = HandlePlayerMapLookup(pController, sMapSubstring);
+ int iPlayerCount = g_playerManager->GetOnlinePlayerCount(false);
- int iFoundIndex = foundIndexes[0];
+ if (iFoundIndex == -1)
+ return;
if (!GetMapEnabledStatus(iFoundIndex))
- return std::make_pair(NominationReturnCodes::MAP_DISABLED, foundIndexes);
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's disabled.", GetMapName(iFoundIndex));
+ return;
+ }
if (GetCurrentMapIndex() == iFoundIndex)
- return std::make_pair(NominationReturnCodes::MAP_CURRENT, foundIndexes);
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's already the current map!", GetMapName(iFoundIndex));
+ return;
+ }
- if (GetCooldownMap(iFoundIndex) > 0)
- return std::make_pair(NominationReturnCodes::MAP_COOLDOWN, foundIndexes);
+ if (GetMapCooldown(iFoundIndex)->IsOnCooldown())
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's on a %s cooldown.", GetMapName(iFoundIndex), GetMapCooldownText(iFoundIndex, false).c_str());
+ return;
+ }
- if (iOnlinePlayers < m_vecMapList[iFoundIndex].GetMinPlayers())
- return std::make_pair(NominationReturnCodes::MAP_MINPLAYERS, foundIndexes);
+ if (iPlayerCount < GetMapMinPlayers(iFoundIndex))
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i more players.", GetMapName(iFoundIndex), GetMapMinPlayers(iFoundIndex) - iPlayerCount);
+ return;
+ }
- if (iOnlinePlayers > m_vecMapList[iFoundIndex].GetMaxPlayers())
- return std::make_pair(NominationReturnCodes::MAP_MAXPLAYERS, foundIndexes);
+ if (iPlayerCount > GetMapMaxPlayers(iFoundIndex))
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i less players.", GetMapName(iFoundIndex), iPlayerCount - GetMapMaxPlayers(iFoundIndex));
+ return;
+ }
+
+ if (pPlayer->GetNominateTime() + 60.0f > GetGlobals()->curtime)
+ {
+ int iRemainingTime = (int)(pPlayer->GetNominateTime() + 60.0f - GetGlobals()->curtime);
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Wait %i seconds before you can nominate again.", iRemainingTime);
+ return;
+ }
m_arrPlayerNominations[iSlot] = iFoundIndex;
- return std::make_pair(NominationReturnCodes::MAP_NOMINATED, foundIndexes);
+ int iNominations = GetTotalNominations(iFoundIndex);
+
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01was nominated by %s. It now has %d nomination%s.", GetMapName(iFoundIndex), pController->GetPlayerName(), iNominations, iNominations > 1 ? "s" : "");
+ pPlayer->SetNominateTime(GetGlobals()->curtime);
+}
+
+void CMapVoteSystem::PrintMapList(CCSPlayerController* pController)
+{
+ std::vector> vecSortedMaps;
+ int iPlayerCount = g_playerManager->GetOnlinePlayerCount(false);
+
+ for (int i = 0; i < GetMapListSize(); i++)
+ if (GetMapEnabledStatus(i))
+ vecSortedMaps.push_back(std::make_pair(i, GetMapName(i)));
+
+ std::sort(vecSortedMaps.begin(), vecSortedMaps.end(), [](auto left, auto right) {
+ return V_strcasecmp(right.second.c_str(), left.second.c_str()) > 0;
+ });
+
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "The list of all maps will be shown in console.");
+ ClientPrint(pController, HUD_PRINTCONSOLE, "The list of all maps is:");
+
+ for (std::pair pair : vecSortedMaps)
+ {
+ int mapIndex = pair.first;
+ const char* name = pair.second.c_str();
+ int minPlayers = GetMapMinPlayers(mapIndex);
+ int maxPlayers = GetMapMaxPlayers(mapIndex);
+
+ if (mapIndex == GetCurrentMapIndex())
+ ClientPrint(pController, HUD_PRINTCONSOLE, "- %s - Current Map", name);
+ else if (GetMapCooldown(mapIndex)->IsOnCooldown())
+ ClientPrint(pController, HUD_PRINTCONSOLE, "- %s - Cooldown: %s", name, GetMapCooldownText(mapIndex, true).c_str());
+ else if (iPlayerCount < minPlayers)
+ ClientPrint(pController, HUD_PRINTCONSOLE, "- %s - +%d Players", name, minPlayers - iPlayerCount);
+ else if (iPlayerCount > maxPlayers)
+ ClientPrint(pController, HUD_PRINTCONSOLE, "- %s - -%d Players", name, iPlayerCount - maxPlayers);
+ else
+ ClientPrint(pController, HUD_PRINTCONSOLE, "- %s", name);
+ }
}
-std::pair> CMapVoteSystem::ForceNextMap(const char* sMapSubstring)
+void CMapVoteSystem::ForceNextMap(CCSPlayerController* pController, const char* sMapSubstring)
{
if (sMapSubstring[0] == '\0')
{
- if (m_iForcedNextMapIndex != -1)
+ if (GetForcedNextMap() == -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 std::make_pair(-2, std::vector());
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "There is no next map to reset!");
+ }
+ else
+ {
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01is no longer the forced next map.\n", GetForcedNextMapName().c_str());
+ m_iForcedNextMap = -1;
}
- return std::make_pair(-3, std::vector());
+ return;
}
- 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);
+ uint64 iFoundMap = HandlePlayerMapLookup(pController, sMapSubstring, true);
- int iFoundIndex = foundIndexes[0];
-
- if (m_iForcedNextMapIndex == iFoundIndex)
- return std::make_pair(0, foundIndexes);
+ if (GetForcedNextMap() == iFoundMap)
+ {
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "\x06%s\x01 is already the next map!", GetForcedNextMapName().c_str());
+ return;
+ }
// 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 std::make_pair(0, foundIndexes);
-}
-
-static int __cdecl OrderMapsByWorkshopId(const CMapInfo* a, const CMapInfo* b)
-{
- int valueA = a->GetWorkshopId();
- int valueB = b->GetWorkshopId();
-
- if (valueA < valueB)
- return -1;
- else if (valueA == valueB)
- return 0;
- else
- return 1;
+ m_iForcedNextMap = iFoundMap;
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01has been forced as the next map.\n", GetForcedNextMapName().c_str());
}
void CMapVoteSystem::PrintDownloadProgress()
@@ -800,15 +974,31 @@ bool CMapVoteSystem::LoadMapList()
{
// This is called when the Steam API is init'd, now is the time to register this
m_CallbackDownloadItemResult.Register(this, &CMapVoteSystem::OnMapDownloaded);
+ m_vecMapList.clear();
+ m_vecGroups.clear();
+ m_vecCooldowns.clear();
- m_vecMapList.Purge();
- KeyValues* pKV = new KeyValues("maplist");
- KeyValues::AutoDelete autoDelete(pKV);
+ const char* pszJsonPath = "addons/cs2fixes/configs/maplist.jsonc";
+ char szPath[MAX_PATH];
+ V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszJsonPath);
+ std::ifstream jsonFile(szPath);
- const char* pszPath = "addons/cs2fixes/configs/maplist.cfg";
- if (!pKV->LoadFromFile(g_pFullFileSystem, pszPath))
+ if (!jsonFile.is_open())
{
- Panic("Failed to load %s\n", pszPath);
+ if (!ConvertMapListKVToJSON())
+ {
+ Panic("Failed to open %s and convert KV1 maplist.cfg to JSON format, map list not loaded!\n", pszJsonPath);
+ return false;
+ }
+
+ jsonFile.open(szPath);
+ }
+
+ ordered_json jsonMaps = ordered_json::parse(jsonFile, nullptr, false, true);
+
+ if (jsonMaps.is_discarded())
+ {
+ Panic("Failed parsing JSON from %s, map list not loaded!\n", pszJsonPath);
return false;
}
@@ -819,44 +1009,56 @@ bool CMapVoteSystem::LoadMapList()
if (!pKVcooldowns->LoadFromFile(g_pFullFileSystem, pszCooldownFilePath))
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();
+ time_t timeCooldown = pKey->GetUint64();
- for (int i = 0; sMapName[i]; i++)
- sMapName[i] = tolower(sMapName[i]);
+ if (timeCooldown > std::time(0))
+ {
+ std::shared_ptr pCooldown = std::make_shared(pKey->GetName());
- mapCooldowns[sMapName] = iCooldown;
+ pCooldown->SetTimeCooldown(timeCooldown);
+ m_vecCooldowns.push_back(pCooldown);
+ }
}
- for (KeyValues* pKey = pKV->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey())
+ for (auto& [sSection, jsonSection] : jsonMaps.items())
{
- const char* pszName = pKey->GetName();
- std::string sName = pszName;
+ for (auto& [sEntry, jsonEntry] : jsonSection.items())
+ {
+ if (sSection == "Groups")
+ {
+ m_vecGroups.push_back(std::make_shared(sEntry, jsonEntry.value("enabled", true), jsonEntry.value("cooldown", 0.0f)));
+ }
+ else if (sSection == "Maps")
+ {
+ // Seems like uint64 needs special handling
+ uint64 iWorkshopId = 0;
+
+ if (jsonEntry.contains("workshop_id"))
+ iWorkshopId = jsonEntry["workshop_id"].get();
- for (int i = 0; sName[i]; i++)
- sName[i] = tolower(sName[i]);
+ bool bIsEnabled = jsonEntry.value("enabled", true);
+ int iMinPlayers = jsonEntry.value("min_players", 0);
+ int iMaxPlayers = jsonEntry.value("max_players", 64);
+ float fCooldown = jsonEntry.value("cooldown", 0.0f);
+ std::vector vecGroups;
- 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 = mapCooldowns[sName];
+ if (jsonEntry.contains("groups") && jsonEntry["groups"].size() > 0)
+ for (auto& [key, group] : jsonEntry["groups"].items())
+ vecGroups.push_back(group);
- if (iWorkshopId != 0)
- QueueMapDownload(iWorkshopId);
+ if (iWorkshopId != 0)
+ QueueMapDownload(iWorkshopId);
- // We just append the maps to the map list
- m_vecMapList.AddToTail(CMapInfo(pszName, iWorkshopId, bIsEnabled, iMinPlayers, iMaxPlayers, iBaseCooldown, iCurrentCooldown));
+ // We just append the maps to the map list
+ m_vecMapList.push_back(std::make_shared(sEntry, iWorkshopId, bIsEnabled, iMinPlayers, iMaxPlayers, fCooldown, vecGroups));
+ }
+ }
}
new CTimer(0.f, true, true, []() {
- if (g_pMapVoteSystem->m_DownloadQueue.Count() == 0)
+ if (g_pMapVoteSystem->GetDownloadQueueSize() == 0)
return -1.f;
g_pMapVoteSystem->PrintDownloadProgress();
@@ -864,18 +1066,22 @@ bool CMapVoteSystem::LoadMapList()
return 1.f;
});
- // Sort the map list by the workshop id
- m_vecMapList.Sort(OrderMapsByWorkshopId);
-
- // Print all the maps to show the order
- FOR_EACH_VEC(m_vecMapList, i)
+ // Print all the maps
+ for (int i = 0; i < GetMapListSize(); i++)
{
- CMapInfo map = m_vecMapList[i];
+ std::shared_ptr map = m_vecMapList[i];
+ std::string groups = "";
+
+ for (std::string group : map->GetGroups())
+ groups += group + ", ";
- if (map.GetWorkshopId() == 0)
- 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());
+ if (groups != "")
+ groups.erase(groups.length() - 2);
+
+ if (map->GetWorkshopId() == 0)
+ ConMsg("Map %d is %s, which is %s. MinPlayers: %d MaxPlayers: %d Custom Cooldown: %.2f Groups: %s\n", i, map->GetName(), map->IsEnabled() ? "enabled" : "disabled", map->GetMinPlayers(), map->GetMaxPlayers(), map->GetCustomCooldown(), groups.c_str());
else
- 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());
+ ConMsg("Map %d is %s with workshop id %llu, which is %s. MinPlayers: %d MaxPlayers: %d Custom Cooldown: %.2f Groups: %s\n", i, map->GetName(), map->GetWorkshopId(), map->IsEnabled() ? "enabled" : "disabled", map->GetMinPlayers(), map->GetMaxPlayers(), map->GetCustomCooldown(), groups.c_str());
}
m_bMapListLoaded = true;
@@ -907,15 +1113,6 @@ CUtlStringList CMapVoteSystem::CreateWorkshopMapGroup()
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");
@@ -923,17 +1120,9 @@ bool CMapVoteSystem::WriteMapCooldownsToFile()
const char* pszPath = "addons/cs2fixes/data/cooldowns.txt";
- FOR_EACH_VEC(m_vecMapList, i)
- {
- std::string mapName = m_vecMapList[i].GetName();
- const int mapCooldown = m_vecMapList[i].GetCooldown();
-
- for (int i = 0; mapName[i]; i++)
- mapName[i] = tolower(mapName[i]);
-
- if (mapCooldown > 0)
- pKV->AddInt(mapName.c_str(), mapCooldown);
- }
+ for (std::shared_ptr pCooldown : m_vecCooldowns)
+ if (pCooldown->GetTimeCooldown() > std::time(0))
+ pKV->AddUint64(pCooldown->GetMapName(), pCooldown->GetTimeCooldown());
if (!pKV->SaveToFile(g_pFullFileSystem, pszPath))
{
@@ -946,10 +1135,10 @@ bool CMapVoteSystem::WriteMapCooldownsToFile()
void CMapVoteSystem::ClearInvalidNominations()
{
- if (!g_bVoteManagerEnable || m_bIsVoteOngoing)
+ if (!g_bVoteManagerEnable || m_bIsVoteOngoing || !GetGlobals())
return;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
int iNominatedMapIndex = m_arrPlayerNominations[i];
@@ -968,4 +1157,278 @@ void CMapVoteSystem::ClearInvalidNominations()
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
+}
+
+void CMapVoteSystem::UpdateCurrentMapIndex()
+{
+ for (int i = 0; i < GetMapListSize(); i++)
+ {
+ if (!V_strcasecmp(GetMapName(i), GetCurrentMapName()) || (GetCurrentWorkshopMap() != 0 && GetCurrentWorkshopMap() == GetMapWorkshopId(i)))
+ {
+ m_iCurrentMapIndex = i;
+ return;
+ }
+ }
+
+ m_iCurrentMapIndex = -1;
+}
+
+void CMapVoteSystem::ApplyGameSettings(KeyValues* pKV)
+{
+ if (!g_bVoteManagerEnable)
+ return;
+
+ if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode"))
+ SetCurrentWorkshopMap(pKV->FindKey("launchoptions")->GetUint64("customgamemode"));
+ else
+ SetCurrentWorkshopMap(0);
+
+ if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname"))
+ SetCurrentMapName(pKV->FindKey("launchoptions")->GetString("levelname"));
+ else
+ SetCurrentMapName("MISSING_MAP");
+
+ UpdateCurrentMapIndex();
+ ProcessGroupCooldowns();
+}
+
+void CMapVoteSystem::OnLevelShutdown()
+{
+ // Put the map on cooldown as we transition to the next map
+ PutMapOnCooldown(GetCurrentMapName());
+
+ // Fully apply pending group cooldowns
+ for (std::shared_ptr pCooldown : m_vecCooldowns)
+ if (pCooldown->GetPendingCooldown() > 0.0f)
+ PutMapOnCooldown(pCooldown->GetMapName(), pCooldown->GetPendingCooldown());
+
+ WriteMapCooldownsToFile();
+}
+
+std::string CMapVoteSystem::ConvertFloatToString(float fValue, int precision)
+{
+ std::stringstream stream;
+ stream.precision(precision);
+ stream << std::fixed << fValue;
+ std::string str = stream.str();
+
+ // Ensure that there is a decimal point somewhere (there should be)
+ if (str.find('.') != std::string::npos)
+ {
+ // Remove trailing zeroes
+ str = str.substr(0, str.find_last_not_of('0') + 1);
+ // If the decimal point is now the last character, remove that as well
+ if (str.find('.') == str.size() - 1)
+ str = str.substr(0, str.size() - 1);
+ }
+
+ return str;
+}
+
+std::string CMapVoteSystem::StringToLower(std::string sValue)
+{
+ for (int i = 0; sValue[i]; i++)
+ sValue[i] = tolower(sValue[i]);
+
+ return sValue;
+}
+
+std::string CMapVoteSystem::GetMapCooldownText(const char* pszMapName, bool bPlural)
+{
+ std::shared_ptr pCooldown = GetMapCooldown(pszMapName);
+ float fValue;
+ std::string response;
+
+ if (pCooldown->GetCurrentCooldown() > 23.99f)
+ {
+ fValue = roundf(pCooldown->GetCurrentCooldown() / 24 * 100) / 100;
+ response = ConvertFloatToString(fValue, 2) + " day";
+ }
+ else if (pCooldown->GetCurrentCooldown() <= 0.995f)
+ {
+ fValue = roundf(pCooldown->GetCurrentCooldown() * 60);
+
+ // Rounding edge case
+ if (fValue == 0.0f)
+ fValue = 1.0f;
+
+ response = ConvertFloatToString(fValue, 0) + " minute";
+ }
+ else
+ {
+ fValue = roundf(pCooldown->GetCurrentCooldown() * 100) / 100;
+ response = ConvertFloatToString(fValue, 2) + " hour";
+ }
+
+ if (bPlural && fValue != 1.0f)
+ response += "s";
+
+ if (pCooldown->IsPending())
+ response += " pending";
+
+ return response;
+}
+
+void CMapVoteSystem::PutMapOnCooldown(const char* pszMapName, float fCooldown)
+{
+ if (g_bDisableCooldowns)
+ return;
+
+ int iMapIndex = GetMapIndexFromString(pszMapName);
+
+ // If custom cooldown wasn't passed, use the normal cooldown for this map
+ if (fCooldown == 0.0f)
+ {
+ if (iMapIndex != -1 && GetMapCustomCooldown(iMapIndex) != 0.0f)
+ fCooldown = GetMapCustomCooldown(iMapIndex);
+ else
+ fCooldown = GetDefaultMapCooldown();
+ }
+
+ time_t timeCooldown = std::time(0) + (time_t)(fCooldown * 60 * 60);
+ std::shared_ptr pCooldown = GetMapCooldown(pszMapName);
+
+ // Ensure we don't overwrite a longer cooldown
+ if (pCooldown->GetTimeCooldown() < timeCooldown)
+ {
+ pCooldown->SetTimeCooldown(timeCooldown);
+
+ // Pending cooldown should be invalidated at this point
+ pCooldown->SetPendingCooldown(0.0f);
+ }
+}
+
+void CMapVoteSystem::ProcessGroupCooldowns()
+{
+ int iCurrentMapIndex = GetCurrentMapIndex();
+
+ if (iCurrentMapIndex == -1)
+ return;
+
+ std::vector vecCurrentMapGroups = m_vecMapList[iCurrentMapIndex]->GetGroups();
+
+ for (std::string groupName : vecCurrentMapGroups)
+ {
+ std::shared_ptr pGroup = GetGroupFromString(groupName.c_str());
+
+ if (!pGroup)
+ {
+ Panic("Invalid group name %s defined for map %s\n", groupName.c_str(), GetMapName(iCurrentMapIndex));
+ continue;
+ }
+
+ if (!pGroup->IsEnabled())
+ continue;
+
+ // Check entire map list for other maps in this group, and give them the group cooldown (pending)
+ for (int i = 0; i < GetMapListSize(); i++)
+ {
+ if (iCurrentMapIndex != i && GetMapEnabledStatus(i) && m_vecMapList[i]->HasGroup(groupName))
+ {
+ float fCooldown = pGroup->GetCooldown() == 0.0f ? GetDefaultMapCooldown() : pGroup->GetCooldown();
+ std::shared_ptr pCooldown = GetMapCooldown(i);
+
+ // Ensure we don't overwrite a longer cooldown
+ if (pCooldown->GetPendingCooldown() < fCooldown)
+ pCooldown->SetPendingCooldown(fCooldown);
+ }
+ }
+ }
+}
+
+std::shared_ptr CMapVoteSystem::GetMapCooldown(const char* pszMapName)
+{
+ for (std::shared_ptr pCooldown : m_vecCooldowns)
+ if (!V_strcasecmp(pszMapName, pCooldown->GetMapName()))
+ return pCooldown;
+
+ // Never been on cooldown, create new object
+ std::shared_ptr pCooldown = std::make_shared(pszMapName);
+ m_vecCooldowns.push_back(pCooldown);
+
+ return pCooldown;
+}
+
+float CCooldown::GetCurrentCooldown()
+{
+ time_t timeCurrent = std::time(0);
+
+ // Use pending cooldown first if present
+ float fRemainingTime = GetPendingCooldown();
+
+ // Calculate decimal hours
+ float fCurrentRemainingTime = (GetTimeCooldown() - timeCurrent) / 60.0f / 60.0f;
+
+ // Check if current cooldown should override
+ if (GetTimeCooldown() > timeCurrent && fCurrentRemainingTime > fRemainingTime)
+ fRemainingTime = fCurrentRemainingTime;
+
+ return fRemainingTime;
+}
+
+// TODO: remove this once servers have been given at least a few months to update cs2fixes
+bool CMapVoteSystem::ConvertMapListKVToJSON()
+{
+ Message("Attempting to convert KV1 maplist.cfg to JSON format...\n");
+
+ const char* pszPath = "addons/cs2fixes/configs/maplist.cfg";
+
+ KeyValues* pKV = new KeyValues("maplist");
+ KeyValues::AutoDelete autoDelete(pKV);
+
+ if (!pKV->LoadFromFile(g_pFullFileSystem, pszPath))
+ {
+ Panic("Failed to load %s\n", pszPath);
+ return false;
+ }
+
+ ordered_json jsonMapList;
+
+ jsonMapList["Groups"] = ordered_json(ordered_json::value_t::object);
+
+ for (KeyValues* pKey = pKV->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey())
+ {
+ ordered_json jsonMap;
+
+ if (pKey->FindKey("enabled"))
+ jsonMap["enabled"] = pKey->GetBool("enabled");
+ if (pKey->FindKey("workshop_id"))
+ jsonMap["workshop_id"] = pKey->GetUint64("workshop_id");
+ if (pKey->FindKey("min_players"))
+ jsonMap["min_players"] = pKey->GetInt("min_players");
+ if (pKey->FindKey("max_players"))
+ jsonMap["max_players"] = pKey->GetInt("max_players");
+ if (pKey->FindKey("cooldown"))
+ jsonMap["cooldown"] = pKey->GetInt("cooldown");
+
+ jsonMapList["Maps"][pKey->GetName()] = jsonMap;
+ }
+
+ const char* pszJsonPath = "addons/cs2fixes/configs/maplist.jsonc";
+ const char* pszKVConfigRenamePath = "addons/cs2fixes/configs/maplist_old.cfg";
+ char szPath[MAX_PATH];
+ V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszJsonPath);
+ std::ofstream jsonFile(szPath);
+
+ if (!jsonFile.is_open())
+ {
+ Panic("Failed to open %s\n", pszJsonPath);
+ return false;
+ }
+
+ jsonFile << std::setfill('\t') << std::setw(1) << jsonMapList << std::endl;
+
+ char szKVRenamePath[MAX_PATH];
+ V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszPath);
+ V_snprintf(szKVRenamePath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszKVConfigRenamePath);
+
+ std::rename(szPath, szKVRenamePath);
+
+ // remove old cfg example if it exists
+ const char* pszKVExamplePath = "addons/cs2fixes/configs/maplist.cfg.example";
+ V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszKVExamplePath);
+ std::remove(szPath);
+
+ Message("Successfully converted KV1 maplist.cfg to JSON format at %s\n", pszJsonPath);
+ return true;
+}
diff --git a/src/map_votes.h b/src/map_votes.h
index f6373413..e858093e 100644
--- a/src/map_votes.h
+++ b/src/map_votes.h
@@ -19,74 +19,97 @@
#include "KeyValues.h"
#include "common.h"
+#include "entity/ccsplayercontroller.h"
#include "steam/isteamugc.h"
#include "steam/steam_api_common.h"
#include "utlqueue.h"
#include "utlstring.h"
#include "utlvector.h"
+#undef snprintf
+#include "vendor/nlohmann/json_fwd.hpp"
#include
#include
#include
-// Nomination constants, used as return codes for nomination commands
-#ifndef NOMINATION_CONSTANTS_H
- #define NOMINATION_CONSTANTS_H
-namespace NominationReturnCodes
-{
- static const int VOTE_STARTED = -100;
- 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_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;
-} // namespace NominationReturnCodes
-#endif
-
-class CMapInfo
+using ordered_json = nlohmann::ordered_json;
+
+class CMap
{
public:
- CMapInfo(const char* pszName, uint64 iWorkshopId, bool bIsEnabled, int iMinPlayers, int iMaxPlayers, int iBaseCooldown, int iCurrentCooldown)
+ CMap(std::string sName, uint64 iWorkshopId, bool bIsEnabled, int iMinPlayers, int iMaxPlayers, float fCustomCooldown, std::vector vecGroups)
{
- V_strcpy(m_pszName, pszName);
+ m_strName = sName;
m_iWorkshopId = iWorkshopId;
m_bIsEnabled = bIsEnabled;
- m_iBaseCooldown = iBaseCooldown;
- m_iCurrentCooldown = iCurrentCooldown;
+ m_fCustomCooldown = fCustomCooldown;
m_iMinPlayers = iMinPlayers;
m_iMaxPlayers = iMaxPlayers;
+ m_vecGroups = vecGroups;
}
- const char* GetName() { return (const char*)m_pszName; };
+ const char* GetName() { return m_strName.c_str(); };
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)); }
+ float GetCustomCooldown() { return m_fCustomCooldown; };
int GetMinPlayers() { return m_iMinPlayers; };
int GetMaxPlayers() { return m_iMaxPlayers; };
+ std::vector GetGroups() { return m_vecGroups; };
+ bool HasGroup(std::string strGroup) { return std::find(m_vecGroups.begin(), m_vecGroups.end(), strGroup) != m_vecGroups.end(); };
private:
- char m_pszName[64];
+ std::string m_strName;
uint64 m_iWorkshopId;
bool m_bIsEnabled;
int m_iMinPlayers;
int m_iMaxPlayers;
- int m_iBaseCooldown;
- int m_iCurrentCooldown;
+ float m_fCustomCooldown;
+ std::vector m_vecGroups;
};
-typedef struct
+class CGroup
{
- const char* name;
- int index;
-} MapIndexPair;
+public:
+ CGroup(std::string sName, bool bIsEnabled, float fCooldown)
+ {
+ m_strName = sName;
+ m_bIsEnabled = bIsEnabled;
+ m_fCooldown = fCooldown;
+ }
+
+ const char* GetName() { return m_strName.c_str(); };
+ bool IsEnabled() { return m_bIsEnabled; };
+ float GetCooldown() { return m_fCooldown; };
+
+private:
+ std::string m_strName;
+ bool m_bIsEnabled;
+ float m_fCooldown;
+};
+
+class CCooldown
+{
+public:
+ CCooldown(std::string sMapName)
+ {
+ m_strMapName = sMapName;
+ m_timeCooldown = 0;
+ m_fPendingCooldown = 0.0f;
+ }
+
+ const char* GetMapName() { return m_strMapName.c_str(); };
+ time_t GetTimeCooldown() { return m_timeCooldown; };
+ void SetTimeCooldown(time_t timeCooldown) { m_timeCooldown = timeCooldown; };
+ float GetPendingCooldown() { return m_fPendingCooldown; };
+ void SetPendingCooldown(float fPendingCooldown) { m_fPendingCooldown = fPendingCooldown; };
+ bool IsOnCooldown() { return GetCurrentCooldown() > 0.0f; }
+ bool IsPending() { return m_fPendingCooldown > 0.0f && m_fPendingCooldown == GetCurrentCooldown(); };
+ float GetCurrentCooldown();
+
+private:
+ std::string m_strMapName;
+ time_t m_timeCooldown;
+ float m_fPendingCooldown;
+};
class CMapVoteSystem
{
@@ -106,19 +129,27 @@ class CMapVoteSystem
void FinishVote();
bool RegisterPlayerVote(CPlayerSlot iPlayerSlot, int iVoteOption);
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();
+ uint64 HandlePlayerMapLookup(CCSPlayerController* pController, const char* sMapSubstring, bool bAllowWorkshopID = false);
+ int GetMapIndexFromString(const char* pszMapString);
+ std::shared_ptr GetGroupFromString(const char* pszName);
+ std::shared_ptr GetMapCooldown(const char* pszMapName);
+ std::shared_ptr GetMapCooldown(int iMapIndex) { return GetMapCooldown(GetMapName(iMapIndex)); };
+ std::string GetMapCooldownText(const char* pszMapName, bool bPlural);
+ std::string GetMapCooldownText(int iMapIndex, bool bPlural) { return GetMapCooldownText(GetMapName(iMapIndex), bPlural); };
+ float GetMapCustomCooldown(int iMapIndex) { return m_vecMapList[iMapIndex]->GetCustomCooldown(); };
+ void PutMapOnCooldown(const char* pszMapName, float fCooldown = 0.0f);
void SetMaxNominatedMaps(int iMaxNominatedMaps) { m_iMaxNominatedMaps = iMaxNominatedMaps; };
int GetMaxNominatedMaps() { return m_iMaxNominatedMaps; };
- std::pair> AddMapNomination(CPlayerSlot iPlayerSlot, const char* sMapSubstring);
+ void SetMaxVoteMaps(int iMaxVoteMaps) { m_iMaxVoteMaps = iMaxVoteMaps; };
+ int GetMaxVoteMaps() { return m_iMaxVoteMaps; };
+ void AttemptNomination(CCSPlayerController* pController, const char* sMapSubstring);
+ void PrintMapList(CCSPlayerController* pController);
bool IsMapIndexEnabled(int iMapIndex);
int GetTotalNominations(int iMapIndex);
- 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(); };
+ void ForceNextMap(CCSPlayerController* pController, const char* sMapSubstring);
+ int GetMapListSize() { return m_vecMapList.size(); };
+ const char* GetMapName(int iMapIndex) { return m_vecMapList[iMapIndex]->GetName(); };
+ uint64 GetMapWorkshopId(int iMapIndex) { return m_vecMapList[iMapIndex]->GetWorkshopId(); };
void ClearPlayerInfo(int iSlot);
bool IsVoteOngoing() { return m_bIsVoteOngoing; }
bool IsIntermissionAllowed();
@@ -126,51 +157,58 @@ class CMapVoteSystem
CUtlStringList CreateWorkshopMapGroup();
void QueueMapDownload(PublishedFileId_t iWorkshopId);
void PrintDownloadProgress();
- void SetCurrentWorkshopMap(uint64 iCurrentWorkshopMap)
- {
- m_iCurrentWorkshopMap = iCurrentWorkshopMap;
- m_strCurrentMap = "";
- }
- void SetCurrentMap(std::string strCurrentMap)
- {
- m_iCurrentWorkshopMap = 0;
- m_strCurrentMap = strCurrentMap;
- }
+ void SetCurrentMapName(const char* pszCurrentMap) { m_strCurrentMap = pszCurrentMap; }
+ const char* GetCurrentMapName() { return m_strCurrentMap.c_str(); }
+ void SetCurrentWorkshopMap(uint64 iCurrentWorkshopMap) { m_iCurrentWorkshopMap = iCurrentWorkshopMap; }
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; }
- 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; }
+ void UpdateCurrentMapIndex();
+ 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(); }
+ float GetDefaultMapCooldown() { return m_fDefaultMapCooldown; }
+ void SetDefaultMapCooldown(float fMapCooldown) { m_fDefaultMapCooldown = fMapCooldown; }
void ClearInvalidNominations();
- int GetForcedNextMap() { return m_iForcedNextMapIndex; }
+ uint64 GetForcedNextMap() { return m_iForcedNextMap; }
+ std::string GetForcedNextMapName() { return GetForcedNextMap() > GetMapListSize() ? std::to_string(GetForcedNextMap()) : GetMapName(GetForcedNextMap()); }
+ bool ConvertMapListKVToJSON();
+ std::unordered_map GetNominatedMaps();
+ void ApplyGameSettings(KeyValues* pKV);
+ void OnLevelShutdown();
+ std::vector> GetMapCooldowns() { return m_vecCooldowns; }
+ std::string ConvertFloatToString(float fValue, int precision);
+ std::string StringToLower(std::string sValue);
+ void SetDisabledCooldowns(bool bValue) { g_bDisableCooldowns = bValue; } // Can be used by custom fork features, e.g. an auto-restart
+ void ProcessGroupCooldowns();
private:
int WinningMapIndex();
bool UpdateWinningMap();
- void GetNominatedMapsForVote(CUtlVector& vecChosenNominatedMaps);
+ std::vector GetNominatedMapsForVote();
bool WriteMapCooldownsToFile();
STEAM_GAMESERVER_CALLBACK_MANUAL(CMapVoteSystem, OnMapDownloaded, DownloadItemResult_t, m_CallbackDownloadItemResult);
CUtlQueue m_DownloadQueue;
- CUtlVector m_vecMapList;
+ std::vector> m_vecMapList;
+ std::vector> m_vecGroups;
+ std::vector> m_vecCooldowns;
int m_arrPlayerNominations[MAXPLAYERS];
- int m_iForcedNextMapIndex = -1;
- int m_iDefaultMapCooldown = 10;
+ uint64 m_iForcedNextMap = -1; // Can be a map index or a workshop ID
+ float m_fDefaultMapCooldown = 6.0f;
int m_iMaxNominatedMaps = 10;
+ int m_iMaxVoteMaps = 10;
int m_iRandomWinnerShift = 0;
int m_arrPlayerVotes[MAXPLAYERS];
- int m_iCurrentMapIndex;
+ int m_iCurrentMapIndex = -1;
bool m_bIsVoteOngoing = false;
bool m_bMapListLoaded = false;
bool m_bIntermissionStarted = false;
uint64 m_iCurrentWorkshopMap = 0;
- std::string m_strCurrentMap = "";
+ std::string m_strCurrentMap = "MISSING_MAP";
+ int m_iVoteSize = 0;
+ bool g_bDisableCooldowns = false;
};
extern CMapVoteSystem* g_pMapVoteSystem;
\ No newline at end of file
diff --git a/src/netmessages.h b/src/netmessages.h
index f0dc9762..9aed1732 100644
--- a/src/netmessages.h
+++ b/src/netmessages.h
@@ -9,6 +9,8 @@
#include
#include
#include
+#undef min
+#undef max
class CNETMsg_Tick_t : public CNetMessagePB
{
diff --git a/src/playermanager.cpp b/src/playermanager.cpp
index ade32224..2d7e3f65 100644
--- a/src/playermanager.cpp
+++ b/src/playermanager.cpp
@@ -34,13 +34,14 @@
#include "user_preferences.h"
#include "utils/entity.h"
#include "utlstring.h"
+#include "votemanager.h"
#include <../cs2fixes.h>
#include "tier0/memdbgon.h"
extern IVEngineServer2* g_pEngineServer2;
extern CGameEntitySystem* g_pEntitySystem;
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern IGameEventSystem* g_gameEventSystem;
extern CUtlVector* GetClientList();
@@ -245,9 +246,10 @@ FAKE_STRING_CVAR(cs2f_beacon_particle, ".vpcf file to be precached and used for
bool ZEPlayer::IsFlooding()
{
- if (m_bGagged) return false;
+ if (m_bGagged || !GetGlobals())
+ return false;
- float time = gpGlobals->curtime;
+ float time = GetGlobals()->curtime;
float newTime = time + g_flFloodInterval;
if (m_flLastTalkTime >= time)
@@ -644,6 +646,12 @@ void CPlayerManager::OnClientDisconnect(CPlayerSlot slot)
g_pMapVoteSystem->ClearPlayerInfo(slot.Get());
g_pMapVoteSystem->ClearInvalidNominations();
+ // One tick delay, to ensure player count decrements
+ new CTimer(0.01f, false, true, []() {
+ g_pVoteManager->CheckRTVStatus();
+ return -1.0f;
+ });
+
g_pPanoramaVoteHandler->RemovePlayerFromVote(slot.Get());
}
@@ -656,7 +664,10 @@ void CPlayerManager::OnClientPutInServer(CPlayerSlot slot)
void CPlayerManager::OnLateLoad()
{
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
@@ -731,7 +742,10 @@ void CPlayerManager::OnValidateAuthTicket(ValidateAuthTicketResponse_t* pRespons
void CPlayerManager::CheckInfractions()
{
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
if (m_vecPlayers[i] == nullptr || m_vecPlayers[i]->IsFakeClient())
continue;
@@ -748,12 +762,12 @@ FAKE_BOOL_CVAR(cs2f_flashlight_enable, "Whether to enable flashlights", g_bFlash
void CPlayerManager::FlashLightThink()
{
- if (!g_bFlashLightEnable)
+ if (!g_bFlashLightEnable || !GetGlobals())
return;
VPROF("CPlayerManager::FlashLightThink");
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(i);
@@ -774,12 +788,12 @@ FAKE_BOOL_CVAR(cs2f_hide_teammates_only, "Whether to hide teammates only", g_bHi
void CPlayerManager::CheckHideDistances()
{
- if (!g_pEntitySystem)
+ if (!g_pEntitySystem || !GetGlobals())
return;
VPROF("CPlayerManager::CheckHideDistances");
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
auto player = GetPlayer(i);
@@ -805,7 +819,7 @@ void CPlayerManager::CheckHideDistances()
auto vecPosition = pPawn->GetAbsOrigin();
int team = pController->m_iTeamNum;
- for (int j = 0; j < gpGlobals->maxClients; j++)
+ for (int j = 0; j < GetGlobals()->maxClients; j++)
{
if (j == i)
continue;
@@ -840,7 +854,10 @@ extern bool g_bEnableHide;
void CPlayerManager::UpdatePlayerStates()
{
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = GetPlayer(i);
@@ -880,12 +897,12 @@ FAKE_BOOL_CVAR(cs2f_infinite_reserve_ammo, "Whether to enable infinite reserve a
void CPlayerManager::SetupInfiniteAmmo()
{
new CTimer(5.0f, false, true, []() {
- if (!g_bInfiniteAmmo)
+ if (!g_bInfiniteAmmo || !GetGlobals())
return 5.0f;
VPROF("CPlayerManager::InfiniteAmmoTimer");
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
@@ -968,6 +985,9 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer,
int& iNumClients, int* rgiClients, uint64 iBlockedFlags,
ETargetType& nType)
{
+ if (!GetGlobals())
+ return ETargetError::INVALID;
+
nType = ETargetType::NONE;
ZEPlayer* zpPlayer = pPlayer ? pPlayer->GetZEPlayer() : nullptr;
bool bTargetMultiple = false;
@@ -1215,7 +1235,7 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer,
}
else if (bTargetMultiple)
{
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
if (m_vecPlayers[i] == nullptr)
continue;
@@ -1232,7 +1252,7 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer,
while (iNumClients == 0 && iAttempts < 10000)
{
- int iSlot = rand() % (gpGlobals->maxClients - 1);
+ int iSlot = rand() % (GetGlobals()->maxClients - 1);
// Prevent infinite loop
iAttempts++;
@@ -1255,7 +1275,7 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer,
while (iNumClients == 0 && iAttempts < 10000)
{
- int iSlot = rand() % (gpGlobals->maxClients - 1);
+ int iSlot = rand() % (GetGlobals()->maxClients - 1);
// Prevent infinite loop
iAttempts++;
@@ -1276,7 +1296,7 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer,
if (pRandomPlayer == nullptr)
return ETargetError::INVALID;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
if (m_vecPlayers[i] == nullptr)
continue;
@@ -1312,7 +1332,7 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer,
CBaseEntity* entTarget = nullptr;
entTarget = UTIL_FindPickerEntity(pPlayer);
- if (!entTarget->IsPawn())
+ if (!entTarget || !entTarget->IsPawn())
return ETargetError::INVALID;
CCSPlayerController* pAimed = CCSPlayerController::FromPawn(static_cast(entTarget));
@@ -1323,7 +1343,7 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer,
nType = ETargetType::ALL_BUT_AIM;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
if (m_vecPlayers[i] == nullptr)
continue;
@@ -1376,7 +1396,7 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer,
if (bExactName)
pszTarget++;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
if (m_vecPlayers[i] == nullptr)
continue;
@@ -1497,7 +1517,7 @@ bool CPlayerManager::CanTargetPlayers(CCSPlayerController* pPlayer, const char*
ZEPlayer* CPlayerManager::GetPlayer(CPlayerSlot slot)
{
- if (slot.Get() < 0 || slot.Get() >= gpGlobals->maxClients)
+ if (slot.Get() < 0 || slot.Get() >= MAXPLAYERS)
return nullptr;
return m_vecPlayers[slot.Get()];
@@ -1513,7 +1533,7 @@ ZEPlayer* CPlayerManager::GetPlayerFromUserId(uint16 userid)
{
uint8 index = userid & 0xFF;
- if (index >= gpGlobals->maxClients)
+ if (index >= MAXPLAYERS)
return nullptr;
return m_vecPlayers[index];
@@ -1606,6 +1626,9 @@ int CPlayerManager::GetOnlinePlayerCount(bool bCountBots)
{
int iOnlinePlayers = 0;
+ if (!GetClientList())
+ return iOnlinePlayers;
+
for (int i = 0; i < GetClientList()->Count(); i++)
{
CServerSideClient* pClient = (*GetClientList())[i];
diff --git a/src/serversideclient.h b/src/serversideclient.h
index 2b369fd7..89872659 100644
--- a/src/serversideclient.h
+++ b/src/serversideclient.h
@@ -18,6 +18,8 @@
#include
#include
+#undef min
+#undef max
class CHLTVServer;
class INetMessage;
diff --git a/src/utils/entity.cpp b/src/utils/entity.cpp
index ad938cd1..2ee95566 100644
--- a/src/utils/entity.cpp
+++ b/src/utils/entity.cpp
@@ -35,6 +35,9 @@ extern CCSGameRules* g_pGameRules;
CBaseEntity* UTIL_FindPickerEntity(CBasePlayerController* pPlayer)
{
+ if (!g_pGameRules)
+ return nullptr;
+
static int offset = g_GameConfig->GetOffset("CGameRules_FindPickerEntity");
if (offset < 0)
diff --git a/src/votemanager.cpp b/src/votemanager.cpp
index 3e51e925..3d9b4cc3 100644
--- a/src/votemanager.cpp
+++ b/src/votemanager.cpp
@@ -22,19 +22,16 @@
#include "ctimer.h"
#include "entity/cgamerules.h"
#include "icvar.h"
-#include "panoramavote.h"
#include "playermanager.h"
#include "tier0/memdbgon.h"
extern CGameEntitySystem* g_pEntitySystem;
extern IVEngineServer2* g_pEngineServer2;
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern CCSGameRules* g_pGameRules;
-ERTVState g_RTVState = ERTVState::MAP_START;
-EExtendState g_ExtendState = EExtendState::MAP_START;
-int g_iExtends = 0;
+CVoteManager* g_pVoteManager = nullptr;
bool g_bVoteManagerEnable = false;
int g_iMaxExtends = 1;
@@ -42,12 +39,10 @@ float g_flExtendSucceedRatio = 0.5f;
int g_iExtendTimeToAdd = 20;
float g_flRTVSucceedRatio = 0.6f;
bool g_bRTVEndRound = false;
-
int g_ExtendVoteMode = (int)EExtendVoteMode::EXTENDVOTE_ADMINONLY;
float g_flExtendVoteStartTime = 4.0f;
float g_flExtendVoteDuration = 30.0f;
float g_flExtendBeginRatio = 0.4f;
-
float g_flExtendVoteDelay = 300.0f;
float g_flRtvDelay = 300.0f;
@@ -65,52 +60,48 @@ FAKE_FLOAT_CVAR(cs2f_rtv_vote_delay, "Time after map start until RTV votes can b
FAKE_FLOAT_CVAR(cs2f_rtv_success_ratio, "Ratio needed to pass RTV", g_flRTVSucceedRatio, 0.6f, false)
FAKE_BOOL_CVAR(cs2f_rtv_endround, "Whether to immediately end the round when RTV succeeds", g_bRTVEndRound, false, false)
-static float flExtendVoteTickrate = 1.0f;
-
-void VoteManager_Init()
+void CVoteManager::VoteManager_Init()
{
// Disable RTV and Extend votes after map has just started
- g_RTVState = ERTVState::MAP_START;
- g_ExtendState = EExtendState::MAP_START;
+ m_RTVState = ERTVState::MAP_START;
+ m_ExtendState = EExtendState::MAP_START;
- g_iExtends = 0;
+ m_iExtends = 0;
- new CTimer(g_flExtendVoteDelay, false, true, []() {
- if (g_ExtendState < EExtendState::POST_EXTEND_NO_EXTENDS_LEFT)
- g_ExtendState = EExtendState::EXTEND_ALLOWED;
+ new CTimer(g_flExtendVoteDelay, false, true, [this]() {
+ if (m_ExtendState < EExtendState::POST_EXTEND_NO_EXTENDS_LEFT)
+ m_ExtendState = EExtendState::EXTEND_ALLOWED;
return -1.0f;
});
- new CTimer(g_flRtvDelay, false, true, []() {
- if (g_RTVState != ERTVState::BLOCKED_BY_ADMIN)
- g_RTVState = ERTVState::RTV_ALLOWED;
+ new CTimer(g_flRtvDelay, false, true, [this]() {
+ if (m_RTVState != ERTVState::BLOCKED_BY_ADMIN)
+ m_RTVState = ERTVState::RTV_ALLOWED;
return -1.0f;
});
- new CTimer(flExtendVoteTickrate, false, true, TimerCheckTimeleft);
+ new CTimer(m_flExtendVoteTickrate, false, true, std::bind(&CVoteManager::TimerCheckTimeleft, this));
}
-int iVoteStartTicks = 3;
-bool bVoteStarting = false;
-float TimerCheckTimeleft()
+float CVoteManager::TimerCheckTimeleft()
{
- if (!gpGlobals || !g_pGameRules)
- return flExtendVoteTickrate;
+ if (!GetGlobals() || !g_pGameRules)
+ return m_flExtendVoteTickrate;
if (!g_bVoteManagerEnable)
- return flExtendVoteTickrate;
+ return m_flExtendVoteTickrate;
// Auto votes disabled, dont stop the timer in case this changes mid-map
if (g_ExtendVoteMode != EExtendVoteMode::EXTENDVOTE_AUTO)
- return flExtendVoteTickrate;
+ return m_flExtendVoteTickrate;
// Vote already happening
- if (bVoteStarting || g_ExtendState == EExtendState::IN_PROGRESS)
- return flExtendVoteTickrate;
+ if (m_bVoteStarting || m_ExtendState == EExtendState::IN_PROGRESS)
+ return m_flExtendVoteTickrate;
// No more extends or map RTVd
- if ((g_iMaxExtends - g_iExtends) <= 0 || g_ExtendState >= EExtendState::POST_EXTEND_NO_EXTENDS_LEFT)
- return flExtendVoteTickrate;
+ if ((g_iMaxExtends - m_iExtends) <= 0 || m_ExtendState >= EExtendState::POST_EXTEND_NO_EXTENDS_LEFT)
+ return m_flExtendVoteTickrate;
ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_timelimit"));
// CONVAR_TODO
@@ -118,39 +109,42 @@ float TimerCheckTimeleft()
float flTimelimit = *(float*)&cvar->values;
if (flTimelimit <= 0.0)
- return flExtendVoteTickrate;
+ return m_flExtendVoteTickrate;
- float flTimeleft = (g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - gpGlobals->curtime;
+ float flTimeleft = (g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - GetGlobals()->curtime;
// Not yet time to start a vote
if (flTimeleft > (g_flExtendVoteStartTime * 60.0))
- return flExtendVoteTickrate;
+ return m_flExtendVoteTickrate;
- bVoteStarting = true;
+ m_bVoteStarting = true;
ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Extend vote starting in 10 seconds!");
- new CTimer(7.0f, false, true, []() {
- if (iVoteStartTicks == 0)
+ new CTimer(7.0f, false, true, [this]() {
+ if (m_iVoteStartTicks == 0)
{
- iVoteStartTicks = 3;
- StartExtendVote(VOTE_CALLER_SERVER);
- bVoteStarting = false;
+ m_iVoteStartTicks = 3;
+ g_pVoteManager->StartExtendVote(VOTE_CALLER_SERVER);
+ m_bVoteStarting = false;
return -1.0f;
}
- ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Extend vote starting in %d....", iVoteStartTicks);
- iVoteStartTicks--;
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Extend vote starting in %d....", m_iVoteStartTicks);
+ m_iVoteStartTicks--;
return 1.0f;
});
- return flExtendVoteTickrate;
+ return m_flExtendVoteTickrate;
}
-int GetCurrentRTVCount()
+int CVoteManager::GetCurrentRTVCount()
{
int iVoteCount = 0;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return iVoteCount;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -161,16 +155,19 @@ int GetCurrentRTVCount()
return iVoteCount;
}
-int GetNeededRTVCount()
+int CVoteManager::GetNeededRTVCount()
{
return (int)(g_playerManager->GetOnlinePlayerCount(false) * g_flRTVSucceedRatio) + 1;
}
-int GetCurrentExtendCount()
+int CVoteManager::GetCurrentExtendCount()
{
int iVoteCount = 0;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return iVoteCount;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -181,12 +178,16 @@ int GetCurrentExtendCount()
return iVoteCount;
}
-int GetNeededExtendCount()
+// TODO: wtf is going on here? function should be checked/tested, normally off our radar because not used in auto-extend mode
+int CVoteManager::GetNeededExtendCount()
{
int iOnlinePlayers = 0.0f;
int iVoteCount = 0;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return 0;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
@@ -203,7 +204,7 @@ int GetNeededExtendCount()
CON_COMMAND_CHAT(rtv, "- Vote to end the current map sooner")
{
- if (!g_bVoteManagerEnable)
+ if (!g_bVoteManagerEnable || !GetGlobals())
return;
if (!player)
@@ -223,7 +224,7 @@ CON_COMMAND_CHAT(rtv, "- Vote to end the current map sooner")
return;
}
- switch (g_RTVState)
+ switch (g_pVoteManager->GetRTVState())
{
case ERTVState::MAP_START:
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV is not open yet.");
@@ -239,57 +240,24 @@ CON_COMMAND_CHAT(rtv, "- Vote to end the current map sooner")
return;
}
- int iCurrentRTVCount = GetCurrentRTVCount();
- int iNeededRTVCount = GetNeededRTVCount();
-
if (pPlayer->GetRTVVote())
{
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have already rocked the vote (%i voted, %i needed).", iCurrentRTVCount, iNeededRTVCount);
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have already rocked the vote (%i voted, %i needed).", g_pVoteManager->GetCurrentRTVCount(), g_pVoteManager->GetNeededRTVCount());
return;
}
- if (pPlayer->GetRTVVoteTime() + 60.0f > gpGlobals->curtime)
+ if (pPlayer->GetRTVVoteTime() + 60.0f > GetGlobals()->curtime)
{
- int iRemainingTime = (int)(pPlayer->GetRTVVoteTime() + 60.0f - gpGlobals->curtime);
+ int iRemainingTime = (int)(pPlayer->GetRTVVoteTime() + 60.0f - GetGlobals()->curtime);
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Wait %i seconds before you can RTV again.", iRemainingTime);
return;
}
- if (iCurrentRTVCount + 1 >= iNeededRTVCount)
- {
- g_RTVState = ERTVState::POST_RTV_SUCCESSFULL;
- g_ExtendState = EExtendState::POST_RTV;
- // CONVAR_TODO
- g_pEngineServer2->ServerCommand("mp_timelimit 0.01");
-
- if (g_bRTVEndRound)
- {
- ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "RTV succeeded! Ending the map now...");
-
- new CTimer(3.0f, false, true, []() {
- g_pGameRules->TerminateRound(5.0f, CSRoundEndReason::Draw);
-
- return -1.0f;
- });
- }
- else
- {
- ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "RTV succeeded! This is the last round of the map!");
- }
-
- for (int i = 0; i < gpGlobals->maxClients; i++)
- {
- ZEPlayer* pPlayer2 = g_playerManager->GetPlayer(i);
- if (pPlayer2)
- pPlayer2->SetRTVVote(false);
- }
-
- return;
- }
-
pPlayer->SetRTVVote(true);
- pPlayer->SetRTVVoteTime(gpGlobals->curtime);
- ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "%s wants to rock the vote (%i voted, %i needed).", player->GetPlayerName(), iCurrentRTVCount + 1, iNeededRTVCount);
+ pPlayer->SetRTVVoteTime(GetGlobals()->curtime);
+
+ if (!g_pVoteManager->CheckRTVStatus())
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "%s wants to rock the vote (%i voted, %i needed).", player->GetPlayerName(), g_pVoteManager->GetCurrentRTVCount(), g_pVoteManager->GetNeededRTVCount());
}
CON_COMMAND_CHAT(unrtv, "- Remove your vote to end the current map sooner")
@@ -326,7 +294,7 @@ CON_COMMAND_CHAT(unrtv, "- Remove your vote to end the current map sooner")
CON_COMMAND_CHAT(ve, "- Vote to extend current map")
{
- if (!g_bVoteManagerEnable)
+ if (!g_bVoteManagerEnable || !GetGlobals() || !g_pGameRules)
return;
if (!player)
@@ -345,7 +313,7 @@ CON_COMMAND_CHAT(ve, "- Vote to extend current map")
return;
case EExtendVoteMode::EXTENDVOTE_AUTO:
{
- if (g_ExtendState == EExtendState::EXTEND_ALLOWED)
+ if (g_pVoteManager->GetExtendState() == EExtendState::EXTEND_ALLOWED)
{
ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_timelimit"));
@@ -355,7 +323,7 @@ CON_COMMAND_CHAT(ve, "- Vote to extend current map")
if (flTimelimit <= 0.0)
return;
- float flTimeleft = (g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - gpGlobals->curtime;
+ float flTimeleft = (g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - GetGlobals()->curtime;
int iTimeTillVote = (int)(flTimeleft - (g_flExtendVoteStartTime * 60.0));
div_t div = std::div(iTimeTillVote, 60);
@@ -382,7 +350,7 @@ CON_COMMAND_CHAT(ve, "- Vote to extend current map")
return;
}
- switch (g_ExtendState)
+ switch (g_pVoteManager->GetExtendState())
{
case EExtendState::MAP_START:
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Extend vote is not open yet.");
@@ -410,8 +378,8 @@ CON_COMMAND_CHAT(ve, "- Vote to extend current map")
return;
}
- int iCurrentExtendCount = GetCurrentExtendCount();
- int iNeededExtendCount = GetNeededExtendCount();
+ int iCurrentExtendCount = g_pVoteManager->GetCurrentExtendCount();
+ int iNeededExtendCount = g_pVoteManager->GetNeededExtendCount();
if (pPlayer->GetExtendVote())
{
@@ -419,22 +387,22 @@ CON_COMMAND_CHAT(ve, "- Vote to extend current map")
return;
}
- if (pPlayer->GetExtendVoteTime() + 60.0f > gpGlobals->curtime)
+ if (pPlayer->GetExtendVoteTime() + 60.0f > GetGlobals()->curtime)
{
- int iRemainingTime = (int)(pPlayer->GetExtendVoteTime() + 60.0f - gpGlobals->curtime);
+ int iRemainingTime = (int)(pPlayer->GetExtendVoteTime() + 60.0f - GetGlobals()->curtime);
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Wait %i seconds before you can vote extend again.", iRemainingTime);
return;
}
if (iCurrentExtendCount + 1 >= iNeededExtendCount)
{
- StartExtendVote(VOTE_CALLER_SERVER);
+ g_pVoteManager->StartExtendVote(VOTE_CALLER_SERVER);
return;
}
pPlayer->SetExtendVote(true);
- pPlayer->SetExtendVoteTime(gpGlobals->curtime);
+ pPlayer->SetExtendVoteTime(GetGlobals()->curtime);
ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "%s wants to extend the map (%i voted, %i needed).", player->GetPlayerName(), iCurrentExtendCount + 1, iNeededExtendCount);
}
@@ -487,7 +455,7 @@ CON_COMMAND_CHAT_FLAGS(adminve, "Start a vote extend immediately.", ADMFLAG_CHAN
return;
}
- if (g_ExtendState == EExtendState::IN_PROGRESS || bVoteStarting)
+ if (g_pVoteManager->GetExtendState() == EExtendState::IN_PROGRESS || g_pVoteManager->IsVoteStarting())
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "An extend vote is already in progress.");
return;
@@ -497,7 +465,7 @@ CON_COMMAND_CHAT_FLAGS(adminve, "Start a vote extend immediately.", ADMFLAG_CHAN
if (player)
slot = player->GetPlayerSlot();
- StartExtendVote(slot);
+ g_pVoteManager->StartExtendVote(slot);
}
CON_COMMAND_CHAT_FLAGS(disablertv, "- Disable the ability for players to vote to end current map sooner", ADMFLAG_CHANGEMAP)
@@ -505,7 +473,7 @@ CON_COMMAND_CHAT_FLAGS(disablertv, "- Disable the ability for players to vote to
if (!g_bVoteManagerEnable)
return;
- if (g_RTVState == ERTVState::BLOCKED_BY_ADMIN)
+ if (g_pVoteManager->GetRTVState() == ERTVState::BLOCKED_BY_ADMIN)
{
if (player)
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV is already disabled.");
@@ -516,7 +484,7 @@ CON_COMMAND_CHAT_FLAGS(disablertv, "- Disable the ability for players to vote to
const char* pszCommandPlayerName = player ? player->GetPlayerName() : CONSOLE_NAME;
- g_RTVState = ERTVState::BLOCKED_BY_ADMIN;
+ g_pVoteManager->SetRTVState(ERTVState::BLOCKED_BY_ADMIN);
ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX ADMIN_PREFIX "disabled vote for RTV.", pszCommandPlayerName);
}
@@ -526,7 +494,7 @@ CON_COMMAND_CHAT_FLAGS(enablertv, "- Restore the ability for players to vote to
if (!g_bVoteManagerEnable)
return;
- if (g_RTVState == ERTVState::RTV_ALLOWED)
+ if (g_pVoteManager->GetRTVState() == ERTVState::RTV_ALLOWED)
{
if (player)
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV is not disabled.");
@@ -537,7 +505,7 @@ CON_COMMAND_CHAT_FLAGS(enablertv, "- Restore the ability for players to vote to
const char* pszCommandPlayerName = player ? player->GetPlayerName() : CONSOLE_NAME;
- g_RTVState = ERTVState::RTV_ALLOWED;
+ g_pVoteManager->SetRTVState(ERTVState::RTV_ALLOWED);
ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX ADMIN_PREFIX "enabled vote for RTV.", pszCommandPlayerName);
}
@@ -547,16 +515,19 @@ CON_COMMAND_CHAT(extendsleft, "- Display amount of extends left for the current
if (!g_bVoteManagerEnable)
return;
- if (g_iMaxExtends - g_iExtends <= 0)
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "There are no extends left, the map was already extended %i/%i times.", g_iExtends, g_iMaxExtends);
- else if (g_ExtendState == EExtendState::POST_EXTEND_FAILED)
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The map had %i/%i extends left, but the last extend vote failed.", g_iMaxExtends - g_iExtends, g_iMaxExtends);
+ if (g_iMaxExtends - g_pVoteManager->GetExtends() <= 0)
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "There are no extends left, the map was already extended %i/%i times.", g_pVoteManager->GetExtends(), g_iMaxExtends);
+ else if (g_pVoteManager->GetExtendState() == EExtendState::POST_EXTEND_FAILED)
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The map had %i/%i extends left, but the last extend vote failed.", g_iMaxExtends - g_pVoteManager->GetExtends(), g_iMaxExtends);
else
- ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The map has %i/%i extends left.", g_iMaxExtends - g_iExtends, g_iMaxExtends);
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "The map has %i/%i extends left.", g_iMaxExtends - g_pVoteManager->GetExtends(), g_iMaxExtends);
}
CON_COMMAND_CHAT(timeleft, "- Display time left to end of current map.")
{
+ if (!GetGlobals() || !g_pGameRules)
+ return;
+
if (!player)
{
ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "You cannot use this command from the server console.");
@@ -575,7 +546,7 @@ CON_COMMAND_CHAT(timeleft, "- Display time left to end of current map.")
return;
}
- int iTimeleft = (int)((g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - gpGlobals->curtime);
+ int iTimeleft = (int)((g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - GetGlobals()->curtime);
if (iTimeleft < 0)
{
@@ -593,9 +564,11 @@ CON_COMMAND_CHAT(timeleft, "- Display time left to end of current map.")
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Timeleft: %i seconds", iSecondsLeft);
}
-void ExtendMap(int iMinutes)
+void CVoteManager::ExtendMap(int iMinutes, bool bAllowExtraTime)
{
- // mimic behaviour of !extend
+ if (!GetGlobals() || !g_pGameRules)
+ return;
+
// CONVAR_TODO
ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_timelimit"));
@@ -603,17 +576,13 @@ void ExtendMap(int iMinutes)
// HACK: values is actually the cvar value itself, hence this ugly cast.
float flTimelimit = *(float*)&cvar->values;
- if (gpGlobals->curtime - g_pGameRules->m_flGameStartTime > flTimelimit * 60)
- flTimelimit = (gpGlobals->curtime - g_pGameRules->m_flGameStartTime) / 60.0f + iMinutes;
+ if (bAllowExtraTime && GetGlobals()->curtime - g_pGameRules->m_flGameStartTime > flTimelimit * 60)
+ flTimelimit = (GetGlobals()->curtime - g_pGameRules->m_flGameStartTime) / 60.0f + iMinutes;
else
- {
- if (flTimelimit == 1)
- flTimelimit = 0;
flTimelimit += iMinutes;
- }
if (flTimelimit <= 0)
- flTimelimit = 1;
+ flTimelimit = 0.01f;
char buf[32];
V_snprintf(buf, sizeof(buf), "mp_timelimit %.6f", flTimelimit);
@@ -622,7 +591,7 @@ void ExtendMap(int iMinutes)
g_pEngineServer2->ServerCommand(buf);
}
-void VoteExtendHandler(YesNoVoteAction action, int param1, int param2)
+void CVoteManager::VoteExtendHandler(YesNoVoteAction action, int param1, int param2)
{
switch (action)
{
@@ -646,7 +615,7 @@ void VoteExtendHandler(YesNoVoteAction action, int param1, int param2)
// Admin cancelled so stop further votes
// It will reenable if an admin manually calls a vote
if (g_ExtendVoteMode == EExtendVoteMode::EXTENDVOTE_AUTO)
- g_ExtendState = EExtendState::POST_EXTEND_FAILED;
+ m_ExtendState = EExtendState::POST_EXTEND_FAILED;
}
break;
@@ -655,7 +624,7 @@ void VoteExtendHandler(YesNoVoteAction action, int param1, int param2)
}
// return true to show vote pass, false to show fail
-bool VoteExtendEndCallback(YesNoVoteInfo info)
+bool CVoteManager::VoteExtendEndCallback(YesNoVoteInfo info)
{
// ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Vote end: numvotes:%d yes:%d no:%d numclients:%d", info.num_votes, info.yes_votes, info.no_votes, info.num_clients);
@@ -669,36 +638,36 @@ bool VoteExtendEndCallback(YesNoVoteInfo info)
if (yes_percent >= g_flExtendSucceedRatio)
{
ExtendMap(g_iExtendTimeToAdd);
- g_iExtends++;
+ m_iExtends++;
- if (g_iMaxExtends - g_iExtends <= 0)
+ if (g_iMaxExtends - m_iExtends <= 0)
// there are no extends left after a successfull extend vote
- g_ExtendState = EExtendState::POST_EXTEND_NO_EXTENDS_LEFT;
+ m_ExtendState = EExtendState::POST_EXTEND_NO_EXTENDS_LEFT;
else
{
// there's an extend left after a successfull extend vote
if (g_ExtendVoteMode == EExtendVoteMode::EXTENDVOTE_AUTO)
{
// small delay to allow cvar change to go through
- new CTimer(0.1, false, true, []() {
- g_ExtendState = EExtendState::EXTEND_ALLOWED;
+ new CTimer(0.1, false, true, [this]() {
+ m_ExtendState = EExtendState::EXTEND_ALLOWED;
return -1.0f;
});
}
else
{
- g_ExtendState = EExtendState::POST_EXTEND_COOLDOWN;
+ m_ExtendState = EExtendState::POST_EXTEND_COOLDOWN;
// Allow another extend vote after added time lapses
- new CTimer(g_iExtendTimeToAdd * 60.0f, false, true, []() {
- if (g_ExtendState == EExtendState::POST_EXTEND_COOLDOWN)
- g_ExtendState = EExtendState::EXTEND_ALLOWED;
+ new CTimer(g_iExtendTimeToAdd * 60.0f, false, true, [this]() {
+ if (m_ExtendState == EExtendState::POST_EXTEND_COOLDOWN)
+ m_ExtendState = EExtendState::EXTEND_ALLOWED;
return -1.0f;
});
}
}
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < MAXPLAYERS; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
if (pPlayer)
@@ -711,36 +680,99 @@ bool VoteExtendEndCallback(YesNoVoteInfo info)
}
// Vote failed so we don't allow any more votes
- g_ExtendState = EExtendState::POST_EXTEND_FAILED;
+ m_ExtendState = EExtendState::POST_EXTEND_FAILED;
ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Extend vote failed! Further extend votes disabled!", g_iExtendTimeToAdd);
return false;
}
-static int iVoteEndTicks = 3;
-void StartExtendVote(int iCaller)
+void CVoteManager::StartExtendVote(int iCaller)
{
- if (g_ExtendState == EExtendState::IN_PROGRESS)
+ if (m_ExtendState == EExtendState::IN_PROGRESS)
return;
char sDetailStr[64];
V_snprintf(sDetailStr, sizeof(sDetailStr), "Vote to extend the map for another %d minutes", g_iExtendTimeToAdd);
- g_ExtendState = EExtendState::IN_PROGRESS;
+ m_ExtendState = EExtendState::IN_PROGRESS;
- g_pPanoramaVoteHandler->SendYesNoVoteToAll(g_flExtendVoteDuration, iCaller, "#SFUI_vote_passed_nextlevel_extend",
- sDetailStr, &VoteExtendEndCallback, &VoteExtendHandler);
+ g_pPanoramaVoteHandler->SendYesNoVoteToAll(g_flExtendVoteDuration, iCaller, "#SFUI_vote_passed_nextlevel_extend", sDetailStr,
+ std::bind(&CVoteManager::VoteExtendEndCallback, this, std::placeholders::_1), std::bind(&CVoteManager::VoteExtendHandler, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
- new CTimer(g_flExtendVoteDuration - 3.0f, false, true, []() {
- if (iVoteEndTicks == 0 || g_ExtendState != EExtendState::IN_PROGRESS)
+ new CTimer(g_flExtendVoteDuration - 3.0f, false, true, [this]() {
+ if (m_iVoteEndTicks == 0 || m_ExtendState != EExtendState::IN_PROGRESS)
{
- iVoteEndTicks = 3;
+ m_iVoteEndTicks = 3;
return -1.0f;
}
- ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Extend vote ending in %d....", iVoteEndTicks);
- iVoteEndTicks--;
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Extend vote ending in %d....", m_iVoteEndTicks);
+ m_iVoteEndTicks--;
return 1.0f;
});
}
+
+void CVoteManager::OnRoundEnd()
+{
+ if (!GetGlobals() || !g_pGameRules)
+ return;
+
+ ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_timelimit"));
+
+ // CONVAR_TODO
+ // HACK: values is actually the cvar value itself, hence this ugly cast.
+ float flTimelimit = *(float*)&cvar->values;
+
+ int iTimeleft = (int)((g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - GetGlobals()->curtime);
+
+ // check for end of last round
+ if (iTimeleft < 0)
+ {
+ m_RTVState = ERTVState::POST_LAST_ROUND_END;
+ m_ExtendState = EExtendState::POST_LAST_ROUND_END;
+ }
+}
+
+bool CVoteManager::CheckRTVStatus()
+{
+ if (!g_bVoteManagerEnable || m_RTVState != ERTVState::RTV_ALLOWED || !GetGlobals() || !g_pGameRules)
+ return false;
+
+ int iCurrentRTVCount = GetCurrentRTVCount();
+ int iNeededRTVCount = GetNeededRTVCount();
+
+ if (iCurrentRTVCount >= iNeededRTVCount)
+ {
+ m_RTVState = ERTVState::POST_RTV_SUCCESSFULL;
+ m_ExtendState = EExtendState::POST_RTV;
+ // CONVAR_TODO
+ g_pEngineServer2->ServerCommand("mp_timelimit 0.01");
+
+ if (g_bRTVEndRound)
+ {
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "RTV succeeded! Ending the map now...");
+
+ new CTimer(3.0f, false, true, []() {
+ g_pGameRules->TerminateRound(5.0f, CSRoundEndReason::Draw);
+
+ return -1.0f;
+ });
+ }
+ else
+ {
+ ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "RTV succeeded! This is the last round of the map!");
+ }
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
+ {
+ ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
+ if (pPlayer)
+ pPlayer->SetRTVVote(false);
+ }
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/votemanager.h b/src/votemanager.h
index 51b2016a..85e4cc45 100644
--- a/src/votemanager.h
+++ b/src/votemanager.h
@@ -20,6 +20,8 @@
#pragma once
+#include "panoramavote.h"
+
enum class ERTVState
{
MAP_START,
@@ -50,13 +52,39 @@ enum EExtendVoteMode
EXTENDVOTE_AUTO, // Extend votes can be triggered by !ve or when map timelimit reaches a given value
};
-extern ERTVState g_RTVState;
-extern EExtendState g_ExtendState;
extern bool g_bVoteManagerEnable;
-void VoteManager_Init();
-void SetExtendsLeft();
-void ExtendMap(int iMinutes);
+class CVoteManager
+{
+public:
+ void VoteManager_Init();
+ float TimerCheckTimeleft();
+ int GetCurrentRTVCount();
+ int GetNeededRTVCount();
+ int GetCurrentExtendCount();
+ int GetNeededExtendCount();
+ void ExtendMap(int iMinutes, bool bAllowExtraTime = true);
+ void VoteExtendHandler(YesNoVoteAction action, int param1, int param2);
+ bool VoteExtendEndCallback(YesNoVoteInfo info);
+ void StartExtendVote(int iCaller);
+ void OnRoundEnd();
+ bool CheckRTVStatus();
+
+ ERTVState GetRTVState() { return m_RTVState; }
+ EExtendState GetExtendState() { return m_ExtendState; }
+ int GetExtends() { return m_iExtends; }
+ bool IsVoteStarting() { return m_bVoteStarting; }
+ void SetRTVState(ERTVState RTVState) { m_RTVState = RTVState; }
+ void SetExtendState(EExtendState ExtendState) { m_ExtendState = ExtendState; }
+
+private:
+ ERTVState m_RTVState = ERTVState::MAP_START;
+ EExtendState m_ExtendState = EExtendState::MAP_START;
+ int m_iExtends = 0;
+ int m_iVoteEndTicks = 3;
+ int m_iVoteStartTicks = 3;
+ bool m_bVoteStarting = false;
+ const float m_flExtendVoteTickrate = 1.0f;
+};
-float TimerCheckTimeleft();
-void StartExtendVote(int iCaller);
\ No newline at end of file
+extern CVoteManager* g_pVoteManager;
\ No newline at end of file
diff --git a/src/zombiereborn.cpp b/src/zombiereborn.cpp
index 891c087d..a46e7969 100644
--- a/src/zombiereborn.cpp
+++ b/src/zombiereborn.cpp
@@ -47,7 +47,7 @@ using ordered_json = nlohmann::ordered_json;
extern CGameEntitySystem* g_pEntitySystem;
extern IVEngineServer2* g_pEngineServer2;
-extern CGlobalVars* gpGlobals;
+extern CGlobalVars* GetGlobals();
extern CCSGameRules* g_pGameRules;
extern IGameEventManager2* g_gameEventManager;
extern IGameEventSystem* g_gameEventSystem;
@@ -375,7 +375,13 @@ void CZRPlayerClassManager::LoadPlayerClass()
// Less code than constantly traversing the full class vectors, temporary lifetime anyways
std::set setClassNames;
- ordered_json jsonPlayerClasses = ordered_json::parse(jsoncFile, nullptr, true, true);
+ ordered_json jsonPlayerClasses = ordered_json::parse(jsoncFile, nullptr, false, true);
+
+ if (jsonPlayerClasses.is_discarded())
+ {
+ Panic("Failed parsing JSON from %s. Playerclasses not loaded\n", pszJsonPath);
+ return;
+ }
for (auto& [szTeamName, jsonTeamClasses] : jsonPlayerClasses.items())
{
@@ -959,7 +965,10 @@ std::shared_ptr ZRHitgroupConfig::FindHitgroupIndex(int iIndex)
void ZR_RespawnAll()
{
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
@@ -988,7 +997,10 @@ void ZR_OnRoundPrestart(IGameEvent* pEvent)
g_ZRRoundState = EZRRoundState::ROUND_START;
ToggleRespawn(true, true);
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
@@ -1035,7 +1047,10 @@ void ZR_OnRoundStart(IGameEvent* pEvent)
SetupRespawnToggler();
CZRRegenTimer::RemoveAllTimers();
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals())
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
@@ -1241,9 +1256,13 @@ void ZR_InfectShake(CCSPlayerController* pController)
std::vector ZR_GetSpawns()
{
+ std::vector spawns;
+
+ if (!g_pGameRules)
+ return spawns;
+
CUtlVector* ctSpawns = g_pGameRules->m_CTSpawnPoints();
CUtlVector* tSpawns = g_pGameRules->m_TerroristSpawnPoints();
- std::vector spawns;
FOR_EACH_VEC(*ctSpawns, i)
spawns.push_back((*ctSpawns)[i]);
@@ -1343,9 +1362,12 @@ void ZR_InfectMotherZombie(CCSPlayerController* pVictimController, std::vector pCandidateControllers;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
if (!pController || !pController->IsConnected() || pController->m_iTeamNum() != CS_TEAM_CT)
@@ -1432,7 +1454,7 @@ void ZR_InitialInfection()
}
// reduce everyone's immunity except mz
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);
if (!pPlayer || vecIsMZ[i])
@@ -1570,6 +1592,9 @@ void SpawnPlayer(CCSPlayerController* pController)
// Make sure the round ends if spawning into an empty server
if (!ZR_IsTeamAlive(CS_TEAM_CT) && !ZR_IsTeamAlive(CS_TEAM_T) && g_ZRRoundState != EZRRoundState::ROUND_END)
{
+ if (!g_pGameRules)
+ return;
+
g_pGameRules->TerminateRound(1.0f, CSRoundEndReason::GameStart);
g_ZRRoundState = EZRRoundState::ROUND_END;
return;
@@ -1721,7 +1746,10 @@ void ZR_EndRoundAndAddTeamScore(int iTeamNum)
{
bool bServerIdle = true;
- for (int i = 0; i < gpGlobals->maxClients; i++)
+ if (!GetGlobals() || !g_pGameRules)
+ return;
+
+ for (int i = 0; i < GetGlobals()->maxClients; i++)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(i);