Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions Client/mods/deathmatch/logic/CClientGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <game/CTaskManager.h>
#include <game/CWanted.h>
#include <game/CWeapon.h>
#include <game/CWeaponInfo.h>
#include <game/CWeaponStatManager.h>
#include <game/CWeather.h>
#include <game/Task.h>
Expand All @@ -38,6 +39,7 @@
#include <game/CVehicleAudioSettingsManager.h>
#include <windowsx.h>
#include "CServerInfo.h"
#include "CClientPed.h"

SString StringZeroPadout(const SString& strInput, uint uiPadoutSize)
{
Expand Down Expand Up @@ -102,10 +104,11 @@ CClientGame::CClientGame(bool bLocalPlay) : m_ServerInfo(new CServerInfo())
m_TargetedPlayerID = INVALID_ELEMENT_ID;
m_pDamageEntity = NULL;
m_DamagerID = INVALID_ELEMENT_ID;
m_ucDamageBodyPiece = 0xFF;
m_ucDamageWeapon = 0xFF;
m_ucDamageBodyPiece = BODYPART_INVALID;
m_ucDamageWeapon = WEAPONTYPE_INVALID;
m_ulDamageTime = 0;
m_bDamageSent = true;
m_serverProcessedDeath = false;
m_bShowNetstat = false;
m_bShowFPS = false;
m_bHudAreaNameDisabled = false;
Expand Down Expand Up @@ -1456,10 +1459,18 @@ void CClientGame::DoPulses()
if (CClientTime::GetTime() - m_ulDamageTime > 2000)
{
m_DamagerID = INVALID_ELEMENT_ID;
m_ucDamageWeapon = 0xFF;
m_ucDamageBodyPiece = 0xFF;
m_ucDamageWeapon = WEAPONTYPE_INVALID;
m_ucDamageBodyPiece = BODYPART_INVALID;
}
// Check if we need to trigger death event
if (!m_pLocalPlayer->IsDeadOnNetwork() && m_pLocalPlayer->GetHealth() == 0.0f)
{
// Only call DoWastedCheck if server hasn't already processed this death
// This prevents duplicate events when server processes death via unified context system
if (!m_serverProcessedDeath) {
DoWastedCheck(m_DamagerID, m_ucDamageWeapon, m_ucDamageBodyPiece);
}
}
DoWastedCheck(m_DamagerID, m_ucDamageWeapon, m_ucDamageBodyPiece);
}

