Skip to content

Commit e804d4d

Browse files
committed
Fix instant-respawn flows leaving player dead/stuck after setElementHealth(player, 0)
1 parent 75a1e94 commit e804d4d

2 files changed

Lines changed: 34 additions & 5 deletions

File tree

Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,25 @@ bool CStaticFunctionDefinitions::SetElementHealth(CElement* pElement, float fHea
17881788
if (pPed->IsDead() && fHealth > 0.0f)
17891789
pPed->SetIsDead(false);
17901790
else if (fHealth <= 0.0f && !pPed->IsDead())
1791-
KillPed(pElement, nullptr, 0xFF, 0xFF, false);
1791+
{
1792+
// Preserve #4482 (onPlayerWasted fires server-side from setElementHealth(p, 0)) without
1793+
// regressing instant-respawn flows (e.g. race respawntime=0). Two things matter:
1794+
// 1) Skip the WASTED broadcast to the dying player so the originator isn't forced into
1795+
// CClientPed::Kill() / TaskComplexDie - that traps the camera in GTA's death cam
1796+
// because the immediately-following PLAYER_SPAWN can't cleanly cancel the transition.
1797+
// 2) Send SET_ELEMENT_HEALTH=0 BEFORE KillPed and return early. KillPed fires
1798+
// onPlayerWasted, whose handlers commonly call spawnPlayer; if the trailing health
1799+
// RPC ran after that PLAYER_SPAWN, all clients would reset the freshly-spawned
1800+
// player back to 0 health.
1801+
CBitStream BitStream;
1802+
BitStream.pBitStream->Write(fHealth);
1803+
BitStream.pBitStream->Write(pElement->GenerateSyncTimeContext());
1804+
m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pElement, SET_ELEMENT_HEALTH, *BitStream.pBitStream));
1805+
1806+
CPlayer* pSkipBroadcastPlayer = IS_PLAYER(pPed) ? static_cast<CPlayer*>(pPed) : nullptr;
1807+
KillPed(pElement, nullptr, 0xFF, 0xFF, false, pSkipBroadcastPlayer);
1808+
return true;
1809+
}
17921810

17931811
break;
17941812
}
@@ -3808,9 +3826,13 @@ bool CStaticFunctionDefinitions::SetPedArmor(CElement* pElement, float armor)
38083826
return true;
38093827
}
38103828

3811-
bool CStaticFunctionDefinitions::KillPed(CElement* pElement, CElement* pKiller, unsigned char ucKillerWeapon, unsigned char ucBodyPart, bool bStealth)
3829+
bool CStaticFunctionDefinitions::KillPed(CElement* pElement, CElement* pKiller, unsigned char ucKillerWeapon, unsigned char ucBodyPart, bool bStealth,
3830+
CPlayer* pSkipBroadcastPlayer)
38123831
{
38133832
assert(pElement);
3833+
// Note: pSkipBroadcastPlayer is intentionally NOT propagated through RUN_CHILDREN. It only ever applies
3834+
// to a single, specific player in the SetElementHealth auto-kill path, not to recursive kills on element
3835+
// hierarchies (which historically broadcast the wasted packet to everyone).
38143836
RUN_CHILDREN(KillPed(*iter, pKiller, ucKillerWeapon, ucBodyPart))
38153837

38163838
if (IS_PED(pElement))
@@ -3868,9 +3890,11 @@ bool CStaticFunctionDefinitions::KillPed(CElement* pElement, CElement* pKiller,
38683890
// TODO: change to onPedWasted
38693891
if (IS_PLAYER(pPed))
38703892
{
3871-
// Tell everyone to kill this player
3893+
// Tell everyone to kill this player. pSkipBroadcastPlayer (when set) is excluded so that
3894+
// server-initiated kills via SetElementHealth do not push the dying player into a forced
3895+
// client-side TaskComplexDie. See header comment on KillPed for the full rationale.
38723896
CPlayerWastedPacket WastedPacket(pPed, pKiller, ucKillerWeapon, ucBodyPart, bStealth);
3873-
m_pPlayerManager->BroadcastOnlyJoined(WastedPacket);
3897+
m_pPlayerManager->BroadcastOnlyJoined(WastedPacket, pSkipBroadcastPlayer);
38743898
pPed->CallEvent("onPlayerWasted", Arguments);
38753899
}
38763900
else

Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,13 @@ class CStaticFunctionDefinitions
194194

195195
// Ped set funcs
196196
static bool SetPedArmor(CElement* pElement, float armor);
197+
// pSkipBroadcastPlayer: optional player excluded from the CPlayerWastedPacket / CPedWastedPacket broadcast.
198+
// Used by SetElementHealth(player, 0) so the dying player is killed server-side and other clients are
199+
// notified, but the originating client is NOT pushed into a forced TaskComplexDie via the wasted packet.
200+
// That keeps GTA's natural death path on the originator, which is required for instant-respawn flows
201+
// (e.g. race maps with respawntime=0) to cancel cleanly. See PR #4486 / regression on race instant respawn.
197202
static bool KillPed(CElement* pElement, CElement* pKiller = NULL, unsigned char ucKillerWeapon = 0xFF, unsigned char ucBodyPart = 0xFF,
198-
bool bStealth = false);
203+
bool bStealth = false, CPlayer* pSkipBroadcastPlayer = nullptr);
199204
static bool SetPedRotation(CElement* pElement, float fRotation, bool bNewWay);
200205
static bool SetPedStat(CElement* pElement, unsigned short usStat, float fValue);
201206
static bool AddPedClothes(CElement* pElement, const char* szTexture, const char* szModel, unsigned char ucType);

0 commit comments

Comments
 (0)