From b46ef4a0fe694752b0dc77924ac8e4e3c3e835ae Mon Sep 17 00:00:00 2001 From: notkoen <45914779+notkoen@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:54:12 -0800 Subject: [PATCH 1/2] Update TopDefender --- 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 | 422 +++++++++++++++++++++++ src/topdefender.h | 36 ++ 9 files changed, 501 insertions(+), 91 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..060435773 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -31,6 +31,7 @@ #include "leader.h" #include "map_votes.h" #include "panoramavote.h" +#include "topdefender.h" #include "recipientfilters.h" #include "votemanager.h" #include "zombiereborn.h" @@ -160,27 +161,12 @@ 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 +177,10 @@ 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,19 +203,9 @@ 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++) + if (g_cvarEnableTopDefender.Get()) { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (!pPlayer) - continue; - - pPlayer->SetTotalDamage(0); - pPlayer->SetTotalHits(0); - pPlayer->SetTotalKills(0); + TD_OnRoundStart(pEvent); } } @@ -250,50 +214,9 @@ 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++) + if (g_cvarEnableTopDefender.Get()) { - 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); + TD_OnRoundEnd(pEvent); } } 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..35cdd3b10 --- /dev/null +++ b/src/topdefender.cpp @@ -0,0 +1,422 @@ +/** + * ============================================================================= + * 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 c299a378e8e584fc3644b80f426de8bc0d4d74f0 Mon Sep 17 00:00:00 2001 From: Vauff Date: Tue, 23 Dec 2025 01:44:10 -0500 Subject: [PATCH 2/2] formatter --- src/events.cpp | 10 +------- src/topdefender.cpp | 62 --------------------------------------------- 2 files changed, 1 insertion(+), 71 deletions(-) diff --git a/src/events.cpp b/src/events.cpp index 060435773..ad32f4c51 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -31,8 +31,8 @@ #include "leader.h" #include "map_votes.h" #include "panoramavote.h" -#include "topdefender.h" #include "recipientfilters.h" +#include "topdefender.h" #include "votemanager.h" #include "zombiereborn.h" @@ -164,9 +164,7 @@ GAME_EVENT_F(player_spawn) GAME_EVENT_F(player_hurt) { if (g_cvarEnableTopDefender.Get()) - { TD_OnPlayerHurt(pEvent); - } } GAME_EVENT_F(player_death) @@ -178,9 +176,7 @@ GAME_EVENT_F(player_death) EW_PlayerDeath(pEvent); if (g_cvarEnableTopDefender.Get()) - { TD_OnPlayerDeath(pEvent); - } } CConVar g_cvarFullAllTalk("cs2f_full_alltalk", FCVAR_NONE, "Whether to enforce sv_full_alltalk 1", false); @@ -204,9 +200,7 @@ GAME_EVENT_F(round_start) g_pEngineServer2->ServerCommand("mp_warmup_end"); if (g_cvarEnableTopDefender.Get()) - { TD_OnRoundStart(pEvent); - } } GAME_EVENT_F(round_end) @@ -215,9 +209,7 @@ GAME_EVENT_F(round_end) g_pGameRules->m_bGameRestart = false; if (g_cvarEnableTopDefender.Get()) - { TD_OnRoundEnd(pEvent); - } } GAME_EVENT_F(round_freeze_end) diff --git a/src/topdefender.cpp b/src/topdefender.cpp index 35cdd3b10..b0091b480 100644 --- a/src/topdefender.cpp +++ b/src/topdefender.cpp @@ -47,37 +47,25 @@ CConVar g_cvarTopDefenderClanTag("cs2f_topdefender_clantag", FCVAR_N 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) @@ -87,17 +75,13 @@ void TD_OnPlayerHurt(IGameEvent* pEvent) // 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) { @@ -105,9 +89,7 @@ void TD_OnPlayerHurt(IGameEvent* pEvent) pAttackerPlayer->SetTotalHits(pAttackerPlayer->GetTotalHits() + 1); if (pEvent->GetInt("hitgroup") == 1) - { pAttackerPlayer->SetTotalHeadshots(pAttackerPlayer->GetTotalHeadshots() + 1); - } } } @@ -118,15 +100,11 @@ void TD_OnPlayerDeath(IGameEvent* pEvent) // 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) @@ -139,9 +117,7 @@ void TD_OnRoundStart(IGameEvent* pEvent) { ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); if (!pPlayer) - { continue; - } pPlayer->SetTotalDamage(0); pPlayer->SetTotalHits(0); @@ -168,9 +144,7 @@ void TD_OnRoundStart(IGameEvent* pEvent) m_pUpdateTimer = CTimer::Create(g_cvarTopDefenderRate.Get(), TIMERFLAG_MAP | TIMERFLAG_ROUND, []() { if (!GetGlobals()) - { return -1.0f; - } sortedPlayers.clear(); @@ -178,9 +152,7 @@ void TD_OnRoundStart(IGameEvent* pEvent) { ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); if (!pPlayer || pPlayer->GetTotalDamage() == 0) - { continue; - } sortedPlayers.push_back(pPlayer->GetHandle()); } @@ -193,15 +165,11 @@ void TD_OnRoundStart(IGameEvent* pEvent) { ZEPlayer* pPlayer = sortedPlayers[i].Get(); if (!pPlayer) - { continue; - } CCSPlayerController* pController = CCSPlayerController::FromSlot(pPlayer->GetPlayerSlot()); if (!pController) - { continue; - } if (pController->m_iMVPs() != i + 1) { @@ -219,14 +187,10 @@ 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(); @@ -234,15 +198,11 @@ void TD_OnRoundEnd(IGameEvent* pEvent) { 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()) @@ -274,15 +234,11 @@ void TD_OnRoundEnd(IGameEvent* pEvent) { 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"); @@ -302,15 +258,11 @@ void TD_OnRoundEnd(IGameEvent* pEvent) { 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"); } @@ -349,17 +301,13 @@ void TopDefenderSearch(CCSPlayerController* player, const CCommand& args) { 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; @@ -378,23 +326,17 @@ void TopDefenderSearch(CCSPlayerController* player, const CCommand& args) 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; @@ -406,15 +348,11 @@ void TopDefenderSearch(CCSPlayerController* player, const CCommand& args) { 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"); }