// Game hacks, restore certain variables
Expand Down Expand Up @@ -2002,7 +2013,7 @@ void CClientGame::UpdateFireKey()
{
if (pTargetPed->IsLocalEntity())
{
CStaticFunctionDefinitions::KillPed(*pTargetPed, m_pLocalPlayer, 4 /*WEAPONTYPE_KNIFE*/, 9 /*BODYPART_HEAD*/, true);
CStaticFunctionDefinitions::KillPed(*pTargetPed, m_pLocalPlayer, WEAPONTYPE_KNIFE, BODYPART_HEAD, true);
return;
}

Expand Down Expand Up @@ -4502,7 +4513,9 @@ bool CClientGame::ApplyPedDamageFromGame(eWeaponType weaponUsed, float fDamage,
GetDeathAnim(pDamagedPed, pEvent, animGroup, animID);

// Check if we're dead
DoWastedCheck(damagerID, weaponUsed, hitZone, animGroup, animID);
if (!m_serverProcessedDeath) {
DoWastedCheck(damagerID, weaponUsed, hitZone, animGroup, animID);
}
}

// Allow GTA to kill us if we've fell to our death
Expand Down Expand Up @@ -4570,6 +4583,14 @@ void CClientGame::DeathHandler(CPed* pKilledPedSA, unsigned char ucDeathReason,
if (!pKilledPed)
return;

// For local player in vehicle explosions, set damage data for consistent client events
if (IS_PLAYER(pKilledPed) && pKilledPed->IsLocalPlayer() && ucDeathReason == WEAPONTYPE_EXPLOSION)
{
// Set explosion damage data so DoWastedCheck uses correct parameters
SetExplosionDamageData();
return; // Local player death is handled by DoWastedCheck
}

// Not required for remote players. Local player is handled in DoPulses->DoWastedCheck
if (IS_PLAYER(pKilledPed))
return;
Expand Down Expand Up @@ -5596,11 +5617,11 @@ void CClientGame::DoWastedCheck(ElementID damagerID, unsigned char ucWeapon, uns
Arguments.PushElement(pKiller);
else
Arguments.PushBoolean(false);
if (ucWeapon != 0xFF)
if (ucWeapon != WEAPONTYPE_INVALID)
Arguments.PushNumber(ucWeapon);
else
Arguments.PushBoolean(false);
if (ucBodyPiece != 0xFF)
if (ucBodyPiece != BODYPART_INVALID)
Arguments.PushNumber(ucBodyPiece);
else
Arguments.PushBoolean(false);
Expand Down Expand Up @@ -7150,3 +7171,20 @@ void CClientGame::AudioZoneRadioSwitchHandler(DWORD dwStationID)
g_pGame->GetAudioEngine()->StartRadio(dwStationID);
}
}

//////////////////////////////////////////////////////////////////
//
// CClientGame::TryGetCurrentWeapon
//
// Helper method to get current weapon type with error handling
// Returns actual weapon type or WEAPONTYPE_UNARMED as fallback
//
//////////////////////////////////////////////////////////////////
std::uint8_t CClientGame::TryGetCurrentWeapon(CClientPlayer* player)
{
if (!player)
return WEAPONTYPE_UNARMED;

eWeaponType weaponType = player->GetCurrentWeaponType();
return (weaponType != WEAPONTYPE_INVALID) ? static_cast<std::uint8_t>(weaponType) : WEAPONTYPE_UNARMED;
}
42 changes: 42 additions & 0 deletions Client/mods/deathmatch/logic/CClientGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,44 @@ class CClientGame
AnimationId animId = 15);
void SendPedWastedPacket(CClientPed* Ped, ElementID damagerID = INVALID_ELEMENT_ID, unsigned char ucWeapon = 0xFF, unsigned char ucBodyPiece = 0xFF,
AssocGroupId animGroup = 0, AnimationId animID = 15);

void ClearDamageData() noexcept {
m_DamagerID = INVALID_ELEMENT_ID;
m_ucDamageWeapon = WEAPONTYPE_INVALID;
m_ucDamageBodyPiece = BODYPART_INVALID;
m_ulDamageTime = 0;
m_serverProcessedDeath = true;
}

void ResetDeathProcessingFlag() noexcept {
m_serverProcessedDeath = false;
}

void SetScriptedDeathData() {
auto* localPlayer = GetLocalPlayer();
if (!localPlayer) {
m_DamagerID = INVALID_ELEMENT_ID;
m_ucDamageWeapon = WEAPONTYPE_INVALID;
m_ucDamageBodyPiece = BODYPART_INVALID;
m_ulDamageTime = CClientTime::GetTime();
m_serverProcessedDeath = false;
return;
}

m_DamagerID = INVALID_ELEMENT_ID;
m_ucDamageWeapon = TryGetCurrentWeapon(localPlayer);
m_ucDamageBodyPiece = BODYPART_TORSO;
m_ulDamageTime = CClientTime::GetTime();
m_serverProcessedDeath = false;
}

void SetExplosionDamageData() noexcept {
m_DamagerID = INVALID_ELEMENT_ID;
m_ucDamageWeapon = WEAPONTYPE_EXPLOSION;
m_ucDamageBodyPiece = BODYPART_TORSO;
m_ulDamageTime = CClientTime::GetTime();
m_serverProcessedDeath = false;
}

CClientGUIElement* GetClickedGUIElement() { return m_pClickedGUIElement; }
void SetClickedGUIElement(CClientGUIElement* pElement) { m_pClickedGUIElement = NULL; }
Expand Down Expand Up @@ -616,6 +654,9 @@ class CClientGame
AnimationId DrivebyAnimationHandler(AnimationId animGroup, AssocGroupId animId);
void AudioZoneRadioSwitchHandler(DWORD dwStationID);

