diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index fe8714c5..c1e5972a 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -40,6 +40,7 @@ cs2f_flashlight_shadows 1 // Whether to enable flashlight shadows cs2f_flashlight_transmit_others 0 // Whether to transmit other players' flashlights, recommended to have shadows disabled with this cs2f_flashlight_brightness 1.0 // How bright should flashlights be cs2f_flashlight_distance 54 // How far flashlights should be from the player's head (the default is such that an equipped awp doesn't block the light if shadows are enabled) +cs2f_flashlight_angle 45.0 // How wide should the flashlight be in degrees cs2f_flashlight_color "255 255 255 0" // What color to use for flashlights cs2f_flashlight_attachment axis_of_intent // Which attachment to parent a flashlight to. If the player model is not properly setup, you might have to use clip_limit here instead diff --git a/configs/zr/playerclass.jsonc.example b/configs/zr/playerclass.jsonc.example index ee4fe1c6..0509d863 100644 --- a/configs/zr/playerclass.jsonc.example +++ b/configs/zr/playerclass.jsonc.example @@ -69,7 +69,7 @@ ] } ], - "scale": 1.05, + "scale": 1.0, "speed": 1.0, "gravity": 1.0, "knockback": 1.0, @@ -91,7 +91,7 @@ ] } ], - "scale": 1.15, + "scale": 1.0, "speed": 1.0, "gravity": 1.0, "admin_flag": "", diff --git a/gamedata/cs2fixes.games.txt b/gamedata/cs2fixes.games.txt index a8d31454..85fa85ae 100644 --- a/gamedata/cs2fixes.games.txt +++ b/gamedata/cs2fixes.games.txt @@ -42,8 +42,8 @@ "ServerMovementUnlock" { "library" "server" - "windows" "\x76\x2A\xF2\x0F\x10\x4E\x2A\x41\x0F\x28\xD2\x0F\x28\xC1" - "linux" "\x0F\x87\x2A\x2A\x2A\x2A\x49\x8B\x7C\x24\x2A\x48\x85\xFF" + "windows" "\x0F\x86\xB8\x2A\x2A\x2A\xF3\x0F\x58\xD4" + "linux" "\x0F\x87\x2A\x2A\x2A\x2A\xF3\x0F\x10\x3D\x2A\x2A\x2A\x2A\xF3\x0F\x11\xBD" } // String: "CCSPlayerPawnBase::SwitchTeam", just keep in mind this is actually CCSPlayerController::SwitchTeam "CCSPlayerController_SwitchTeam" @@ -57,7 +57,7 @@ { "library" "server" "windows" "\xC8\x42\xEB\x2A\x4C\x39\x67\x30" - "linux" "\xC8\x42\xEB\x2A\x48\x89\xDF\xE8\x2A\x2A\x2A\x2A" + "linux" "\xC8\x42\x41\xC7\x85\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\xE9\x2A\x2A\x2A\x2A\x4C\x89\xE7" } // Called right after "Removed %s(%s)\n" "UTIL_Remove" @@ -102,7 +102,7 @@ { "library" "server" "windows" "\x48\x83\xEC\x68\x45\x33\xC9" - "linux" "\x55\x45\x31\xC0\x31\xC9\x48\x89\xE5\x53" + "linux" "\x55\x45\x31\xC0\x31\xC9\x48\x89\xE5\x53\x48\x8D\x5D\x2A\x48\x83\xEC\x2A\x48\x89\xDF" } // "commentary_semaphore" is passed to this "CGameEntitySystem_FindEntityByName" @@ -161,7 +161,7 @@ { "library" "server" "windows" "\x48\x89\x5C\x24\x2A\x44\x89\x4C\x24\x2A\x48\x89\x54\x24\x2A\x48\x89\x4C\x24\x2A\x55\x56\x57\x41\x54\x41\x55\x41\x56\x41\x57\x48\x8D\xAC\x24" - "linux" "\x55\x48\x8D\x05\x2A\x2A\x2A\x2A\x48\x89\xE5\x41\x57\x4D\x89\xC7" + "linux" "\x55\x48\x8D\x05\x2A\x2A\x2A\x2A\x48\x89\xE5\x41\x57\x41\x56\x41\x55\x49\x89\xD5\x41\x54\x4C\x8D\x25" } // Search "Changes's player's model", look for a function containing 'models/%s.vmdl'. Below V_snprintf is the one // This matches 2 functions, however they're literally identical @@ -199,7 +199,7 @@ { "library" "server" "windows" "\x48\x89\x5C\x24\x10\x57\x48\x83\xEC\x30\x48\x8B\xDA\x48\x8B\xF9\x48\x85\xC9" - "linux" "\x48\x85\xFF\x74\x2A\x55\x48\x89\xE5\x41\x55\x41\x54\x49\x89\xFC" + "linux" "\x48\x85\xFF\x74\x2A\x55\x48\x89\xE5\x41\x55\x49\x89\xFD" } // Look for "SetEntityName", that will be the vscript binding definition // Scroll a bit down and you'll find something like this (note the offset): *(_QWORD *)(v453 + 64) = sub_1807B0350; @@ -251,7 +251,7 @@ "CNetworkStringTable_DeleteAllStrings" { "library" "engine" - "windows" "\x48\x89\x5C\x24\x2A\x48\x89\x6C\x24\x2A\x48\x89\x74\x24\x2A\x48\x89\x7C\x24\x2A\x41\x56\x48\x83\xEC\x2A\x45\x33\xF6\x48\x8B\xD9" + "windows" "\x48\x89\x5C\x24\x2A\x48\x89\x7C\x24\x2A\x41\x56\x48\x83\xEC\x2A\x45\x33\xF6" "linux" "\x55\x48\x89\xE5\x41\x57\x41\x56\x41\x55\x41\x54\x45\x31\xE4\x53\x48\x89\xFB\x48\x83\xEC\x2A\x48\x83\x7F" } // "PlayerMovementTraces" @@ -321,7 +321,7 @@ { "library" "server" "windows" "\x4C\x89\x44\x24\x2A\xF3\x0F\x11\x4C\x24\x2A\x55\x53\x56" - "linux" "\x55\x48\x89\xE5\x41\x57\x49\x89\xFF\x41\x56\x48\x8D\x3D\x2A\x2A\x2A\x2A\x41\x55\x41\x54\x53\x48\x81\xEC" + "linux" "\x55\x48\x89\xE5\x41\x57\x41\x56\x41\x55\x49\x89\xFD\x41\x54\x48\x8D\x3D\x2A\x2A\x2A\x2A\x53\x48\x81\xEC" } "TraceFunc" { @@ -334,7 +334,7 @@ { "library" "server" "windows" "\x48\x89\x5C\x24\x2A\x48\x89\x4C\x24\x2A\x55\x57" - "linux" "\x55\x48\x89\xE5\x41\x57\x49\x89\xCF\x41\x56\x41\x55\x4D\x89\xC5\x41\x54\x49\x89\xD4\x53\x4C\x89\xCB" + "linux" "\x55\x48\x89\xE5\x41\x57\x49\x89\xCF\x41\x56\x49\x89\xF6\x41\x55\x4D\x89\xC5" } "CBasePlayerPawn_GetEyePosition" { @@ -459,28 +459,28 @@ } "CCSPlayerController_Respawn" { - "windows" "274" - "linux" "276" + "windows" "275" + "linux" "277" } // CBaseTrigger "PassesTriggerFilters" { - "windows" "268" - "linux" "269" + "windows" "269" + "linux" "270" } // Long function with "player_hurt" in the middle and then inserts userid, health, priority, attacker strings "CBasePlayerPawn::OnTakeDamage_Alive" { - "windows" "251" - "linux" "252" + "windows" "252" + "linux" "253" } // Look for the kill command, go through its callback and you should a find call like this, with v9 being a pawn pointer: // return (*(*v9 + 2976LL))(v9, v27, 0LL); // 2976 (372 * 8) is the offset "CBasePlayerPawn_CommitSuicide" { - "windows" "403" - "linux" "403" + "windows" "408" + "linux" "408" } "GameEntitySystem" { @@ -495,8 +495,8 @@ } "CCSGameRules_GoToIntermission" { - "windows" "124" - "linux" "125" + "windows" "127" + "linux" "128" } "CheckTransmitPlayerSlot" { @@ -544,7 +544,7 @@ // Server "ServerMovementUnlock" { - "windows" "\xEB" + "windows" "\xE9\xB9\x00\x00\x00\x90" "linux" "\x90\x90\x90\x90\x90\x90" } "FixWaterFloorJump" @@ -552,11 +552,6 @@ "windows" "\x11\x43" "linux" "\x11\x43" } - "WaterLevelGravity" - { - "windows" "\x3C\x02" - "linux" "\x3C\x02" - } // Jumping over a check for nav mesh "BotNavIgnore" { diff --git a/sdk b/sdk index 1a21ada9..0f84fcf1 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 1a21ada9bef33a6dc204eaaa17a2c4061a246bc0 +Subproject commit 0f84fcf129f20dcaed07f9628c3555ee9b0b0f6c diff --git a/src/addresses.cpp b/src/addresses.cpp index 55bb6880..85683dba 100644 --- a/src/addresses.cpp +++ b/src/addresses.cpp @@ -23,8 +23,6 @@ #include "tier0/memdbgon.h" -extern CGameConfig* g_GameConfig; - #define RESOLVE_SIG(gameConfig, name, variable) \ variable = (decltype(variable))gameConfig->ResolveSignature(name); \ if (!variable) \ diff --git a/src/adminsystem.cpp b/src/adminsystem.cpp index 939c2159..5bd7cb1e 100644 --- a/src/adminsystem.cpp +++ b/src/adminsystem.cpp @@ -42,13 +42,6 @@ #undef snprintf #include "vendor/nlohmann/json.hpp" -extern IVEngineServer2* g_pEngineServer2; -extern CGameEntitySystem* g_pEntitySystem; -extern CGlobalVars* GetGlobals(); -extern CCSGameRules* g_pGameRules; -extern CPlayerManager* g_playerManager; -extern CUtlVector* GetClientList(); - CAdminSystem* g_pAdminSystem = nullptr; void ParseInfraction(const CCommand& args, CCSPlayerController* pAdmin, bool bAdding, CInfractionBase::EInfractionType infType); @@ -584,6 +577,8 @@ CON_COMMAND_CHAT_FLAGS(hsay, " - Say something as a hud hint", ADMFLAG_ EscapeHTMLSpecialCharacters(args.ArgS()).c_str()); } +CLoggingListener g_LoggingListener; + CON_COMMAND_CHAT_FLAGS(rcon, " - Send a command to server console", ADMFLAG_RCON) { if (!player) @@ -598,7 +593,62 @@ CON_COMMAND_CHAT_FLAGS(rcon, " - Send a command to server console", ADM return; } + // Normally this should be done on plugin init, but for whatever reason "log_flags +donotecho" crashes AFTER this + ExecuteOnce + ( + LoggingSystem_RegisterBackdoorLoggingListener(&g_LoggingListener); + LoggingSystem_EnableBackdoorLoggingListeners(true); + ); + + // We don't have the equivalent of ServerExecute (to immediately execute commands) in source2, so manually find and execute the command + + ConCommandRef cmdRef(args[1], true); + + if (cmdRef.IsValidRef()) + { + CCommandContext context(CT_FIRST_SPLITSCREEN_CLIENT, player->GetPlayerSlot()); + + CCommand newArgs; + newArgs.Tokenize(args.ArgS()); + + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Command executed, see console for output (if any)"); + + g_LoggingListener.SetPlayer(player); + cmdRef.Dispatch(context, newArgs); + g_LoggingListener.SetPlayer(nullptr); + + return; + } + + ConVarRefAbstract cvarRef(args[1], true); + + if (cvarRef.IsValidRef()) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Command executed, see console for output (if any)"); + + if (args.ArgC() == 2) + { + g_LoggingListener.SetPlayer(player); + ConVar_PrintDescription(&cvarRef); + g_LoggingListener.SetPlayer(nullptr); + + return; + } + + CCommand newArgs; + newArgs.Tokenize(args.ArgS()); + + g_LoggingListener.SetPlayer(player); + cvarRef.SetString(newArgs.ArgS()); + g_LoggingListener.SetPlayer(nullptr); + + return; + } + + // Just in case it's not an actual ConCommand (for example an alias) g_pEngineServer2->ServerCommand(args.ArgS()); + + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Command executed"); } CON_COMMAND_CHAT_FLAGS(extend, " - Extend current map (negative value reduces map duration)", ADMFLAG_CHANGEMAP) diff --git a/src/buttonwatch.cpp b/src/buttonwatch.cpp index 99636cf1..6062a8b3 100644 --- a/src/buttonwatch.cpp +++ b/src/buttonwatch.cpp @@ -134,7 +134,7 @@ void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEnt // Limit each button to only printing out at most once every 5 seconds int iIndex = pCaller->GetEntityIndex().Get(); mapRecentEnts[iIndex] = true; - new CTimer(5.0f, true, true, [iIndex]() { + CTimer::Create(5.0f, TIMERFLAG_NONE, [iIndex]() { mapRecentEnts.erase(iIndex); return -1.0f; }); diff --git a/src/cdetour.h b/src/cdetour.h index afbd5b59..7f7b6980 100644 --- a/src/cdetour.h +++ b/src/cdetour.h @@ -36,6 +36,8 @@ class CDetourBase virtual void DisableDetour() = 0; }; +extern CUtlVector g_vecDetours; + template class CDetour : public CDetourBase { @@ -72,8 +74,6 @@ class CDetour : public CDetourBase bool m_bInstalled; }; -extern CUtlVector g_vecDetours; - template CDetour::CDetour(T* pfnDetour, const char* pszName) : m_pfnDetour(pfnDetour), m_pszName(pszName) diff --git a/src/commands.cpp b/src/commands.cpp index a5eaeb48..9162361a 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -48,16 +48,17 @@ using json = nlohmann::json; -extern IGameEventSystem* g_gameEventSystem; -extern CGameEntitySystem* g_pEntitySystem; -extern IVEngineServer2* g_pEngineServer2; -extern ISteamHTTP* g_http; -extern CConVar g_cvarFlashLightAttachment; - CConVar g_cvarEnableCommands("cs2f_commands_enable", FCVAR_NONE, "Whether to enable chat commands", false); CConVar g_cvarEnableAdminCommands("cs2f_admin_commands_enable", FCVAR_NONE, "Whether to enable admin chat commands", false); CConVar g_cvarEnableWeapons("cs2f_weapons_enable", FCVAR_NONE, "Whether to enable weapon commands", false); +// We need to use a helper function to avoid command macros accessing command list before its initialized +std::map>& CommandList() +{ + static std::map> commandList; + return commandList; +} + int GetGrenadeAmmo(CCSPlayer_WeaponServices* pWeaponServices, const WeaponInfo_t* pWeaponInfo) { if (!pWeaponServices || pWeaponInfo->m_eSlot != GEAR_SLOT_GRENADES) @@ -245,7 +246,7 @@ void RegisterWeaponCommands() { for (const auto& alias : aliases) { - new CChatCommand(alias.c_str(), ParseWeaponCommand, "- Buys this weapon", ADMFLAG_NONE, CMDFLAG_NOHELP); + CChatCommand::Create(alias.c_str(), ParseWeaponCommand, "- Buys this weapon", ADMFLAG_NONE, CMDFLAG_NOHELP); char cmdName[64]; V_snprintf(cmdName, sizeof(cmdName), "%s%s", COMMAND_PREFIX, alias.c_str()); @@ -255,12 +256,6 @@ void RegisterWeaponCommands() } } -std::map& CommandList() -{ - static std::map commandList; - return commandList; -} - void ParseChatCommand(const char* pMessage, CCSPlayerController* pController) { if (!pController || !pController->IsConnected()) @@ -496,7 +491,7 @@ void PrintHelp(const CCommand& args, CCSPlayerController* player) for (const auto& cmdPair : CommandList()) { - CChatCommand* cmd = cmdPair.second; + auto cmd = cmdPair.second; if (!cmd->IsCommandFlagSet(CMDFLAG_NOHELP)) rgstrCommands.push_back(std::string("c_") + cmd->GetName() + " " + cmd->GetDescription()); @@ -511,7 +506,7 @@ void PrintHelp(const CCommand& args, CCSPlayerController* player) for (const auto& cmdPair : CommandList()) { - CChatCommand* cmd = cmdPair.second; + auto cmd = cmdPair.second; uint64 flags = cmd->GetAdminFlags(); if ((pZEPlayer->IsAdminFlagSet(flags) || ((flags & FLAG_LEADER) == FLAG_LEADER && pZEPlayer->IsLeader())) @@ -543,7 +538,7 @@ void PrintHelp(const CCommand& args, CCSPlayerController* player) { for (const auto& cmdPair : CommandList()) { - CChatCommand* cmd = cmdPair.second; + auto cmd = cmdPair.second; if (!cmd->IsCommandFlagSet(CMDFLAG_NOHELP) && ((!bOnlyCheckStart && V_stristr(cmd->GetName(), pszSearchTerm)) @@ -557,7 +552,7 @@ void PrintHelp(const CCommand& args, CCSPlayerController* player) for (const auto& cmdPair : CommandList()) { - CChatCommand* cmd = cmdPair.second; + auto cmd = cmdPair.second; uint64 flags = cmd->GetAdminFlags(); if ((pZEPlayer->IsAdminFlagSet(flags) || ((flags & FLAG_LEADER) == FLAG_LEADER && pZEPlayer->IsLeader())) @@ -650,7 +645,7 @@ CON_COMMAND_CHAT(spec, "[name] - Spectate another player or join spectators") // 1 frame delay as observer services will be null on same frame as spectator team switch CHandle hPlayer = player->GetHandle(); CHandle hTarget = pTarget->GetHandle(); - new CTimer(0.0f, false, false, [hPlayer, hTarget]() { + CTimer::Create(0.0f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hPlayer, hTarget]() { CCSPlayerController* pPlayer = hPlayer.Get(); CCSPlayerController* pTargetPlayer = hTarget.Get(); if (!pPlayer || !pTargetPlayer) diff --git a/src/commands.h b/src/commands.h index cbbd0bf5..97b177c8 100644 --- a/src/commands.h +++ b/src/commands.h @@ -24,26 +24,23 @@ #include "leader.h" #include -#define CMDFLAG_NONE (0) -#define CMDFLAG_NOHELP (1 << 0) // Don't show in !help menu - -#define COMMAND_PREFIX "c_" -#define CHAT_PREFIX " \7[CS2Fixes]\1 " - -typedef void (*FnChatCommandCallback_t)(const CCommand& args, CCSPlayerController* player); - class CChatCommand; extern CConVar g_cvarEnableCommands; extern CConVar g_cvarEnableAdminCommands; - -extern CConVar g_cvarEnableHide; +extern std::map>& CommandList(); extern CConVar g_cvarEnableStopSound; extern CConVar g_cvarEnableNoShake; extern CConVar g_cvarMaxShakeAmp; +extern CConVar g_cvarEnableHide; + +#define CMDFLAG_NONE (0) +#define CMDFLAG_NOHELP (1 << 0) // Don't show in !help menu + +#define COMMAND_PREFIX "c_" +#define CHAT_PREFIX " \7[CS2Fixes]\1 " -// We need to use a helper function to avoid command macros accessing command list before its initialized -extern std::map& CommandList(); +typedef void (*FnChatCommandCallback_t)(const CCommand& args, CCSPlayerController* player); void ClientPrintAll(int destination, const char* msg, ...); void ClientPrint(CCSPlayerController* player, int destination, const char* msg, ...); @@ -52,10 +49,16 @@ void ClientPrint(CCSPlayerController* player, int destination, const char* msg, class CChatCommand { public: - CChatCommand(const char* cmd, FnChatCommandCallback_t callback, const char* description, uint64 adminFlags = ADMFLAG_NONE, uint64 cmdFlags = CMDFLAG_NONE) : + CChatCommand(const char* cmd, FnChatCommandCallback_t callback, const char* description, uint64 adminFlags, uint64 cmdFlags) : m_pfnCallback(callback), m_sName(cmd), m_sDescription(description), m_nAdminFlags(adminFlags), m_nCmdFlags(cmdFlags) + {} + + static std::shared_ptr Create(const char* cmd, FnChatCommandCallback_t callback, const char* description, uint64 adminFlags = ADMFLAG_NONE, uint64 cmdFlags = CMDFLAG_NONE) { - CommandList().insert(std::make_pair(hash_32_fnv1a_const(cmd), this)); + auto pCommand = std::make_shared(cmd, callback, description, adminFlags, cmdFlags); + + CommandList().insert(std::make_pair(hash_32_fnv1a_const(cmd), pCommand)); + return pCommand; } void operator()(const CCommand& args, CCSPlayerController* player) @@ -102,14 +105,14 @@ void ParseChatCommand(const char*, CCSPlayerController*); #define CON_COMMAND_CHAT_FLAGS(name, description, flags) \ void name##_callback(const CCommand& args, CCSPlayerController* player); \ - static CChatCommand name##_chat_command(#name, name##_callback, description, flags); \ + static auto name##_chat_command = CChatCommand::Create(#name, name##_callback, description, flags); \ static void name##_con_callback(const CCommandContext& context, const CCommand& args) \ { \ CCSPlayerController* pController = nullptr; \ if (context.GetPlayerSlot().Get() != -1) \ pController = (CCSPlayerController*)g_pEntitySystem->GetEntityInstance((CEntityIndex)(context.GetPlayerSlot().Get() + 1)); \ \ - name##_chat_command(args, pController); \ + (*name##_chat_command)(args, pController); \ } \ static ConCommand name##_command(COMMAND_PREFIX #name, name##_con_callback, \ description, FCVAR_CLIENT_CAN_EXECUTE | FCVAR_LINKED_CONCOMMAND); \ @@ -117,3 +120,20 @@ void ParseChatCommand(const char*, CCSPlayerController*); #define CON_COMMAND_CHAT(name, description) CON_COMMAND_CHAT_FLAGS(name, description, ADMFLAG_NONE) #define CON_COMMAND_CHAT_LEADER(name, description) CON_COMMAND_CHAT_FLAGS(name, description, FLAG_LEADER) + +class CLoggingListener : public ILoggingListener +{ +public: + void SetPlayer(CCSPlayerController* pController) { m_pController = pController; } + + void Log(const LoggingContext_t* pContext, const tchar* pMessage) override + { + if (m_pController) + ClientPrint(m_pController, HUD_PRINTCONSOLE, pMessage); + } + +private: + CCSPlayerController* m_pController = nullptr; +}; + +extern CLoggingListener g_LoggingListener; diff --git a/src/cs2_sdk/entity/cbaseentity.h b/src/cs2_sdk/entity/cbaseentity.h index 0ccd879f..bf1a2e2f 100644 --- a/src/cs2_sdk/entity/cbaseentity.h +++ b/src/cs2_sdk/entity/cbaseentity.h @@ -30,8 +30,6 @@ #include "schema.h" #include "tier1/utlstringtoken.h" -extern CGameConfig* g_GameConfig; - class CGameUI; class CEnvHudHint; class CPointViewControl; diff --git a/src/cs2_sdk/entity/cbaseplayerpawn.h b/src/cs2_sdk/entity/cbaseplayerpawn.h index c61e1b6e..55abdf05 100644 --- a/src/cs2_sdk/entity/cbaseplayerpawn.h +++ b/src/cs2_sdk/entity/cbaseplayerpawn.h @@ -19,14 +19,11 @@ #pragma once +#include "../entwatch.h" #include "cbaseentity.h" #include "cbasemodelentity.h" #include "services.h" -extern CConVar g_cvarDropMapWeapons; -void EW_PlayerDeathPre(CCSPlayerController* pController); -extern CConVar g_cvarEnableEntWatch; - class CBasePlayerPawn : public CBaseModelEntity { public: diff --git a/src/cs2_sdk/entity/ccsplayercontroller.h b/src/cs2_sdk/entity/ccsplayercontroller.h index b2f1206a..fe01a6ec 100644 --- a/src/cs2_sdk/entity/ccsplayercontroller.h +++ b/src/cs2_sdk/entity/ccsplayercontroller.h @@ -24,10 +24,6 @@ #include "cbaseplayercontroller.h" #include "services.h" -extern CServerSideClient* GetClientBySlot(CPlayerSlot slot); - -extern CGameEntitySystem* g_pEntitySystem; - class CCSPlayerController : public CBasePlayerController { public: diff --git a/src/cs2_sdk/entity/ccsplayerpawn.cpp b/src/cs2_sdk/entity/ccsplayerpawn.cpp index 9750afad..382bfb4e 100644 --- a/src/cs2_sdk/entity/ccsplayerpawn.cpp +++ b/src/cs2_sdk/entity/ccsplayerpawn.cpp @@ -33,7 +33,7 @@ void CCSPlayerPawn::FixPlayerModelAnimations() Teleport(nullptr, nullptr, &vec3_origin); SetMoveType(MOVETYPE_OBSOLETE); - new CTimer(0.02f, false, false, [hPawn, originalVelocity]() { + CTimer::Create(0.02f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hPawn, originalVelocity]() { CCSPlayerPawn* pPawn = hPawn.Get(); if (!pPawn || !pPawn->IsAlive()) diff --git a/src/cs2_sdk/entity/ccsweaponbase.h b/src/cs2_sdk/entity/ccsweaponbase.h index 4afcbe81..efcaa010 100644 --- a/src/cs2_sdk/entity/ccsweaponbase.h +++ b/src/cs2_sdk/entity/ccsweaponbase.h @@ -19,10 +19,9 @@ #pragma once +#include "../cs2fixes.h" #include "cbasemodelentity.h" -extern CGlobalVars* GetGlobals(); - enum gear_slot_t : uint32_t { GEAR_SLOT_INVALID = 0xffffffff, diff --git a/src/cs2_sdk/entity/services.h b/src/cs2_sdk/entity/services.h index 03bcdf93..7e0f1d6d 100644 --- a/src/cs2_sdk/entity/services.h +++ b/src/cs2_sdk/entity/services.h @@ -1,4 +1,4 @@ -/** +/** * ============================================================================= * CS2Fixes * Copyright (C) 2023-2025 Source2ZE @@ -25,14 +25,14 @@ #include #include +extern bool g_bAwsChangingTeam; + #define AMMO_OFFSET_HEGRENADE 13 #define AMMO_OFFSET_FLASHBANG 14 #define AMMO_OFFSET_SMOKEGRENADE 15 #define AMMO_OFFSET_MOLOTOV 16 #define AMMO_OFFSET_DECOY 17 -extern bool g_bAwsChangingTeam; - struct CSPerRoundStats_t { public: diff --git a/src/cs2_sdk/schema.cpp b/src/cs2_sdk/schema.cpp index 974ed7a0..48461cc2 100644 --- a/src/cs2_sdk/schema.cpp +++ b/src/cs2_sdk/schema.cpp @@ -26,8 +26,6 @@ #include "tier0/memdbgon.h" -extern CGlobalVars* GetGlobals(); - using SchemaKeyValueMap_t = std::map; using SchemaTableMap_t = std::map; @@ -45,9 +43,9 @@ static bool IsFieldNetworked(SchemaClassFieldData_t& field) return false; } -// Try to recursively find __m_pChainEntity in base classes +// Try to recursively find __m_pChainEntity in base classes // (e.g. CCSGameRules -> CTeamplayRules -> CMultiplayRules -> CGameRules, in this case it's in CGameRules) -static void InitChainOffset(SchemaClassInfoData_t *pClassInfo, SchemaKeyValueMap_t &keyValueMap) +static void InitChainOffset(SchemaClassInfoData_t* pClassInfo, SchemaKeyValueMap_t& keyValueMap) { short fieldsSize = pClassInfo->m_nFieldCount; SchemaClassFieldData_t* pFields = pClassInfo->m_pFields; @@ -55,7 +53,7 @@ static void InitChainOffset(SchemaClassInfoData_t *pClassInfo, SchemaKeyValueMap for (int i = 0; i < fieldsSize; ++i) { SchemaClassFieldData_t& field = pFields[i]; - + if (hash_32_fnv1a_const(field.m_pszName) != g_ChainKey) continue; @@ -73,7 +71,7 @@ static void InitChainOffset(SchemaClassInfoData_t *pClassInfo, SchemaKeyValueMap return InitChainOffset(pClassInfo->m_pBaseClasses[0].m_pClass, keyValueMap); } -static void InitSchemaKeyValueMap(SchemaClassInfoData_t *pClassInfo, SchemaKeyValueMap_t& keyValueMap) +static void InitSchemaKeyValueMap(SchemaClassInfoData_t* pClassInfo, SchemaKeyValueMap_t& keyValueMap) { short fieldsSize = pClassInfo->m_nFieldCount; SchemaClassFieldData_t* pFields = pClassInfo->m_pFields; diff --git a/src/cs2_sdk/schema.h b/src/cs2_sdk/schema.h index 284938b7..aa66a461 100644 --- a/src/cs2_sdk/schema.h +++ b/src/cs2_sdk/schema.h @@ -83,131 +83,133 @@ inline constexpr uint64_t hash_64_fnv1a_const(const char* const str, const uint6 return (str[0] == '\0') ? value : hash_64_fnv1a_const(&str[1], (value ^ uint64_t(str[0])) * prime_64_const); } -#define SCHEMA_FIELD_OFFSET(type, varName, extra_offset) \ - class varName##_prop \ - { \ - public: \ - std::add_lvalue_reference_t Get() \ - { \ - static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ - static const auto m_offset = offsetof(ThisClass, varName); \ - \ - uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ - \ - return *reinterpret_cast>(pThisClass + m_key.offset + extra_offset); \ - } \ - void Set(type& val) \ - { \ - static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ - static const auto m_chain = schema::FindChainOffset(m_className, m_classNameHash); \ - static const auto m_offset = offsetof(ThisClass, varName); \ - \ - uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ - \ - NetworkStateChanged(); \ - *reinterpret_cast>(pThisClass + m_key.offset + extra_offset) = val; \ - } \ - void NetworkStateChanged() \ - { \ - static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ - static const auto m_chain = schema::FindChainOffset(m_className, m_classNameHash); \ - static const auto m_offset = offsetof(ThisClass, varName); \ - \ - uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ - \ - if (m_chain != 0 && m_key.networked) \ - { \ - ::ChainNetworkStateChanged(pThisClass + m_chain, m_key.offset + extra_offset); \ - } \ - else if (m_key.networked) \ - { \ - if (!m_networkStateChangedOffset) \ - ::EntityNetworkStateChanged(pThisClass, m_key.offset + extra_offset); \ - else \ - ::NetworkVarStateChanged(pThisClass, m_key.offset + extra_offset, m_networkStateChangedOffset); \ - } \ - } \ - operator std::add_lvalue_reference_t() \ - { \ - return Get(); \ - } \ - std::add_lvalue_reference_t operator()() \ - { \ - return Get(); \ - } \ - std::add_lvalue_reference_t operator->() \ - { \ - return Get(); \ - } \ - void operator()(type val) \ - { \ - Set(val); \ - } \ - std::add_lvalue_reference_t operator=(type val) \ - { \ - Set(val); \ - return Get(); \ - } \ - std::add_lvalue_reference_t operator=(varName##_prop& val) \ - { \ - Set(val()); \ - return Get(); \ - } \ - private: \ - /*Prevent accidentally copying this wrapper class instead of the underlying field*/ \ - varName##_prop(const varName##_prop&) = delete; \ - static constexpr auto m_varNameHash = hash_32_fnv1a_const(#varName); \ +#define SCHEMA_FIELD_OFFSET(type, varName, extra_offset) \ + class varName##_prop \ + { \ + public: \ + std::add_lvalue_reference_t Get() \ + { \ + static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ + static const auto m_offset = offsetof(ThisClass, varName); \ + \ + uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ + \ + return *reinterpret_cast>(pThisClass + m_key.offset + extra_offset); \ + } \ + void Set(type& val) \ + { \ + static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ + static const auto m_chain = schema::FindChainOffset(m_className, m_classNameHash); \ + static const auto m_offset = offsetof(ThisClass, varName); \ + \ + uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ + \ + NetworkStateChanged(); \ + *reinterpret_cast>(pThisClass + m_key.offset + extra_offset) = val; \ + } \ + void NetworkStateChanged() \ + { \ + static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ + static const auto m_chain = schema::FindChainOffset(m_className, m_classNameHash); \ + static const auto m_offset = offsetof(ThisClass, varName); \ + \ + uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ + \ + if (m_chain != 0 && m_key.networked) \ + { \ + ::ChainNetworkStateChanged(pThisClass + m_chain, m_key.offset + extra_offset); \ + } \ + else if (m_key.networked) \ + { \ + if (!m_networkStateChangedOffset) \ + ::EntityNetworkStateChanged(pThisClass, m_key.offset + extra_offset); \ + else \ + ::NetworkVarStateChanged(pThisClass, m_key.offset + extra_offset, m_networkStateChangedOffset); \ + } \ + } \ + operator std::add_lvalue_reference_t() \ + { \ + return Get(); \ + } \ + std::add_lvalue_reference_t operator()() \ + { \ + return Get(); \ + } \ + std::add_lvalue_reference_t operator->() \ + { \ + return Get(); \ + } \ + void operator()(type val) \ + { \ + Set(val); \ + } \ + std::add_lvalue_reference_t operator=(type val) \ + { \ + Set(val); \ + return Get(); \ + } \ + std::add_lvalue_reference_t operator=(varName##_prop& val) \ + { \ + Set(val()); \ + return Get(); \ + } \ + \ + private: \ + /*Prevent accidentally copying this wrapper class instead of the underlying field*/ \ + varName##_prop(const varName##_prop&) = delete; \ + static constexpr auto m_varNameHash = hash_32_fnv1a_const(#varName); \ } varName; -#define SCHEMA_FIELD_POINTER_OFFSET(type, varName, extra_offset) \ - class varName##_prop \ - { \ - public: \ - type* Get() \ - { \ - static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ - static const auto m_offset = offsetof(ThisClass, varName); \ - \ - uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ - \ - return reinterpret_cast>(pThisClass + m_key.offset + extra_offset); \ - } \ - void NetworkStateChanged() /*Call this after editing the field*/ \ - { \ - static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ - static const auto m_chain = schema::FindChainOffset(m_className, m_classNameHash); \ - static const auto m_offset = offsetof(ThisClass, varName); \ - \ - uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ - \ - if (m_chain != 0 && m_key.networked) \ - { \ - ::ChainNetworkStateChanged(pThisClass + m_chain, m_key.offset + extra_offset); \ - } \ - else if (m_key.networked) \ - { \ - if (!m_networkStateChangedOffset) \ - ::EntityNetworkStateChanged(pThisClass, m_key.offset + extra_offset); \ - else \ - ::NetworkVarStateChanged(pThisClass, m_key.offset + extra_offset, m_networkStateChangedOffset); \ - } \ - } \ - operator type*() \ - { \ - return Get(); \ - } \ - type* operator()() \ - { \ - return Get(); \ - } \ - type* operator->() \ - { \ - return Get(); \ - } \ - private: \ - /*Prevent accidentally copying this wrapper class instead of the underlying field*/ \ - varName##_prop(const varName##_prop&) = delete; \ - static constexpr auto m_varNameHash = hash_32_fnv1a_const(#varName); \ +#define SCHEMA_FIELD_POINTER_OFFSET(type, varName, extra_offset) \ + class varName##_prop \ + { \ + public: \ + type* Get() \ + { \ + static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ + static const auto m_offset = offsetof(ThisClass, varName); \ + \ + uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ + \ + return reinterpret_cast>(pThisClass + m_key.offset + extra_offset); \ + } \ + void NetworkStateChanged() /*Call this after editing the field*/ \ + { \ + static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ + static const auto m_chain = schema::FindChainOffset(m_className, m_classNameHash); \ + static const auto m_offset = offsetof(ThisClass, varName); \ + \ + uintptr_t pThisClass = ((uintptr_t)this - m_offset); \ + \ + if (m_chain != 0 && m_key.networked) \ + { \ + ::ChainNetworkStateChanged(pThisClass + m_chain, m_key.offset + extra_offset); \ + } \ + else if (m_key.networked) \ + { \ + if (!m_networkStateChangedOffset) \ + ::EntityNetworkStateChanged(pThisClass, m_key.offset + extra_offset); \ + else \ + ::NetworkVarStateChanged(pThisClass, m_key.offset + extra_offset, m_networkStateChangedOffset); \ + } \ + } \ + operator type*() \ + { \ + return Get(); \ + } \ + type* operator()() \ + { \ + return Get(); \ + } \ + type* operator->() \ + { \ + return Get(); \ + } \ + \ + private: \ + /*Prevent accidentally copying this wrapper class instead of the underlying field*/ \ + varName##_prop(const varName##_prop&) = delete; \ + static constexpr auto m_varNameHash = hash_32_fnv1a_const(#varName); \ } varName; // Use this when you want the member's value itself @@ -219,13 +221,14 @@ inline constexpr uint64_t hash_64_fnv1a_const(const char* const str, const uint6 SCHEMA_FIELD_POINTER_OFFSET(type, varName, 0) // If the class needs a specific offset for its NetworkStateChanged (like CEconItemView), use this and provide the offset -#define DECLARE_SCHEMA_CLASS_BASE(ClassName, offset) \ - private: \ - typedef ClassName ThisClass; \ - static constexpr const char* m_className = #ClassName; \ - static constexpr uint32_t m_classNameHash = hash_32_fnv1a_const(#ClassName);\ - static constexpr int m_networkStateChangedOffset = offset; \ - public: +#define DECLARE_SCHEMA_CLASS_BASE(ClassName, offset) \ +private: \ + typedef ClassName ThisClass; \ + static constexpr const char* m_className = #ClassName; \ + static constexpr uint32_t m_classNameHash = hash_32_fnv1a_const(#ClassName); \ + static constexpr int m_networkStateChangedOffset = offset; \ + \ +public: #define DECLARE_SCHEMA_CLASS(className) DECLARE_SCHEMA_CLASS_BASE(className, 0) diff --git a/src/cs2_sdk/serversideclient.h b/src/cs2_sdk/serversideclient.h index e38b7b72..be76fee0 100644 --- a/src/cs2_sdk/serversideclient.h +++ b/src/cs2_sdk/serversideclient.h @@ -196,7 +196,6 @@ class CServerSideClientBase : public CUtlSlot, public INetworkChannelNotify, pub public: virtual bool ProcessMove(const CCLCMsg_Move_t& msg) = 0; virtual bool ProcessVoiceData(const CCLCMsg_VoiceData_t& msg) = 0; - virtual bool ProcessFileCRCCheck(const CCLCMsg_FileCRCCheck_t& msg) = 0; virtual bool ProcessRespondCvarValue(const CCLCMsg_RespondCvarValue_t& msg) = 0; virtual bool ProcessPacketStart(const NetMessagePacketStart_t& msg) = 0; @@ -294,7 +293,7 @@ class CServerSideClientBase : public CUtlSlot, public INetworkChannelNotify, pub CEntityIndex m_nEntityIndex; // 92 CNetworkGameServerBase* m_Server; // 96 INetChannel* m_NetChannel; // 104 - uint8 m_nUnkVariable; // 112 + uint16 m_nConnectionTypeFlags; // 112 bool m_bMarkedToKick; // 113 SignonState_t m_nSignonState; // 116 bool m_bSplitScreenUser; // 120 diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index ecf0b8f3..6fd83b48 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -28,10 +28,8 @@ #include "ctimer.h" #include "detours.h" #include "discord.h" -#include "engine/igameeventsystem.h" #include "entities.h" #include "entity/ccsplayercontroller.h" -#include "entity/cgamerules.h" #include "entity/services.h" #include "entitylistener.h" #include "entitysystem.h" @@ -65,11 +63,6 @@ #include "tier0/memdbgon.h" -double g_flUniversalTime; -float g_flLastTickedTime; -bool g_bHasTicked; -int g_iRoundNum = 0; - void Message(const char* msg, ...) { va_list args; @@ -129,18 +122,15 @@ SH_DECL_MANUALHOOK3_void(DropWeapon, 0, 0, 0, CBasePlayerWeapon*, Vector*, Vecto SH_DECL_HOOK1_void(IServer, SetGameSpawnGroupMgr, SH_NOATTRIB, 0, IGameSpawnGroupMgr*); CS2Fixes g_CS2Fixes; - IGameEventSystem* g_gameEventSystem = nullptr; IGameEventManager2* g_gameEventManager = nullptr; CGameEntitySystem* g_pEntitySystem = nullptr; -CEntityListener* g_pEntityListener = nullptr; -CPlayerManager* g_playerManager = nullptr; IVEngineServer2* g_pEngineServer2 = nullptr; -CGameConfig* g_GameConfig = nullptr; ISteamHTTP* g_http = nullptr; CSteamGameServerAPIContext g_steamAPI; CCSGameRules* g_pGameRules = nullptr; // Will be null between map end & new map startup, null check if necessary! CSpawnGroupMgrGameSystem* g_pSpawnGroupMgr = nullptr; // Will be null between map end & new map startup, null check if necessary! + int g_iCGamePlayerEquipUseId = -1; int g_iCGamePlayerEquipPrecacheId = -1; int g_iCTriggerGravityPrecacheId = -1; @@ -154,6 +144,10 @@ int g_iPhysicsTouchShuffle = -1; int g_iWeaponServiceDropWeaponId = -1; int g_iSetGameSpawnGroupMgrId = -1; +double g_flUniversalTime = 0.0; +float g_flLastTickedTime = 0.0f; +bool g_bHasTicked = false; + CGameEntitySystem* GameEntitySystem() { static int offset = g_GameConfig->GetOffset("GameEntitySystem"); @@ -392,19 +386,19 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool RegisterWeaponCommands(); // Check hide distance - new CTimer(0.5f, true, true, []() { + CTimer::Create(0.5f, TIMERFLAG_NONE, []() { g_playerManager->CheckHideDistances(); return 0.5f; }); // Check for the expiration of infractions like mutes or gags - new CTimer(30.0f, true, true, []() { + CTimer::Create(30.0f, TIMERFLAG_NONE, []() { g_playerManager->CheckInfractions(); return 30.0f; }); // Check for idle players and kick them if permitted by cs2f_idle_kick_* 'convars' - new CTimer(5.0f, true, true, []() { + CTimer::Create(5.0f, TIMERFLAG_NONE, []() { g_pIdleSystem->CheckForIdleClients(); return 5.0f; }); @@ -483,7 +477,7 @@ bool CS2Fixes::Unload(char* error, size_t maxlen) FlushAllDetours(); UndoPatches(); - RemoveTimers(); + RemoveAllTimers(); UnregisterEventListeners(); if (g_GameConfig) @@ -644,7 +638,7 @@ void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISou RegisterEventListeners(); if (g_bHasTicked) - RemoveMapTimers(); + RemoveTimers(TIMERFLAG_MAP); g_bHasTicked = false; @@ -993,39 +987,10 @@ void CS2Fixes::Hook_GameFramePost(bool simulating, bool bFirstTick, bool bLastTi g_flLastTickedTime = GetGlobals()->curtime; g_bHasTicked = true; - for (int i = g_timers.Tail(); i != g_timers.InvalidIndex();) - { - auto timer = g_timers[i]; - - int prevIndex = i; - i = g_timers.Previous(i); - - if (timer->m_flLastExecute == -1) - timer->m_flLastExecute = g_flUniversalTime; - - // Timer execute - if (timer->m_flLastExecute + timer->m_flInterval <= g_flUniversalTime) - { - if ((!timer->m_bPreserveRoundChange && timer->m_iRoundNum != g_iRoundNum) || !timer->Execute()) - { - delete timer; - g_timers.Remove(prevIndex); - } - else - { - timer->m_flLastExecute = g_flUniversalTime; - } - } - } - - if (g_cvarEnableZR.Get()) - CZRRegenTimer::Tick(); - + RunTimers(); EntityHandler_OnGameFramePost(simulating, GetGlobals()->tickcount); } -extern CConVar g_cvarFlashLightTransmitOthers; - void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts, CBitVec<16384>&, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities) { @@ -1337,7 +1302,6 @@ void CS2Fixes::OnLevelInit(char const* pMapName, bool background) { Message("OnLevelInit(%s)\n", pMapName); - g_iRoundNum = 0; // run our cfg g_pEngineServer2->ServerCommand("exec cs2fixes/cs2fixes"); diff --git a/src/cs2fixes.h b/src/cs2fixes.h index 7b66ba66..68a4502b 100644 --- a/src/cs2fixes.h +++ b/src/cs2fixes.h @@ -1,4 +1,4 @@ -/** +/** * ============================================================================= * CS2Fixes * Copyright (C) 2023-2025 Source2ZE @@ -19,10 +19,13 @@ #pragma once +#include "engine/igameeventsystem.h" +#include "entity/cgamerules.h" #include "gamesystems/spawngroup_manager.h" #include "igameevents.h" #include "networksystem/inetworkserializer.h" #include "public/ics2fixes.h" +#include "steam/isteamhttp.h" #include #include #include @@ -41,6 +44,21 @@ struct TouchLinked_t; class CCSPlayer_WeaponServices; class CBasePlayerWeapon; +extern IGameEventSystem* g_gameEventSystem; +extern IGameEventManager2* g_gameEventManager; +extern CGameEntitySystem* g_pEntitySystem; +extern IVEngineServer2* g_pEngineServer2; +extern ISteamHTTP* g_http; +extern CSteamGameServerAPIContext g_steamAPI; +extern CCSGameRules* g_pGameRules; +extern CSpawnGroupMgrGameSystem* g_pSpawnGroupMgr; +extern double g_flUniversalTime; +extern CGlobalVars* GetGlobals(); +extern CUtlVector* GetClientList(); +extern CServerSideClient* GetClientBySlot(CPlayerSlot slot); +extern void FullUpdateAllClients(); +extern CConVar g_cvarDropMapWeapons; + class CS2Fixes : public ISmmPlugin, public IMetamodListener, public ICS2Fixes { public: diff --git a/src/ctimer.cpp b/src/ctimer.cpp index 6578b965..8e018de9 100644 --- a/src/ctimer.cpp +++ b/src/ctimer.cpp @@ -19,24 +19,75 @@ #include "ctimer.h" -CUtlLinkedList g_timers; +std::list> g_timers; -void RemoveTimers() +void RunTimers() { - g_timers.PurgeAndDeleteElements(); + auto iterator = g_timers.begin(); + + while (iterator != g_timers.end()) + { + auto pTimer = *iterator; + pTimer->Initialize(); + + // Timer execute + if (pTimer->GetLastExecute() + pTimer->GetInterval() <= g_flUniversalTime && !pTimer->Execute(true)) + iterator = g_timers.erase(iterator); + else + iterator++; + } } -void RemoveMapTimers() +void RemoveAllTimers() { - for (int i = g_timers.Tail(); i != g_timers.InvalidIndex();) - { - int prevIndex = i; - i = g_timers.Previous(i); + g_timers.clear(); +} + +void RemoveTimers(uint64 iTimerFlag) +{ + auto iterator = g_timers.begin(); + + while (iterator != g_timers.end()) + if ((*iterator)->IsTimerFlagSet(iTimerFlag)) + iterator = g_timers.erase(iterator); + else + iterator++; +} + +std::weak_ptr CTimer::Create(float flInitialInterval, uint64 nTimerFlags, std::function func) +{ + auto pTimer = std::make_shared(flInitialInterval, nTimerFlags, func, _timer_constructor_tag{}); - if (g_timers[prevIndex]->m_bPreserveMapChange) - continue; + g_timers.push_back(pTimer); + return pTimer; +} + +bool CTimer::Execute(bool bAutomaticExecute) +{ + SetInterval(m_func()); + SetLastExecute(g_flUniversalTime); + + bool bContinue = GetInterval() >= 0; + + // Only scan the timer list if this isn't an automatic execute (RunTimers() already has the iterator to erase) + if (!bAutomaticExecute && !bContinue) + Cancel(); + + return bContinue; +} + +void CTimer::Cancel() +{ + auto iterator = g_timers.begin(); + + while (iterator != g_timers.end()) + { + if (*iterator == shared_from_this()) + { + g_timers.erase(iterator); + break; + } - delete g_timers[prevIndex]; - g_timers.Remove(prevIndex); + iterator++; } } \ No newline at end of file diff --git a/src/ctimer.h b/src/ctimer.h index ef19fb03..e8a314cd 100644 --- a/src/ctimer.h +++ b/src/ctimer.h @@ -18,51 +18,70 @@ */ #pragma once -#include "utllinkedlist.h" +#include "cs2fixes.h" #include +#include +#include -extern int g_iRoundNum; +// clang-format off +#define TIMERFLAG_NONE (0) +#define TIMERFLAG_MAP (1 << 0) // Only valid for this map, cancels on map change +#define TIMERFLAG_ROUND (1 << 1) // Only valid for this round, cancels on new round +// clang-format on class CTimerBase { +protected: + CTimerBase(float flInitialInterval, uint64 nTimerFlags) : + m_flInterval(flInitialInterval), m_nTimerFlags(nTimerFlags) + {} + + void SetInterval(float flInterval) { m_flInterval = flInterval; } + void SetLastExecute(float flLastExecute) { m_flLastExecute = flLastExecute; } + public: - CTimerBase(float flInitialInterval, bool bPreserveMapChange, bool bPreserveRoundChange) : - m_flInterval(flInitialInterval), m_bPreserveMapChange(bPreserveMapChange), m_bPreserveRoundChange(bPreserveRoundChange) + virtual bool Execute(bool bAutomaticExecute = false) = 0; + virtual void Cancel() = 0; + + float GetInterval() { return m_flInterval; } + float GetLastExecute() { return m_flLastExecute; } + bool IsTimerFlagSet(uint64 iTimerFlag) { return !iTimerFlag || (m_nTimerFlags & iTimerFlag); } + void Initialize() { - m_iRoundNum = g_iRoundNum; + if (m_flLastExecute == -1) + m_flLastExecute = g_flUniversalTime; } - virtual bool Execute() = 0; - +private: float m_flInterval; float m_flLastExecute = -1; - bool m_bPreserveMapChange; - bool m_bPreserveRoundChange; - int m_iRoundNum; + uint64 m_nTimerFlags; }; -extern CUtlLinkedList g_timers; - // Timer functions should return the time until next execution, or a negative value like -1.0f to stop // Having an interval of 0 is fine, in this case it will run on every game frame -class CTimer : public CTimerBase +class CTimer : public CTimerBase, public std::enable_shared_from_this { -public: - CTimer(float flInitialInterval, bool bPreserveMapChange, bool bPreserveRoundChange, std::function func) : - CTimerBase(flInitialInterval, bPreserveMapChange, bPreserveRoundChange), m_func(func) +private: + // Silly workaround to achieve a "private constructor" only Create() can call + struct _timer_constructor_tag { - g_timers.AddToTail(this); + explicit _timer_constructor_tag() = default; }; - inline bool Execute() override - { - m_flInterval = m_func(); +public: + CTimer(float flInitialInterval, uint64 nTimerFlags, std::function func, _timer_constructor_tag) : + CTimerBase(flInitialInterval, nTimerFlags), m_func(func) + {} - return m_flInterval >= 0; - } + static std::weak_ptr Create(float flInitialInterval, uint64 nTimerFlags, std::function func); + bool Execute(bool bAutomaticExecute) override; + void Cancel() override; +private: std::function m_func; }; -void RemoveTimers(); -void RemoveMapTimers(); \ No newline at end of file +void RunTimers(); +void RemoveAllTimers(); +void RemoveTimers(uint64 iTimerFlag); \ No newline at end of file diff --git a/src/customio.cpp b/src/customio.cpp index afd94082..b3430d4b 100644 --- a/src/customio.cpp +++ b/src/customio.cpp @@ -33,8 +33,6 @@ #include #include -extern CGlobalVars* GetGlobals(); - struct AddOutputKey_t { AddOutputKey_t(const char* pName, int32_t parts, bool prefix = false) : @@ -537,7 +535,7 @@ bool IgnitePawn(CCSPlayerPawn* pPawn, float flDuration, CBaseEntity* pInflictor, CHandle hAttacker(pAttacker); CHandle hAbility(pAbility); - new CTimer(0.f, false, false, [hPawn, hInflictor, hAttacker, hAbility, nDamageType]() { + CTimer::Create(0.f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hPawn, hInflictor, hAttacker, hAbility, nDamageType]() { CCSPlayerPawn* pPawn = hPawn.Get(); if (!pPawn || !GetGlobals()) diff --git a/src/customio.h b/src/customio.h index 447f37da..799ed864 100644 --- a/src/customio.h +++ b/src/customio.h @@ -19,12 +19,17 @@ #pragma once +#include "convar.h" +#include "utlstring.h" + class CEntityIdentity; class CEntityInstance; class CBaseEntity; class CCSPlayerPawn; enum DamageTypes_t : unsigned int; +extern CConVar g_cvarBurnParticle; + bool CustomIO_HandleInput(CEntityInstance* pEntityInstance, const char* pParams, CEntityInstance* pActivator, diff --git a/src/detours.cpp b/src/detours.cpp index 2c31c01f..5610e8d1 100644 --- a/src/detours.cpp +++ b/src/detours.cpp @@ -54,12 +54,6 @@ #include "tier0/memdbgon.h" -extern CGlobalVars* GetGlobals(); -extern CGameEntitySystem* g_pEntitySystem; -extern IGameEventManager2* g_gameEventManager; -extern CCSGameRules* g_pGameRules; -extern CUtlVector* GetClientList(); - CUtlVector g_vecDetours; DECLARE_DETOUR(UTIL_SayTextFilter, Detour_UTIL_SayTextFilter); @@ -559,7 +553,8 @@ void* FASTCALL Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd uint64 button = iterator->button(); // Remove normal subtick movement inputs by button & subtick movement viewangles by pitch/yaw - if ((button >= IN_JUMP && button <= IN_MOVERIGHT && button != IN_USE) || iterator->analog_pitch_delta() != 0.0f || iterator->analog_yaw_delta() != 0.0f) + // Unfortunately, we also need to ignore IN_JUMP, because de-subticking jumps somehow conflicts with other subtick inputs pressed at the same time + if ((button >= IN_DUCK && button <= IN_MOVERIGHT && button != IN_USE) || iterator->analog_pitch_delta() != 0.0f || iterator->analog_yaw_delta() != 0.0f) subtickMoves->erase(iterator); else iterator++; diff --git a/src/detours.h b/src/detours.h index e14a00bd..b2642b52 100644 --- a/src/detours.h +++ b/src/detours.h @@ -1,4 +1,4 @@ -/** +/** * ============================================================================= * CS2Fixes * Copyright (C) 2023-2025 Source2ZE @@ -53,6 +53,10 @@ class Vector; class QAngle; class CEconItemView; +// Add callback functions to this map that wish to hook into Detour_CEntityIOOutput_FireOutputInternal +// to make it more modular/cleaner than shoving everything into the detour (buttonwatch, entwatch, etc.) +extern std::map> mapIOFunctions; + enum class AcquireMethod { PickUp, @@ -78,10 +82,6 @@ bool InitDetours(CGameConfig* gameConfig); void FlushAllDetours(); bool SetupFireOutputInternalDetour(); -// Add callback functions to this map that wish to hook into Detour_CEntityIOOutput_FireOutputInternal -// to make it more modular/cleaner than shoving everything into the detour (buttonwatch, entwatch, etc.) -extern std::map> mapIOFunctions; - void FASTCALL Detour_UTIL_SayTextFilter(IRecipientFilter&, const char*, CCSPlayerController*, uint64); void FASTCALL Detour_UTIL_SayText2Filter(IRecipientFilter&, CCSPlayerController*, uint64, const char*, const char*, const char*, const char*, const char*); bool FASTCALL Detour_IsHearingClient(void*, int); diff --git a/src/entities.cpp b/src/entities.cpp index 568b73b8..135583a1 100644 --- a/src/entities.cpp +++ b/src/entities.cpp @@ -34,8 +34,6 @@ // #define ENTITY_HANDLER_ASSERTION -extern CCSGameRules* g_pGameRules; - static constexpr uint32_t ENTITY_MURMURHASH_SEED = 0x97984357; static constexpr uint32_t ENTITY_UNIQUE_INVALID = ~0u; @@ -97,7 +95,7 @@ static void DelayInput(CBaseEntity* pCaller, const char* input, const char* para { const auto eh = pCaller->GetHandle(); - new CTimer(0.f, false, false, [eh, input, param]() { + CTimer::Create(0.f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [eh, input, param]() { if (const auto entity = reinterpret_cast(eh.Get())) entity->AcceptInput(input, param, nullptr, entity); @@ -111,7 +109,7 @@ static void DelayInput(CBaseEntity* pCaller, CBaseEntity* pActivator, const char const auto eh = pCaller->GetHandle(); const auto ph = pActivator->GetHandle(); - new CTimer(0.f, false, false, [eh, ph, input, param]() { + CTimer::Create(0.f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [eh, ph, input, param]() { const auto player = reinterpret_cast(ph.Get()); if (const auto entity = reinterpret_cast(eh.Get())) entity->AcceptInput(input, param, player, entity); diff --git a/src/entitylistener.cpp b/src/entitylistener.cpp index 182b35bc..23ed8300 100644 --- a/src/entitylistener.cpp +++ b/src/entitylistener.cpp @@ -27,8 +27,7 @@ #include "gameconfig.h" #include "plat.h" -extern CGameConfig* g_GameConfig; -extern CCSGameRules* g_pGameRules; +CEntityListener* g_pEntityListener = nullptr; CConVar g_cvarGrenadeNoBlock("cs2f_noblock_grenades", FCVAR_NONE, "Whether to use noblock on grenade projectiles", false); diff --git a/src/entitylistener.h b/src/entitylistener.h index 6a57ef4a..d7992f93 100644 --- a/src/entitylistener.h +++ b/src/entitylistener.h @@ -20,12 +20,12 @@ #pragma once #include "entitysystem.h" -extern CGameEntitySystem* g_pEntitySystem; - class CEntityListener : public IEntityListener { void OnEntitySpawned(CEntityInstance* pEntity) override; void OnEntityCreated(CEntityInstance* pEntity) override; void OnEntityDeleted(CEntityInstance* pEntity) override; void OnEntityParentChanged(CEntityInstance* pEntity, CEntityInstance* pNewParent) override; -}; \ No newline at end of file +}; + +extern CEntityListener* g_pEntityListener; \ No newline at end of file diff --git a/src/entwatch.cpp b/src/entwatch.cpp index b63391a6..8cc83a00 100644 --- a/src/entwatch.cpp +++ b/src/entwatch.cpp @@ -25,6 +25,8 @@ #include "detours.h" #include "engine/igameeventsystem.h" #include "entity/cbasebutton.h" +#include "entity/ccsplayercontroller.h" +#include "entity/ccsplayerpawn.h" #include "entity/cgamerules.h" #include "entity/cmathcounter.h" #include "entity/cpointworldtext.h" @@ -49,12 +51,6 @@ #include "tier0/memdbgon.h" -extern CGlobalVars* GetGlobals(); -extern IGameEventManager2* g_gameEventManager; -extern IGameEventSystem* g_gameEventSystem; -extern INetworkMessages* g_pNetworkMessages; -extern CCSGameRules* g_pGameRules; - CEWHandler* g_pEWHandler = nullptr; SH_DECL_MANUALHOOK1_void(CBaseButton_Use, 0, 0, 0, InputData_t*); @@ -986,7 +982,7 @@ void EWItemInstance::StartGlow() int a = colorGlow.a(); int iTeam = iTeamNum; CHandle hWep = pItemWeapon->GetHandle(); - new CTimer(0.1f, false, false, [hWep, iTeam, r, g, b, a] { + CTimer::Create(0.1f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hWep, iTeam, r, g, b, a] { CCSWeaponBase* pWep = hWep.Get(); if (pWep) { @@ -1625,7 +1621,7 @@ void CEWHandler::PlayerPickup(CCSPlayerPawn* pPawn, int iItemInstance) if (g_cvarEnableEntwatchHud.Get() && !m_bHudTicking) { m_bHudTicking = true; - new CTimer(EW_HUD_TICKRATE, false, false, [] { + CTimer::Create(EW_HUD_TICKRATE, TIMERFLAG_MAP | TIMERFLAG_ROUND, [] { return EW_UpdateHud(); }); } @@ -1940,7 +1936,7 @@ void CEWHandler::Hook_Use(InputData_t* pInput) if (!pController || pController->GetPlayerSlot() != pItem->iOwnerSlot) RETURN_META(resVal); - + // // WE SHOW USE MESSAGE IN FireOutput // This is just to prevent unnecessary stuff with buttons like movement @@ -2077,7 +2073,7 @@ void EW_OnEntitySpawned(CEntityInstance* pEntity) if (itemindex != -1) { std::shared_ptr item = g_pEWHandler->vecItems[itemindex]; - new CTimer(0.5, false, false, [item] { + CTimer::Create(0.5, TIMERFLAG_MAP | TIMERFLAG_ROUND, [item] { if (item) item->FindExistingHandlers(); return -1.0f; @@ -2097,7 +2093,7 @@ void EW_OnEntitySpawned(CEntityInstance* pEntity) // delay it cuz stupid spawn orders CHandle hEntity = pEnt->GetHandle(); - new CTimer(0.25, false, false, [hEntity] { + CTimer::Create(0.25, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hEntity] { if (hEntity.Get()) g_pEWHandler->RegisterHandler(hEntity.Get()); return -1.0; diff --git a/src/entwatch.h b/src/entwatch.h index 8ce0dcb3..42ba3cd3 100644 --- a/src/entwatch.h +++ b/src/entwatch.h @@ -21,14 +21,18 @@ #include "common.h" #include "ctimer.h" -#include "entity/ccsplayercontroller.h" -#include "entity/ccsplayerpawn.h" #include "eventlistener.h" #include "gamesystem.h" #include "vendor/nlohmann/json_fwd.hpp" using ordered_json = nlohmann::ordered_json; +class CCSPlayerController; +class CCSPlayerPawn; + +extern CConVar g_cvarEnableEntWatch; +extern CConVar g_cvarEnableEntwatchHud; + #define EW_PREFIX " \4[EntWatch]\1 " #define EW_PREF_HUD_MODE "entwatch_hud" @@ -45,9 +49,6 @@ using ordered_json = nlohmann::ordered_json; #define EW_HUD_TICKRATE 0.5f -extern CConVar g_cvarEnableEntWatch; -extern CConVar g_cvarEnableEntwatchHud; - enum EWHandlerType { Type_None, diff --git a/src/eventlistener.h b/src/eventlistener.h index 940b9644..769e93d9 100644 --- a/src/eventlistener.h +++ b/src/eventlistener.h @@ -23,11 +23,12 @@ #include "utlstring.h" #include "utlvector.h" -typedef void (*FnEventListenerCallback)(IGameEvent* event); - class CGameEventListener; extern CUtlVector g_vecEventListeners; +extern CConVar g_cvarFreeArmor; + +typedef void (*FnEventListenerCallback)(IGameEvent* event); class CGameEventListener : public IGameEventListener2 { diff --git a/src/events.cpp b/src/events.cpp index 6eb6a88d..cef1c6b1 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -38,15 +38,6 @@ #include "tier0/memdbgon.h" -extern IGameEventManager2* g_gameEventManager; -extern IServerGameClients* g_pSource2GameClients; -extern CGameEntitySystem* g_pEntitySystem; -extern CGlobalVars* GetGlobals(); -extern CCSGameRules* g_pGameRules; -extern IVEngineServer2* g_pEngineServer2; - -extern int g_iRoundNum; - CUtlVector g_vecEventListeners; void RegisterEventListeners() @@ -79,11 +70,9 @@ void UnregisterEventListeners() CConVar g_cvarPurgeEntityNames("cs2f_purge_entity_strings", FCVAR_NONE, "Whether to purge the EntityNames stringtable on new rounds", false); -extern void FullUpdateAllClients(); - GAME_EVENT_F(round_prestart) { - g_iRoundNum++; + RemoveTimers(TIMERFLAG_ROUND); if (g_cvarPurgeEntityNames.Get()) { @@ -153,7 +142,7 @@ GAME_EVENT_F(player_spawn) CHandle hController = pController->GetHandle(); // Gotta do this on the next frame... - new CTimer(0.0f, false, false, [hController]() { + CTimer::Create(0.0f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hController]() { CCSPlayerController* pController = hController.Get(); if (!pController) diff --git a/src/gameconfig.cpp b/src/gameconfig.cpp index f60fe575..458a3f7b 100644 --- a/src/gameconfig.cpp +++ b/src/gameconfig.cpp @@ -1,6 +1,8 @@ #include "gameconfig.h" #include "addresses.h" +CGameConfig* g_GameConfig = nullptr; + CGameConfig::CGameConfig(const std::string& gameDir, const std::string& path) { this->m_szGameDir = gameDir; diff --git a/src/gameconfig.h b/src/gameconfig.h index f6085ae7..479920fb 100644 --- a/src/gameconfig.h +++ b/src/gameconfig.h @@ -39,3 +39,5 @@ class CGameConfig std::unordered_map m_umLibraries; std::unordered_map m_umPatches; }; + +extern CGameConfig* g_GameConfig; diff --git a/src/gamesystem.cpp b/src/gamesystem.cpp index d9c0c612..366fc849 100644 --- a/src/gamesystem.cpp +++ b/src/gamesystem.cpp @@ -21,6 +21,7 @@ #include "addresses.h" #include "adminsystem.h" #include "common.h" +#include "customio.h" #include "entities.h" #include "entity/cgamerules.h" #include "gameconfig.h" @@ -32,15 +33,8 @@ #include "tier0/memdbgon.h" -extern CGlobalVars* GetGlobals(); -extern CGameConfig* g_GameConfig; -extern CCSGameRules* g_pGameRules; -extern CSpawnGroupMgrGameSystem* g_pSpawnGroupMgr; -extern CUtlVector* GetClientList(); - -CBaseGameSystemFactory** CBaseGameSystemFactory::sm_pFirst = nullptr; - CGameSystem g_GameSystem; +CBaseGameSystemFactory** CBaseGameSystemFactory::sm_pFirst = nullptr; CGameSystemStaticCustomFactory* CGameSystem::sm_Factory = nullptr; // This mess is needed to get the pointer to sm_pFirst so we can insert game systems @@ -155,8 +149,6 @@ bool UnregisterGameSystem() return true; } -extern CConVar g_cvarBurnParticle; - GS_EVENT_MEMBER(CGameSystem, BuildGameSessionManifest) { Message("CGameSystem::BuildGameSessionManifest\n"); diff --git a/src/gamesystem.h b/src/gamesystem.h index bf88548d..2c764ccc 100644 --- a/src/gamesystem.h +++ b/src/gamesystem.h @@ -90,6 +90,8 @@ class CGameSystem : public CBaseGameSystem static CGameSystemStaticCustomFactory* sm_Factory; }; +extern CGameSystem g_GameSystem; + // Quick and dirty definition // MSVC for whatever reason flips overload ordering, and this has three of them // So this is based on the linux bin which is correct, and MSVC will flip it to match the windows bin, fun @@ -125,5 +127,3 @@ struct AddedGameSystem_t int m_nPriority; int m_nInsertionOrder; }; - -extern CGameSystem g_GameSystem; diff --git a/src/httpmanager.cpp b/src/httpmanager.cpp index f1fd55bc..b557f487 100644 --- a/src/httpmanager.cpp +++ b/src/httpmanager.cpp @@ -30,8 +30,6 @@ #include "vendor/nlohmann/json.hpp" #include -extern ISteamHTTP* g_http; - HTTPManager g_HTTPManager; #undef strdup diff --git a/src/httpmanager.h b/src/httpmanager.h index 824681de..fdc41e03 100644 --- a/src/httpmanager.h +++ b/src/httpmanager.h @@ -37,9 +37,6 @@ using json = nlohmann::json; -class HTTPManager; -extern HTTPManager g_HTTPManager; - #define CompletedCallback std::function #define ErrorCallback std::function @@ -101,3 +98,5 @@ class HTTPManager CompletedCallback callbackCompleted, ErrorCallback callbackError, std::vector* headers); }; + +extern HTTPManager g_HTTPManager; diff --git a/src/idlemanager.cpp b/src/idlemanager.cpp index c470cfe8..87463fb7 100644 --- a/src/idlemanager.cpp +++ b/src/idlemanager.cpp @@ -19,12 +19,9 @@ #include "idlemanager.h" #include "commands.h" +#include "cs2fixes.h" #include -extern IVEngineServer2* g_pEngineServer2; -extern CGlobalVars* GetGlobals(); -extern CPlayerManager* g_playerManager; - CIdleSystem* g_pIdleSystem = nullptr; CConVar g_cvarIdleKickTime("cs2f_idle_kick_time", FCVAR_NONE, "Amount of minutes before kicking idle players. 0 to disable afk kicking.", 0.0f, true, 0.0f, false, 0.0f); diff --git a/src/leader.cpp b/src/leader.cpp index fa64b385..0b19f94b 100644 --- a/src/leader.cpp +++ b/src/leader.cpp @@ -26,11 +26,6 @@ #include "tier0/memdbgon.h" -extern IVEngineServer2* g_pEngineServer2; -extern CGameEntitySystem* g_pEntitySystem; -extern CGlobalVars* GetGlobals(); -extern IGameEventManager2* g_gameEventManager; - // All colors MUST have 255 alpha // clang-format off std::map mapColorPresets = { @@ -331,7 +326,7 @@ static bool Leader_CreateDefendMarker(ZEPlayer* pPlayer, Color clrTint, int iDur g_iMarkerCount++; - new CTimer(iDuration, false, false, []() { + CTimer::Create(iDuration, TIMERFLAG_MAP | TIMERFLAG_ROUND, []() { if (g_iMarkerCount > 0) g_iMarkerCount--; diff --git a/src/leader.h b/src/leader.h index d0695eb8..d49e575b 100644 --- a/src/leader.h +++ b/src/leader.h @@ -24,6 +24,11 @@ #include "playermanager.h" #include "utils/entity.h" +extern CUtlVector g_vecLeaders; +extern CConVar g_cvarEnableLeader; +extern CConVar g_cvarLeaderActionsHumanOnly; +extern CConVar g_cvarMarkParticlePath; + // Inside IsAdminFlagSet, checks for ADMFLAG_GENERIC. // Inside command permissions, checks for if leader system is enabled and player is leader // OR if player has ADMFLAG_GENERIC @@ -37,11 +42,6 @@ struct ColorPreset }; extern std::map mapColorPresets; -extern CUtlVector g_vecLeaders; - -extern CConVar g_cvarEnableLeader; -extern CConVar g_cvarLeaderActionsHumanOnly; -extern CConVar g_cvarMarkParticlePath; void Leader_ApplyLeaderVisuals(CCSPlayerPawn* pPawn); void Leader_PostEventAbstract_Source1LegacyGameEvent(const uint64* clients, const CNetMessage* pData); diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 80538026..3e69452e 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -37,12 +37,6 @@ #include #include -extern CGlobalVars* GetGlobals(); -extern CCSGameRules* g_pGameRules; -extern IVEngineServer2* g_pEngineServer2; -extern CSteamGameServerAPIContext g_steamAPI; -extern IGameTypes* g_pGameTypes; - CMapVoteSystem* g_pMapVoteSystem = nullptr; CConVar g_cvarVoteMapsCooldown("cs2f_vote_maps_cooldown", FCVAR_NONE, "Default number of hours until a map can be played again i.e. cooldown", 6.0f); @@ -54,25 +48,10 @@ CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current if (!g_cvarVoteManagerEnable.Get()) return; - if (g_pMapVoteSystem->GetDownloadQueueSize() != 0) - { - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Please wait for current map downloads to finish before loading map list again."); - return; - } - - if (!g_pMapVoteSystem->LoadMapList() || !V_strcmp(g_pMapVoteSystem->GetCurrentMapName(), "MISSING_MAP")) - { + if (g_pMapVoteSystem->ReloadMapList()) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Map list reloaded!"); + else 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"); - - // Updating the mapgroup requires reloading the map for everything to load properly - g_pMapVoteSystem->ReloadCurrentMap(); - - ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Map list reloaded!"); } CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP) @@ -86,57 +65,13 @@ CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP) return; } - std::string sMapInput = g_pMapVoteSystem->StringToLower(args[1]); - - for (int i = 0; sMapInput[i]; i++) - { - // Injection prevention, because we may pass user input to ServerCommand - if (sMapInput[i] == ';' || sMapInput[i] == '|') - return; - } - - const char* pszMapInput = sMapInput.c_str(); - - if (g_pEngineServer2->IsMapValid(pszMapInput)) - { - ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to \x06%s\x01...", pszMapInput); - - new CTimer(5.0f, false, true, [sMapInput]() { - g_pEngineServer2->ChangeLevel(sMapInput.c_str(), nullptr); + g_pMapVoteSystem->HandlePlayerMapLookup(player, args[1], true, [](std::shared_ptr pMap, CCSPlayerController* pController) { + CTimer::Create(5.0f, TIMERFLAG_MAP, [pMap]() { + pMap->Load(); return -1.0f; }); - return; - } - - std::string sCommand; - std::string sMapName; - uint64 iMap = g_pMapVoteSystem->HandlePlayerMapLookup(player, pszMapInput, true); - - if (iMap == -1) - return; - - 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); - - if (workshopId == 0) - sCommand = "map " + sMapName; - else - sCommand = "host_workshop_map " + std::to_string(workshopId); - } - - ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to \x06%s\x01...", sMapName.c_str()); - - new CTimer(5.0f, false, true, [sCommand]() { - g_pEngineServer2->ServerCommand(sCommand.c_str()); - return -1.0f; + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to \x06%s\x01...", pMap->GetName()); }); } @@ -161,9 +96,9 @@ CON_COMMAND_CHAT(nomlist, "- List the list of nominations") if (!g_cvarVoteManagerEnable.Get()) return; - if (g_pMapVoteSystem->GetForcedNextMap() != -1) + if (g_pMapVoteSystem->GetForcedNextMap()) { - 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()); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Nominations are disabled because the next map has been forced to \x06%s\x01.", g_pMapVoteSystem->GetForcedNextMap()->GetName()); return; } @@ -192,7 +127,7 @@ CON_COMMAND_CHAT(mapcooldowns, "- List the maps currently in cooldown") for (std::shared_ptr pCooldown : g_pMapVoteSystem->GetMapCooldowns()) { // Only print maps that are added to maplist.cfg - if (pCooldown->IsOnCooldown() && g_pMapVoteSystem->GetMapIndexFromString(pCooldown->GetMapName()) != -1) + if (pCooldown->IsOnCooldown() && g_pMapVoteSystem->GetMapFromString(pCooldown->GetMapName())) vecCooldowns.push_back(std::make_pair(pCooldown->GetMapName(), pCooldown->GetCurrentCooldown())); } @@ -218,13 +153,13 @@ CON_COMMAND_CHAT(nextmap, "- Check the next map if it was forced") if (!g_cvarVoteManagerEnable.Get()) return; - if (g_pMapVoteSystem->GetForcedNextMap() == -1) + if (!g_pMapVoteSystem->GetForcedNextMap()) { 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()); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Next map is \x06%s\x01.", g_pMapVoteSystem->GetForcedNextMap()->GetName()); } CON_COMMAND_CHAT(maplist, "- List the maps in the server") @@ -232,32 +167,62 @@ CON_COMMAND_CHAT(maplist, "- List the maps in the server") g_pMapVoteSystem->PrintMapList(player); } -bool CMapVoteSystem::IsMapIndexEnabled(int iMapIndex) +bool CMap::IsAvailable() { - if (iMapIndex >= GetMapListSize() || iMapIndex < 0) return false; - if (GetMapCooldown(iMapIndex)->IsOnCooldown() || GetCurrentMapIndex() == iMapIndex) return false; - if (!m_vecMapList[iMapIndex]->IsEnabled()) return false; + auto pThis = shared_from_this(); + + if (GetCooldown()->IsOnCooldown()) + return false; + + if (*g_pMapVoteSystem->GetCurrentMap() == *pThis) + return false; + + if (!IsEnabled()) + return false; int iOnlinePlayers = g_playerManager->GetOnlinePlayerCount(false); - bool bMeetsMaxPlayers = iOnlinePlayers <= GetMapMaxPlayers(iMapIndex); - bool bMeetsMinPlayers = iOnlinePlayers >= GetMapMinPlayers(iMapIndex); + bool bMeetsMaxPlayers = iOnlinePlayers <= GetMaxPlayers(); + bool bMeetsMinPlayers = iOnlinePlayers >= GetMinPlayers(); return bMeetsMaxPlayers && bMeetsMinPlayers; } -void CMapVoteSystem::OnLevelInit(const char* pMapName) +bool CMap::Load() +{ + if (IsMissingIdentifier()) + return false; + + if (GetWorkshopId() == 0) + g_pEngineServer2->ChangeLevel(GetName(), nullptr); + else + g_pEngineServer2->ServerCommand(("host_workshop_map " + std::to_string(GetWorkshopId())).c_str()); + + return true; +} + +std::shared_ptr CMap::GetCooldown() +{ + return g_pMapVoteSystem->GetMapCooldown(GetName()); +} + +std::string CMap::GetCooldownText(bool bPlural) +{ + return g_pMapVoteSystem->GetMapCooldownText(GetName(), bPlural); +} + +void CMapVoteSystem::OnLevelInit(const char* pszMapName) { if (!g_cvarVoteManagerEnable.Get()) return; m_bIsVoteOngoing = false; m_bIntermissionStarted = false; - m_iForcedNextMap = -1; + m_pForcedNextMap = nullptr; for (int i = 0; i < MAXPLAYERS; i++) ClearPlayerInfo(i); // Delay one tick to override any .cfg's - new CTimer(0.02f, false, true, []() { + CTimer::Create(0.02f, TIMERFLAG_MAP, []() { g_pEngineServer2->ServerCommand("mp_match_end_changelevel 0"); g_pEngineServer2->ServerCommand("mp_endmatch_votenextmap 1"); @@ -275,15 +240,15 @@ void CMapVoteSystem::StartVote() // Select random maps that meet requirements to appear std::vector vecPossibleMaps; for (int i = 0; i < GetMapListSize(); i++) - if (IsMapIndexEnabled(i)) + if (GetMapByIndex(i)->IsAvailable()) vecPossibleMaps.push_back(i); m_iVoteSize = std::min((int)vecPossibleMaps.size(), g_cvarVoteMaxMaps.Get()); bool bAbort = false; - if (m_iForcedNextMap != -1) + if (GetForcedNextMap()) { - new CTimer(6.0f, false, true, []() { + CTimer::Create(6.0f, TIMERFLAG_MAP, []() { g_pMapVoteSystem->FinishVote(); return -1.0f; }); @@ -299,8 +264,8 @@ void CMapVoteSystem::StartVote() // Reload the current map as a fallback // Previously we fell back to game behaviour which could choose a random map in mapgroup, but a crash bug with default map changes was introduced in 2025-05-07 CS2 update - new CTimer(6.0f, false, true, []() { - g_pMapVoteSystem->ReloadCurrentMap(); + CTimer::Create(6.0f, TIMERFLAG_MAP, []() { + g_pMapVoteSystem->GetCurrentMap()->Load(); return -1.0f; }); } @@ -362,17 +327,22 @@ void CMapVoteSystem::StartVote() } } - // Print the maps chosen in the vote to console 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, GetMapName(iMapIndex)); } - // Start the end-of-vote timer to finish the vote static ConVarRefAbstract mp_endmatch_votenextleveltime("mp_endmatch_votenextleveltime"); - float flVoteTime = mp_endmatch_votenextleveltime.GetFloat(); - new CTimer(flVoteTime, false, true, []() { + static ConVarRefAbstract mp_match_restart_delay("mp_match_restart_delay"); + + // Game logic seems to be higher value wins + float flVoteTime = std::max(mp_endmatch_votenextleveltime.GetFloat(), mp_match_restart_delay.GetFloat()); + + // But it's also always 3 seconds off + flVoteTime = flVoteTime - 3.0f; + + CTimer::Create(flVoteTime, TIMERFLAG_MAP, []() { g_pMapVoteSystem->FinishVote(); return -1.0; }); @@ -405,13 +375,12 @@ 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 + std::shared_ptr pNextMap; - if (bIsNextMapForced) + if (GetForcedNextMap()) { - iWinningMap = m_iForcedNextMap; + pNextMap = GetForcedNextMap(); } else { @@ -422,22 +391,22 @@ void CMapVoteSystem::FinishVote() } g_pGameRules->m_nEndMatchMapVoteWinner = iNextMapVoteIndex; - iWinningMap = g_pGameRules->m_nEndMatchMapGroupVoteOptions[iNextMapVoteIndex]; + pNextMap = GetMapByIndex(g_pGameRules->m_nEndMatchMapGroupVoteOptions[iNextMapVoteIndex]); } // 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()); + if (GetForcedNextMap()) + V_snprintf(buffer, sizeof(buffer), "The vote was overriden. \x06%s\x01 will be the next map!\n", pNextMap->GetName()); else if (bIsNextMapVoted) - V_snprintf(buffer, sizeof(buffer), "The vote has ended. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap)); + V_snprintf(buffer, sizeof(buffer), "The vote has ended. \x06%s\x01 will be the next map!\n", pNextMap->GetName()); else - V_snprintf(buffer, sizeof(buffer), "No map was chosen. \x06%s\x01 will be the next map!\n", GetMapName(iWinningMap)); + V_snprintf(buffer, sizeof(buffer), "No map was chosen. \x06%s\x01 will be the next map!\n", pNextMap->GetName()); ClientPrintAll(HUD_PRINTTALK, buffer); Message(buffer); // Print vote result information: how many votes did each map get? - if (!bIsNextMapForced && GetGlobals()) + if (!GetForcedNextMap() && GetGlobals()) { int arrMapVotes[10] = {0}; Message("Map vote result --- total votes per map:\n"); @@ -456,17 +425,9 @@ void CMapVoteSystem::FinishVote() } // Wait a second and force-change the map - new CTimer(1.0, false, true, [iWinningMap]() { - char sChangeMapCmd[128]; - 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)); - else - V_snprintf(sChangeMapCmd, sizeof(sChangeMapCmd), "host_workshop_map %llu", workshopId); - - g_pEngineServer2->ServerCommand(sChangeMapCmd); - return -1.0; + CTimer::Create(1.0, TIMERFLAG_MAP, [pNextMap]() { + pNextMap->Load(); + return -1.0f; }); } @@ -484,14 +445,11 @@ bool CMapVoteSystem::RegisterPlayerVote(CPlayerSlot iPlayerSlot, int iVoteOption int iSlot = pController->GetPlayerSlot(); m_arrPlayerVotes[iSlot] = iVoteOption; - // Log vote to console - const char* sMapName = GetMapName(iMapIndexToVote); - Message("Adding vote to map %i (%s) for player %s (slot %i).\n", iVoteOption, sMapName, pController->GetPlayerName(), iSlot); + Message("Adding vote to map %i (%s) for player %s (slot %i).\n", iVoteOption, GetMapName(iMapIndexToVote), pController->GetPlayerName(), iSlot); // Update the winning map for every player vote UpdateWinningMap(); - // Vote was counted return true; } @@ -557,7 +515,7 @@ std::unordered_map CMapVoteSystem::GetNominatedMaps() int iNominatedMapIndex = m_arrPlayerNominations[i]; // Introduce nominated map indexes and count the total number - if (iNominatedMapIndex != -1 && pController && pController->IsConnected() && IsMapIndexEnabled(iNominatedMapIndex)) + if (iNominatedMapIndex != -1 && pController && pController->IsConnected() && GetMapByIndex(iNominatedMapIndex)->IsAvailable()) ++mapNominatedMaps[iNominatedMapIndex]; } @@ -636,67 +594,84 @@ std::vector CMapVoteSystem::GetNominatedMapsForVote() return vecChosenNominatedMaps; } -std::vector CMapVoteSystem::GetMapIndexesFromSubstring(const char* sMapSubstring) +std::vector> CMapVoteSystem::GetMapsFromSubstring(const char* pszMapSubstring) { - std::vector vecMaps; + std::vector> vecMaps; - for (int i = 0; i < GetMapListSize(); i++) - if (V_stristr(GetMapName(i), sMapSubstring)) - vecMaps.push_back(i); + for (auto pMap : GetMapList()) + if (V_stristr(pMap->GetName(), pszMapSubstring)) + vecMaps.push_back(pMap); return vecMaps; } -uint64 CMapVoteSystem::HandlePlayerMapLookup(CCSPlayerController* pController, const char* sMapSubstring, bool bAdmin) +void CMapVoteSystem::HandlePlayerMapLookup(CCSPlayerController* pController, std::string strMapSubstring, bool bAdmin, QueryCallback_t callbackSuccess) { - if (bAdmin) - { - 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); + strMapSubstring = g_pMapVoteSystem->StringToLower(strMapSubstring); + const char* pszMapSubstring = strMapSubstring.c_str(); + auto vecFoundMaps = GetMapsFromSubstring(pszMapSubstring); // Don't list disabled maps in non-admin commands if (!bAdmin) { - for (int iIndex : foundIndexes) + auto iterator = vecFoundMaps.begin(); + + while (iterator != vecFoundMaps.end()) { // Only erase if vector has multiple elements, so we can still give "map disabled" output in single-match scenarios - if (!GetMapEnabledStatus(iIndex) && foundIndexes.size() > 1) - foundIndexes.erase(std::remove(foundIndexes.begin(), foundIndexes.end(), iIndex), foundIndexes.end()); + if (!(*iterator)->IsEnabled() && vecFoundMaps.size() > 1) + vecFoundMaps.erase(iterator); + else + iterator++; } } - if (foundIndexes.size() > 0) + if (vecFoundMaps.size() > 0) { - if (foundIndexes.size() > 1) + if (vecFoundMaps.size() > 1) { - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Multiple maps matched \x06%s\x01, try being more specific:", sMapSubstring); + // If we have an exact match here, just use that + for (auto pMap : vecFoundMaps) + { + if (!V_strcmp(pMap->GetName(), pszMapSubstring)) + { + callbackSuccess(pMap, pController); + return; + } + } + + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Multiple maps matched \x06%s\x01, try being more specific:", pszMapSubstring); - for (int i = 0; i < foundIndexes.size() && i < 5; i++) - ClientPrint(pController, HUD_PRINTTALK, "- %s", GetMapName(foundIndexes[i])); + for (int i = 0; i < vecFoundMaps.size() && i < 5; i++) + ClientPrint(pController, HUD_PRINTTALK, "- %s", vecFoundMaps[i]->GetName()); } else { - return foundIndexes[0]; + callbackSuccess(vecFoundMaps[0], pController); } + + return; } - else + + if (bAdmin) { - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to find a map matching \x06%s\x01.", sMapSubstring); + uint64 iWorkshopId = V_StringToUint64(pszMapSubstring, 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING); + + // Check if input is numeric (workshop ID) + if (iWorkshopId != 0) + { + CWorkshopDetailsQuery::Create(iWorkshopId, pController, callbackSuccess); + return; + } + + if (g_pEngineServer2->IsMapValid(pszMapSubstring)) + { + callbackSuccess(std::make_shared(pszMapSubstring, 0), pController); + return; + } } - return -1; + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to find a map matching \x06%s\x01.", pszMapSubstring); } void CMapVoteSystem::ClearPlayerInfo(int iSlot) @@ -708,13 +683,13 @@ void CMapVoteSystem::ClearPlayerInfo(int iSlot) m_arrPlayerVotes[iSlot] = -1; } -int CMapVoteSystem::GetMapIndexFromString(const char* pszMapString) +std::shared_ptr CMapVoteSystem::GetMapFromString(const char* pszMapString) { - for (int i = 0; i < GetMapListSize(); i++) - if (!V_strcasecmp(GetMapName(i), pszMapString)) - return i; + for (auto pMap : m_vecMapList) + if (!V_strcasecmp(pMap->GetName(), pszMapString)) + return pMap; - return -1; + return nullptr; } std::shared_ptr CMapVoteSystem::GetGroupFromString(const char* pszName) @@ -726,12 +701,11 @@ std::shared_ptr CMapVoteSystem::GetGroupFromString(const char* pszName) return nullptr; } -void CMapVoteSystem::AttemptNomination(CCSPlayerController* pController, const char* sMapSubstring) +void CMapVoteSystem::AttemptNomination(CCSPlayerController* pController, const char* pszMapSubstring) { int iSlot = pController->GetPlayerSlot(); - ZEPlayer* pPlayer = g_playerManager->GetPlayer(iSlot); - if (!pPlayer || !GetGlobals()) + if (!GetGlobals()) return; if (g_cvarVoteMaxNominations.Get() == 0) @@ -740,9 +714,9 @@ void CMapVoteSystem::AttemptNomination(CCSPlayerController* pController, const c return; } - if (GetForcedNextMap() != -1) + if (GetForcedNextMap()) { - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Nominations are disabled because the next map has been forced to \x06%s\x01.", GetForcedNextMapName().c_str()); + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Nominations are disabled because the next map has been forced to \x06%s\x01.", GetForcedNextMap()->GetName()); return; } @@ -752,7 +726,7 @@ void CMapVoteSystem::AttemptNomination(CCSPlayerController* pController, const c return; } - if (sMapSubstring[0] == '\0') + if (pszMapSubstring[0] == '\0') { if (m_arrPlayerNominations[iSlot] != -1) { @@ -767,83 +741,84 @@ void CMapVoteSystem::AttemptNomination(CCSPlayerController* pController, const c return; } - int iFoundIndex = HandlePlayerMapLookup(pController, sMapSubstring); - int iPlayerCount = g_playerManager->GetOnlinePlayerCount(false); + g_pMapVoteSystem->HandlePlayerMapLookup(pController, pszMapSubstring, false, [](std::shared_ptr pMap, CCSPlayerController* pController) { + int iPlayerCount = g_playerManager->GetOnlinePlayerCount(false); + int iMapIndex = g_pMapVoteSystem->GetMapInfoByIdentifiers(pMap->GetName(), pMap->GetWorkshopId()).first; - if (iFoundIndex == -1) - return; + int iSlot = pController->GetPlayerSlot(); + ZEPlayer* pPlayer = g_playerManager->GetPlayer(iSlot); - if (!GetMapEnabledStatus(iFoundIndex)) - { - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's disabled.", GetMapName(iFoundIndex)); - return; - } + if (!pMap->IsEnabled()) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's disabled.", pMap->GetName()); + return; + } - if (GetCurrentMapIndex() == iFoundIndex) - { - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's already the current map!", GetMapName(iFoundIndex)); - return; - } + if (*pMap == *g_pMapVoteSystem->GetCurrentMap()) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's already the current map!", pMap->GetName()); + return; + } - 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 (pMap->GetCooldown()->IsOnCooldown()) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it's on a %s cooldown.", pMap->GetName(), pMap->GetCooldownText(false).c_str()); + return; + } - 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 (iPlayerCount < pMap->GetMinPlayers()) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i more players.", pMap->GetName(), pMap->GetMinPlayers() - iPlayerCount); + return; + } - 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 (iPlayerCount > pMap->GetMaxPlayers()) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Cannot nominate \x06%s\x01 because it needs %i less players.", pMap->GetName(), iPlayerCount - pMap->GetMaxPlayers()); + 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; - } + 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; - int iNominations = GetTotalNominations(iFoundIndex); + g_pMapVoteSystem->SetPlayerNomination(iSlot, iMapIndex); + int iNominations = g_pMapVoteSystem->GetTotalNominations(iMapIndex); - 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); + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01was nominated by %s. It now has %d nomination%s.", pMap->GetName(), pController->GetPlayerName(), iNominations, iNominations > 1 ? "s" : ""); + pPlayer->SetNominateTime(GetGlobals()->curtime); + }); } void CMapVoteSystem::PrintMapList(CCSPlayerController* pController) { - std::vector> vecSortedMaps; + 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))); + for (auto pMap : m_vecMapList) + if (pMap->IsEnabled()) + vecSortedMaps.push_back(pMap); std::sort(vecSortedMaps.begin(), vecSortedMaps.end(), [](auto left, auto right) { - return V_strcasecmp(right.second.c_str(), left.second.c_str()) > 0; + return V_strcasecmp(right->GetName(), left->GetName()) > 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) + for (auto pMap : vecSortedMaps) { - int mapIndex = pair.first; - const char* name = pair.second.c_str(); - int minPlayers = GetMapMinPlayers(mapIndex); - int maxPlayers = GetMapMaxPlayers(mapIndex); + const char* name = pMap->GetName(); + int minPlayers = pMap->GetMinPlayers(); + int maxPlayers = pMap->GetMaxPlayers(); - if (mapIndex == GetCurrentMapIndex()) + if (*pMap == *GetCurrentMap()) 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 (pMap->GetCooldown()->IsOnCooldown()) + ClientPrint(pController, HUD_PRINTCONSOLE, "- %s - Cooldown: %s", name, pMap->GetCooldownText(true).c_str()); else if (iPlayerCount < minPlayers) ClientPrint(pController, HUD_PRINTCONSOLE, "- %s - +%d Players", name, minPlayers - iPlayerCount); else if (iPlayerCount > maxPlayers) @@ -853,48 +828,45 @@ void CMapVoteSystem::PrintMapList(CCSPlayerController* pController) } } -void CMapVoteSystem::ForceNextMap(CCSPlayerController* pController, const char* sMapSubstring) +void CMapVoteSystem::ForceNextMap(CCSPlayerController* pController, const char* pszMapSubstring) { - if (sMapSubstring[0] == '\0') + if (pszMapSubstring[0] == '\0') { - if (GetForcedNextMap() == -1) + if (!GetForcedNextMap()) { 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; + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01is no longer the forced next map.\n", GetForcedNextMap()->GetName()); + SetForcedNextMap(nullptr); } return; } - uint64 iFoundMap = HandlePlayerMapLookup(pController, sMapSubstring, true); - - if (iFoundMap == -1) - return; - - if (GetForcedNextMap() == iFoundMap) - { - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "\x06%s\x01 is already the next map!", GetForcedNextMapName().c_str()); - return; - } + g_pMapVoteSystem->HandlePlayerMapLookup(pController, pszMapSubstring, true, [](std::shared_ptr pMap, CCSPlayerController* pController) { + if (g_pMapVoteSystem->GetForcedNextMap() && *pMap == *g_pMapVoteSystem->GetForcedNextMap()) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "\x06%s\x01 is already the next map!", g_pMapVoteSystem->GetForcedNextMap()->GetName()); + return; + } - // When found, print the map and store the forced map - m_iForcedNextMap = iFoundMap; - ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01has been forced as the next map.\n", GetForcedNextMapName().c_str()); + // When found, print the map and store the forced map + g_pMapVoteSystem->SetForcedNextMap(pMap); + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01has been forced as the next map.\n", g_pMapVoteSystem->GetForcedNextMap()->GetName()); + }); } void CMapVoteSystem::PrintDownloadProgress() { - if (m_DownloadQueue.Count() == 0) + if (GetDownloadQueueSize() == 0) return; uint64 iBytesDownloaded = 0; uint64 iTotalBytes = 0; - if (!g_steamAPI.SteamUGC()->GetItemDownloadInfo(m_DownloadQueue.Head(), &iBytesDownloaded, &iTotalBytes) || !iTotalBytes) + if (!g_steamAPI.SteamUGC()->GetItemDownloadInfo(m_DownloadQueue.front(), &iBytesDownloaded, &iTotalBytes) || !iTotalBytes) return; double flMBDownloaded = (double)iBytesDownloaded / 1024 / 1024; @@ -903,30 +875,45 @@ void CMapVoteSystem::PrintDownloadProgress() double flProgress = (double)iBytesDownloaded / (double)iTotalBytes; flProgress *= 100.f; - Message("Downloading map %lli: %.2f/%.2f MB (%.2f%%)\n", m_DownloadQueue.Head(), flMBDownloaded, flTotalMB, flProgress); + Message("Downloading map %lli: %.2f/%.2f MB (%.2f%%)\n", m_DownloadQueue.front(), flMBDownloaded, flTotalMB, flProgress); } void CMapVoteSystem::OnMapDownloaded(DownloadItemResult_t* pResult) { - if (!m_DownloadQueue.Check(pResult->m_nPublishedFileId)) + if (std::find(m_DownloadQueue.begin(), m_DownloadQueue.end(), pResult->m_nPublishedFileId) == m_DownloadQueue.end()) + return; + + // Some weird rate limiting that's been observed? Back off for a while then retry download + if (pResult->m_eResult == k_EResultNoConnection) + { + PublishedFileId_t workshopID = m_DownloadQueue.front(); + Message("Addon %llu download failed with status code 3, retrying in 2 minutes\n", workshopID); + + m_pRateLimitedDownloadTimer = CTimer::Create(120.0f, TIMERFLAG_NONE, [workshopID]() { + g_steamAPI.SteamUGC()->DownloadItem(workshopID, false); + + return -1.0f; + }); + return; + } - m_DownloadQueue.RemoveAtHead(); + m_DownloadQueue.pop_front(); - if (m_DownloadQueue.Count() == 0) + if (GetDownloadQueueSize() == 0) return; - g_steamAPI.SteamUGC()->DownloadItem(m_DownloadQueue.Head(), false); + g_steamAPI.SteamUGC()->DownloadItem(m_DownloadQueue.front(), false); } void CMapVoteSystem::QueueMapDownload(PublishedFileId_t iWorkshopId) { - if (m_DownloadQueue.Check(iWorkshopId)) + if (std::find(m_DownloadQueue.begin(), m_DownloadQueue.end(), iWorkshopId) != m_DownloadQueue.end()) return; - m_DownloadQueue.Insert(iWorkshopId); + m_DownloadQueue.push_back(iWorkshopId); - if (m_DownloadQueue.Head() == iWorkshopId) + if (m_DownloadQueue.front() == iWorkshopId) g_steamAPI.SteamUGC()->DownloadItem(iWorkshopId, false); } @@ -934,63 +921,42 @@ 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(); - const char* pszJsonPath = "addons/cs2fixes/configs/maplist.jsonc"; + const char* pszMapListPath = "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); + V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszMapListPath); + std::ifstream mapListFile(szPath); - if (!jsonFile.is_open()) + if (!mapListFile.is_open()) { - 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); + Panic("Failed to open %s, map list not loaded!\n", pszMapListPath); + return false; } - ordered_json jsonMaps = ordered_json::parse(jsonFile, nullptr, false, true); + ordered_json jsonMaps = ordered_json::parse(mapListFile, nullptr, false, true); if (jsonMaps.is_discarded()) { - Panic("Failed parsing JSON from %s, map list not loaded!\n", pszJsonPath); + Panic("Failed parsing JSON from %s, map list not loaded!\n", pszMapListPath); return false; } - // Load map cooldowns from file - KeyValues* pKVcooldowns = new KeyValues("cooldowns"); - KeyValues::AutoDelete autoDeleteKVcooldowns(pKVcooldowns); - const char* pszCooldownFilePath = "addons/cs2fixes/data/cooldowns.txt"; - if (!pKVcooldowns->LoadFromFile(g_pFullFileSystem, pszCooldownFilePath)) - Message("Failed to load cooldown file at %s - resetting all cooldowns to 0\n", pszCooldownFilePath); - - for (KeyValues* pKey = pKVcooldowns->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey()) - { - time_t timeCooldown = pKey->GetUint64(); - - if (timeCooldown > std::time(0)) - { - std::shared_ptr pCooldown = std::make_shared(pKey->GetName()); - - pCooldown->SetTimeCooldown(timeCooldown); - m_vecCooldowns.push_back(pCooldown); - } - } + m_timeMapListModified = std::filesystem::last_write_time(szPath); + LoadCooldowns(); - for (auto& [sSection, jsonSection] : jsonMaps.items()) + for (auto& [strSection, jsonSection] : jsonMaps.items()) { - for (auto& [sEntry, jsonEntry] : jsonSection.items()) + for (auto& [strEntry, jsonEntry] : jsonSection.items()) { - if (sSection == "Groups") + if (strSection == "Groups") { - m_vecGroups.push_back(std::make_shared(sEntry, jsonEntry.value("enabled", true), jsonEntry.value("cooldown", 0.0f))); + m_vecGroups.push_back(std::make_shared(strEntry, jsonEntry.value("enabled", true), jsonEntry.value("cooldown", 0.0f))); } - else if (sSection == "Maps") + else if (strSection == "Maps") { // Seems like uint64 needs special handling uint64 iWorkshopId = 0; @@ -1013,12 +979,12 @@ bool CMapVoteSystem::LoadMapList() QueueMapDownload(iWorkshopId); // We just append the maps to the map list - m_vecMapList.push_back(std::make_shared(sEntry, strDisplayName, iWorkshopId, bIsEnabled, iMinPlayers, iMaxPlayers, fCooldown, vecGroups)); + m_vecMapList.push_back(std::make_shared(strEntry, iWorkshopId, false, strDisplayName, bIsEnabled, iMinPlayers, iMaxPlayers, fCooldown, vecGroups)); } } } - new CTimer(0.f, true, true, []() { + m_pDownloadProgressTimer = CTimer::Create(0.f, TIMERFLAG_NONE, []() { if (g_pMapVoteSystem->GetDownloadQueueSize() == 0) return -1.f; @@ -1050,6 +1016,50 @@ bool CMapVoteSystem::LoadMapList() return true; } +bool CMapVoteSystem::LoadCooldowns() +{ + const char* pszCooldownsFilePath = "addons/cs2fixes/data/cooldowns.jsonc"; + char szPath[MAX_PATH]; + V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszCooldownsFilePath); + std::ifstream cooldownsFile(szPath); + + if (!cooldownsFile.is_open()) + { + if (!ConvertCooldownsKVToJSON()) + { + Message("Failed to open %s and convert KV1 cooldowns.txt to JSON format, resetting all cooldowns to 0\n", pszCooldownsFilePath); + return false; + } + + cooldownsFile.open(szPath); + } + + ordered_json jsonCooldownsRoot = ordered_json::parse(cooldownsFile, nullptr, false, true); + + if (jsonCooldownsRoot.is_discarded()) + { + Message("Failed parsing JSON from %s, resetting all cooldowns to 0\n", pszCooldownsFilePath); + return false; + } + + ordered_json jsonCooldowns = jsonCooldownsRoot.value("Cooldowns", ordered_json()); + + for (auto& [strMapName, iCooldown] : jsonCooldowns.items()) + { + time_t timeCooldown = iCooldown; + + if (timeCooldown > std::time(0)) + { + std::shared_ptr pCooldown = std::make_shared(strMapName); + + pCooldown->SetTimeCooldown(timeCooldown); + m_vecCooldowns.push_back(pCooldown); + } + } + + return true; +} + bool CMapVoteSystem::IsIntermissionAllowed(bool bCheckOnly) { // We need to prevent "ending the map twice" as it messes with ongoing map votes @@ -1068,29 +1078,33 @@ CUtlStringList CMapVoteSystem::CreateWorkshopMapGroup() { CUtlStringList mapList; - for (int i = 0; i < GetMapListSize(); i++) - mapList.CopyAndAddToTail(GetMapDisplayName(i)); + for (auto pMap : m_vecMapList) + mapList.CopyAndAddToTail(pMap->GetDisplayName()); return mapList; } bool CMapVoteSystem::WriteMapCooldownsToFile() { - KeyValues* pKV = new KeyValues("cooldowns"); - KeyValues::AutoDelete autoDelete(pKV); - - const char* pszPath = "addons/cs2fixes/data/cooldowns.txt"; - - for (std::shared_ptr pCooldown : m_vecCooldowns) - if (pCooldown->GetTimeCooldown() > std::time(0)) - pKV->AddUint64(pCooldown->GetMapName(), pCooldown->GetTimeCooldown()); + const char* pszJsonPath = "addons/cs2fixes/data/cooldowns.jsonc"; + char szPath[MAX_PATH]; + V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszJsonPath); + std::ofstream jsonFile(szPath); + ordered_json jsonCooldowns; - if (!pKV->SaveToFile(g_pFullFileSystem, pszPath)) + if (!jsonFile.is_open()) { - Panic("Failed to write cooldowns to file: %s\n", pszPath); + Panic("Failed to open %s\n", pszJsonPath); return false; } + jsonCooldowns["Cooldowns"] = ordered_json(ordered_json::value_t::object); + + for (std::shared_ptr pCooldown : m_vecCooldowns) + if (pCooldown->GetTimeCooldown() > std::time(0)) + jsonCooldowns["Cooldowns"][pCooldown->GetMapName()] = pCooldown->GetTimeCooldown(); + + jsonFile << std::setfill('\t') << std::setw(1) << jsonCooldowns << std::endl; return true; } @@ -1107,56 +1121,53 @@ void CMapVoteSystem::ClearInvalidNominations() if (iNominatedMapIndex < 0) continue; + auto pMap = GetMapByIndex(iNominatedMapIndex); + // Check if nominated index still meets criteria for nomination - if (!IsMapIndexEnabled(iNominatedMapIndex)) + if (!pMap->IsEnabled()) { ClearPlayerInfo(i); CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(i); if (!pPlayer) continue; - ClientPrint(pPlayer, HUD_PRINTTALK, CHAT_PREFIX "Your nomination for \x06%s \x01has been removed because the player count requirements are no longer met.", GetMapName(iNominatedMapIndex)); + ClientPrint(pPlayer, HUD_PRINTTALK, CHAT_PREFIX "Your nomination for \x06%s \x01has been removed because the player count requirements are no longer met.", pMap->GetName()); } } } -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_cvarVoteManagerEnable.Get()) return; + const char* pszMapName; + uint64 iWorkshopId; + + if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname")) + pszMapName = pKV->FindKey("launchoptions")->GetString("levelname"); + else + pszMapName = ""; + if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode")) - SetCurrentWorkshopMap(pKV->FindKey("launchoptions")->GetUint64("customgamemode")); + iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode"); else - SetCurrentWorkshopMap(0); + iWorkshopId = 0; - if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname")) - SetCurrentMapName(pKV->FindKey("launchoptions")->GetString("levelname")); + auto pair = GetMapInfoByIdentifiers(pszMapName, iWorkshopId); + + if (pair.first != -1) + SetCurrentMap(pair.second); else - SetCurrentMapName("MISSING_MAP"); + SetCurrentMap(std::make_shared(pszMapName, iWorkshopId, pszMapName[0] == '\0' && iWorkshopId == 0)); - UpdateCurrentMapIndex(); ProcessGroupCooldowns(); } void CMapVoteSystem::OnLevelShutdown() { // Put the map on cooldown as we transition to the next map - PutMapOnCooldown(GetCurrentMapName()); + PutMapOnCooldown(GetCurrentMap()->GetName()); // Fully apply pending group cooldowns for (std::shared_ptr pCooldown : m_vecCooldowns) @@ -1169,7 +1180,16 @@ void CMapVoteSystem::OnLevelShutdown() } if (IsMapListLoaded()) + { WriteMapCooldownsToFile(); + + char szPath[MAX_PATH]; + V_snprintf(szPath, sizeof(szPath), "%s%s", Plat_GetGameDirectory(), "/csgo/addons/cs2fixes/configs/maplist.jsonc"); + + // If maplist.jsonc was updated, automatically reload the map list without a map change (map is about to change anyways) + if (m_timeMapListModified != std::filesystem::last_write_time(szPath)) + ReloadMapList(false); + } } std::string CMapVoteSystem::ConvertFloatToString(float fValue, int precision) @@ -1192,12 +1212,12 @@ std::string CMapVoteSystem::ConvertFloatToString(float fValue, int precision) return str; } -std::string CMapVoteSystem::StringToLower(std::string sValue) +std::string CMapVoteSystem::StringToLower(std::string strValue) { - for (int i = 0; sValue[i]; i++) - sValue[i] = tolower(sValue[i]); + for (int i = 0; strValue[i]; i++) + strValue[i] = tolower(strValue[i]); - return sValue; + return strValue; } std::string CMapVoteSystem::GetMapCooldownText(const char* pszMapName, bool bPlural) @@ -1241,13 +1261,13 @@ void CMapVoteSystem::PutMapOnCooldown(const char* pszMapName, float fCooldown) if (g_bDisableCooldowns) return; - int iMapIndex = GetMapIndexFromString(pszMapName); + auto pMap = GetMapFromString(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); + if (pMap && pMap->GetCustomCooldown() != 0.0f) + fCooldown = pMap->GetCustomCooldown(); else fCooldown = g_cvarVoteMapsCooldown.Get(); } @@ -1262,12 +1282,7 @@ void CMapVoteSystem::PutMapOnCooldown(const char* pszMapName, float fCooldown) void CMapVoteSystem::ProcessGroupCooldowns() { - int iCurrentMapIndex = GetCurrentMapIndex(); - - if (iCurrentMapIndex == -1) - return; - - std::vector vecCurrentMapGroups = m_vecMapList[iCurrentMapIndex]->GetGroups(); + std::vector vecCurrentMapGroups = GetCurrentMap()->GetGroups(); for (std::string groupName : vecCurrentMapGroups) { @@ -1275,7 +1290,7 @@ void CMapVoteSystem::ProcessGroupCooldowns() if (!pGroup) { - Panic("Invalid group name %s defined for map %s\n", groupName.c_str(), GetMapName(iCurrentMapIndex)); + Panic("Invalid group name %s defined for map %s\n", groupName.c_str(), GetCurrentMap()->GetName()); continue; } @@ -1283,12 +1298,12 @@ void CMapVoteSystem::ProcessGroupCooldowns() 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++) + for (auto pMap : m_vecMapList) { - if (iCurrentMapIndex != i && GetMapEnabledStatus(i) && m_vecMapList[i]->HasGroup(groupName)) + if (*GetCurrentMap() != *pMap && pMap->IsEnabled() && pMap->HasGroup(groupName)) { float fCooldown = pGroup->GetCooldown() == 0.0f ? g_cvarVoteMapsCooldown.Get() : pGroup->GetCooldown(); - std::shared_ptr pCooldown = GetMapCooldown(i); + std::shared_ptr pCooldown = pMap->GetCooldown(); // Ensure we don't overwrite a longer cooldown if (pCooldown->GetPendingCooldown() < fCooldown) @@ -1328,26 +1343,101 @@ float CCooldown::GetCurrentCooldown() return fRemainingTime; } -void CMapVoteSystem::ReloadCurrentMap() +bool CMapVoteSystem::ReloadMapList(bool bReloadMap) { - char sChangeMapCmd[128] = ""; + if (g_pMapVoteSystem->GetDownloadQueueSize() != 0) + { + m_DownloadQueue.clear(); - if (GetCurrentWorkshopMap() != 0) - V_snprintf(sChangeMapCmd, sizeof(sChangeMapCmd), "host_workshop_map %llu", GetCurrentWorkshopMap()); - else - V_snprintf(sChangeMapCmd, sizeof(sChangeMapCmd), "map %s", GetCurrentMapName()); + if (!m_pDownloadProgressTimer.expired()) + m_pDownloadProgressTimer.lock()->Cancel(); + + if (!m_pRateLimitedDownloadTimer.expired()) + m_pRateLimitedDownloadTimer.lock()->Cancel(); + } + + if (!g_pMapVoteSystem->LoadMapList()) + return false; - g_pEngineServer2->ServerCommand(sChangeMapCmd); + // 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"); + + // Updating the mapgroup requires reloading the map for everything to load properly + if (bReloadMap) + return GetCurrentMap()->Load(); + + return true; } -// TODO: remove this once servers have been given at least a few months to update cs2fixes -bool CMapVoteSystem::ConvertMapListKVToJSON() +std::pair> CMapVoteSystem::GetMapInfoByIdentifiers(const char* pszMapName, uint64 iWorkshopId) { - Message("Attempting to convert KV1 maplist.cfg to JSON format...\n"); + for (int i = 0; i < GetMapListSize(); i++) + { + auto pMap = GetMapByIndex(i); - const char* pszPath = "addons/cs2fixes/configs/maplist.cfg"; + if ((pMap->GetName()[0] != '\0' && !V_strcasecmp(pMap->GetName(), pszMapName)) || (pMap->GetWorkshopId() != 0 && pMap->GetWorkshopId() == iWorkshopId)) + return {i, pMap}; + } + + return {-1, nullptr}; +} + +std::shared_ptr CWorkshopDetailsQuery::Create(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess) +{ + uint64 iWorkshopIDArray[1] = {iWorkshopId}; + UGCQueryHandle_t hQuery = g_steamAPI.SteamUGC()->CreateQueryUGCDetailsRequest(iWorkshopIDArray, 1); + + if (hQuery == k_UGCQueryHandleInvalid) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", iWorkshopId); + return nullptr; + } + + g_steamAPI.SteamUGC()->SetAllowCachedResponse(hQuery, 0); + SteamAPICall_t hCall = g_steamAPI.SteamUGC()->SendQueryUGCRequest(hQuery); + + auto pQuery = std::make_shared(hQuery, iWorkshopId, pController, callbackSuccess); + g_pMapVoteSystem->AddWorkshopDetailsQuery(pQuery); + pQuery->m_CallResult.Set(hCall, pQuery.get(), &CWorkshopDetailsQuery::OnQueryCompleted); - KeyValues* pKV = new KeyValues("maplist"); + return pQuery; +} + +void CWorkshopDetailsQuery::OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed) +{ + CCSPlayerController* pController = m_hController.Get(); + SteamUGCDetails_t details; + + // Only allow null controller if controller was originally null (console) + if (m_bConsole || pController) + { + if (bFailed || pCompletedQuery->m_eResult != k_EResultOK || pCompletedQuery->m_unNumResultsReturned < 1 || !g_steamAPI.SteamUGC()->GetQueryUGCResult(pCompletedQuery->m_handle, 0, &details) || details.m_eResult != k_EResultOK) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", m_iWorkshopId); + } + else if (details.m_nConsumerAppID != 730 || details.m_eFileType != k_EWorkshopFileTypeCommunity) + { + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "The ID \x06%llu\x01 is not a valid CS2 workshop map.", m_iWorkshopId); + } + else + { + // Try to get a head start on downloading the map if needed + g_steamAPI.SteamUGC()->DownloadItem(m_iWorkshopId, false); + m_callbackSuccess(std::make_shared(details.m_rgchTitle, m_iWorkshopId), pController); + } + } + + g_steamAPI.SteamUGC()->ReleaseQueryUGCRequest(m_hQuery); + g_pMapVoteSystem->RemoveWorkshopDetailsQuery(shared_from_this()); +} + +// TODO: remove this once servers have been given at least a few months to update cs2fixes +bool CMapVoteSystem::ConvertCooldownsKVToJSON() +{ + Message("Attempting to convert KV1 cooldowns.txt to JSON format...\n"); + + const char* pszPath = "addons/cs2fixes/data/cooldowns.txt"; + KeyValues* pKV = new KeyValues("cooldowns"); KeyValues::AutoDelete autoDelete(pKV); if (!pKV->LoadFromFile(g_pFullFileSystem, pszPath)) @@ -1356,30 +1446,14 @@ bool CMapVoteSystem::ConvertMapListKVToJSON() return false; } - ordered_json jsonMapList; + ordered_json jsonCooldowns; - jsonMapList["Groups"] = ordered_json(ordered_json::value_t::object); + jsonCooldowns["Cooldowns"] = 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; - } + jsonCooldowns["Cooldowns"][pKey->GetName()] = pKey->GetUint64(); - const char* pszJsonPath = "addons/cs2fixes/configs/maplist.jsonc"; - const char* pszKVConfigRenamePath = "addons/cs2fixes/configs/maplist_old.cfg"; + const char* pszJsonPath = "addons/cs2fixes/data/cooldowns.jsonc"; char szPath[MAX_PATH]; V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszJsonPath); std::ofstream jsonFile(szPath); @@ -1390,19 +1464,12 @@ bool CMapVoteSystem::ConvertMapListKVToJSON() return false; } - jsonFile << std::setfill('\t') << std::setw(1) << jsonMapList << std::endl; + jsonFile << std::setfill('\t') << std::setw(1) << jsonCooldowns << std::endl; - char szKVRenamePath[MAX_PATH]; + // remove old file 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); + Message("Successfully converted KV1 cooldowns.txt to JSON format at %s\n", pszJsonPath); return true; -} +} \ No newline at end of file diff --git a/src/map_votes.h b/src/map_votes.h index cd00180b..e80d5bb1 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -22,35 +22,66 @@ #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 #include #include +#include +class CMap; using ordered_json = nlohmann::ordered_json; +using QueryCallback_t = std::function, CCSPlayerController*)>; -class CMap +class CCooldown +{ +public: + CCooldown(std::string strMapName) + { + m_strMapName = strMapName; + 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 CMap : public std::enable_shared_from_this { public: - CMap(std::string sName, std::string sDisplayName, uint64 iWorkshopId, bool bIsEnabled, int iMinPlayers, int iMaxPlayers, float fCustomCooldown, std::vector vecGroups) + CMap(std::string strName, uint64 iWorkshopId, bool bMissingIdentifier = false, std::string strDisplayName = "", bool bIsEnabled = false, int iMinPlayers = 0, int iMaxPlayers = 64, float fCustomCooldown = 0.0f, std::optional> vecGroups = std::nullopt) { - m_strName = sName; - m_strDisplayName = sDisplayName; + m_strName = strName; m_iWorkshopId = iWorkshopId; + m_bMissingIdentifier = bMissingIdentifier; + m_strDisplayName = strDisplayName; m_bIsEnabled = bIsEnabled; m_fCustomCooldown = fCustomCooldown; m_iMinPlayers = iMinPlayers; m_iMaxPlayers = iMaxPlayers; - m_vecGroups = vecGroups; + + if (vecGroups.has_value()) + m_vecGroups = vecGroups.value(); } const char* GetName() { return m_strName.c_str(); }; const char* GetDisplayName() { return m_strDisplayName.empty() ? m_strName.c_str() : m_strDisplayName.c_str(); }; uint64 GetWorkshopId() const { return m_iWorkshopId; }; + bool IsMissingIdentifier() { return m_bMissingIdentifier; } bool IsEnabled() { return m_bIsEnabled; }; float GetCustomCooldown() { return m_fCustomCooldown; }; int GetMinPlayers() { return m_iMinPlayers; }; @@ -58,10 +89,21 @@ class CMap std::vector GetGroups() { return m_vecGroups; }; bool HasGroup(std::string strGroup) { return std::find(m_vecGroups.begin(), m_vecGroups.end(), strGroup) != m_vecGroups.end(); }; + bool IsAvailable(); + bool Load(); + std::shared_ptr GetCooldown(); + std::string GetCooldownText(bool bPlural); + + bool operator==(CMap& other) + { + return (this->GetName()[0] != '\0' && !V_strcasecmp(this->GetName(), other.GetName())) || (this->GetWorkshopId() != 0 && this->GetWorkshopId() == other.GetWorkshopId()); + } + private: std::string m_strName; std::string m_strDisplayName; uint64 m_iWorkshopId; + bool m_bMissingIdentifier; bool m_bIsEnabled; int m_iMinPlayers; int m_iMaxPlayers; @@ -72,9 +114,9 @@ class CMap class CGroup { public: - CGroup(std::string sName, bool bIsEnabled, float fCooldown) + CGroup(std::string strName, bool bIsEnabled, float fCooldown) { - m_strName = sName; + m_strName = strName; m_bIsEnabled = bIsEnabled; m_fCooldown = fCooldown; } @@ -89,29 +131,35 @@ class CGroup float m_fCooldown; }; -class CCooldown +// Implementation is a bit hardcoded for HandlePlayerMapLookup use +class CWorkshopDetailsQuery : public std::enable_shared_from_this { public: - CCooldown(std::string sMapName) + CWorkshopDetailsQuery(UGCQueryHandle_t hQuery, uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess) : + m_hQuery(hQuery), m_iWorkshopId(iWorkshopId), m_callbackSuccess(callbackSuccess) { - m_strMapName = sMapName; - m_timeCooldown = 0; - m_fPendingCooldown = 0.0f; + if (pController) + { + m_bConsole = false; + m_hController = pController->GetHandle(); + } + else + { + m_bConsole = true; + } } - 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(); + static std::shared_ptr Create(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess); private: - std::string m_strMapName; - time_t m_timeCooldown; - float m_fPendingCooldown; + void OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed); + + UGCQueryHandle_t m_hQuery; + CCallResult m_CallResult; + uint64 m_iWorkshopId; + bool m_bConsole; + CHandle m_hController; + QueryCallback_t m_callbackSuccess; }; class CMapVoteSystem @@ -127,29 +175,27 @@ class CMapVoteSystem } } bool LoadMapList(); - void OnLevelInit(const char* pMapName); + bool LoadCooldowns(); + void OnLevelInit(const char* pszMapName); void StartVote(); void FinishVote(); bool RegisterPlayerVote(CPlayerSlot iPlayerSlot, int iVoteOption); - std::vector GetMapIndexesFromSubstring(const char* sMapSubstring); - uint64 HandlePlayerMapLookup(CCSPlayerController* pController, const char* sMapSubstring, bool bAdmin = false); - int GetMapIndexFromString(const char* pszMapString); + std::vector> GetMapsFromSubstring(const char* pszMapSubstring); + void HandlePlayerMapLookup(CCSPlayerController* pController, std::string strMapSubstring, bool bAdmin, QueryCallback_t callbackSuccess); + std::shared_ptr GetMapFromString(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 AttemptNomination(CCSPlayerController* pController, const char* sMapSubstring); + void AttemptNomination(CCSPlayerController* pController, const char* pszMapSubstring); void PrintMapList(CCSPlayerController* pController); - bool IsMapIndexEnabled(int iMapIndex); int GetTotalNominations(int iMapIndex); - void ForceNextMap(CCSPlayerController* pController, const char* sMapSubstring); + void ForceNextMap(CCSPlayerController* pController, const char* pszMapSubstring); + std::vector> GetMapList() { return m_vecMapList; } int GetMapListSize() { return m_vecMapList.size(); }; + std::shared_ptr GetMapByIndex(int iMapIndex) { return m_vecMapList[iMapIndex]; } + std::pair> GetMapInfoByIdentifiers(const char* pszMapName = "", uint64 iWorkshopId = 0); const char* GetMapName(int iMapIndex) { return m_vecMapList[iMapIndex]->GetName(); }; - const char* GetMapDisplayName(int iMapIndex) { return m_vecMapList[iMapIndex]->GetDisplayName(); }; - uint64 GetMapWorkshopId(int iMapIndex) { return m_vecMapList[iMapIndex]->GetWorkshopId(); }; void ClearPlayerInfo(int iSlot); bool IsVoteOngoing() { return m_bIsVoteOngoing; } bool IsIntermissionAllowed(bool bCheckOnly = true); @@ -157,29 +203,25 @@ class CMapVoteSystem CUtlStringList CreateWorkshopMapGroup(); void QueueMapDownload(PublishedFileId_t iWorkshopId); void PrintDownloadProgress(); - 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; } - int GetDownloadQueueSize() { return m_DownloadQueue.Count(); } - int GetCurrentMapIndex() { return m_iCurrentMapIndex; } - 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(); } + std::shared_ptr GetCurrentMap() { return m_pCurrentMap; } + void SetCurrentMap(std::shared_ptr pCurrentMap) { m_pCurrentMap = pCurrentMap; } + int GetDownloadQueueSize() { return m_DownloadQueue.size(); } void ClearInvalidNominations(); - uint64 GetForcedNextMap() { return m_iForcedNextMap; } - std::string GetForcedNextMapName() { return GetForcedNextMap() > GetMapListSize() ? std::to_string(GetForcedNextMap()) : GetMapName(GetForcedNextMap()); } - bool ConvertMapListKVToJSON(); + std::shared_ptr GetForcedNextMap() { return m_pForcedNextMap; } + void SetForcedNextMap(std::shared_ptr pForcedNextMap) { m_pForcedNextMap = pForcedNextMap; } 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); + std::string StringToLower(std::string strValue); void SetDisabledCooldowns(bool bValue) { g_bDisableCooldowns = bValue; } // Can be used by custom fork features, e.g. an auto-restart void ProcessGroupCooldowns(); - void ReloadCurrentMap(); + bool ReloadMapList(bool bReloadMap = true); + bool ConvertCooldownsKVToJSON(); + void AddWorkshopDetailsQuery(std::shared_ptr pQuery) { m_vecWorkshopDetailsQueries.push_back(pQuery); } + void RemoveWorkshopDetailsQuery(std::shared_ptr pQuery) { m_vecWorkshopDetailsQueries.erase(std::remove(m_vecWorkshopDetailsQueries.begin(), m_vecWorkshopDetailsQueries.end(), pQuery), m_vecWorkshopDetailsQueries.end()); } + void SetPlayerNomination(int iPlayerSlot, int iMapIndex) { m_arrPlayerNominations[iPlayerSlot] = iMapIndex; } private: int WinningMapIndex(); @@ -188,23 +230,25 @@ class CMapVoteSystem bool WriteMapCooldownsToFile(); STEAM_GAMESERVER_CALLBACK_MANUAL(CMapVoteSystem, OnMapDownloaded, DownloadItemResult_t, m_CallbackDownloadItemResult); - CUtlQueue m_DownloadQueue; + std::deque m_DownloadQueue; std::vector> m_vecMapList; std::vector> m_vecGroups; std::vector> m_vecCooldowns; int m_arrPlayerNominations[MAXPLAYERS]; - uint64 m_iForcedNextMap = -1; // Can be a map index or a workshop ID + std::shared_ptr m_pForcedNextMap; int m_iRandomWinnerShift = 0; int m_arrPlayerVotes[MAXPLAYERS]; - int m_iCurrentMapIndex = -1; bool m_bIsVoteOngoing = false; bool m_bMapListLoaded = false; bool m_bIntermissionStarted = false; - uint64 m_iCurrentWorkshopMap = 0; - std::string m_strCurrentMap = "MISSING_MAP"; + std::shared_ptr m_pCurrentMap; int m_iVoteSize = 0; bool g_bDisableCooldowns = false; + std::filesystem::file_time_type m_timeMapListModified = std::filesystem::file_time_type::min(); + std::weak_ptr m_pDownloadProgressTimer; + std::weak_ptr m_pRateLimitedDownloadTimer; + std::vector> m_vecWorkshopDetailsQueries; }; extern CMapVoteSystem* g_pMapVoteSystem; \ No newline at end of file diff --git a/src/panoramavote.cpp b/src/panoramavote.cpp index f14f5ae9..61660d8a 100644 --- a/src/panoramavote.cpp +++ b/src/panoramavote.cpp @@ -33,10 +33,6 @@ #include "tier0/memdbgon.h" -extern IGameEventManager2* g_gameEventManager; -extern IGameEventSystem* g_gameEventSystem; -extern INetworkMessages* g_pNetworkMessages; - CPanoramaVoteHandler* g_pPanoramaVoteHandler = nullptr; void CPanoramaVoteHandler::Reset() @@ -193,7 +189,7 @@ bool CPanoramaVoteHandler::SendYesNoVote(float flDuration, int iCaller, const ch (m_VoteHandler)(YesNoVoteAction::VoteAction_Start, 0, 0); int voteNum = m_iVoteCount; - new CTimer(flDuration, false, true, [voteNum]() { + CTimer::Create(flDuration, TIMERFLAG_MAP, [voteNum]() { // Ensure we dont end the wrong vote if (voteNum == g_pPanoramaVoteHandler->m_iVoteCount) g_pPanoramaVoteHandler->EndVote(YesNoVoteEndReason::VoteEnd_TimeUp); @@ -258,7 +254,7 @@ void CPanoramaVoteHandler::CheckForEarlyVoteClose() if (votes >= m_iVoterCount) { // Do this next frame to prevent a crash - new CTimer(0.0, false, true, []() { + CTimer::Create(0.0, TIMERFLAG_MAP, []() { g_pPanoramaVoteHandler->EndVote(YesNoVoteEndReason::VoteEnd_AllVotes); return -1.0; }); diff --git a/src/patches.cpp b/src/patches.cpp index 0e83f11f..64cf37f7 100644 --- a/src/patches.cpp +++ b/src/patches.cpp @@ -29,8 +29,6 @@ #include "tier0/memdbgon.h" -extern CGameConfig* g_GameConfig; - CMemPatch g_CommonPatches[] = { CMemPatch("ServerMovementUnlock", "ServerMovementUnlock"), diff --git a/src/playermanager.cpp b/src/playermanager.cpp index c3e4b04f..14d00396 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -40,12 +40,7 @@ #include "tier0/memdbgon.h" -extern IVEngineServer2* g_pEngineServer2; -extern CGameEntitySystem* g_pEntitySystem; -extern CGlobalVars* GetGlobals(); -extern IGameEventSystem* g_gameEventSystem; -extern CUtlVector* GetClientList(); -extern CSpawnGroupMgrGameSystem* g_pSpawnGroupMgr; +CPlayerManager* g_playerManager = nullptr; CConVar g_cvarAdminImmunityTargetting("cs2f_admin_immunity", FCVAR_NONE, "Mode for which admin immunity system targetting allows: 0 - strictly lower, 1 - equal to or lower, 2 - ignore immunity levels", 0, true, 0, true, 2); CConVar g_cvarEnableMapSteamIds("cs2f_map_steamids_enable", FCVAR_NONE, "Whether to make Steam ID's available to maps", false); @@ -105,7 +100,7 @@ void ZEPlayer::OnSpawn() SetSpeedMod(1.f); ZEPlayerHandle handle = GetHandle(); - new CTimer(0.0f, false, false, [handle] { + CTimer::Create(0.0f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [handle] { if (handle.Get()) { handle.Get()->CreatePointOrient(); @@ -172,26 +167,48 @@ CConVar g_cvarFlashLightShadows("cs2f_flashlight_shadows", FCVAR_NONE, "Wh CConVar g_cvarFlashLightTransmitOthers("cs2f_flashlight_transmit_others", FCVAR_NONE, "Whether to transmit other player's flashlights, recommended to have shadows off for this", false); CConVar g_cvarFlashLightBrightness("cs2f_flashlight_brightness", FCVAR_NONE, "How bright should flashlights be", 1.0f); CConVar g_cvarFlashLightDistance("cs2f_flashlight_distance", FCVAR_NONE, "How far flashlights should be from the player's head", 54.0f); // The minimum distance such that an awp wouldn't block the light +CConVar g_cvarFlashLightAngle("cs2f_flashlight_angle", FCVAR_NONE, "How wide should the flashlight be in degrees", 45.0f); CConVar g_cvarFlashLightColor("cs2f_flashlight_color", FCVAR_NONE, "What color to use for flashlights", Color(255, 255, 255)); CConVar g_cvarFlashLightAttachment("cs2f_flashlight_attachment", FCVAR_NONE, "Which attachment to parent a flashlight to. If the player model is not properly setup, you might have to use clip_limit here instead", "axis_of_intent"); -void ZEPlayer::SpawnFlashLight() +void TeleportFlashLight(CCSPlayerPawn* pPawn, CBaseEntity* pLight, float flDistance = -1.f, QAngle angOverride = vec3_angle) { - if (GetFlashLight()) + if (!pPawn || !pLight) return; - CCSPlayerPawn* pPawn = (CCSPlayerPawn*)CCSPlayerController::FromSlot(GetPlayerSlot())->GetPawn(); - Vector origin = pPawn->GetAbsOrigin(); Vector forward; AngleVectors(pPawn->m_angEyeAngles(), &forward); - origin.z += 64.0f; - origin += forward * g_cvarFlashLightDistance.Get(); + float flScale = pPawn->m_CBodyComponent()->m_pSceneNode->m_flScale; + + if (flDistance == -1.f) + flDistance = g_cvarFlashLightDistance.Get(); + + origin.z += 64.f * flScale; + origin += forward * flDistance * flScale; + + pLight->AcceptInput("ClearParent"); + + if (angOverride == vec3_angle) + angOverride = pPawn->m_angEyeAngles(); + + pLight->Teleport(&origin, &angOverride, nullptr); + + pLight->SetParent(pPawn); + pLight->AcceptInput("SetParentAttachmentMaintainOffset", g_cvarFlashLightAttachment.Get().String()); +} + +void ZEPlayer::SpawnFlashLight() +{ + if (GetFlashLight()) + return; + + CCSPlayerPawn* pPawn = (CCSPlayerPawn*)CCSPlayerController::FromSlot(GetPlayerSlot())->GetPawn(); CBarnLight* pLight = CreateEntityByName("light_barn"); - pLight->m_bEnabled = true; + pLight->m_bEnabled = false; pLight->m_Color->SetColor(g_cvarFlashLightColor.Get().r(), g_cvarFlashLightColor.Get().g(), g_cvarFlashLightColor.Get().b()); pLight->m_flBrightness = g_cvarFlashLightBrightness.Get(); pLight->m_flRange = 2048.0f; @@ -199,10 +216,9 @@ void ZEPlayer::SpawnFlashLight() pLight->m_flSoftY = 1.0f; pLight->m_flSkirt = 0.5f; pLight->m_flSkirtNear = 1.0f; - pLight->m_vSizeParams->Init(45.0f, 45.0f, 0.02f); + pLight->m_vSizeParams->Init(g_cvarFlashLightAngle.Get(), g_cvarFlashLightAngle.Get(), 0.02f); pLight->m_nCastShadows = g_cvarFlashLightShadows.Get(); pLight->m_nDirectLight = 3; - pLight->Teleport(&origin, &pPawn->m_angEyeAngles(), nullptr); // Have to use keyvalues for this since the schema prop is a resource handle CEntityKeyValues* pKeyValues = new CEntityKeyValues(); @@ -210,28 +226,26 @@ void ZEPlayer::SpawnFlashLight() pLight->DispatchSpawn(pKeyValues); - pLight->SetParent(pPawn); - pLight->AcceptInput("SetParentAttachmentMaintainOffset", g_cvarFlashLightAttachment.Get().String()); + TeleportFlashLight(pPawn, pLight); SetFlashLight(pLight); } void ZEPlayer::ToggleFlashLight() { + CCSPlayerController* pController = CCSPlayerController::FromSlot(GetPlayerSlot()); + // Play the "click" sound CSingleRecipientFilter filter(GetPlayerSlot()); - CCSPlayerController::FromSlot(GetPlayerSlot())->EmitSoundFilter(filter, "HudChat.Message"); - - CBarnLight* pLight = GetFlashLight(); + pController->EmitSoundFilter(filter, "HudChat.Message"); - // Create a flashlight if we don't have one, and don't bother with the input since it spawns enabled - if (!pLight) - { + if (!GetFlashLight()) SpawnFlashLight(); - return; - } - pLight->AcceptInput(pLight->m_bEnabled() ? "Disable" : "Enable"); + GetFlashLight()->AcceptInput(m_hFlashLight->m_bEnabled ? "Disable" : "Enable"); + + if (m_hFlashLight->m_bEnabled) + TeleportFlashLight(pController->GetPlayerPawn(), GetFlashLight()); } CConVar g_cvarFloodInterval("cs2f_flood_interval", FCVAR_NONE, "Amount of time allowed between chat messages acquiring flood tokens", 0.75f, true, 0.0f, false, 0.0f); @@ -309,7 +323,7 @@ void ZEPlayer::StartBeacon(Color color, ZEPlayerHandle hGiver /* = 0*/) if (pGiver && pGiver->IsLeader()) bLeaderBeacon = true; - new CTimer(0.0f, false, false, [hPlayer, hParticle, hGiver, iTeamNum, bLeaderBeacon]() { + CTimer::Create(0.0f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hPlayer, hParticle, hGiver, iTeamNum, bLeaderBeacon]() { CParticleSystem* pParticle = hParticle.Get(); if (!hPlayer.IsValid() || !pParticle) @@ -326,7 +340,7 @@ void ZEPlayer::StartBeacon(Color color, ZEPlayerHandle hGiver /* = 0*/) pParticle->AcceptInput("Start"); // delayed DestroyImmediately input so particle effect can be replayed (and default particle doesn't bug out) - new CTimer(0.5f, false, false, [hParticle]() { + CTimer::Create(0.5f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hParticle]() { CParticleSystem* particle = hParticle.Get(); if (particle) particle->AcceptInput("DestroyImmediately"); @@ -464,7 +478,7 @@ void ZEPlayer::StartGlow(Color color, int duration) int iTeamNum = hPawn->m_iTeamNum(); // check if player's team or model changed - new CTimer(0.5f, false, false, [hGlowModel, hPawn, iTeamNum]() { + CTimer::Create(0.5f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hGlowModel, hPawn, iTeamNum]() { CBaseModelEntity* pModel = hGlowModel.Get(); CCSPlayerPawn* pawn = hPawn.Get(); @@ -493,7 +507,7 @@ void ZEPlayer::StartGlow(Color color, int duration) if (duration < 1) return; - new CTimer((float)duration, false, false, [hGlowModel]() { + CTimer::Create((float)duration, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hGlowModel]() { CBaseModelEntity* pModel = hGlowModel.Get(); if (!pModel) @@ -813,7 +827,7 @@ void CPlayerManager::OnClientDisconnect(CPlayerSlot slot) g_pMapVoteSystem->ClearPlayerInfo(slot.Get()); // One tick delay, to ensure player count decrements - new CTimer(0.01f, false, true, []() { + CTimer::Create(0.01f, TIMERFLAG_MAP, []() { g_pVoteManager->CheckRTVStatus(); g_pMapVoteSystem->ClearInvalidNominations(); return -1.0f; @@ -905,7 +919,7 @@ void CPlayerManager::OnValidateAuthTicket(ValidateAuthTicketResponse_t* pRespons ClientPrint(pController, HUD_PRINTTALK, " \7WARNING: You will be kicked in %i seconds due to failed Steam authentication.\n", g_cvarDelayAuthFailKick.Get()); ZEPlayerHandle hPlayer = pPlayer->GetHandle(); - new CTimer(g_cvarDelayAuthFailKick.Get(), true, true, [hPlayer]() { + CTimer::Create(g_cvarDelayAuthFailKick.Get(), TIMERFLAG_NONE, [hPlayer]() { if (!hPlayer.IsValid()) return -1.f; @@ -1022,8 +1036,6 @@ static const char* g_szPlayerStates[] = "STATE_GUNGAME_RESPAWN", "STATE_DORMANT"}; -extern CConVar g_cvarEnableHide; - void CPlayerManager::UpdatePlayerStates() { if (!GetGlobals()) @@ -1074,7 +1086,7 @@ CConVar g_cvarInfiniteAmmo("cs2f_infinite_reserve_ammo", FCVAR_NONE, "Whet void CPlayerManager::SetupInfiniteAmmo() { - new CTimer(5.0f, false, true, []() { + CTimer::Create(5.0f, TIMERFLAG_MAP, []() { if (!g_cvarInfiniteAmmo.Get() || !GetGlobals()) return 5.0f; diff --git a/src/playermanager.h b/src/playermanager.h index 1455644d..c8e08933 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -31,6 +31,9 @@ #include "utlvector.h" #include +extern CConVar g_cvarFlashLightTransmitOthers; +extern CConVar g_cvarFlashLightAttachment; + #define NO_TARGET_BLOCKS (0) #define NO_RANDOM (1 << 1) #define NO_MULTIPLE (1 << 2) diff --git a/src/user_preferences.h b/src/user_preferences.h index 60091a9d..7e0afb42 100644 --- a/src/user_preferences.h +++ b/src/user_preferences.h @@ -60,6 +60,8 @@ class CUserPreferencesStorage virtual void StorePreferences(uint64 iSteamId, UserPrefsMap_t& preferences, StorageCallback_t cb) = 0; }; +extern CUserPreferencesStorage* g_pUserPreferencesStorage; + class CUserPreferencesREST : public CUserPreferencesStorage { public: @@ -99,5 +101,4 @@ class CUserPreferencesSystem bool m_mPreferencesLoaded[MAXPLAYERS]; }; -extern CUserPreferencesStorage* g_pUserPreferencesStorage; extern CUserPreferencesSystem* g_pUserPreferencesSystem; \ No newline at end of file diff --git a/src/utils/entity.cpp b/src/utils/entity.cpp index ac82c7c7..0e3d452f 100644 --- a/src/utils/entity.cpp +++ b/src/utils/entity.cpp @@ -21,6 +21,7 @@ #include "../addresses.h" #include "../common.h" +#include "../cs2fixes.h" #include "../gameconfig.h" #include "../utils/virtual.h" #include "entity/cgamerules.h" @@ -29,10 +30,6 @@ #include "tier0/memdbgon.h" -extern CGameEntitySystem* g_pEntitySystem; -extern CGameConfig* g_GameConfig; -extern CCSGameRules* g_pGameRules; - CBaseEntity* UTIL_FindPickerEntity(CBasePlayerController* pPlayer) { if (!g_pGameRules) diff --git a/src/utils/hud_manager.cpp b/src/utils/hud_manager.cpp index 3127c452..63ed1060 100644 --- a/src/utils/hud_manager.cpp +++ b/src/utils/hud_manager.cpp @@ -26,14 +26,10 @@ #include "gameevents.pb.h" #include "networksystem/inetworkmessages.h" -extern CCSGameRules* g_pGameRules; -extern IGameEventManager2* g_gameEventManager; -extern IGameEventSystem* g_gameEventSystem; -extern CGlobalVars* GetGlobals(); - CConVar g_cvarFixHudFlashing("cs2f_fix_hud_flashing", FCVAR_NONE, "Whether to fix hud flashing using a workaround, this BREAKS warmup so pick one or the other", false); CConVar g_cvarDisableHudOutsideRound("cs2f_disable_hud_outside_round", FCVAR_NONE, "Whether to disable hud messages that would flash when a round is not ongoing, since flashing fix cannot run then", false); CConVar g_cvarHudDurationLeeway("cs2f_hud_duration_leeway", FCVAR_NONE, "Extra seconds duration to leave hud messages visible (without priority), reduces transition flashes between different priority messages", 2); + static std::vector> g_vecHudMessages; bool ShouldDisplayForPlayer(ZEPlayerHandle hPlayer, EHudPriority ePriority) @@ -84,7 +80,7 @@ void CreateHudMessage(std::shared_ptr pHudMessage) g_vecHudMessages.push_back(pHudMessage); // Start a timer to remove this hud message after its duration passes - new CTimer(pHudMessage->GetDuration(), true, true, [pHudMessage]() { + CTimer::Create(pHudMessage->GetDuration(), TIMERFLAG_NONE, [pHudMessage]() { g_vecHudMessages.erase(std::remove(g_vecHudMessages.begin(), g_vecHudMessages.end(), pHudMessage), g_vecHudMessages.end()); return -1.0f; @@ -140,7 +136,7 @@ void SendHudMessageAll(int iDuration, EHudPriority ePriority, const char* pszMes void StartFlashingFixTimer() { // Timer that fakes m_bGameRestart enabled, to fix flashing with show_survival_respawn_status - new CTimer(0.5f, false, true, []() { + CTimer::Create(0.5f, TIMERFLAG_MAP, []() { if (!g_cvarFixHudFlashing.Get() || !g_pGameRules) return 0.5f; diff --git a/src/utils/hud_manager.h b/src/utils/hud_manager.h index 6e4c0ba4..bfd21ef7 100644 --- a/src/utils/hud_manager.h +++ b/src/utils/hud_manager.h @@ -23,6 +23,8 @@ #include #include +extern CConVar g_cvarFixHudFlashing; + enum class EHudPriority { InfectionCountdown = 2, @@ -59,6 +61,4 @@ void SendHudMessage(ZEPlayer* pPlayer, int iDuration, EHudPriority ePriority, co void SendHudMessageAll(int iDuration, EHudPriority ePriority, const char* pszMessage, ...); void StartFlashingFixTimer(); -std::string EscapeHTMLSpecialCharacters(std::string strMsg); - -extern CConVar g_cvarFixHudFlashing; \ No newline at end of file +std::string EscapeHTMLSpecialCharacters(std::string strMsg); \ No newline at end of file diff --git a/src/utils/weapon.cpp b/src/utils/weapon.cpp index 0526e580..da98b3ae 100644 --- a/src/utils/weapon.cpp +++ b/src/utils/weapon.cpp @@ -97,18 +97,17 @@ static std::unordered_map s_WeaponMap = { {"ammo_50ae", {"ammo_50ae", 0, 0, GEAR_SLOT_UTILITY} }, }; -const WeaponInfo_t* FindWeaponInfoByClass(const char* pClass) +const WeaponInfo_t* FindWeaponInfoByClass(std::string strClass) { - const auto& find = s_WeaponMap.find(pClass); + const auto& find = s_WeaponMap.find(strClass); return find == s_WeaponMap.end() ? nullptr : &find->second; } -const WeaponInfo_t* FindWeaponInfoByClassCaseInsensitive(const char* pClass) +const WeaponInfo_t* FindWeaponInfoByClassCaseInsensitive(std::string strClass) { - CUtlString string(pClass); - string.ToLowerFast(); - const auto& find = s_WeaponMap.find(string.Get()); - return find == s_WeaponMap.end() ? nullptr : &find->second; + std::transform(strClass.begin(), strClass.end(), strClass.begin(), [](unsigned char c) { return std::tolower(c); }); + + return FindWeaponInfoByClass(strClass); } const WeaponInfo_t* FindWeaponInfoByAlias(const char* pAlias) diff --git a/src/utils/weapon.h b/src/utils/weapon.h index dc12704b..1960b6a0 100644 --- a/src/utils/weapon.h +++ b/src/utils/weapon.h @@ -39,8 +39,8 @@ struct WeaponInfo_t {} }; -const WeaponInfo_t* FindWeaponInfoByClass(const char* pClass); -const WeaponInfo_t* FindWeaponInfoByClassCaseInsensitive(const char* pClass); +const WeaponInfo_t* FindWeaponInfoByClass(std::string strClass); +const WeaponInfo_t* FindWeaponInfoByClassCaseInsensitive(std::string strClass); const WeaponInfo_t* FindWeaponInfoByAlias(const char* pAlias); const WeaponInfo_t* FindWeaponInfoByItemDefIndex(int16_t iItemDefinitionIndex); diff --git a/src/votemanager.cpp b/src/votemanager.cpp index 89f50a4e..0985ab1f 100644 --- a/src/votemanager.cpp +++ b/src/votemanager.cpp @@ -26,11 +26,6 @@ #include "tier0/memdbgon.h" -extern CGameEntitySystem* g_pEntitySystem; -extern IVEngineServer2* g_pEngineServer2; -extern CGlobalVars* GetGlobals(); -extern CCSGameRules* g_pGameRules; - CVoteManager* g_pVoteManager = nullptr; CConVar g_cvarVoteManagerEnable("cs2f_votemanager_enable", FCVAR_NONE, "Whether to enable votemanager features such as RTV and extends", false); @@ -54,19 +49,19 @@ void CVoteManager::VoteManager_Init() m_iExtends = 0; - new CTimer(g_cvarExtendVoteDelay.Get(), false, true, [this]() { + CTimer::Create(g_cvarExtendVoteDelay.Get(), TIMERFLAG_MAP, [this]() { if (m_ExtendState < EExtendState::POST_EXTEND_NO_EXTENDS_LEFT) m_ExtendState = EExtendState::EXTEND_ALLOWED; return -1.0f; }); - new CTimer(g_cvarRtvDelay.Get(), false, true, [this]() { + CTimer::Create(g_cvarRtvDelay.Get(), TIMERFLAG_MAP, [this]() { if (m_RTVState != ERTVState::BLOCKED_BY_ADMIN) m_RTVState = ERTVState::RTV_ALLOWED; return -1.0f; }); - new CTimer(m_flExtendVoteTickrate, false, true, std::bind(&CVoteManager::TimerCheckTimeleft, this)); + CTimer::Create(m_flExtendVoteTickrate, TIMERFLAG_MAP, std::bind(&CVoteManager::TimerCheckTimeleft, this)); } float CVoteManager::TimerCheckTimeleft() @@ -104,7 +99,7 @@ float CVoteManager::TimerCheckTimeleft() m_bVoteStarting = true; ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Extend vote starting in 10 seconds!"); - new CTimer(7.0f, false, true, [this]() { + CTimer::Create(7.0f, TIMERFLAG_MAP, [this]() { if (m_iVoteStartTicks == 0) { m_iVoteStartTicks = 3; @@ -634,7 +629,7 @@ bool CVoteManager::VoteExtendEndCallback(YesNoVoteInfo info) if (g_cvarExtendVoteMode.Get() == EExtendVoteMode::EXTENDVOTE_AUTO) { // small delay to allow cvar change to go through - new CTimer(0.1, false, true, [this]() { + CTimer::Create(0.1, TIMERFLAG_MAP, [this]() { m_ExtendState = EExtendState::EXTEND_ALLOWED; return -1.0f; }); @@ -644,7 +639,7 @@ bool CVoteManager::VoteExtendEndCallback(YesNoVoteInfo info) m_ExtendState = EExtendState::POST_EXTEND_COOLDOWN; // Allow another extend vote after added time lapses - new CTimer(g_cvarExtendTimeToAdd.Get() * 60.0f, false, true, [this]() { + CTimer::Create(g_cvarExtendTimeToAdd.Get() * 60.0f, TIMERFLAG_MAP, [this]() { if (m_ExtendState == EExtendState::POST_EXTEND_COOLDOWN) m_ExtendState = EExtendState::EXTEND_ALLOWED; return -1.0f; @@ -678,7 +673,7 @@ void CVoteManager::StartExtendVote(int iCaller) g_pPanoramaVoteHandler->SendYesNoVoteToAll(g_cvarExtendVoteDuration.Get(), 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_cvarExtendVoteDuration.Get() - 3.0f, false, true, [this]() { + CTimer::Create(g_cvarExtendVoteDuration.Get() - 3.0f, TIMERFLAG_MAP, [this]() { if (m_iVoteEndTicks == 0 || m_ExtendState != EExtendState::IN_PROGRESS) { m_iVoteEndTicks = 3; @@ -717,7 +712,7 @@ bool CVoteManager::CheckRTVStatus() { ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "RTV succeeded! Ending the map now..."); - new CTimer(3.0f, false, true, []() { + CTimer::Create(3.0f, TIMERFLAG_MAP, []() { g_pGameRules->TerminateRound(5.0f, CSRoundEndReason::Draw); return -1.0f; diff --git a/src/votemanager.h b/src/votemanager.h index ab850de9..6622cfa7 100644 --- a/src/votemanager.h +++ b/src/votemanager.h @@ -22,6 +22,8 @@ #include "panoramavote.h" +extern CConVar g_cvarVoteManagerEnable; + enum class ERTVState { MAP_START, @@ -52,8 +54,6 @@ enum EExtendVoteMode EXTENDVOTE_AUTO, // Extend votes can be triggered by !ve or when map timelimit reaches a given value }; -extern CConVar g_cvarVoteManagerEnable; - class CVoteManager { public: diff --git a/src/zombiereborn.cpp b/src/zombiereborn.cpp index 8720f505..a711c0ed 100644 --- a/src/zombiereborn.cpp +++ b/src/zombiereborn.cpp @@ -44,17 +44,6 @@ #include "tier0/memdbgon.h" -using ordered_json = nlohmann::ordered_json; - -extern CGameEntitySystem* g_pEntitySystem; -extern IVEngineServer2* g_pEngineServer2; -extern CGlobalVars* GetGlobals(); -extern CCSGameRules* g_pGameRules; -extern IGameEventManager2* g_gameEventManager; -extern IGameEventSystem* g_gameEventSystem; -extern double g_flUniversalTime; -extern CConVar g_cvarFreeArmor; - void ZR_Infect(CCSPlayerController* pAttackerController, CCSPlayerController* pVictimController, bool bBroadcast); void ZR_Cure(CCSPlayerController* pTargetController); void ZR_EndRoundAndAddTeamScore(int iTeamNum); @@ -535,19 +524,18 @@ void CZRPlayerClassManager::ApplyHumanClass(std::shared_ptr pClass { ApplyBaseClass(pClass, pPawn); CCSPlayerController* pController = CCSPlayerController::FromPawn(pPawn); - if (pController) - CZRRegenTimer::StopRegen(pController); - if (!g_cvarEnableLeader.Get() || !pController) + if (!pController) return; + CancelRegenTimer(pController->GetPlayerSlot()); ZEPlayer* pPlayer = g_playerManager->GetPlayer(pController->GetPlayerSlot()); - if (pPlayer && pPlayer->IsLeader()) + if (g_cvarEnableLeader.Get() && pPlayer && pPlayer->IsLeader()) { CHandle hPawn = pPawn->GetHandle(); - new CTimer(0.02f, false, false, [hPawn]() { + CTimer::Create(0.02f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hPawn]() { CCSPlayerPawn* pPawn = hPawn.Get(); if (pPawn) Leader_ApplyLeaderVisuals(pPawn); @@ -628,8 +616,9 @@ void CZRPlayerClassManager::ApplyZombieClass(std::shared_ptr pCla { ApplyBaseClass(pClass, pPawn); CCSPlayerController* pController = CCSPlayerController::FromPawn(pPawn); + if (pController) - CZRRegenTimer::StartRegen(pClass->flHealthRegenInterval, pClass->iHealthRegenCount, pController); + CreateRegenTimer(pController->GetPlayerSlot(), pPawn->GetHandle(), pClass->flHealthRegenInterval, pClass->iHealthRegenCount); } void CZRPlayerClassManager::ApplyPreferredOrDefaultZombieClass(CCSPlayerPawn* pPawn) @@ -686,83 +675,38 @@ void CZRPlayerClassManager::GetZRClassList(int iTeam, std::vector hPawn, float flInterval, int iAmount) { - CCSPlayerPawn* pPawn = m_hPawnHandle.Get(); - if (!pPawn || !pPawn->IsAlive()) - return false; + // Double check a regen timer isn't somehow already running + CancelRegenTimer(iPlayerSlot); - // Do we even need to regen? - if (pPawn->m_iHealth() >= pPawn->m_iMaxHealth()) - return true; + auto wTimer = CTimer::Create(flInterval, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hPawn, flInterval, iAmount]() { + CCSPlayerPawn* pPawn = hPawn.Get(); - int iHealth = pPawn->m_iHealth() + m_iRegenAmount; - pPawn->m_iHealth = pPawn->m_iMaxHealth() < iHealth ? pPawn->m_iMaxHealth() : iHealth; - return true; -} + if (!pPawn || !pPawn->IsAlive()) + return -1.0f; -void CZRRegenTimer::StartRegen(float flRegenInterval, int iRegenAmount, CCSPlayerController* pController) -{ - int slot = pController->GetPlayerSlot(); - CZRRegenTimer* pTimer = s_vecRegenTimers[slot]; - if (pTimer != nullptr) - { - pTimer->m_flInterval = flRegenInterval; - pTimer->m_iRegenAmount = iRegenAmount; - return; - } - s_vecRegenTimers[slot] = new CZRRegenTimer(flRegenInterval, iRegenAmount, pController->m_hPlayerPawn()); -} + // Do we even need to regen? + if (pPawn->m_iHealth() >= pPawn->m_iMaxHealth()) + return flInterval; -void CZRRegenTimer::StopRegen(CCSPlayerController* pController) -{ - int slot = pController->GetPlayerSlot(); - if (!s_vecRegenTimers[slot]) - return; + int iHealth = pPawn->m_iHealth() + iAmount; + pPawn->m_iHealth = pPawn->m_iMaxHealth() < iHealth ? pPawn->m_iMaxHealth() : iHealth; + return flInterval; + }); - delete s_vecRegenTimers[slot]; - s_vecRegenTimers[slot] = nullptr; + m_vecRegenTimers[iPlayerSlot] = wTimer; } -void CZRRegenTimer::Tick() +void CZRPlayerClassManager::CancelRegenTimer(int iPlayerSlot) { - // check every timer every 0.1 - if (s_flNextExecution > g_flUniversalTime) + if (iPlayerSlot < 0 || iPlayerSlot > 63) return; - VPROF("CZRRegenTimer::Tick"); - - s_flNextExecution = g_flUniversalTime + 0.1f; - for (int i = MAXPLAYERS - 1; i >= 0; i--) - { - CZRRegenTimer* pTimer = s_vecRegenTimers[i]; - if (!pTimer) - continue; - - if (pTimer->m_flLastExecute == -1) - pTimer->m_flLastExecute = g_flUniversalTime; + auto wTimer = m_vecRegenTimers[iPlayerSlot]; - // Timer execute - if (pTimer->m_flLastExecute + pTimer->m_flInterval <= g_flUniversalTime) - { - pTimer->Execute(); - pTimer->m_flLastExecute = g_flUniversalTime; - } - } -} - -void CZRRegenTimer::RemoveAllTimers() -{ - for (int i = MAXPLAYERS - 1; i >= 0; i--) - { - if (!s_vecRegenTimers[i]) - continue; - delete s_vecRegenTimers[i]; - s_vecRegenTimers[i] = nullptr; - } + if (!wTimer.expired()) + wTimer.lock()->Cancel(); } void ZR_OnLevelInit() @@ -770,7 +714,7 @@ void ZR_OnLevelInit() g_ZRRoundState = EZRRoundState::ROUND_START; // Delay one tick to override any .cfg's - new CTimer(0.02f, false, true, []() { + CTimer::Create(0.02f, TIMERFLAG_MAP, []() { // Here we force some cvars that are necessary for the gamemode g_pEngineServer2->ServerCommand("mp_give_player_c4 0"); g_pEngineServer2->ServerCommand("mp_friendlyfire 0"); @@ -989,11 +933,11 @@ void SetupCTeams() void ZR_OnRoundStart(IGameEvent* pEvent) { + SetupRespawnToggler(); ClientPrintAll(HUD_PRINTTALK, ZR_PREFIX "The game is \x05Humans vs. Zombies\x01, the goal for zombies is to infect all humans by knifing them."); + if (g_cvarInfectSpawnWarning.Get() && g_cvarInfectSpawnType.Get() == (int)EZRSpawnType::IN_PLACE) ClientPrintAll(HUD_PRINTTALK, ZR_PREFIX "Classic spawn is enabled! Zombies will be \x07spawning between humans\x01!"); - SetupRespawnToggler(); - CZRRegenTimer::RemoveAllTimers(); if (!GetGlobals()) return; @@ -1031,7 +975,7 @@ void ZR_OnPlayerSpawn(CCSPlayerController* pController) } CHandle handle = pController->GetHandle(); - new CTimer(0.05f, false, false, [handle, bInfect]() { + CTimer::Create(0.05f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [handle, bInfect]() { CCSPlayerController* pController = (CCSPlayerController*)handle.Get(); if (!pController) return -1.0f; @@ -1260,7 +1204,7 @@ void ZR_Infect(CCSPlayerController* pAttackerController, CCSPlayerController* pV pZEPlayer->SetInfectState(true); ZEPlayerHandle hPlayer = pZEPlayer->GetHandle(); - new CTimer(rand() % (int)g_cvarMoanInterval.Get(), false, false, [hPlayer]() { return ZR_MoanTimer(hPlayer); }); + CTimer::Create(rand() % (int)g_cvarMoanInterval.Get(), TIMERFLAG_MAP | TIMERFLAG_ROUND, [hPlayer]() { return ZR_MoanTimer(hPlayer); }); } } @@ -1301,7 +1245,7 @@ void ZR_InfectMotherZombie(CCSPlayerController* pVictimController, std::vectorSetInfectState(true); ZEPlayerHandle hPlayer = pZEPlayer->GetHandle(); - new CTimer(rand() % (int)g_cvarMoanInterval.Get(), false, false, [hPlayer]() { return ZR_MoanTimer(hPlayer); }); + CTimer::Create(rand() % (int)g_cvarMoanInterval.Get(), TIMERFLAG_MAP | TIMERFLAG_ROUND, [hPlayer]() { return ZR_MoanTimer(hPlayer); }); } // make players who've been picked as MZ recently less likely to be picked again @@ -1427,7 +1371,7 @@ void ZR_StartInitialCountdown() int iRand = rand(); auto iSecondsElapsed = std::make_shared(0); - new CTimer(0.0f, false, false, [iRand, iSecondsElapsed]() { + CTimer::Create(0.0f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [iRand, iSecondsElapsed]() { if (g_ZRRoundState != EZRRoundState::ROUND_START) return -1.0f; @@ -1567,7 +1511,7 @@ void SpawnPlayer(CCSPlayerController* pController) } CHandle handle = pController->GetHandle(); - new CTimer(2.0f, false, false, [handle]() { + CTimer::Create(2.0f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [handle]() { CCSPlayerController* pController = (CCSPlayerController*)handle.Get(); if (!pController || !g_bRespawnEnabled || pController->m_iTeamNum < CS_TEAM_T) return -1.0f; @@ -1671,7 +1615,7 @@ void ZR_OnPlayerDeath(IGameEvent* pEvent) // respawn player CHandle handle = pVictimController->GetHandle(); - new CTimer(g_cvarRespawnDelay.Get() < 0.0f ? 2.0f : g_cvarRespawnDelay.Get(), false, false, [handle]() { + CTimer::Create(g_cvarRespawnDelay.Get() < 0.0f ? 2.0f : g_cvarRespawnDelay.Get(), TIMERFLAG_MAP | TIMERFLAG_ROUND, [handle]() { CCSPlayerController* pController = (CCSPlayerController*)handle.Get(); if (!pController || !g_bRespawnEnabled || pController->m_iTeamNum < CS_TEAM_T) return -1.0f; @@ -1688,7 +1632,7 @@ void ZR_OnRoundFreezeEnd(IGameEvent* pEvent) // there is probably a better way to check when time is running out... void ZR_OnRoundTimeWarning(IGameEvent* pEvent) { - new CTimer(10.0, false, false, []() { + CTimer::Create(10.0, TIMERFLAG_MAP | TIMERFLAG_ROUND, []() { if (g_ZRRoundState == EZRRoundState::ROUND_END) return -1.0f; ZR_EndRoundAndAddTeamScore(g_cvarDefaultWinnerTeam.Get()); @@ -1855,7 +1799,7 @@ CON_COMMAND_CHAT(ztele, "- Teleport to spawn") CHandle pawnHandle = pPawn->GetHandle(); - new CTimer(5.0f, false, false, [spawnHandle, pawnHandle, initialpos]() { + CTimer::Create(5.0f, TIMERFLAG_MAP | TIMERFLAG_ROUND, [spawnHandle, pawnHandle, initialpos]() { CCSPlayerPawn* pPawn = pawnHandle.Get(); SpawnPoint* pSpawn = spawnHandle.Get(); diff --git a/src/zombiereborn.h b/src/zombiereborn.h index 87cb7d4c..8cdc6f5a 100644 --- a/src/zombiereborn.h +++ b/src/zombiereborn.h @@ -28,6 +28,8 @@ using ordered_json = nlohmann::ordered_json; +extern CConVar g_cvarEnableZR; + #define ZR_PREFIX " \4[Zombie:Reborn]\1 " #define HUMAN_CLASS_KEY_NAME "zr_human_class" #define ZOMBIE_CLASS_KEY_NAME "zr_zombie_class" @@ -39,6 +41,8 @@ enum class EZRRoundState ROUND_END, }; +extern EZRRoundState g_ZRRoundState; + enum EZRSpawnType { IN_PLACE, @@ -207,6 +211,8 @@ class CZRPlayerClassManager void ApplyPreferredOrDefaultZombieClass(CCSPlayerPawn* pPawn); void PrecacheModels(IEntityResourceManifest* pResourceManifest); void GetZRClassList(int iTeam, std::vector>& vecClasses, CCSPlayerController* pController = nullptr); + void CreateRegenTimer(int iPlayerSlot, CHandle hPawn, float flInterval, int iAmount); + void CancelRegenTimer(int iPlayerSlot); private: void ApplyBaseClass(std::shared_ptr pClass, CCSPlayerPawn* pPawn); @@ -217,27 +223,10 @@ class CZRPlayerClassManager // These exist so we can iterate the class maps in insertion order std::vector m_ZombieClassKeys; std::vector m_HumanClassKeys; + std::weak_ptr m_vecRegenTimers[MAXPLAYERS]; }; -class CZRRegenTimer : public CTimerBase -{ -public: - CZRRegenTimer(float flRegenInterval, int iRegenAmount, CHandle hPawnHandle) : - CTimerBase(flRegenInterval, false, false), m_iRegenAmount(iRegenAmount), m_hPawnHandle(hPawnHandle){}; - - bool Execute(); - static void StartRegen(float flRegenInterval, int iRegenAmount, CCSPlayerController* pController); - static void StopRegen(CCSPlayerController* pController); - static int GetIndex(CPlayerSlot slot); - static void Tick(); - static void RemoveAllTimers(); - -private: - static double s_flNextExecution; - static CZRRegenTimer* s_vecRegenTimers[MAXPLAYERS]; - int m_iRegenAmount; - CHandle m_hPawnHandle; -}; +extern CZRPlayerClassManager* g_pZRPlayerClassManager; struct ZRWeapon { @@ -259,6 +248,8 @@ class ZRWeaponConfig std::map> m_WeaponMap; }; +extern ZRWeaponConfig* g_pZRWeaponConfig; + class ZRHitgroupConfig { public: @@ -269,12 +260,7 @@ class ZRHitgroupConfig std::map> m_HitgroupMap; }; -extern ZRWeaponConfig* g_pZRWeaponConfig; extern ZRHitgroupConfig* g_pZRHitgroupConfig; -extern CZRPlayerClassManager* g_pZRPlayerClassManager; - -extern CConVar g_cvarEnableZR; -extern EZRRoundState g_ZRRoundState; void ZR_OnLevelInit(); void ZR_OnRoundPrestart(IGameEvent* pEvent);