Skip to content

Award XPs to oppositing team if player tries to prevent ghost cap#556

Merged
nullsystem merged 2 commits intoNeotokyoRebuild:masterfrom
nullsystem:GH-215_GhostcapSuicideXP
Sep 19, 2024
Merged

Award XPs to oppositing team if player tries to prevent ghost cap#556
nullsystem merged 2 commits intoNeotokyoRebuild:masterfrom
nullsystem:GH-215_GhostcapSuicideXP

Conversation

@nullsystem
Copy link
Copy Markdown
Collaborator

@nullsystem nullsystem commented Aug 18, 2024

Description

  • 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.
  • Checks for: suicide, suicide by environment, disconnects, and posthumous teamkill (suicide/disconnects)
  • New convar:
    • neo_sv_suicide_prevent_cap_punish - Default: 1 to enable this feature

Toolchain

  • Linux GCC Distro Native Arch/GCC 14

Linked Issues

@nullsystem nullsystem requested a review from a team August 18, 2024 19:32
@Rainyan
Copy link
Copy Markdown
Member

Rainyan commented Aug 18, 2024

Some edge cases to consider:

  • Death by environment: Death by world iirc is attributed to attacker entidx 0 (world). Same deal with other environment hazards. Should probably be treated as a suicide.
  • Posthumous teamkill: toss a nade at your teammate, then suicide. The final team member gets killed by your grenade as a teamkill, bypassing detection.
  • Disconnect or timeout: Last player disconnecting & then reconnecting bypasses the suicide check.

@nullsystem
Copy link
Copy Markdown
Collaborator Author

@Rainyan Updated it to check for those edge-cases now

Comment thread mp/src/game/shared/neo/neo_gamerules.cpp Outdated
Comment thread mp/src/game/shared/neo/neo_gamerules.cpp Outdated
Comment thread mp/src/game/shared/neo/neo_gamerules.cpp Outdated
@nullsystem nullsystem requested a review from Rainyan August 19, 2024 18:04
Rainyan
Rainyan previously approved these changes Aug 19, 2024
Copy link
Copy Markdown
Member

@Rainyan Rainyan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and all seems to work well.

We could probably do without needing to track state in member variables, something like below (untested), but I'm ok with merging what we've got.

diff --git a/mp/src/game/shared/neo/neo_gamerules.cpp b/mp/src/game/shared/neo/neo_gamerules.cpp
index e2f14702..03728fcc 100644
--- a/mp/src/game/shared/neo/neo_gamerules.cpp
+++ b/mp/src/game/shared/neo/neo_gamerules.cpp
@@ -449,9 +449,6 @@ void CNEORules::ResetMapSessionCommon()
 	m_flNeoNextRoundStartTime = 0.0f;
 #ifdef GAME_DLL
 	m_pRestoredInfos.Purge();
-	m_bTeamBeenAwardedDueToCapPrevent = false;
-	V_memset(m_arrayiEntPrevCap, 0, sizeof(m_arrayiEntPrevCap));
-	m_iEntPrevCapSize = 0;
 #endif
 }
 
@@ -974,9 +971,6 @@ 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();
@@ -1632,14 +1626,7 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo
 		}
 		else if (iWinReason == NEO_VICTORY_TEAM_ELIMINATION)
 		{
-			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"));
-			}
+			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)
 		{
@@ -1679,7 +1666,6 @@ 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)
 	{
@@ -1708,17 +1694,8 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo
 				int xpAward = 1;	// Base reward for being on winning team
 				if (player->IsAlive())
 				{
-					if (m_bTeamBeenAwardedDueToCapPrevent)
-					{
-						AwardRankUp(player);
-						xpAward = 0; // Already been rewarded rank-up XPs
-						++iRankupCapPrev;
-					}
-					else
-					{
-						++xpAward;
-						xpAward += static_cast<int>(player->IsCarryingGhost());
-					}
+					++xpAward;
+					xpAward += static_cast<int>(player->IsCarryingGhost());
 				}
 				player->m_iXP.GetForModify() += xpAward;
 			}
