From bc0ccf864228174adbcdcff18faaa21ffd8ac05b Mon Sep 17 00:00:00 2001 From: Vauff Date: Sat, 6 Dec 2025 11:04:21 -0500 Subject: [PATCH 1/9] Update compiler to Clang 21 --- .github/workflows/build.yml | 9 ++++++--- Dockerfile | 7 +++++-- sdk | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 989219ea8..81aed9c34 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,11 +61,14 @@ jobs: run: | cd ambuild && python setup.py install && cd .. - - name: Install Clang 16 + - name: Install Clang 21 if: matrix.os == 'ubuntu-latest' run: | - apt update && apt install -y clang-16 - ln -sf /usr/bin/clang-16 /usr/bin/clang && ln -sf /usr/bin/clang++-16 /usr/bin/clang++ + echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list + echo "deb-src http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + apt update && apt install -y clang-21 + ln -sf /usr/bin/clang-21 /usr/bin/clang && ln -sf /usr/bin/clang++-21 /usr/bin/clang++ - name: Build working-directory: CS2Fixes diff --git a/Dockerfile b/Dockerfile index e50f30feb..1608da652 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,11 @@ FROM registry.gitlab.steamos.cloud/steamrt/sniper/sdk WORKDIR /app -RUN apt update && apt install -y clang-16 -RUN ln -sf /usr/bin/clang-16 /usr/bin/clang && ln -sf /usr/bin/clang++-16 /usr/bin/clang++ +RUN echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list +RUN echo "deb-src http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list +RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - +RUN apt update && apt install -y clang-21 +RUN ln -sf /usr/bin/clang-21 /usr/bin/clang && ln -sf /usr/bin/clang++-21 /usr/bin/clang++ RUN git clone https://github.com/alliedmodders/ambuild RUN cd ambuild && python setup.py install && cd .. RUN git clone https://github.com/alliedmodders/metamod-source diff --git a/sdk b/sdk index 3aa193690..3bb772f7c 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 3aa1936902b9730341f6f785999d51727cbcf394 +Subproject commit 3bb772f7c0652cd78089522ac58235b9b34f71d6 From 3fc59f0e81c619ed1b7f16fb277ec0581b2997f7 Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 10 Dec 2025 15:30:58 -0500 Subject: [PATCH 2/9] Add utilities file --- AMBuilder | 1 + CS2Fixes.vcxproj | 2 + CS2Fixes.vcxproj.filters | 6 +++ src/adminsystem.cpp | 6 +-- src/commands.cpp | 6 +++ src/common.h | 6 +-- src/cs2_sdk/entity/globaltypes.h | 6 +-- src/cs2fixes.cpp | 69 +-------------------------- src/cs2fixes.h | 7 +-- src/map_votes.cpp | 10 +--- src/map_votes.h | 3 +- src/playermanager.cpp | 11 +++++ src/playermanager.h | 3 +- src/utils/utils.cpp | 80 ++++++++++++++++++++++++++++++++ src/utils/utils.h | 34 ++++++++++++++ src/zombiereborn.h | 2 +- 16 files changed, 155 insertions(+), 97 deletions(-) create mode 100644 src/utils/utils.cpp create mode 100644 src/utils/utils.h diff --git a/AMBuilder b/AMBuilder index d5f527955..23c072a4b 100644 --- a/AMBuilder +++ b/AMBuilder @@ -49,6 +49,7 @@ for sdk_target in MMSPlugin.sdk_targets: 'src/utils/entity.cpp', 'src/utils/weapon.cpp', 'src/utils/hud_manager.cpp', + 'src/utils/utils.cpp', 'src/cs2_sdk/entity/services.cpp', 'src/cs2_sdk/entity/ccsplayerpawn.cpp', 'src/cs2_sdk/entity/cbasemodelentity.cpp', diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index e442bc9e1..524de1858 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -218,6 +218,7 @@ + @@ -296,6 +297,7 @@ + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index d19d113dc..4b15140a0 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -185,6 +185,9 @@ Source Files\utils + + Source Files\utils + Source Files\cs2_sdk\entity @@ -412,6 +415,9 @@ Header Files\utils + + Header Files\utils + Header Files\cs2_sdk\entity diff --git a/src/adminsystem.cpp b/src/adminsystem.cpp index acc950504..fb358cf9f 100644 --- a/src/adminsystem.cpp +++ b/src/adminsystem.cpp @@ -594,11 +594,9 @@ CON_COMMAND_CHAT_FLAGS(rcon, " - Send a command to server console", ADM } // Normally this should be done on plugin init, but for whatever reason "log_flags +donotecho" crashes AFTER this - ExecuteOnce - ( + ExecuteOnce( LoggingSystem_RegisterBackdoorLoggingListener(&g_LoggingListener); - LoggingSystem_EnableBackdoorLoggingListeners(true); - ); + LoggingSystem_EnableBackdoorLoggingListeners(true);); // We don't have the equivalent of ServerExecute (to immediately execute commands) in source2, so manually find and execute the command diff --git a/src/commands.cpp b/src/commands.cpp index 159d3b4f0..30b882571 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -744,6 +744,12 @@ CON_COMMAND_CHAT(showteam, " - Get a player's current team") } } +// Because sv_fullupdate doesn't work +CON_COMMAND_F(cs2f_fullupdate, "- Force a full update for all clients.", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) +{ + g_playerManager->FullUpdateAllClients(); +} + #if _DEBUG CON_COMMAND_CHAT(myuid, "- Test") { diff --git a/src/common.h b/src/common.h index 327a4605c..c9a25fd2f 100644 --- a/src/common.h +++ b/src/common.h @@ -20,6 +20,7 @@ #pragma once #include "platform.h" +#include "utils.h" #define VPROF_LEVEL 1 @@ -44,7 +45,4 @@ #endif void UnlockConVars(); -void UnlockConCommands(); - -void Message(const char*, ...); -void Panic(const char*, ...); \ No newline at end of file +void UnlockConCommands(); \ No newline at end of file diff --git a/src/cs2_sdk/entity/globaltypes.h b/src/cs2_sdk/entity/globaltypes.h index 7ad24dd57..f6a078cb1 100644 --- a/src/cs2_sdk/entity/globaltypes.h +++ b/src/cs2_sdk/entity/globaltypes.h @@ -152,9 +152,9 @@ struct EmitSound_t { } const char* m_pSoundName; // 0x0 - Vector m_vecSoundOrigin; // 0x8 - float m_flVolume; // 0x14 - float m_flSoundTime; // 0x18 + Vector m_vecSoundOrigin; // 0x8 + float m_flVolume; // 0x14 + float m_flSoundTime; // 0x18 private: uint8_t pad_1c[0x4]; diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 4625da83c..1db9190bb 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -1,4 +1,4 @@ -/** +/** * ============================================================================= * CS2Fixes * Copyright (C) 2023-2025 Source2ZE @@ -63,32 +63,6 @@ #include "tier0/memdbgon.h" -void Message(const char* msg, ...) -{ - va_list args; - va_start(args, msg); - - char buf[1024] = {}; - V_vsnprintf(buf, sizeof(buf) - 1, msg, args); - - ConColorMsg(Color(255, 0, 255, 255), "[CS2Fixes] %s", buf); - - va_end(args); -} - -void Panic(const char* msg, ...) -{ - va_list args; - va_start(args, msg); - - char buf[1024] = {}; - V_vsnprintf(buf, sizeof(buf) - 1, msg, args); - - Warning("[CS2Fixes] %s", buf); - - va_end(args); -} - class GameSessionConfiguration_t {}; @@ -691,11 +665,6 @@ void CS2Fixes::Hook_GameServerSteamAPIDeactivated() RETURN_META(MRES_IGNORED); } -uint32 GetSoundEventHash(const char* pszSoundEventName) -{ - return MurmurHash2LowerCase(pszSoundEventName, 0x53524332); -} - void CS2Fixes::Hook_PostEvent(CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64* clients, INetworkMessageInternal* pEvent, const CNetMessage* pData, unsigned long nSize, NetChannelBufType_t bufType) { @@ -839,42 +808,6 @@ void CS2Fixes::AllPluginsLoaded() Message("AllPluginsLoaded\n"); } -CUtlVector* GetClientList() -{ - if (!GetNetworkGameServer()) - return nullptr; - - static int offset = g_GameConfig->GetOffset("CNetworkGameServer_ClientList"); - return (CUtlVector*)(&GetNetworkGameServer()[offset]); -} - -CServerSideClient* GetClientBySlot(CPlayerSlot slot) -{ - CUtlVector* pClients = GetClientList(); - - if (!pClients) - return nullptr; - - return pClients->Element(slot.Get()); -} - -void FullUpdateAllClients() -{ - auto pClients = GetClientList(); - - if (!pClients) - return; - - FOR_EACH_VEC(*pClients, i) - (*pClients)[i]->ForceFullUpdate(); -} - -// Because sv_fullupdate doesn't work -CON_COMMAND_F(cs2f_fullupdate, "- Force a full update for all clients.", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) -{ - FullUpdateAllClients(); -} - void CS2Fixes::Hook_ClientActive(CPlayerSlot slot, bool bLoadGame, const char* pszName, uint64 xuid) { Message("Hook_ClientActive(%d, %d, \"%s\", %lli)\n", slot, bLoadGame, pszName, xuid); diff --git a/src/cs2fixes.h b/src/cs2fixes.h index b9c17ff06..f340b92e8 100644 --- a/src/cs2fixes.h +++ b/src/cs2fixes.h @@ -1,4 +1,4 @@ -/** +/** * ============================================================================= * CS2Fixes * Copyright (C) 2023-2025 Source2ZE @@ -52,11 +52,8 @@ extern CSteamGameServerAPIContext g_steamAPI; extern CCSGameRules* g_pGameRules; extern CSpawnGroupMgrGameSystem* g_pSpawnGroupMgr; extern double g_flUniversalTime; +extern INetworkGameServer* GetNetworkGameServer(); extern CGlobalVars* GetGlobals(); -extern uint32 GetSoundEventHash(const char* pszSoundEventName); -extern CUtlVector* GetClientList(); -extern CServerSideClient* GetClientBySlot(CPlayerSlot slot); -extern void FullUpdateAllClients(); extern CConVar g_cvarDropMapWeapons; class CS2Fixes : public ISmmPlugin, public IMetamodListener, public ICS2Fixes diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 3e69452e4..e02484c33 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -607,7 +607,7 @@ std::vector> CMapVoteSystem::GetMapsFromSubstring(const ch void CMapVoteSystem::HandlePlayerMapLookup(CCSPlayerController* pController, std::string strMapSubstring, bool bAdmin, QueryCallback_t callbackSuccess) { - strMapSubstring = g_pMapVoteSystem->StringToLower(strMapSubstring); + strMapSubstring = StringToLower(strMapSubstring); const char* pszMapSubstring = strMapSubstring.c_str(); auto vecFoundMaps = GetMapsFromSubstring(pszMapSubstring); @@ -1212,14 +1212,6 @@ std::string CMapVoteSystem::ConvertFloatToString(float fValue, int precision) return str; } -std::string CMapVoteSystem::StringToLower(std::string strValue) -{ - for (int i = 0; strValue[i]; i++) - strValue[i] = tolower(strValue[i]); - - return strValue; -} - std::string CMapVoteSystem::GetMapCooldownText(const char* pszMapName, bool bPlural) { std::shared_ptr pCooldown = GetMapCooldown(pszMapName); diff --git a/src/map_votes.h b/src/map_votes.h index e80d5bb1d..b3fe7184b 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -26,10 +26,10 @@ #include "vendor/nlohmann/json_fwd.hpp" #include #include +#include #include #include #include -#include class CMap; using ordered_json = nlohmann::ordered_json; @@ -214,7 +214,6 @@ class CMapVoteSystem void OnLevelShutdown(); std::vector> GetMapCooldowns() { return m_vecCooldowns; } std::string ConvertFloatToString(float fValue, int precision); - 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(); bool ReloadMapList(bool bReloadMap = true); diff --git a/src/playermanager.cpp b/src/playermanager.cpp index 7af260f8d..53d470722 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -1845,4 +1845,15 @@ int CPlayerManager::GetOnlinePlayerCount(bool bCountBots) } return iOnlinePlayers; +} + +void CPlayerManager::FullUpdateAllClients() +{ + auto pClients = GetClientList(); + + if (!pClients) + return; + + FOR_EACH_VEC(*pClients, i) + (*pClients)[i]->ForceFullUpdate(); } \ No newline at end of file diff --git a/src/playermanager.h b/src/playermanager.h index 5cf1e3204..07833f0e5 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -399,7 +399,7 @@ class CPlayerManager V_memset(m_vecPlayers, 0, sizeof(m_vecPlayers)); m_nUsingStopSound = -1; // On by default m_nUsingSilenceSound = 0; - m_nUsingZSounds = -1; // On by default + m_nUsingZSounds = -1; // On by default m_nUsingStopDecals = -1; // On by default m_nUsingNoShake = 0; } @@ -447,6 +447,7 @@ class CPlayerManager void UpdatePlayerStates(); int GetOnlinePlayerCount(bool bCountBots); + void FullUpdateAllClients(); STEAM_GAMESERVER_CALLBACK_MANUAL(CPlayerManager, OnValidateAuthTicket, ValidateAuthTicketResponse_t, m_CallbackValidateAuthTicketResponse); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp new file mode 100644 index 000000000..34953d01f --- /dev/null +++ b/src/utils/utils.cpp @@ -0,0 +1,80 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2025 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "utils.h" +#include "../cs2fixes.h" +#include "../gameconfig.h" + +void Message(const char* msg, ...) +{ + va_list args; + va_start(args, msg); + + char buf[1024] = {}; + V_vsnprintf(buf, sizeof(buf) - 1, msg, args); + + ConColorMsg(Color(255, 0, 255, 255), "[CS2Fixes] %s", buf); + + va_end(args); +} + +void Panic(const char* msg, ...) +{ + va_list args; + va_start(args, msg); + + char buf[1024] = {}; + V_vsnprintf(buf, sizeof(buf) - 1, msg, args); + + Warning("[CS2Fixes] %s", buf); + + va_end(args); +} + +CUtlVector* GetClientList() +{ + if (!GetNetworkGameServer()) + return nullptr; + + static int offset = g_GameConfig->GetOffset("CNetworkGameServer_ClientList"); + return (CUtlVector*)(&GetNetworkGameServer()[offset]); +} + +CServerSideClient* GetClientBySlot(CPlayerSlot slot) +{ + CUtlVector* pClients = GetClientList(); + + if (!pClients) + return nullptr; + + return pClients->Element(slot.Get()); +} + +uint32 GetSoundEventHash(const char* pszSoundEventName) +{ + return MurmurHash2LowerCase(pszSoundEventName, 0x53524332); +} + +std::string StringToLower(std::string strValue) +{ + for (int i = 0; strValue[i]; i++) + strValue[i] = tolower(strValue[i]); + + return strValue; +} \ No newline at end of file diff --git a/src/utils/utils.h b/src/utils/utils.h new file mode 100644 index 000000000..7ff530abb --- /dev/null +++ b/src/utils/utils.h @@ -0,0 +1,34 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2025 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "playerslot.h" +#include "utlvector.h" + +class CServerSideClient; + +void Message(const char*, ...); +void Panic(const char*, ...); + +CUtlVector* GetClientList(); +CServerSideClient* GetClientBySlot(CPlayerSlot slot); + +uint32 GetSoundEventHash(const char* pszSoundEventName); +std::string StringToLower(std::string strValue); \ No newline at end of file diff --git a/src/zombiereborn.h b/src/zombiereborn.h index dd3729ecc..19cbb2b1d 100644 --- a/src/zombiereborn.h +++ b/src/zombiereborn.h @@ -23,8 +23,8 @@ #include "entity/ccsplayercontroller.h" #include "entity/ccsplayerpawn.h" #include "eventlistener.h" -#include "gamesystem.h" #include "gameevents.pb.h" +#include "gamesystem.h" #include "vendor/nlohmann/json_fwd.hpp" using ordered_json = nlohmann::ordered_json; From 4f7d71eb7db437ca6c2399677ecfc9465bdc0822 Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 10 Dec 2025 16:44:57 -0500 Subject: [PATCH 3/9] Improve clan tag handling No more stutters! --- src/cs2_sdk/entity/cbaseplayercontroller.h | 2 +- src/cs2_sdk/entity/ccsplayercontroller.h | 21 +++++- src/entwatch.cpp | 85 +--------------------- src/entwatch.h | 2 - src/playermanager.cpp | 6 -- src/playermanager.h | 4 - src/user_preferences.cpp | 2 - 7 files changed, 25 insertions(+), 97 deletions(-) diff --git a/src/cs2_sdk/entity/cbaseplayercontroller.h b/src/cs2_sdk/entity/cbaseplayercontroller.h index deb085aee..a60aa4b64 100644 --- a/src/cs2_sdk/entity/cbaseplayercontroller.h +++ b/src/cs2_sdk/entity/cbaseplayercontroller.h @@ -41,7 +41,7 @@ class CBasePlayerController : public CBaseEntity SCHEMA_FIELD(uint64, m_steamID) SCHEMA_FIELD(CHandle, m_hPawn) - SCHEMA_FIELD_POINTER(char, m_iszPlayerName) + SCHEMA_FIELD_POINTER(char, m_iszPlayerName) // char m_iszPlayerName[128] SCHEMA_FIELD(PlayerConnectedState, m_iConnected) SCHEMA_FIELD(bool, m_bIsHLTV) SCHEMA_FIELD(uint, m_iDesiredFOV) diff --git a/src/cs2_sdk/entity/ccsplayercontroller.h b/src/cs2_sdk/entity/ccsplayercontroller.h index fe01a6eca..f4bb5772e 100644 --- a/src/cs2_sdk/entity/ccsplayercontroller.h +++ b/src/cs2_sdk/entity/ccsplayercontroller.h @@ -33,7 +33,6 @@ class CCSPlayerController : public CBasePlayerController SCHEMA_FIELD(CCSPlayerController_ActionTrackingServices*, m_pActionTrackingServices) SCHEMA_FIELD(uint32_t, m_iPing) SCHEMA_FIELD(CUtlSymbolLarge, m_szClan) - SCHEMA_FIELD_POINTER(char, m_szClanName) // char m_szClanName[32] SCHEMA_FIELD(bool, m_bEverFullyConnected) SCHEMA_FIELD(bool, m_bPawnIsAlive) SCHEMA_FIELD(int32_t, m_nDisconnectionTick) @@ -144,4 +143,24 @@ class CCSPlayerController : public CBasePlayerController { m_iScore() = m_iScore() + iScore; } + + void SetClanTag(const char* pszClanTag) + { + // Skip if clan tag is unchanged, since name swap trick has a bit of overhead + if (!V_strcmp(m_szClan().String(), pszClanTag)) + return; + + m_szClan = g_pEntitySystem->AllocPooledString(pszClanTag); + + // This name swap trick is necessary to get clients to display the new clan tag + std::string strName = GetPlayerName(); + + if (!strName.empty() && strName.back() == ' ') + strName.pop_back(); + else + strName.push_back(' '); + + V_strncpy(m_iszPlayerName, strName.c_str(), 128); + m_iszPlayerName.NetworkStateChanged(); + } }; \ No newline at end of file diff --git a/src/entwatch.cpp b/src/entwatch.cpp index 8cc83a004..11f4c9011 100644 --- a/src/entwatch.cpp +++ b/src/entwatch.cpp @@ -798,14 +798,12 @@ void EWItemInstance::Pickup(int slot) if (bShouldSetClantag) { bHasThisClantag = true; - pController->m_szClan(sClantag); + pController->SetClanTag(sClantag); if (g_cvarItemHolderScore.Get() > -1) { int score = pController->m_iScore + g_cvarItemHolderScore.Get(); pController->m_iScore = score; } - - EW_UpdateClientClanTags(); } } @@ -840,7 +838,7 @@ void EWItemInstance::Drop(EWDropReason reason, CCSPlayerController* pController) { // Player IS holding another item, score doesnt need adjusting - pController->m_szClan(g_pEWHandler->vecItems[otherItem]->sClantag); + pController->SetClanTag(g_pEWHandler->vecItems[otherItem]->sClantag); g_pEWHandler->vecItems[otherItem]->bHasThisClantag = true; bSetAnotherClantag = true; break; @@ -857,10 +855,8 @@ void EWItemInstance::Drop(EWDropReason reason, CCSPlayerController* pController) pController->m_iScore = score; } - pController->m_szClan(""); + pController->SetClanTag(""); } - - EW_UpdateClientClanTags(); } char sPlayerInfo[64]; @@ -1504,10 +1500,8 @@ void CEWHandler::ResetAllClantags() pController->m_iScore = score; } - pController->m_szClan(""); + pController->SetClanTag(""); } - - EW_UpdateClientClanTags(); } int CEWHandler::RegisterItem(CBasePlayerWeapon* pWeapon) @@ -2259,49 +2253,6 @@ void EW_PlayerDisconnect(int slot) g_pEWHandler->PlayerDrop(EWDropReason::Disconnect, -1, pController); } -// An event needs to be sent to force clients to see up to date clantags -void EW_UpdateClientClanTags() -{ - // Cannot send this event during map vote, as it breaks voting client side - if (!GetGlobals() || !g_pMapVoteSystem->IsIntermissionAllowed()) - return; - - static IGameEvent* pEvent = nullptr; - - if (!pEvent) - pEvent = g_gameEventManager->CreateEvent("nextlevel_changed"); - - if (!pEvent) - { - Panic("Failed to create nextlevel_changed event\n"); - return; - } - - INetworkMessageInternal* pMsg = g_pNetworkMessages->FindNetworkMessageById(GE_Source1LegacyGameEvent); - if (!pMsg) - { - Panic("Failed to create Source1LegacyGameEvent\n"); - return; - } - CNetMessagePB* data = pMsg->AllocateMessage()->ToPB(); - g_gameEventManager->SerializeEvent(pEvent, data); - - CRecipientFilter filter; - for (int i = 0; i < GetGlobals()->maxClients; i++) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsAuthenticated()) - continue; - - if (pPlayer->GetEntwatchClangtags()) - filter.AddRecipient(pPlayer->GetPlayerSlot()); - } - - g_gameEventSystem->PostEventAbstract(-1, false, &filter, pMsg, data, 0); - delete data; -} - bool EW_IsFireOutputHooked() { return std::any_of(mapIOFunctions.begin(), mapIOFunctions.end(), [](const auto& p) { return p.first == "entwatch"; }); @@ -2650,34 +2601,6 @@ CON_COMMAND_CHAT(ew_dump, "- Prints the currently loaded config to console") g_pEWHandler->PrintLoadedConfig(player->GetPlayerSlot()); } -CON_COMMAND_CHAT(etag, "- Toggle EntWatch clantags on the scoreboard") -{ - if (!g_cvarEnableEntWatch.Get()) - return; - - if (!player) - { - ClientPrint(player, HUD_PRINTTALK, EW_PREFIX "Only usable in game."); - return; - } - - ZEPlayer* zpPlayer = g_playerManager->GetPlayer(player->GetPlayerSlot()); - if (!zpPlayer) - { - ClientPrint(player, HUD_PRINTTALK, EW_PREFIX "Something went wrong, try again later."); - return; - } - - bool bCurrentStatus = zpPlayer->GetEntwatchClangtags(); - bCurrentStatus = !bCurrentStatus; - zpPlayer->SetEntwatchClangtags(bCurrentStatus); - - if (bCurrentStatus) - ClientPrint(player, HUD_PRINTTALK, EW_PREFIX "You have\x04 Enabled\x01 EntWatch clantag updates"); - else - ClientPrint(player, HUD_PRINTTALK, EW_PREFIX "You have\x07 Disabled\x01 EntWatch clantag updates"); -} - CON_COMMAND_CHAT(hud, "- Toggle EntWatch HUD") { if (!g_cvarEnableEntWatch.Get()) diff --git a/src/entwatch.h b/src/entwatch.h index 42ba3cd38..1196bc6ec 100644 --- a/src/entwatch.h +++ b/src/entwatch.h @@ -36,7 +36,6 @@ extern CConVar g_cvarEnableEntwatchHud; #define EW_PREFIX " \4[EntWatch]\1 " #define EW_PREF_HUD_MODE "entwatch_hud" -#define EW_PREF_CLANTAG "entwatch_clantag" #define EW_PREF_HUDPOS_X "entwatch_hudpos_x" #define EW_PREF_HUDPOS_Y "entwatch_hudpos_y" #define EW_PREF_HUDCOLOR "entwatch_hudcolor" @@ -297,7 +296,6 @@ void EW_DropWeapon(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* void EW_PlayerDeath(IGameEvent* pEvent); void EW_PlayerDeathPre(CCSPlayerController* pController); void EW_PlayerDisconnect(int slot); -void EW_UpdateClientClanTags(); bool EW_IsFireOutputHooked(); void EW_FireOutput(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay); int GetTemplateSuffixNumber(const char* szName); diff --git a/src/playermanager.cpp b/src/playermanager.cpp index 53d470722..e8ce33b84 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -715,12 +715,6 @@ void ZEPlayer::SetEntwatchHudMode(int iMode) g_pUserPreferencesSystem->SetPreferenceInt(m_slot.Get(), EW_PREF_HUD_MODE, m_iEntwatchHudMode); } -void ZEPlayer::SetEntwatchClangtags(bool bStatus) -{ - m_bEntwatchClantags = bStatus; - g_pUserPreferencesSystem->SetPreferenceInt(m_slot.Get(), EW_PREF_CLANTAG, bStatus ? 1 : 0); -} - void ZEPlayer::SetEntwatchHudColor(Color colorHud) { m_colorEntwatchHud = colorHud; diff --git a/src/playermanager.h b/src/playermanager.h index 07833f0e5..39316bd60 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -192,7 +192,6 @@ class ZEPlayer m_pActiveZRModel = nullptr; m_iButtonWatchMode = 0; m_iEntwatchHudMode = 0; - m_bEntwatchClantags = true; m_colorEntwatchHud = Color(255, 255, 255, 255); m_flEntwatchHudX = -7.5f; m_flEntwatchHudY = -2.0f; @@ -261,7 +260,6 @@ class ZEPlayer void SetActiveZRClass(std::shared_ptr pZRModel) { m_pActiveZRClass = pZRModel; } void SetActiveZRModel(std::shared_ptr pZRClass) { m_pActiveZRModel = pZRClass; } void SetEntwatchHudMode(int iMode); - void SetEntwatchClangtags(bool bStatus); void SetPointOrient(CPointOrient* pOrient) { m_hPointOrient.Set(pOrient); } void SetEntwatchHud(CPointWorldText* pWorldText) { m_hEntwatchHud.Set(pWorldText); } void SetEntwatchHudColor(Color colorHud); @@ -309,7 +307,6 @@ class ZEPlayer std::shared_ptr GetActiveZRModel() { return m_pActiveZRModel; } int GetButtonWatchMode(); int GetEntwatchHudMode(); - bool GetEntwatchClangtags() { return m_bEntwatchClantags; } CPointOrient* GetPointOrient() { return m_hPointOrient.Get(); } CPointWorldText* GetEntwatchHud() { return m_hEntwatchHud.Get(); } Color GetEntwatchHudColor() { return m_colorEntwatchHud; } @@ -384,7 +381,6 @@ class ZEPlayer CHandle m_hPointOrient; CHandle m_hEntwatchHud; int m_iEntwatchHudMode; - bool m_bEntwatchClantags; Color m_colorEntwatchHud; float m_flEntwatchHudX; float m_flEntwatchHudY; diff --git a/src/user_preferences.cpp b/src/user_preferences.cpp index 1e32219cf..240723d75 100644 --- a/src/user_preferences.cpp +++ b/src/user_preferences.cpp @@ -116,7 +116,6 @@ void CUserPreferencesSystem::OnPutPreferences(int iSlot) // EntWatch int iEntwatchMode = GetPreferenceInt(iSlot, EW_PREF_HUD_MODE, 0); - bool bEntwatchClantag = (bool)GetPreferenceInt(iSlot, EW_PREF_CLANTAG, 1); float flEntwatchHudposX = GetPreferenceFloat(iSlot, EW_PREF_HUDPOS_X, EW_HUDPOS_X_DEFAULT); float flEntwatchHudposY = GetPreferenceFloat(iSlot, EW_PREF_HUDPOS_Y, EW_HUDPOS_Y_DEFAULT); Color ewHudColor; @@ -137,7 +136,6 @@ void CUserPreferencesSystem::OnPutPreferences(int iSlot) // Set EntWatch player->SetEntwatchHudMode(iEntwatchMode); - player->SetEntwatchClangtags(bEntwatchClantag); player->SetEntwatchHudPos(flEntwatchHudposX, flEntwatchHudposY); player->SetEntwatchHudColor(ewHudColor); player->SetEntwatchHudSize(flEntwatchHudSize); From 8fca81c27d1ab18b8ec7f138dfd7cb9e4d68216b Mon Sep 17 00:00:00 2001 From: Vauff Date: Thu, 11 Dec 2025 18:47:08 -0500 Subject: [PATCH 4/9] Fix exact name matching being unreliable with clan tags --- src/playermanager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/playermanager.cpp b/src/playermanager.cpp index e8ce33b84..35f4954d4 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -1590,7 +1590,13 @@ ETargetError CPlayerManager::GetPlayersFromString(CCSPlayerController* pPlayer, if (!pTarget || !pTarget->IsController() || !pTarget->IsConnected() || pTarget->m_bIsHLTV) continue; - if ((!bExactName && V_stristr(pTarget->GetPlayerName(), pszTarget)) || !V_strcmp(pTarget->GetPlayerName(), pszTarget)) + std::string strName = pTarget->GetPlayerName(); + + // Ignore space that might be added by clan tag name swap trick + if (!strName.empty() && strName.back() == ' ') + strName.pop_back(); + + if ((!bExactName && V_stristr(strName.c_str(), pszTarget)) || !V_strcmp(strName.c_str(), pszTarget)) { nType = ETargetType::PLAYER; if (iNumClients == 1) From 77711eed85ac73828e79fc5a9fbd1b09f732e3aa Mon Sep 17 00:00:00 2001 From: Vauff Date: Thu, 18 Dec 2025 21:35:17 -0500 Subject: [PATCH 5/9] Add map cooldown randomness --- cfg/cs2fixes/cs2fixes.cfg | 1 + src/map_votes.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 63ecde976..286f56990 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -82,6 +82,7 @@ cs2f_rtv_endround 0 // Whether to immediately end the round when RTV succee // Map vote settings cs2f_vote_maps_cooldown 6.0 // Default number of hours until a map can be played again i.e. cooldown +cs2f_vote_maps_cooldown_rng 0.0 // Randomness range in both directions to apply to map cooldowns cs2f_vote_max_nominations 10 // Number of nominations to include per vote, out of a maximum of 10 cs2f_vote_max_maps 10 // Number of total maps to include per vote, including nominations, out of a maximum of 10 diff --git a/src/map_votes.cpp b/src/map_votes.cpp index e02484c33..b8a5d1c10 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -40,6 +40,7 @@ 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); +CConVar g_cvarVoteMapsCooldownRng("cs2f_vote_maps_cooldown_rng", FCVAR_NONE, "Randomness range in both directions to apply to map cooldowns", 0.0f); CConVar g_cvarVoteMaxNominations("cs2f_vote_max_nominations", FCVAR_NONE, "Number of nominations to include per vote, out of a maximum of 10", 10, true, 0, true, 10); CConVar g_cvarVoteMaxMaps("cs2f_vote_max_maps", FCVAR_NONE, "Number of total maps to include per vote, including nominations, out of a maximum of 10", 10, true, 2, true, 10); @@ -1262,6 +1263,17 @@ void CMapVoteSystem::PutMapOnCooldown(const char* pszMapName, float fCooldown) fCooldown = pMap->GetCustomCooldown(); else fCooldown = g_cvarVoteMapsCooldown.Get(); + + // Add randomness if applicable + if (g_cvarVoteMapsCooldown.Get() != 0.0f) + { + float flRandomValue = ((float)rand() / RAND_MAX) * g_cvarVoteMapsCooldownRng.Get(); + + if (rand() % 2) + fCooldown += flRandomValue; + else + fCooldown -= flRandomValue; + } } time_t timeCooldown = std::time(0) + (time_t)(fCooldown * 60 * 60); From 8b586b2b2899adaa2aec631453b57d21ca7c056f Mon Sep 17 00:00:00 2001 From: notkoen <45914779+notkoen@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:15:17 -0800 Subject: [PATCH 6/9] Expand TopDefender Functionality (#420) * Update TopDefender * formatter --------- Co-authored-by: Vauff --- AMBuilder | 1 + CS2Fixes.vcxproj | 2 + CS2Fixes.vcxproj.filters | 6 + cfg/cs2fixes/cs2fixes.cfg | 10 +- src/cs2_sdk/entity/ccsplayercontroller.h | 4 + src/events.cpp | 103 +------ src/playermanager.h | 8 + src/topdefender.cpp | 360 +++++++++++++++++++++++ src/topdefender.h | 36 +++ 9 files changed, 435 insertions(+), 95 deletions(-) create mode 100644 src/topdefender.cpp create mode 100644 src/topdefender.h diff --git a/AMBuilder b/AMBuilder index 23c072a4b..64ed4706b 100644 --- a/AMBuilder +++ b/AMBuilder @@ -70,6 +70,7 @@ for sdk_target in MMSPlugin.sdk_targets: 'src/entitylistener.cpp', 'src/leader.cpp', 'src/buttonwatch.cpp', + 'src/topdefender.cpp', 'src/idlemanager.cpp', 'sdk/entity2/entitysystem.cpp', 'sdk/entity2/entityidentity.cpp', diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index 524de1858..466bd0d3b 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -207,6 +207,7 @@ + @@ -282,6 +283,7 @@ + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index 4b15140a0..b7d41e59c 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -197,6 +197,9 @@ Source Files\cs2_sdk\entity + + Source Files + @@ -421,5 +424,8 @@ Header Files\cs2_sdk\entity + + Header Files + \ No newline at end of file diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 286f56990..2a87b0db8 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -6,7 +6,6 @@ cs2f_weapons_enable 0 // Whether to enable weapon commands cs2f_stopsound_enable 0 // Whether to enable stopsound cs2f_noblock_enable 0 // Whether to use player noblock, which sets debris collision on every player cs2f_noblock_grenades 0 // Whether to use noblock on grenade projectiles -cs2f_topdefender_enable 0 // Whether to use TopDefender cs2f_block_team_messages 0 // Whether to block team join messages cs2f_movement_unlocker_enable 0 // Whether to enable movement unlocker cs2f_use_old_push 0 // Whether to use the old CSGO trigger_push behavior (Necessary for surf and other modes that heavily use ported pushes) @@ -60,6 +59,15 @@ cs2f_hide_distance_default 250 // The default distance for hide cs2f_hide_distance_max 2000 // The max distance for hide cs2f_hide_teammates_only 0 // Whether to hide teammates only +// TopDefender settings +cs2f_topdefender_enable 0 // Whether to use TopDefender +cs2f_topdefender_scoreboard 0 // Whether to display defender rank on scoreboard as MVP count +cs2f_topdefender_print 1 // Whether to print defender stats to console at round end +cs2f_topdefender_score 5000 // Score given to the top defender +cs2f_topdefender_threshold 1000 // Damage threshold for Top Defenders to be shown on round end +cs2f_topdefender_rate 1.0 // How often TopDefender stats get updated +cs2f_topdefender_clantag "[Top Defender]" // Clan tag given to the top defender + // Chat flood settings cs2f_flood_interval 0.75 // Amount of time allowed between chat messages acquiring flood tokens cs2f_max_flood_tokens 3 // Maximum number of flood tokens allowed before chat messages are blocked diff --git a/src/cs2_sdk/entity/ccsplayercontroller.h b/src/cs2_sdk/entity/ccsplayercontroller.h index f4bb5772e..8b8613660 100644 --- a/src/cs2_sdk/entity/ccsplayercontroller.h +++ b/src/cs2_sdk/entity/ccsplayercontroller.h @@ -46,6 +46,10 @@ class CCSPlayerController : public CBasePlayerController SCHEMA_FIELD(int32_t, m_iRoundsWon) SCHEMA_FIELD(int32_t, m_iMVPs) SCHEMA_FIELD(float, m_flSmoothedPing) + SCHEMA_FIELD(bool, m_bMvpNoMusic) + SCHEMA_FIELD(int32_t, m_eMvpReason) + SCHEMA_FIELD(int32_t, m_iMusicKitID) + SCHEMA_FIELD(int32_t, m_iMusicKitMVPs) static CCSPlayerController* FromPawn(CCSPlayerPawn* pawn) { diff --git a/src/events.cpp b/src/events.cpp index 23b1e5274..ad32f4c51 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -32,6 +32,7 @@ #include "map_votes.h" #include "panoramavote.h" #include "recipientfilters.h" +#include "topdefender.h" #include "votemanager.h" #include "zombiereborn.h" @@ -160,27 +161,10 @@ GAME_EVENT_F(player_spawn) pItemServices->GiveNamedItem("item_assaultsuit"); } -CConVar g_cvarEnableTopDefender("cs2f_topdefender_enable", FCVAR_NONE, "Whether to use TopDefender", false); - GAME_EVENT_F(player_hurt) { - if (!g_cvarEnableTopDefender.Get()) - return; - - CCSPlayerController* pAttacker = (CCSPlayerController*)pEvent->GetPlayerController("attacker"); - CCSPlayerController* pVictim = (CCSPlayerController*)pEvent->GetPlayerController("userid"); - - // Ignore Ts/zombies and CTs hurting themselves - if (!pAttacker || pAttacker->m_iTeamNum() != CS_TEAM_CT || pAttacker->m_iTeamNum() == pVictim->m_iTeamNum()) - return; - - ZEPlayer* pPlayer = pAttacker->GetZEPlayer(); - - if (!pPlayer) - return; - - pPlayer->SetTotalDamage(pPlayer->GetTotalDamage() + pEvent->GetInt("dmg_health")); - pPlayer->SetTotalHits(pPlayer->GetTotalHits() + 1); + if (g_cvarEnableTopDefender.Get()) + TD_OnPlayerHurt(pEvent); } GAME_EVENT_F(player_death) @@ -191,22 +175,8 @@ GAME_EVENT_F(player_death) if (g_cvarEnableEntWatch.Get()) EW_PlayerDeath(pEvent); - if (!g_cvarEnableTopDefender.Get()) - return; - - CCSPlayerController* pAttacker = (CCSPlayerController*)pEvent->GetPlayerController("attacker"); - CCSPlayerController* pVictim = (CCSPlayerController*)pEvent->GetPlayerController("userid"); - - // Ignore Ts/zombie kills and ignore CT teamkilling or suicide - if (!pAttacker || !pVictim || pAttacker->m_iTeamNum != CS_TEAM_CT || pAttacker->m_iTeamNum == pVictim->m_iTeamNum) - return; - - ZEPlayer* pPlayer = pAttacker->GetZEPlayer(); - - if (!pPlayer) - return; - - pPlayer->SetTotalKills(pPlayer->GetTotalKills() + 1); + if (g_cvarEnableTopDefender.Get()) + TD_OnPlayerDeath(pEvent); } CConVar g_cvarFullAllTalk("cs2f_full_alltalk", FCVAR_NONE, "Whether to enforce sv_full_alltalk 1", false); @@ -229,20 +199,8 @@ GAME_EVENT_F(round_start) if (g_cvarFixHudFlashing.Get() && g_pGameRules && g_pGameRules->m_bWarmupPeriod) g_pEngineServer2->ServerCommand("mp_warmup_end"); - if (!g_cvarEnableTopDefender.Get() || !GetGlobals()) - return; - - for (int i = 0; i < GetGlobals()->maxClients; i++) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (!pPlayer) - continue; - - pPlayer->SetTotalDamage(0); - pPlayer->SetTotalHits(0); - pPlayer->SetTotalKills(0); - } + if (g_cvarEnableTopDefender.Get()) + TD_OnRoundStart(pEvent); } GAME_EVENT_F(round_end) @@ -250,51 +208,8 @@ GAME_EVENT_F(round_end) if (g_cvarFixHudFlashing.Get() && g_pGameRules) g_pGameRules->m_bGameRestart = false; - if (!g_cvarEnableTopDefender.Get() || !GetGlobals()) - return; - - CUtlVector sortedPlayers; - - for (int i = 0; i < GetGlobals()->maxClients; i++) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (!pPlayer || pPlayer->GetTotalDamage() == 0) - continue; - - CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); - - if (!pController) - continue; - - sortedPlayers.AddToTail(pPlayer); - } - - if (sortedPlayers.Count() == 0) - return; - - sortedPlayers.Sort([](ZEPlayer* const* a, ZEPlayer* const* b) -> int { - return (*a)->GetTotalDamage() < (*b)->GetTotalDamage(); - }); - - ClientPrintAll(HUD_PRINTTALK, " \x09TOP DEFENDERS"); - - char colorMap[] = {'\x10', '\x08', '\x09', '\x0B'}; - - for (int i = 0; i < sortedPlayers.Count(); i++) - { - ZEPlayer* pPlayer = sortedPlayers[i]; - CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); - - if (i < 5) - ClientPrintAll(HUD_PRINTTALK, " %c%i. %s \x01- \x07%i DMG \x05(%i HITS & %i KILLS)", colorMap[MIN(i, 3)], i + 1, pController->GetPlayerName(), pPlayer->GetTotalDamage(), pPlayer->GetTotalHits(), pPlayer->GetTotalKills()); - else - ClientPrint(pController, HUD_PRINTTALK, " \x0C%i. %s \x01- \x07%i DMG \x05(%i HITS & %i KILLS)", i + 1, pController->GetPlayerName(), pPlayer->GetTotalDamage(), pPlayer->GetTotalHits(), pPlayer->GetTotalKills()); - - pPlayer->SetTotalDamage(0); - pPlayer->SetTotalHits(0); - pPlayer->SetTotalKills(0); - } + if (g_cvarEnableTopDefender.Get()) + TD_OnRoundEnd(pEvent); } GAME_EVENT_F(round_freeze_end) diff --git a/src/playermanager.h b/src/playermanager.h index 39316bd60..fd5165d2a 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -165,6 +165,7 @@ class ZEPlayer m_bConnected = false; m_iTotalDamage = 0; m_iTotalHits = 0; + m_iTotalHeadshots = 0; m_iTotalKills = 0; m_bVotedRTV = false; m_bVotedExtend = false; @@ -196,6 +197,7 @@ class ZEPlayer m_flEntwatchHudX = -7.5f; m_flEntwatchHudY = -2.0f; m_flEntwatchHudSize = 60.0f; + m_bTopDefender = false; } ~ZEPlayer() @@ -230,6 +232,7 @@ class ZEPlayer void SetHideDistance(int distance); void SetTotalDamage(int damage) { m_iTotalDamage = damage; } void SetTotalHits(int hits) { m_iTotalHits = hits; } + void SetTotalHeadshots(int headshots) { m_iTotalHeadshots = headshots; } void SetTotalKills(int kills) { m_iTotalKills = kills; } void SetRTVVote(bool bRTVVote) { m_bVotedRTV = bRTVVote; } void SetRTVVoteTime(float flCurtime) { m_flRTVVoteTime = flCurtime; } @@ -265,6 +268,7 @@ class ZEPlayer void SetEntwatchHudColor(Color colorHud); void SetEntwatchHudPos(float x, float y); void SetEntwatchHudSize(float flSize); + void SetTopDefenderStatus(bool bStatus) { m_bTopDefender = bStatus; } uint64 GetAdminFlags() { return m_iAdminFlags; } int GetAdminImmunity() { return m_iAdminImmunity; } @@ -276,6 +280,7 @@ class ZEPlayer CPlayerSlot GetPlayerSlot() { return m_slot; } int GetTotalDamage() { return m_iTotalDamage; } int GetTotalHits() { return m_iTotalHits; } + int GetTotalHeadshots() { return m_iTotalHeadshots; } int GetTotalKills() { return m_iTotalKills; } bool GetRTVVote() { return m_bVotedRTV; } float GetRTVVoteTime() { return m_flRTVVoteTime; } @@ -313,6 +318,7 @@ class ZEPlayer float GetEntwatchHudX() { return m_flEntwatchHudX; } float GetEntwatchHudY() { return m_flEntwatchHudY; } float GetEntwatchHudSize() { return m_flEntwatchHudSize; } + bool GetTopDefenderStatus() { return m_bTopDefender; } void OnSpawn(); void OnAuthenticated(); @@ -346,6 +352,7 @@ class ZEPlayer CBitVec m_shouldTransmit; int m_iTotalDamage; int m_iTotalHits; + int m_iTotalHeadshots; int m_iTotalKills; bool m_bVotedRTV; float m_flRTVVoteTime; @@ -385,6 +392,7 @@ class ZEPlayer float m_flEntwatchHudX; float m_flEntwatchHudY; float m_flEntwatchHudSize; + bool m_bTopDefender; }; class CPlayerManager diff --git a/src/topdefender.cpp b/src/topdefender.cpp new file mode 100644 index 000000000..b0091b480 --- /dev/null +++ b/src/topdefender.cpp @@ -0,0 +1,360 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2025 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "topdefender.h" +#include "commands.h" +#include "common.h" +#include "ctimer.h" +#include "detours.h" +#include "entity/ccsplayercontroller.h" +#include "idlemanager.h" +#include "playermanager.h" + +std::vector sortedPlayers; +std::vector failMessage = { + "No top defenders? You all are bad.", + "Start defending, less doorhugging.", + "Hold left-click to shoot, FYI", + "WHAT ARE YOU DOING?!?!?"}; + +std::weak_ptr m_pUpdateTimer; + +CConVar g_cvarEnableTopDefender("cs2f_topdefender_enable", FCVAR_NONE, "Whether to use TopDefender", false); +CConVar g_cvarTopDefenderScoreboard("cs2f_topdefender_scoreboard", FCVAR_NONE, "Whether to display defender rank on scoreboard as MVP count", false); +CConVar g_cvarTopDefenderPrint("cs2f_topdefender_print", FCVAR_NONE, "Whether to print defender ranks to console at round end", true); +CConVar g_cvarTopDefenderRate("cs2f_topdefender_rate", FCVAR_NONE, "How often TopDefender stats get updated", 1.0f, true, 0.1f, false, 0.0f); +CConVar g_cvarTopDefenderThreshold("cs2f_topdefender_threshold", FCVAR_NONE, "Damage threshold for Top Defenders to be shown on round end", 1000, true, 0, false, 0); +CConVar g_cvarTopDefenderScore("cs2f_topdefender_score", FCVAR_NONE, "Score given to the top defender", 5000, true, 0, false, 0); +CConVar g_cvarTopDefenderClanTag("cs2f_topdefender_clantag", FCVAR_NONE, "Clan tag given to the top defender", "[Top Defender]"); + +// Array sorting function +bool SortTD(ZEPlayerHandle a, ZEPlayerHandle b) +{ + if (a.Get() && b.Get()) + return a.Get()->GetTotalDamage() > b.Get()->GetTotalDamage(); + else + return false; +} + +void UnfuckMVP(CCSPlayerController* pController) +{ + // stop this shit from spamming mvp music + if (!pController->m_bMvpNoMusic()) + pController->m_bMvpNoMusic = true; + + if (pController->m_eMvpReason() != 0) + pController->m_eMvpReason = 0; + + if (pController->m_iMusicKitID() != 0) + pController->m_iMusicKitID = 0; + + if (pController->m_iMusicKitMVPs() != 0) + pController->m_iMusicKitMVPs = 0; +} + +void TD_OnPlayerHurt(IGameEvent* pEvent) +{ + CCSPlayerController* pAttacker = (CCSPlayerController*)pEvent->GetPlayerController("attacker"); + CCSPlayerController* pVictim = (CCSPlayerController*)pEvent->GetPlayerController("userid"); + + // Ignore Ts/zombies and CTs hurting themselves + if (!pAttacker || pAttacker->m_iTeamNum() != CS_TEAM_CT || pAttacker->m_iTeamNum() == pVictim->m_iTeamNum()) + return; + + ZEPlayer* pAttackerPlayer = pAttacker->GetZEPlayer(); + ZEPlayer* pVictimPlayer = pVictim->GetZEPlayer(); + + if (!pAttackerPlayer || !pVictimPlayer) + return; + + if (g_cvarIdleKickTime.Get() <= 0.0f || (std::time(0) - pVictimPlayer->GetLastInputTime()) < 15) + { + pAttackerPlayer->SetTotalDamage(pAttackerPlayer->GetTotalDamage() + pEvent->GetInt("dmg_health")); + pAttackerPlayer->SetTotalHits(pAttackerPlayer->GetTotalHits() + 1); + + if (pEvent->GetInt("hitgroup") == 1) + pAttackerPlayer->SetTotalHeadshots(pAttackerPlayer->GetTotalHeadshots() + 1); + } +} + +void TD_OnPlayerDeath(IGameEvent* pEvent) +{ + CCSPlayerController* pAttacker = (CCSPlayerController*)pEvent->GetPlayerController("attacker"); + CCSPlayerController* pVictim = (CCSPlayerController*)pEvent->GetPlayerController("userid"); + + // Ignore Ts/zombie kills and ignore CT teamkilling or suicide + if (!pAttacker || !pVictim || pAttacker->m_iTeamNum != CS_TEAM_CT || pAttacker->m_iTeamNum == pVictim->m_iTeamNum) + return; + + ZEPlayer* pPlayer = pAttacker->GetZEPlayer(); + if (pPlayer) + pPlayer->SetTotalKills(pPlayer->GetTotalKills() + 1); +} + +void TD_OnRoundStart(IGameEvent* pEvent) +{ + if (!GetGlobals()) + return; + + // Reset player information + for (int i = 0; i < GetGlobals()->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + if (!pPlayer) + continue; + + pPlayer->SetTotalDamage(0); + pPlayer->SetTotalHits(0); + pPlayer->SetTotalKills(0); + pPlayer->SetTotalHeadshots(0); + + CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); + if (pController) + { + if (g_cvarTopDefenderScoreboard.Get() && pController->m_iMVPs() != 0) + { + UnfuckMVP(pController); + pController->m_iMVPs = 0; + } + + // If player is top defender, we set their score and clan tag + if (g_cvarTopDefenderScore.Get() > 0 && pPlayer->GetTopDefenderStatus()) + { + pController->m_iScore() = pController->m_iScore() + g_cvarTopDefenderScore.Get(); + pController->SetClanTag(g_cvarTopDefenderClanTag.Get().String()); + } + } + } + + m_pUpdateTimer = CTimer::Create(g_cvarTopDefenderRate.Get(), TIMERFLAG_MAP | TIMERFLAG_ROUND, []() { + if (!GetGlobals()) + return -1.0f; + + sortedPlayers.clear(); + + for (int i = 0; i < GetGlobals()->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + if (!pPlayer || pPlayer->GetTotalDamage() == 0) + continue; + + sortedPlayers.push_back(pPlayer->GetHandle()); + } + + std::sort(sortedPlayers.begin(), sortedPlayers.end(), SortTD); + + if (g_cvarTopDefenderScoreboard.Get()) + { + for (int i = 0; i < sortedPlayers.size(); i++) + { + ZEPlayer* pPlayer = sortedPlayers[i].Get(); + if (!pPlayer) + continue; + + CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); + if (!pController) + continue; + + if (pController->m_iMVPs() != i + 1) + { + UnfuckMVP(pController); + pController->m_iMVPs = i + 1; + } + } + } + + return g_cvarTopDefenderRate.Get(); + }); +} + +void TD_OnRoundEnd(IGameEvent* pEvent) +{ + // When the round ends, stop the timer and do final recalculation + if (!m_pUpdateTimer.expired()) + m_pUpdateTimer.lock()->Cancel(); + + if (!GetGlobals()) + return; + + sortedPlayers.clear(); + + for (int i = 0; i < GetGlobals()->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + if (!pPlayer) + continue; + + // Only add players over the threshold to the array + if (pPlayer->GetTotalDamage() >= g_cvarTopDefenderThreshold.Get()) + sortedPlayers.push_back(pPlayer->GetHandle()); + + // Reset the current top defender + if (pPlayer->GetTopDefenderStatus()) + { + CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); + if (pController) + { + pPlayer->SetTopDefenderStatus(false); + pController->m_iScore() = pController->m_iScore() - g_cvarTopDefenderScore.Get(); + pController->SetClanTag(""); + } + } + } + + ClientPrintAll(HUD_PRINTTALK, " \x09*** TOP DEFENDERS ***"); + + // Check if players damaged more than threshold + if (sortedPlayers.size() == 0) + { + ClientPrintAll(HUD_PRINTTALK, " \x02%s", failMessage[rand() % failMessage.size()].c_str()); + return; + } + + std::sort(sortedPlayers.begin(), sortedPlayers.end(), SortTD); + + char colorMap[] = {'\x10', '\x08', '\x09', '\x0B'}; + + for (int i = 0; i < sortedPlayers.size(); i++) + { + ZEPlayer* pPlayer = sortedPlayers[i].Get(); + if (!pPlayer) + continue; + + CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); + if (!pController) + continue; + + if (i < 5) + ClientPrintAll(HUD_PRINTTALK, " %c%i. %s \x01- \x07%i DMG \x05(%i HITS | %.0f%% HS | %i KILL%s)", colorMap[MIN(i, 3)], i + 1, pController->GetPlayerName(), pPlayer->GetTotalDamage(), pPlayer->GetTotalHits(), ((double)pPlayer->GetTotalHeadshots() / (double)pPlayer->GetTotalHits()) * 100.0f, pPlayer->GetTotalKills(), pPlayer->GetTotalKills() == 1 ? "" : "S"); + else + ClientPrint(pController, HUD_PRINTTALK, " \x0C%i. %s \x01- \x07%i DMG \x05(%i HITS | %.0f%% HS | %i KILL%s)", i + 1, pController->GetPlayerName(), pPlayer->GetTotalDamage(), pPlayer->GetTotalHits(), ((double)pPlayer->GetTotalHeadshots() / (double)pPlayer->GetTotalHits()) * 100.0f, pPlayer->GetTotalKills(), pPlayer->GetTotalKills() == 1 ? "" : "S"); + + if (i == 0) + pPlayer->SetTopDefenderStatus(true); + } + + // Because there are other round end stats to be displayed, delay printing it by a second to mitigate conflicts + if (g_cvarTopDefenderPrint.Get()) + { + CTimer::Create(1.0f, TIMERFLAG_MAP | TIMERFLAG_ROUND, []() { + ClientPrintAll(HUD_PRINTCONSOLE, "--------------------------------- [Top Defender] ---------------------------------"); + for (int i = 0; i < sortedPlayers.size(); i++) + { + ZEPlayer* pPlayer = sortedPlayers[i].Get(); + if (!pPlayer) + continue; + + CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); + if (!pController) + continue; + + ClientPrintAll(HUD_PRINTCONSOLE, "%i. %s - %i DMG (%i HITS | %.0f%% HS | %i KILL%s)", i + 1, pController->GetPlayerName(), pPlayer->GetTotalDamage(), pPlayer->GetTotalHits(), ((double)pPlayer->GetTotalHeadshots() / (double)pPlayer->GetTotalHits()) * 100.0f, pPlayer->GetTotalKills(), pPlayer->GetTotalKills() == 1 ? "" : "S"); + } + ClientPrintAll(HUD_PRINTCONSOLE, "----------------------------------------------------------------------------------"); + return -1.0f; + }); + } +} + +CON_COMMAND_CHAT(tdrank, "[player/rank] - Displays your defender stats or a specified player's stats.") +{ + TopDefenderSearch(player, args); +} + +CON_COMMAND_CHAT(tdfind, "[player/rank] - Displays your defender stats or a specified player's stats.") +{ + TopDefenderSearch(player, args); +} + +void TopDefenderSearch(CCSPlayerController* player, const CCommand& args) +{ + if (!player) + { + ClientPrint(player, HUD_PRINTCONSOLE, TD_PREFIX "You cannot use this command from the server console."); + return; + } + + if (sortedPlayers.size() == 0) + { + ClientPrint(player, HUD_PRINTTALK, TD_PREFIX "There are no top defenders at this time."); + return; + } + + // First check if no argument is passed + if (args.ArgC() < 2) + { + ZEPlayer* pPlayer = player->GetZEPlayer(); + if (!pPlayer) + return; + + // Search for the player in the sorted array + for (int i = 0; i < sortedPlayers.size(); i++) + { + if (sortedPlayers[i] != pPlayer) + continue; + + ClientPrint(player, HUD_PRINTTALK, TD_PREFIX "RANK \4%d \1- \4%d \1DMG (\4%d \1HITS | \4%.0f%% \1HS | \4%d \1KILL%s)", i + 1, pPlayer->GetTotalDamage(), pPlayer->GetTotalHits(), ((double)pPlayer->GetTotalHeadshots() / (double)pPlayer->GetTotalHits()) * 100.0f, pPlayer->GetTotalKills(), pPlayer->GetTotalKills() == 1 ? "" : "S"); + return; + } + + ClientPrint(player, HUD_PRINTTALK, TD_PREFIX "You do not have any stats to display at this time."); + } + else + { + // Check if the argument passed is a valid rank + // If invalid, we then assume it's a player's name and search accordingly + int iRank = Q_atoi(args[1]); + if (iRank <= 0 || iRank > sortedPlayers.size()) + { + int iNumClients = 0; + int pSlots[MAXPLAYERS]; + + if (!g_playerManager->CanTargetPlayers(player, args[1], iNumClients, pSlots, NO_MULTIPLE)) + return; + + for (int i = 0; i < sortedPlayers.size(); i++) + { + CCSPlayerController* pController = CCSPlayerController::FromSlot(pSlots[0]); + if (!pController) + continue; + + ZEPlayer* pTarget = pController->GetZEPlayer(); + if (!pTarget || sortedPlayers[i] != pTarget) + continue; + + ClientPrint(player, HUD_PRINTTALK, TD_PREFIX "RANK \4%d\1: \4%s \1 - \4%d \1DMG (\4%d \1HITS | \4%.0f%% \1HS | \4%d \1KILL%s)", i + 1, pController->GetPlayerName(), pTarget->GetTotalDamage(), pTarget->GetTotalHits(), ((double)pTarget->GetTotalHeadshots() / (double)pTarget->GetTotalHits()) * 100.0f, pTarget->GetTotalKills(), pTarget->GetTotalKills() == 1 ? "" : "S"); + return; + } + + ClientPrint(player, HUD_PRINTTALK, TD_PREFIX "%s has no stats to display at this time.", CCSPlayerController::FromSlot(pSlots[0])->GetPlayerName()); + } + else + { + ZEPlayer* pPlayer = sortedPlayers[iRank - 1].Get(); + if (!pPlayer) + return; + + CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); + if (!pController) + return; + + ClientPrint(player, HUD_PRINTTALK, TD_PREFIX "RANK \4%d\1: \4%s \1- \4%d \1DMG (\4%d \1HITS | \4%.0f%% \1HS | \4%d \1KILL%s)", iRank, pController->GetPlayerName(), pPlayer->GetTotalDamage(), pPlayer->GetTotalHits(), ((double)pPlayer->GetTotalHeadshots() / (double)pPlayer->GetTotalHits()) * 100.0f, pPlayer->GetTotalKills(), pPlayer->GetTotalKills() == 1 ? "" : "S"); + } + } +} \ No newline at end of file diff --git a/src/topdefender.h b/src/topdefender.h new file mode 100644 index 000000000..f9dd7d3a3 --- /dev/null +++ b/src/topdefender.h @@ -0,0 +1,36 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2025 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "commands.h" + +#define TD_PREFIX " \4[TopDefender]\1 " + +extern CConVar g_cvarEnableTopDefender; +extern CConVar g_cvarTopDefenderChatTag; +extern CConVar g_cvarTopDefenderNameColor; +extern CConVar g_cvarTopDefenderChatColor; + +void TD_OnPlayerHurt(IGameEvent* pEvent); +void TD_OnPlayerDeath(IGameEvent* pEvent); +void TD_OnRoundStart(IGameEvent* pEvent); +void TD_OnRoundEnd(IGameEvent* pEvent); + +void TopDefenderSearch(CCSPlayerController* player, const CCommand& args); \ No newline at end of file From b302927fa3c76b82e82d1aba661094415b944926 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Mon, 29 Dec 2025 00:27:23 +0800 Subject: [PATCH 7/9] Update utils.h, fix missing #include (#421) --- src/utils/utils.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/utils.h b/src/utils/utils.h index 7ff530abb..de4c434d0 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -21,6 +21,7 @@ #include "playerslot.h" #include "utlvector.h" +#include class CServerSideClient; @@ -31,4 +32,4 @@ CUtlVector* GetClientList(); CServerSideClient* GetClientBySlot(CPlayerSlot slot); uint32 GetSoundEventHash(const char* pszSoundEventName); -std::string StringToLower(std::string strValue); \ No newline at end of file +std::string StringToLower(std::string strValue); From 0fa82ea96d5ce28b5348e3d2b346caa6a97c67b5 Mon Sep 17 00:00:00 2001 From: tilgep Date: Sat, 3 Jan 2026 00:06:40 +0000 Subject: [PATCH 8/9] Entwatch update (#422) - Clantags show item cd/uses - Transferred items can only be picked up by the target for 1s - Dropped map weapons get teleported to player eye position to stop them falling through floors --- src/cs2_sdk/entity/cbaseplayerpawn.h | 11 ++++ src/entwatch.cpp | 98 ++++++++++++++++++++++------ src/entwatch.h | 15 +++-- 3 files changed, 99 insertions(+), 25 deletions(-) diff --git a/src/cs2_sdk/entity/cbaseplayerpawn.h b/src/cs2_sdk/entity/cbaseplayerpawn.h index 55abdf054..fd46d1b87 100644 --- a/src/cs2_sdk/entity/cbaseplayerpawn.h +++ b/src/cs2_sdk/entity/cbaseplayerpawn.h @@ -62,8 +62,19 @@ class CBasePlayerPawn : public CBaseModelEntity } } + Vector pos = GetEyePosition(); for (CBasePlayerWeapon* pWeapon : vecWeaponsToDrop) + { m_pWeaponServices()->DropWeapon(pWeapon); + + CHandle hWep = pWeapon->GetHandle(); + CTimer::Create(0.0, TIMERFLAG_MAP | TIMERFLAG_ROUND, [hWep, pos] { + CBasePlayerWeapon* pWep = hWep.Get(); + if (pWep) + pWep->Teleport(&pos, nullptr, nullptr); + return -1.0f; + }); + } } void CommitSuicide(bool bExplode, bool bForce) diff --git a/src/entwatch.cpp b/src/entwatch.cpp index 11f4c9011..51da6c44e 100644 --- a/src/entwatch.cpp +++ b/src/entwatch.cpp @@ -797,8 +797,9 @@ void EWItemInstance::Pickup(int slot) if (bShouldSetClantag) { + // Hud tick function sets it bHasThisClantag = true; - pController->SetClanTag(sClantag); + if (g_cvarItemHolderScore.Get() > -1) { int score = pController->m_iScore + g_cvarItemHolderScore.Get(); @@ -836,9 +837,8 @@ void EWItemInstance::Drop(EWDropReason reason, CCSPlayerController* pController) { if (g_pEWHandler->vecItems[otherItem]->bShowHud && !g_pEWHandler->vecItems[otherItem]->bHasThisClantag) { - // Player IS holding another item, score doesnt need adjusting + // Player IS holding another item - pController->SetClanTag(g_pEWHandler->vecItems[otherItem]->sClantag); g_pEWHandler->vecItems[otherItem]->bHasThisClantag = true; bSetAnotherClantag = true; break; @@ -847,16 +847,14 @@ void EWItemInstance::Drop(EWDropReason reason, CCSPlayerController* pController) } bHasThisClantag = false; - if (!bSetAnotherClantag) + if (g_cvarItemHolderScore.Get() != 0) { - if (g_cvarItemHolderScore.Get() != 0) - { - int score = pController->m_iScore - g_cvarItemHolderScore.Get(); - pController->m_iScore = score; - } + int score = pController->m_iScore - g_cvarItemHolderScore.Get(); + pController->m_iScore = score; + } + if (!bSetAnotherClantag) pController->SetClanTag(""); - } } char sPlayerInfo[64]; @@ -871,7 +869,7 @@ void EWItemInstance::Drop(EWDropReason reason, CCSPlayerController* pController) Message(EW_PREFIX "%s has dropped %s (weaponid:%d)\n", sPlayerInfo, szItemName.c_str(), iWeaponEnt); if (bShowPickup) - ClientPrintAll(HUD_PRINTTALK, EW_PREFIX "\x03%s \x05has dropped %s%s", pController->GetPlayerName(), sChatColor, szItemName.c_str()); + ClientPrintAll(HUD_PRINTTALK, EW_PREFIX "\x03%s\x05 has dropped %s%s", pController->GetPlayerName(), sChatColor, szItemName.c_str()); break; case EWDropReason::Infected: if (bAllowDrop) @@ -1209,6 +1207,7 @@ void CEWHandler::PrintLoadedConfig(CPlayerSlot slot) void CEWHandler::ClearItems() { + vecActiveTransfers.clear(); mapTransfers.clear(); vecItems.clear(); } @@ -1519,8 +1518,6 @@ int CEWHandler::RegisterItem(CBasePlayerWeapon* pWeapon) std::shared_ptr instance = std::make_shared(pWeapon->entindex(), item); - V_snprintf(instance->sClantag, sizeof(EWItemInstance::sClantag), "[+]%s:", instance->szShortName.c_str()); - bool bKnife = pWeapon->GetWeaponVData()->m_GearSlot() == GEAR_SLOT_KNIFE; instance->bAllowDrop = !bKnife; @@ -1612,13 +1609,10 @@ void CEWHandler::PlayerPickup(CCSPlayerPawn* pPawn, int iItemInstance) item->Pickup(pPawn->m_hOriginalController->GetPlayerSlot()); - if (g_cvarEnableEntwatchHud.Get() && !m_bHudTicking) - { - m_bHudTicking = true; - CTimer::Create(EW_HUD_TICKRATE, TIMERFLAG_MAP | TIMERFLAG_ROUND, [] { + if (m_pHudTimer.expired()) + m_pHudTimer = CTimer::Create(EW_HUD_TICKRATE, TIMERFLAG_MAP | TIMERFLAG_ROUND, [] { return EW_UpdateHud(); }); - } } void CEWHandler::PlayerDrop(EWDropReason reason, int iItemInstance, CCSPlayerController* pController) @@ -1735,6 +1729,31 @@ void CEWHandler::Transfer(CCSPlayerController* pCaller, int iItemInstance, CHand } } + // Add this weapon and receiver to active transfers to prevent others from picking it up + if (GetGlobals()) + { + std::shared_ptr transfer = std::make_shared(); + transfer->hReceiver = hReceiver; + transfer->hWeapon = pItemWeapon->GetHandle(); + transfer->flTime = GetGlobals()->curtime + 0.95; + vecActiveTransfers.push_back(transfer); + CTimer::Create(1.0, TIMERFLAG_MAP | TIMERFLAG_ROUND, [] { + if (!GetGlobals()) + return -1.0f; + + for (int i = 0; i < g_pEWHandler->vecActiveTransfers.size(); i++) + { + std::shared_ptr transfer = g_pEWHandler->vecActiveTransfers[i]; + if (transfer->flTime < GetGlobals()->curtime) + { + g_pEWHandler->vecActiveTransfers.erase(g_pEWHandler->vecActiveTransfers.begin() + i); + i--; + } + } + return -1.0f; + }); + } + // Give the item to the receiver Vector vecOrigin = pReceiverPawn->GetAbsOrigin(); pItemWeapon->Teleport(&vecOrigin, nullptr, nullptr); @@ -1968,8 +1987,17 @@ float EW_UpdateHud() std::string sItemText = pItem->GetHandlerStateText(); - // TODO: std::format not supported in clang16 by default - // sHudText.append(std::format("\n[{}]{}: {}", sItemText, pItem->szShortName, pOwner->GetPlayerName())); + if (g_cvarUseEntwatchClantag.Get()) + { + V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s:", sItemText.c_str(), pItem->szShortName.c_str()); + if (pItem->bHasThisClantag) + pOwner->SetClanTag(pItem->sClantag); + } + + if (!g_cvarEnableEntwatchHud.Get()) + continue; + + // std::format not supported in clang16 by default (steamrt3) if (!bFirst) { sHudText.append("\n"); @@ -2045,7 +2073,6 @@ void EW_RoundPreStart() g_pEWHandler->ResetAllClantags(); g_pEWHandler->ClearItems(); - g_pEWHandler->m_bHudTicking = false; } void EW_OnEntitySpawned(CEntityInstance* pEntity) @@ -2149,6 +2176,27 @@ bool EW_Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices* pWeapon if (!zpPlayer || zpPlayer->IsEbanned()) return false; + // Limit any active transfers to the receiver only + if (g_pEWHandler->vecActiveTransfers.size() == 0) + return true; + + for (int i = 0; i < g_pEWHandler->vecActiveTransfers.size(); i++) + { + std::shared_ptr transfer = g_pEWHandler->vecActiveTransfers[i]; + CHandle hReceiver = transfer->hReceiver; + CHandle hWeapon = transfer->hWeapon; + if (!hReceiver.Get() || !hWeapon.Get()) + continue; + + if (hWeapon.Get() != pPlayerWeapon) + continue; + + if (hReceiver.Get() == ccsPlayer) + return true; + else + return false; + } + return true; } @@ -2241,6 +2289,14 @@ void EW_PlayerDeathPre(CCSPlayerController* pController) void EW_PlayerDisconnect(int slot) { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(CPlayerSlot(slot)); + if (pPlayer) + { + CPointWorldText* pText = pPlayer->GetEntwatchHud(); + if (pText) + pText->Remove(); + } + auto i = g_pEWHandler->mapTransfers.find(slot); if (i != g_pEWHandler->mapTransfers.end()) g_pEWHandler->mapTransfers.erase(slot); diff --git a/src/entwatch.h b/src/entwatch.h index 1196bc6ec..3750cb760 100644 --- a/src/entwatch.h +++ b/src/entwatch.h @@ -178,7 +178,7 @@ struct EWItemInstance : EWItem /* Current instance of defined items */ sClantag(""), bHasThisClantag(false), iTeamNum(CS_TEAM_NONE), - bShouldGlow(false){}; + bShouldGlow(false) {}; bool RegisterHandler(CBaseEntity* pEnt, int iHandlerTemplateNum); bool RemoveHandler(CBaseEntity* pEnt); int FindHandlerByEntIndex(int indexToFind); @@ -205,13 +205,19 @@ struct ETransferInfo float flTime; // The time when the command was initiated }; +struct EActiveTransfer +{ + CHandle hReceiver; + CHandle hWeapon; + float flTime; +}; + class CEWHandler { public: CEWHandler() { bConfigLoaded = false; - m_bHudTicking = false; iBaseBtnUseHookId = -1; iPhysboxUseHookId = -1; @@ -278,9 +284,10 @@ class CEWHandler int iRotBtnUseHookId; int iMomRotBtnUseHookId; - bool m_bHudTicking; + std::weak_ptr m_pHudTimer; - std::map> mapTransfers; // Any etransfers that target multiple items + std::map> mapTransfers; // Any etransfers that target multiple items + std::vector> vecActiveTransfers; // Active transfers where only the receiver can pickup the weapon }; extern CEWHandler* g_pEWHandler; From ccef8801a4c91e434c2c0c4859697d6b2a24ca94 Mon Sep 17 00:00:00 2001 From: Vauff Date: Tue, 6 Jan 2026 15:53:29 -0500 Subject: [PATCH 9/9] Add MOTD/Server Website feature fix The client side implementation exists, but server side seemed to be broken, so let's reimplement it ourselves --- cfg/cs2fixes/cs2fixes.cfg | 24 ++++++++++++------------ src/cs2fixes.cpp | 14 +++++++++++++- src/entwatch.h | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 2a87b0db8..a572988e8 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -1,7 +1,7 @@ // Feature cvars cs2f_commands_enable 0 // Whether to enable chat commands cs2f_admin_commands_enable 0 // Whether to enable admin chat commands -cs2f_admin_immunity 0 // Mode for which admin immunity system targetting allows: 0 - strictly lower, 1 - equal to or lower, 2 - ignore immunity levels +cs2f_admin_immunity 0 // Mode for which admin immunity system targetting allows: 0 - strictly lower, 1 - equal to or lower, 2 - ignore immunity levels cs2f_weapons_enable 0 // Whether to enable weapon commands cs2f_stopsound_enable 0 // Whether to enable stopsound cs2f_noblock_enable 0 // Whether to use player noblock, which sets debris collision on every player @@ -26,10 +26,10 @@ cs2f_fix_game_bans 0 // Whether to fix CS2 game bans spreading to all new jo cs2f_free_armor 0 // Whether kevlar (1+) and/or helmet (2) are given automatically cs2f_beacon_particle "particles/cs2fixes/admin_beacon.vpcf" // .vpcf file to be precached and used for player beacon - +cs2f_motd_url "" // Server MOTD URL, shows up as a "Server Website" button in scoreboard cs2f_delay_auth_fail_kick 0 // How long in seconds to delay kicking players when their Steam authentication fails, use with sv_steamauth_enforce 0 -//Screen Shake settings +// Screen Shake settings cs2f_noshake_enable 0 // Whether to enable noshake command cs2f_maximum_shake_amplitude -1 // Shaking Amplitude bigger than this will be clamped (0-16.0), -1 = no clamp @@ -131,16 +131,16 @@ cs2f_leader_actions_ct_only 1 // Whether to allow leader actions (like !beacon cs2f_leader_marker_ct_only 1 // Whether to have zombie leaders' player_pings spawn in particle markers or not cs2f_leader_mute_player_pings 1 // Whether to mute player pings made by non-leaders cs2f_leader_model_path "" // Path to player model to be used for leaders -cs2f_leader_defend_particle "particles/cs2fixes/leader_defend_mark.vpcf" // Path to defend particle to be used with c_defend -cs2f_leader_mark_particle "particles/cs2fixes/leader_defend_mark.vpcf" // Path to particle to be used when a ct leader using player_ping -cs2f_leader_can_target_players 0 // Whether a leader can target other players with leader commands (not including c_leader) -cs2f_leader_vote_multiple 1 // If 1, players can vote up to cs2f_max_leaders leaders. If 0, they may vote for a single leader +cs2f_leader_defend_particle "particles/cs2fixes/leader_defend_mark.vpcf" // Path to defend particle to be used with c_defend +cs2f_leader_mark_particle "particles/cs2fixes/leader_defend_mark.vpcf" // Path to particle to be used when a ct leader using player_ping +cs2f_leader_can_target_players 0 // Whether a leader can target other players with leader commands (not including c_leader) +cs2f_leader_vote_multiple 1 // If 1, players can vote up to cs2f_max_leaders leaders. If 0, they may vote for a single leader cs2f_leader_extra_score 20000 // Extra score to give a leader to affect their position on the scoreboard -cs2f_leader_max_leaders 3 // Max amount of leaders set via c_vl or a leader using c_leader (doesn't impact admins) -cs2f_leader_max_markers 6 // Max amount of markers set by leaders (doesn't impact admins) -cs2f_leader_max_glows 3 // Max amount of glows set by leaders (doesn't impact admins) -cs2f_leader_max_tracers 3 // Max amount of tracers set by leaders (doesn't impact admins) -cs2f_leader_max_beacons 3 // Max amount of beacons set by leaders (doesn't impact admins) +cs2f_leader_max_leaders 3 // Max amount of leaders set via c_vl or a leader using c_leader (doesn't impact admins) +cs2f_leader_max_markers 6 // Max amount of markers set by leaders (doesn't impact admins) +cs2f_leader_max_glows 3 // Max amount of glows set by leaders (doesn't impact admins) +cs2f_leader_max_tracers 3 // Max amount of tracers set by leaders (doesn't impact admins) +cs2f_leader_max_beacons 3 // Max amount of beacons set by leaders (doesn't impact admins) // Idle Kick Settings cs2f_idle_kick_time 0.0 // Amount of minutes before kicking idle players. 0 to disable afk kicking. diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 1db9190bb..a50738420 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -599,6 +599,8 @@ void CS2Fixes::Hook_DispatchConCommand(ConCommandRef cmdHandle, const CCommandCo RETURN_META(MRES_IGNORED); } +CConVar g_cvarMotdUrl("cs2f_motd_url", FCVAR_NONE, "Server MOTD URL, shows up as a \"Server Website\" button in scoreboard", ""); + void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession* pSession, const char* pszMapName) { g_pEntitySystem = GameEntitySystem(); @@ -618,8 +620,18 @@ void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISou g_pPanoramaVoteHandler->Reset(); g_pVoteManager->VoteManager_Init(); - g_pIdleSystem->Reset(); + + INetworkStringTable* pInfoPanelTable = g_pNetworkStringTableServer->FindTable("InfoPanel"); + + if (pInfoPanelTable && V_strcmp(g_cvarMotdUrl.Get(), "")) + { + SetStringUserDataRequest_t pUserData; + pUserData.m_pRawData = (void*)g_cvarMotdUrl.Get().Get(); + pUserData.m_cbDataSize = g_cvarMotdUrl.Get().Length() + 1; + + pInfoPanelTable->AddString(true, "motd", &pUserData); + } } class CGamePlayerEquip; diff --git a/src/entwatch.h b/src/entwatch.h index 3750cb760..981751675 100644 --- a/src/entwatch.h +++ b/src/entwatch.h @@ -178,7 +178,7 @@ struct EWItemInstance : EWItem /* Current instance of defined items */ sClantag(""), bHasThisClantag(false), iTeamNum(CS_TEAM_NONE), - bShouldGlow(false) {}; + bShouldGlow(false){}; bool RegisterHandler(CBaseEntity* pEnt, int iHandlerTemplateNum); bool RemoveHandler(CBaseEntity* pEnt); int FindHandlerByEntIndex(int indexToFind);