From d49404366c087e6b1a3c76124fd71e505652c092 Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Sun, 18 Aug 2024 20:28:59 +0100 Subject: [PATCH 1/2] Award XPs to oppositing team if player tries to prevent ghost cap * If it's like EX: 1v3 alives, the 3-person team is playing the ghost then the single-alive player decides to suicide, this will reward the ghost playing/winning team a rank up since the suicide player tries to prevent a ghost cap. * New convar: * neo_sv_suicide_prevent_cap_punish - Default: 1 to enable this feature * Add environment suicide as a suicide, add disconnect check * Also check for posthumanus teamkill prevent cap scenario * fixes #215 --- mp/src/game/server/neo/neo_detpack.cpp | 1 + mp/src/game/server/neo/neo_grenade.cpp | 1 + mp/src/game/server/neo/neo_smokegrenade.cpp | 1 + mp/src/game/shared/neo/neo_gamerules.cpp | 134 ++++++++++++++++++-- mp/src/game/shared/neo/neo_gamerules.h | 6 + 5 files changed, 135 insertions(+), 8 deletions(-) diff --git a/mp/src/game/server/neo/neo_detpack.cpp b/mp/src/game/server/neo/neo_detpack.cpp index 934002238..25aa2acf6 100644 --- a/mp/src/game/server/neo/neo_detpack.cpp +++ b/mp/src/game/server/neo/neo_detpack.cpp @@ -300,6 +300,7 @@ CBaseGrenade* NEODeployedDetpack_Create(const Vector& position, const QAngle& an pDet->SetTimer(FLT_MAX, FLT_MAX); pDet->SetVelocity(velocity, angVelocity); pDet->SetThrower(ToBaseCombatCharacter(pOwner)); + if (pOwner) pDet->ChangeTeam(pOwner->GetTeamNumber()); pDet->m_takedamage = DAMAGE_EVENTS_ONLY; return pDet; diff --git a/mp/src/game/server/neo/neo_grenade.cpp b/mp/src/game/server/neo/neo_grenade.cpp index a99b1fcef..f94cbac43 100644 --- a/mp/src/game/server/neo/neo_grenade.cpp +++ b/mp/src/game/server/neo/neo_grenade.cpp @@ -250,6 +250,7 @@ CBaseGrenade *NEOFraggrenade_Create(const Vector &position, const QAngle &angles pGrenade->SetTimer(timer, timer - NEO_FRAG_GRENADE_WARN_TIME); pGrenade->SetVelocity(velocity, angVelocity); pGrenade->SetThrower(ToBaseCombatCharacter(pOwner)); + if (pOwner) pGrenade->ChangeTeam(pOwner->GetTeamNumber()); pGrenade->m_takedamage = DAMAGE_EVENTS_ONLY; return pGrenade; diff --git a/mp/src/game/server/neo/neo_smokegrenade.cpp b/mp/src/game/server/neo/neo_smokegrenade.cpp index 1232194ef..a2c9a5314 100644 --- a/mp/src/game/server/neo/neo_smokegrenade.cpp +++ b/mp/src/game/server/neo/neo_smokegrenade.cpp @@ -298,6 +298,7 @@ CBaseGrenade* NEOSmokegrenade_Create(const Vector& position, const QAngle& angle pGrenade->SetTimer(FLT_MAX, FLT_MAX); pGrenade->SetVelocity(velocity, angVelocity); pGrenade->SetThrower(ToBaseCombatCharacter(pOwner)); + if (pOwner) pGrenade->ChangeTeam(pOwner->GetTeamNumber()); pGrenade->m_takedamage = DAMAGE_EVENTS_ONLY; return pGrenade; diff --git a/mp/src/game/shared/neo/neo_gamerules.cpp b/mp/src/game/shared/neo/neo_gamerules.cpp index 7446815c5..398ded8a9 100644 --- a/mp/src/game/shared/neo/neo_gamerules.cpp +++ b/mp/src/game/shared/neo/neo_gamerules.cpp @@ -5,6 +5,7 @@ #include "ammodef.h" #include "takedamageinfo.h" +#include "basegrenade_shared.h" #ifdef CLIENT_DLL #include "c_neo_player.h" @@ -67,6 +68,11 @@ ConVar neo_sv_mirror_teamdamage_immunity("neo_sv_mirror_teamdamage_immunity", "1 ConVar neo_sv_teamdamage_kick("neo_sv_teamdamage_kick", "0", FCVAR_REPLICATED, "If enabled, the friendly-firing individual will be kicked if damage is received during the neo_sv_mirror_teamdamage_duration, exceeds the neo_sv_teamdamage_kick_hp value, or executes a teammate.", true, 0.0f, true, 1.0f); ConVar neo_sv_teamdamage_kick_hp("neo_sv_teamdamage_kick_hp", "900", FCVAR_REPLICATED, "The threshold for the amount of HP damage inflicted on teammates before the client is kicked.", true, 100.0f, false, 0.0f); ConVar neo_sv_teamdamage_kick_kills("neo_sv_teamdamage_kick_kills", "6", FCVAR_REPLICATED, "The threshold for the amount of team kills before the client is kicked.", true, 1.0f, false, 0.0f); +ConVar neo_sv_suicide_prevent_cap_punish("neo_sv_suicide_prevent_cap_punish", "1", FCVAR_REPLICATED, + "If enabled, if a player suicides and is the only one alive in their team, " + "while the other team is holding the ghost, reward the ghost holder team " + "a rank up.", + true, 0.0f, true, 1.0f); #endif REGISTER_GAMERULES_CLASS( CNEORules ); @@ -479,6 +485,9 @@ void CNEORules::ResetMapSessionCommon() } m_flPrevThinkKick = 0.0f; m_flPrevThinkMirrorDmg = 0.0f; + m_bTeamBeenAwardedDueToCapPrevent = false; + V_memset(m_arrayiEntPrevCap, 0, sizeof(m_arrayiEntPrevCap)); + m_iEntPrevCapSize = 0; #endif } @@ -1057,6 +1066,9 @@ void CNEORules::StartNextRound() m_flIntermissionEndTime = 0; m_flRestartGameTime = 0; m_bCompleteReset = false; + m_bTeamBeenAwardedDueToCapPrevent = false; + V_memset(m_arrayiEntPrevCap, 0, sizeof(m_arrayiEntPrevCap)); + m_iEntPrevCapSize = 0; if (clearXP) { m_pRestoredInfos.Purge(); @@ -1712,7 +1724,14 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo } else if (iWinReason == NEO_VICTORY_TEAM_ELIMINATION) { - V_sprintf_safe(victoryMsg, "Team %s wins by eliminating the other team!\n", (team == TEAM_JINRAI ? "Jinrai" : "NSF")); + if (m_bTeamBeenAwardedDueToCapPrevent) + { + V_sprintf_safe(victoryMsg, "Team %s wins and is awarded rank ups by ghost cap prevention!\n", (team == TEAM_JINRAI ? "Jinrai" : "NSF")); + } + else + { + V_sprintf_safe(victoryMsg, "Team %s wins by eliminating the other team!\n", (team == TEAM_JINRAI ? "Jinrai" : "NSF")); + } } else if (iWinReason == NEO_VICTORY_TIMEOUT_WIN_BY_NUMBERS) { @@ -1752,6 +1771,7 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo soundParams.m_bEmitCloseCaption = false; const int winningTeamNum = winningTeam->GetTeamNumber(); + int iRankupCapPrev = 0; for (int i = 1; i <= gpGlobals->maxClients; ++i) { @@ -1780,8 +1800,17 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo int xpAward = 1; // Base reward for being on winning team if (player->IsAlive()) { - ++xpAward; - xpAward += static_cast(player->IsCarryingGhost()); + if (m_bTeamBeenAwardedDueToCapPrevent) + { + AwardRankUp(player); + xpAward = 0; // Already been rewarded rank-up XPs + ++iRankupCapPrev; + } + else + { + ++xpAward; + xpAward += static_cast(player->IsCarryingGhost()); + } } player->m_iXP.GetForModify() += xpAward; } @@ -1794,6 +1823,16 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo } } + if (m_bTeamBeenAwardedDueToCapPrevent && iWinReason != NEO_VICTORY_GHOST_CAPTURE) + { + UTIL_ClientPrintAll(HUD_PRINTTALK, "Last player of %s1 suicided vs. ghost carrier; awarding capture to team %s2.", + (team == TEAM_JINRAI ? "NSF" : "Jinrai"), (team == TEAM_JINRAI ? "Jinrai" : "NSF")); + char szHudChatPrint[128]; + V_sprintf_safe(szHudChatPrint, "Awarding capture rank-up to %d player%s.", + iRankupCapPrev, iRankupCapPrev == 1 ? "" : "s"); + UTIL_ClientPrintAll(HUD_PRINTTALK, szHudChatPrint); + } + if (gotMatchWinner) { GoToIntermission(); @@ -1830,24 +1869,86 @@ static CNEO_Player* FetchAssists(CNEO_Player* attacker, CNEO_Player* victim) return NULL; } +#ifdef GAME_DLL +void CNEORules::CheckIfCapPrevent(CNEO_Player *capPreventerPlayer) +{ + // If this is the only player alive left before the suicide/disconnect and the other team was holding + // the ghost, reward the other team an XP to the next rank as a ghost cap was prevented. + if (neo_sv_suicide_prevent_cap_punish.GetBool() && m_nRoundStatus == NeoRoundStatus::RoundLive && + !m_bTeamBeenAwardedDueToCapPrevent) + { + bool bOtherTeamPlayingGhost = false; + int iTallyAlive[TEAM__TOTAL] = {}; + const int iPreventerTeam = capPreventerPlayer->GetTeamNumber(); + // Sanity check: Make sure it's only Jinrai/NSF players + const bool bValidTeam = iPreventerTeam == TEAM_JINRAI || iPreventerTeam == TEAM_NSF; + Assert(bValidTeam); + if (bValidTeam) + { + const int iCapPreventerEntIdx = capPreventerPlayer->entindex(); + + // Sanity check: Prevent duplication just in-case + bool bContainsEntIdx = false; + for (int i = 0; i < m_iEntPrevCapSize; ++i) + { + bContainsEntIdx = (m_arrayiEntPrevCap[i] == iCapPreventerEntIdx); + if (bContainsEntIdx) break; + } + if (!bContainsEntIdx) m_arrayiEntPrevCap[m_iEntPrevCapSize++] = iCapPreventerEntIdx; + + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + auto *player = static_cast(UTIL_PlayerByIndex(i)); + if (!player || player->entindex() == iCapPreventerEntIdx) + { + continue; + } + + const int iPlayerTeam = player->GetTeamNumber(); + iTallyAlive[iPlayerTeam] += player->IsAlive(); + if (iPlayerTeam != iPreventerTeam && player->IsCarryingGhost()) + { + bOtherTeamPlayingGhost = true; + } + } + + const int iOppositeTeam = (iPreventerTeam == TEAM_JINRAI) ? TEAM_NSF : TEAM_JINRAI; + m_bTeamBeenAwardedDueToCapPrevent = (bOtherTeamPlayingGhost && + iTallyAlive[iPreventerTeam] == 0 && iTallyAlive[iOppositeTeam] > 0); + } + } +} +#endif + void CNEORules::PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) { BaseClass::PlayerKilled(pVictim, info); auto attacker = dynamic_cast(info.GetAttacker()); auto victim = dynamic_cast(pVictim); + auto grenade = dynamic_cast(info.GetInflictor()); - if (!attacker || !pVictim) + if (!victim) { return; } - // Suicide - if (attacker == victim) + // Suicide or suicide by environment (non-grenade as grenade is likely from a player) + if (attacker == victim || (!attacker && !grenade)) { - attacker->m_iXP.GetForModify() -= 1; + victim->m_iXP.GetForModify() -= 1; +#ifdef GAME_DLL + CheckIfCapPrevent(victim); +#endif } - else +#ifdef GAME_DLL + else if (!attacker && grenade && grenade->GetTeamNumber() == victim->GetTeamNumber()) + { + // Death by own team's grenade, but the player is already disconnected. Check for cap prevent. + CheckIfCapPrevent(victim); + } +#endif + else if (attacker) { // Team kill if (attacker->GetTeamNumber() == victim->GetTeamNumber()) @@ -1858,6 +1959,20 @@ void CNEORules::PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) { ++attacker->m_iTeamKillsInflicted; } + + for (int i = 0; i < m_iEntPrevCapSize; ++i) + { + if (m_arrayiEntPrevCap[i] == attacker->entindex()) + { + // Posthumous teamkill to prevent ghost cap scenario: + // Player-A throws nade at Player-B, Player-A suicides right after, + // Player-B gets killed from the nade - This dodges the general case + // as Player-A is not the final player, but it was Player-A's intention + // to prevent the ghost cap. + CheckIfCapPrevent(victim); + break; + } + } #endif } // Enemy kill @@ -2159,6 +2274,9 @@ void CNEORules::ClientDisconnected(edict_t* pClient) } } } + + // Check if this is done to prevent ghost cap + CheckIfCapPrevent(pNeoPlayer); } BaseClass::ClientDisconnected(pClient); diff --git a/mp/src/game/shared/neo/neo_gamerules.h b/mp/src/game/shared/neo/neo_gamerules.h index a105f5a95..2b3c5a7f5 100644 --- a/mp/src/game/shared/neo/neo_gamerules.h +++ b/mp/src/game/shared/neo/neo_gamerules.h @@ -188,6 +188,9 @@ class CNEORules : public CHL2MPRules, public CGameEventListener float MirrorDamageMultiplier() const; #endif +#ifdef GAME_DLL + void CheckIfCapPrevent(CNEO_Player *capPreventerPlayer); +#endif virtual void PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) OVERRIDE; // IGameEventListener interface: @@ -279,6 +282,9 @@ class CNEORules : public CHL2MPRules, public CGameEventListener CWeaponGhost *m_pGhost = nullptr; float m_flPrevThinkKick = 0.0f; float m_flPrevThinkMirrorDmg = 0.0f; + bool m_bTeamBeenAwardedDueToCapPrevent = false; + int m_arrayiEntPrevCap[MAX_PLAYERS + 1]; // This is to check for cap-prevention workaround attempts + int m_iEntPrevCapSize = 0; #endif CNetworkVar(int, m_nRoundStatus); // NEO TODO (Rain): probably don't need to network this CNetworkVar(int, m_iRoundNumber); From d07a8366f9a67363f395da4c81a5e6dd9bde3883 Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Thu, 19 Sep 2024 00:36:28 +0100 Subject: [PATCH 2/2] Code review styling changes --- mp/src/game/shared/neo/neo_gamerules.cpp | 79 +++++++++++++----------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/mp/src/game/shared/neo/neo_gamerules.cpp b/mp/src/game/shared/neo/neo_gamerules.cpp index 398ded8a9..a37064345 100644 --- a/mp/src/game/shared/neo/neo_gamerules.cpp +++ b/mp/src/game/shared/neo/neo_gamerules.cpp @@ -1827,7 +1827,7 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo { UTIL_ClientPrintAll(HUD_PRINTTALK, "Last player of %s1 suicided vs. ghost carrier; awarding capture to team %s2.", (team == TEAM_JINRAI ? "NSF" : "Jinrai"), (team == TEAM_JINRAI ? "Jinrai" : "NSF")); - char szHudChatPrint[128]; + char szHudChatPrint[42]; V_sprintf_safe(szHudChatPrint, "Awarding capture rank-up to %d player%s.", iRankupCapPrev, iRankupCapPrev == 1 ? "" : "s"); UTIL_ClientPrintAll(HUD_PRINTTALK, szHudChatPrint); @@ -1874,49 +1874,54 @@ void CNEORules::CheckIfCapPrevent(CNEO_Player *capPreventerPlayer) { // If this is the only player alive left before the suicide/disconnect and the other team was holding // the ghost, reward the other team an XP to the next rank as a ghost cap was prevented. - if (neo_sv_suicide_prevent_cap_punish.GetBool() && m_nRoundStatus == NeoRoundStatus::RoundLive && - !m_bTeamBeenAwardedDueToCapPrevent) + const bool bShouldCheck = (neo_sv_suicide_prevent_cap_punish.GetBool() + && m_nRoundStatus == NeoRoundStatus::RoundLive + && !m_bTeamBeenAwardedDueToCapPrevent); + if (!bShouldCheck) { - bool bOtherTeamPlayingGhost = false; - int iTallyAlive[TEAM__TOTAL] = {}; - const int iPreventerTeam = capPreventerPlayer->GetTeamNumber(); - // Sanity check: Make sure it's only Jinrai/NSF players - const bool bValidTeam = iPreventerTeam == TEAM_JINRAI || iPreventerTeam == TEAM_NSF; - Assert(bValidTeam); - if (bValidTeam) - { - const int iCapPreventerEntIdx = capPreventerPlayer->entindex(); + return; + } - // Sanity check: Prevent duplication just in-case - bool bContainsEntIdx = false; - for (int i = 0; i < m_iEntPrevCapSize; ++i) - { - bContainsEntIdx = (m_arrayiEntPrevCap[i] == iCapPreventerEntIdx); - if (bContainsEntIdx) break; - } - if (!bContainsEntIdx) m_arrayiEntPrevCap[m_iEntPrevCapSize++] = iCapPreventerEntIdx; + bool bOtherTeamPlayingGhost = false; + int iTallyAlive[TEAM__TOTAL] = {}; + const int iPreventerTeam = capPreventerPlayer->GetTeamNumber(); + // Sanity check: Make sure it's only Jinrai/NSF players + const bool bValidTeam = iPreventerTeam == TEAM_JINRAI || iPreventerTeam == TEAM_NSF; + Assert(bValidTeam); + if (!bValidTeam) + { + return; + } - for (int i = 1; i <= gpGlobals->maxClients; ++i) - { - auto *player = static_cast(UTIL_PlayerByIndex(i)); - if (!player || player->entindex() == iCapPreventerEntIdx) - { - continue; - } + const int iCapPreventerEntIdx = capPreventerPlayer->entindex(); - const int iPlayerTeam = player->GetTeamNumber(); - iTallyAlive[iPlayerTeam] += player->IsAlive(); - if (iPlayerTeam != iPreventerTeam && player->IsCarryingGhost()) - { - bOtherTeamPlayingGhost = true; - } - } + // Sanity check: Prevent duplication just in-case + bool bContainsEntIdx = false; + for (int i = 0; !bContainsEntIdx && i < m_iEntPrevCapSize; ++i) + { + bContainsEntIdx = (m_arrayiEntPrevCap[i] == iCapPreventerEntIdx); + } + if (!bContainsEntIdx) m_arrayiEntPrevCap[m_iEntPrevCapSize++] = iCapPreventerEntIdx; + + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + auto *player = static_cast(UTIL_PlayerByIndex(i)); + if (!player || player->entindex() == iCapPreventerEntIdx) + { + continue; + } - const int iOppositeTeam = (iPreventerTeam == TEAM_JINRAI) ? TEAM_NSF : TEAM_JINRAI; - m_bTeamBeenAwardedDueToCapPrevent = (bOtherTeamPlayingGhost && - iTallyAlive[iPreventerTeam] == 0 && iTallyAlive[iOppositeTeam] > 0); + const int iPlayerTeam = player->GetTeamNumber(); + iTallyAlive[iPlayerTeam] += player->IsAlive(); + if (iPlayerTeam != iPreventerTeam && player->IsCarryingGhost()) + { + bOtherTeamPlayingGhost = true; } } + + const int iOppositeTeam = (iPreventerTeam == TEAM_JINRAI) ? TEAM_NSF : TEAM_JINRAI; + m_bTeamBeenAwardedDueToCapPrevent = (bOtherTeamPlayingGhost && + iTallyAlive[iPreventerTeam] == 0 && iTallyAlive[iOppositeTeam] > 0); } #endif