@@ -1731,16 +1708,6 @@ 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();
@@ -1778,53 +1745,19 @@ static CNEO_Player* FetchAssists(CNEO_Player* attacker, CNEO_Player* victim)
 }
 
 #ifdef GAME_DLL
-void CNEORules::CheckIfCapPrevent(CNEO_Player *capPreventerPlayer)
+bool CNEORules::CheckIfCapPrevent(CNEO_Player *capPreventerPlayer) const
 {
-	// 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);
-		}
+	if (!neo_sv_suicide_prevent_cap_punish.GetBool() ||
+		m_nRoundStatus != NeoRoundStatus::RoundLive ||
+		ghosterTeam() == TEAM_UNASSIGNED ||
+		ghosterTeam() == capPreventerPlayer->GetTeamNumber())
+	{
+		return false;
 	}
+
+	// -1 because the preventer's death hasn't gone through yet.
+	const auto numAlive = capPreventerPlayer->GetTeam()->GetAliveMembers() - 1;
+	return numAlive == 0;
 }
 #endif
 
@@ -1840,41 +1773,45 @@ void CNEORules::PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info)
 		return;
 	}
 
-	// Suicide (or suicide by environment)
-	if (attacker == victim || !attacker)
+	// Suicide or teamkill
+	if (attacker == victim || (!attacker || attacker->GetTeamNumber() == victim->GetTeamNumber()))
 	{
 		victim->m_iXP.GetForModify() -= 1;
 #ifdef GAME_DLL
-		CheckIfCapPrevent(victim);
-#endif
-	}
-	else if (attacker)
-	{
-		// Team kill
-		if (attacker->GetTeamNumber() == victim->GetTeamNumber())
+		if (CheckIfCapPrevent(victim))
 		{
-			attacker->m_iXP.GetForModify() -= 1;
-#ifdef GAME_DLL
-			for (int i = 0; i < m_iEntPrevCapSize; ++i)
+			Assert(GetOpposingTeam(victim) == ghosterTeam());
+
+			UTIL_ClientPrintAll(HUD_PRINTTALK, "Last player of %s1 suicided vs. ghost carrier; awarding capture to team %s2.",
+				GetGlobalTeam(victim->GetTeamNumber())->GetName(),
+				GetGlobalTeam(ghosterTeam())->GetName());
+
+			int n_rankups = 0;
+			const auto ghoster_team = GetGlobalTeam(ghosterTeam());
+			for (int i = 0; i < ghoster_team->GetNumPlayers(); ++i)
 			{
-				if (m_arrayiEntPrevCap[i] == attacker->entindex())
+				const auto player = static_cast<CNEO_Player*>(ghoster_team->GetPlayer(i));
+				if (player->IsAlive())
 				{
-					// 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;
+					AwardRankUp(player);
+					// Won the round, survived, and possibly is carrying the ghost.
+					player->m_iXP.GetForModify() -= 2 + (GetGhosterPlayer() == player->entindex() ? 1 : 0);
+					
+					++n_rankups;
 				}
 			}
-#endif
-		}
-		// Enemy kill
-		else
-		{
-			attacker->m_iXP.GetForModify() += 1;
+
+			char szHudChatPrint[128];
+			V_sprintf_safe(szHudChatPrint, "Awarding capture rank-up to %d player%s.",
+				n_rankups, n_rankups == 1 ? "" : "s");
+			UTIL_ClientPrintAll(HUD_PRINTTALK, szHudChatPrint);			
 		}
+#endif
+	}
+	// Enemy kill
+	else if (attacker)
+	{
+		attacker->m_iXP.GetForModify() += 1;
 
 		if (auto *assister = FetchAssists(attacker, victim))
 		{
diff --git a/mp/src/game/shared/neo/neo_gamerules.h b/mp/src/game/shared/neo/neo_gamerules.h
index 9de903e3..2ac9f3e9 100644
--- a/mp/src/game/shared/neo/neo_gamerules.h
+++ b/mp/src/game/shared/neo/neo_gamerules.h
@@ -179,7 +179,7 @@ public:
 	float GetRoundRemainingTime();
 
 #ifdef GAME_DLL
-	void CheckIfCapPrevent(CNEO_Player *capPreventerPlayer);
+	bool CheckIfCapPrevent(CNEO_Player* capPreventerPlayer) const;
 #endif
 	virtual void PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) OVERRIDE;
 
@@ -265,9 +265,6 @@ private:
 
 #ifdef GAME_DLL
 	CUtlVector<int> m_pGhostCaps;
-	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);

