Skip to content

Commit

Permalink
Award XPs to oppositing team if player tries to prevent ghost cap
Browse files Browse the repository at this point in the history
* 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 NeotokyoRebuild#215
  • Loading branch information
nullsystem committed Sep 11, 2024
1 parent 51a1ef4 commit 8255d82
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 8 deletions.
125 changes: 117 additions & 8 deletions mp/src/game/shared/neo/neo_gamerules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,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 );
Expand Down Expand Up @@ -477,6 +482,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
}

Expand Down Expand Up @@ -1055,6 +1063,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();
Expand Down Expand Up @@ -1710,7 +1721,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)
{
Expand Down Expand Up @@ -1750,6 +1768,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)
{
Expand Down Expand Up @@ -1778,8 +1797,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<int>(player->IsCarryingGhost());
if (m_bTeamBeenAwardedDueToCapPrevent)
{
AwardRankUp(player);
xpAward = 0; // Already been rewarded rank-up XPs
++iRankupCapPrev;
}
else
{
++xpAward;
xpAward += static_cast<int>(player->IsCarryingGhost());
}
}
player->m_iXP.GetForModify() += xpAward;
}
Expand All @@ -1792,6 +1820,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();
Expand Down Expand Up @@ -1828,24 +1866,78 @@ 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<CNEO_Player*>(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<CNEO_Player*>(info.GetAttacker());
auto victim = dynamic_cast<CNEO_Player*>(pVictim);

if (!attacker || !pVictim)
if (!victim)
{
return;
}

// Suicide
if (attacker == victim)
// Suicide (or suicide by environment)
if (attacker == victim || !attacker)
{
attacker->m_iXP.GetForModify() -= 1;
victim->m_iXP.GetForModify() -= 1;
#ifdef GAME_DLL
CheckIfCapPrevent(victim);
#endif
}
else
else if (attacker)
{
// Team kill
if (attacker->GetTeamNumber() == victim->GetTeamNumber())
Expand All @@ -1856,6 +1948,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
Expand Down Expand Up @@ -2157,6 +2263,9 @@ void CNEORules::ClientDisconnected(edict_t* pClient)
}
}
}

// Check if this is done to prevent ghost cap
CheckIfCapPrevent(pNeoPlayer);
}

BaseClass::ClientDisconnected(pClient);
Expand Down
6 changes: 6 additions & 0 deletions mp/src/game/shared/neo/neo_gamerules.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 8255d82

Please sign in to comment.