Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged

Conversation

nullsystem
Copy link
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
Collaborator

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
Collaborator Author

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

mp/src/game/shared/neo/neo_gamerules.cpp Outdated Show resolved Hide resolved
mp/src/game/shared/neo/neo_gamerules.cpp Outdated Show resolved Hide resolved
mp/src/game/shared/neo/neo_gamerules.cpp Outdated Show resolved Hide resolved
Rainyan
Rainyan previously approved these changes Aug 19, 2024
Copy link
Collaborator

@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
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
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
Copy link
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
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
Collaborator

@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.

mp/src/game/shared/neo/neo_gamerules.cpp Outdated Show resolved Hide resolved
mp/src/game/shared/neo/neo_gamerules.cpp Outdated Show resolved Hide resolved
mp/src/game/shared/neo/neo_gamerules.cpp Outdated Show resolved Hide resolved
mp/src/game/shared/neo/neo_gamerules.cpp Outdated Show resolved Hide resolved
* 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 merged commit ab36375 into NeotokyoRebuild:master Sep 19, 2024
6 checks passed
@nullsystem nullsystem added this to the v8.1-prealpha milestone Sep 19, 2024
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