// Helper method to get current weapon type with error handling
std::uint8_t TryGetCurrentWeapon(CClientPlayer* player);

static bool StaticProcessMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
bool ProcessMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

Expand Down Expand Up @@ -770,6 +811,7 @@ class CClientGame
unsigned char m_ucDamageBodyPiece;
unsigned long m_ulDamageTime;
bool m_bDamageSent;
bool m_serverProcessedDeath{false}; // Flag to track server-processed deaths

eWeaponSlot m_lastWeaponSlot;
SFixedArray<DWORD, WEAPONSLOT_MAX + 1> m_wasWeaponAmmoInClip;
Expand Down
1 change: 1 addition & 0 deletions Client/mods/deathmatch/logic/CClientPed.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ enum eBodyPart
BODYPART_LEFT_LEG = 7,
BODYPART_RIGHT_LEG = 8,
BODYPART_HEAD = 9,
BODYPART_INVALID = 255,
};

enum eMovementState
Expand Down
11 changes: 11 additions & 0 deletions Client/mods/deathmatch/logic/CPacketHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,11 @@ void CPacketHandler::Packet_PlayerSpawn(NetBitStreamInterface& bitStream)

// He's no longer dead
pPlayer->SetDeadOnNetwork(false);

// Reset death processing flag for new life
if (pPlayer->IsLocalPlayer()) {
g_pClientGame->ResetDeathProcessingFlag();
}

// Reset weapons
pPlayer->RemoveAllWeapons();
Expand Down Expand Up @@ -1216,6 +1221,12 @@ void CPacketHandler::Packet_PlayerWasted(NetBitStreamInterface& bitStream)
}
// Update our sync-time context
pPed->SetSyncTimeContext(ucTimeContext);

// Clear stale damage data if this is the local player
// This prevents DoWastedCheck from firing with stale data when server processes death
if (pPed->IsLocalPlayer()) {
g_pClientGame->ClearDamageData();
}

// To at least here needs to be done on the local player to avoid desync
// Caz: Issue 8148 - Desync when calling spawnPlayer from an event handler remotely triggered from within onClientPlayerWasted
Expand Down
6 changes: 6 additions & 0 deletions Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,12 @@ bool CStaticFunctionDefinitions::SetElementHealth(CClientEntity& Entity, float f
// Grab the model
CClientPed& Ped = static_cast<CClientPed&>(Entity);

// If setting health to 0 for local player, clear stale damage data
// and set proper scripted death parameters for DoWastedCheck
if (fHealth == 0.0f && Ped.IsLocalPlayer() && Ped.GetHealth() > 0.0f) {
g_pClientGame->SetScriptedDeathData();
}

// Set the new health
Ped.SetHealth(Clamp(0.0f, fHealth, Ped.GetMaxHealth()));
return true;
Expand Down
7 changes: 7 additions & 0 deletions Client/mods/deathmatch/logic/rpc/CElementRPCs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,14 @@ void CElementRPCs::SetElementHealth(CClientEntity* pSource, NetBitStreamInterfac
if (pPed->IsHealthLocked())
pPed->LockHealth(fHealth);
else
{
pPed->SetHealth(fHealth);
// If server sets health to 0 for local player, mark as server-processed death
// to prevent DoWastedCheck from firing with stale local damage data
if (fHealth == 0.0f && pPed->IsLocalPlayer()) {
g_pClientGame->ClearDamageData();
}
}
break;
}

Expand Down
4 changes: 3 additions & 1 deletion Client/multiplayer_sa/multiplayer_shotsync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1691,7 +1691,9 @@ void CEventVehicleExplosion_NotifyDeathmatch()
CPed* pPed = pPedClientEntity ? pPedClientEntity->pEntity : nullptr;

if (pPed)
m_pDeathHandler(pPed, 63, 3);
{
m_pDeathHandler(pPed, WEAPONTYPE_EXPLOSION, 3);
}
}
}

Expand Down