@nullsystem nullsystem requested a review from a team August 20, 2024 18:59
@AdamTadeusz
Copy link
Copy Markdown
Contributor

AdamTadeusz commented Sep 3, 2024

Screenshot 2024-09-03 124513

In the posthumous teamkill to prevent a ghost cap scenario if Player A is on the team controlling the ghost, they can throw a grenade at the last member of the non-ghosting team and then immediately disconnect to force the suicide to prevent ghost cap system to kick in

I think this is a genuine thing that could happen when the person throwing the grenade is probably receiving ghost calls but the ghost carrier is unable to capture the ghost due to the last enemy positioning themselves between the ghost carrier and cap point

@AdamTadeusz
Copy link
Copy Markdown
Contributor

image
You could do something like this to give the grenade projectile the team of the person throwing the grenade at the time, and then check whether if there is an weapon(projectile), whether the team of that projectile is the same as the victims team

@nullsystem nullsystem force-pushed the GH-215_GhostcapSuicideXP branch from 1acf4d9 to 8255d82 Compare September 11, 2024 18:34
@nullsystem nullsystem marked this pull request as draft September 11, 2024 18:34
@nullsystem nullsystem force-pushed the GH-215_GhostcapSuicideXP branch from 8255d82 to 9a35cc0 Compare September 11, 2024 19:27
@nullsystem nullsystem requested a review from Rainyan September 11, 2024 19:50
@nullsystem nullsystem marked this pull request as ready for review September 11, 2024 19:50
@nullsystem
Copy link
Copy Markdown
Collaborator Author

@AdamTadeusz I've altered the code now to deal with disconnect enemy-grenade causes trigger issue now. It seems ok from the test between two machines.

@AdamTadeusz
Copy link
Copy Markdown
Contributor

AdamTadeusz commented Sep 11, 2024

@AdamTadeusz I've altered the code now to deal with disconnect enemy-grenade causes trigger issue now. It seems ok from the test between two machines.

Can confirm this is fixed now

AdamTadeusz
AdamTadeusz previously approved these changes Sep 11, 2024
Rainyan
Rainyan previously approved these changes Sep 18, 2024
Copy link
Copy Markdown
Member

@Rainyan Rainyan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, left some notes but I'd be happy to merge.

Comment thread mp/src/game/shared/neo/neo_gamerules.cpp Outdated
Comment thread mp/src/game/shared/neo/neo_gamerules.cpp Outdated
Comment thread mp/src/game/shared/neo/neo_gamerules.cpp Outdated
Comment thread mp/src/game/shared/neo/neo_gamerules.cpp Outdated
* 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
@nullsystem nullsystem dismissed stale reviews from Rainyan and AdamTadeusz via d07a836 September 18, 2024 23:36
@nullsystem nullsystem force-pushed the GH-215_GhostcapSuicideXP branch from 9a35cc0 to d07a836 Compare September 18, 2024 23:36
@nullsystem nullsystem merged commit ab36375 into NeotokyoRebuild:master Sep 19, 2024
@nullsystem nullsystem added this to the v8.1-prealpha milestone Sep 19, 2024
@nullsystem nullsystem deleted the GH-215_GhostcapSuicideXP branch May 7, 2025 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Players can't suicide to prevent a ghostcap

4 participants