--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c +++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c @@ -1,5 +1,36 @@ // Explicit, local Workbench diagnostic only. Never compiled into game/server builds. #ifdef WORKBENCH +class ERR_DeathDiagnostic +{ + static bool Enabled() + { + string smoke; + string outcome; + if (!System.GetCLIParam("errSmoke", smoke) || smoke != "1") return false; + return System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "player-death"; + } + static void Event(string stage) + { + if (Enabled()) PrintFormat("ERR DEATH DIAGNOSTIC: %1", stage); + } + static void Actor(string stage, IEntity actor) + { + if (!Enabled()) return; + bool revivable; + bool unconscious; + SCR_CharacterDamageManagerComponent medical; + CharacterControllerComponent controller; + if (actor) + { + medical = SCR_CharacterDamageManagerComponent.Cast(actor.FindComponent(SCR_CharacterDamageManagerComponent)); + controller = CharacterControllerComponent.Cast(actor.FindComponent(CharacterControllerComponent)); + } + if (medical) revivable = medical.ERR_IsRevivablePlayer(); + if (controller) unconscious = controller.IsUnconscious(); + PrintFormat("ERR DEATH DIAGNOSTIC: %1 actor=%2 alive=%3 revivable=%4 unconscious=%5", stage, actor, ERR_Actors.Alive(actor), revivable, unconscious); + } +} + modded class ERR_GameMode { protected bool m_ERRTestPatrolHQ; @@ -756,7 +787,10 @@ if (m_ERRTestPlayerDeath) { m_ERRSmokeStep = 9; + ERR_DeathDiagnostic.Actor("fixture before Kill", m_ERRSmokePlayer); SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent)).Kill(Instigator.CreateInstigator(m_ERRSmokePlayer)); + ERR_DeathDiagnostic.Actor("fixture after Kill", m_ERRSmokePlayer); + if (ERR_DeathDiagnostic.Enabled()) GetGame().GetCallqueue().CallLater(ERR_DeathSample, 250, false); m_ERRSmokeWait = now + 4000; return; } @@ -1048,10 +1082,15 @@ m_ERRSmokeWait = now + 5000; } } + protected void ERR_DeathSample() + { + ERR_DeathDiagnostic.Actor("fixture queued 250ms sample", m_ERRSmokePlayer); + } void ~ERR_GameMode() { if (GetGame()) { + GetGame().GetCallqueue().Remove(ERR_DeathSample); GetGame().GetCallqueue().Remove(ERR_SmokeTick); GetGame().GetCallqueue().Remove(ERR_PrisonScreenshot); GetGame().GetCallqueue().Remove(ERR_EndScreenshot); --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c @@ -27,6 +27,9 @@ // the native damage pipeline. The lethal damage itself is clamped synchronously. void ERR_RequestKnockout() { +#ifdef WORKBENCH + ERR_DeathDiagnostic.Actor("RequestKnockout entry", GetOwner()); +#endif if (!Replication.IsServer() || !m_ERRRevivablePlayer || m_ERRKnockoutPending) return; CharacterControllerComponent controller = CharacterControllerComponent.Cast(GetOwner().FindComponent(CharacterControllerComponent)); if (controller && controller.IsUnconscious()) return; @@ -35,13 +38,22 @@ } protected void ERR_ApplyKnockout() { +#ifdef WORKBENCH + ERR_DeathDiagnostic.Actor("ApplyKnockout entry", GetOwner()); +#endif m_ERRKnockoutPending = false; if (!m_ERRRevivablePlayer || !ERR_Actors.Alive(GetOwner())) return; ForceUnconsciousness(); +#ifdef WORKBENCH + ERR_DeathDiagnostic.Actor("ApplyKnockout after ForceUnconsciousness", GetOwner()); +#endif ERR_HQ.Event("PLAYER CASUALTY", "Lethal injury prevented; prisoner remains alive and can receive medical treatment."); } override void Kill(notnull Instigator instigator) { +#ifdef WORKBENCH + ERR_DeathDiagnostic.Actor("Kill override entry", GetOwner()); +#endif if (!m_ERRRevivablePlayer) { super.Kill(instigator); return; } ERR_RequestKnockout(); } --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnLogic.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnLogic.c @@ -318,12 +318,30 @@ } protected void ObserveAfterLoss(int playerId, UUID identity, SCR_PlayerController expectedController) { - if (!expectedController || !m_Identities.Contains(playerId) || m_Identities.Get(playerId) != identity) return; - if (GetGame().GetPlayerManager().GetPlayerController(playerId) != expectedController || ERR_Actors.Alive(expectedController.GetControlledEntity())) return; +#ifdef WORKBENCH + ERR_DeathDiagnostic.Event("ObserveAfterLoss entry"); +#endif + if (!expectedController || !m_Identities.Contains(playerId) || m_Identities.Get(playerId) != identity) + { +#ifdef WORKBENCH + ERR_DeathDiagnostic.Event("ObserveAfterLoss rejected controller/account"); +#endif + return; + } + if (GetGame().GetPlayerManager().GetPlayerController(playerId) != expectedController || ERR_Actors.Alive(expectedController.GetControlledEntity())) + { +#ifdef WORKBENCH + ERR_DeathDiagnostic.Event("ObserveAfterLoss rejected controller/body"); +#endif + return; + } ERR_GameMode mode = ERR_GameMode.Get(); ERR_PlayerLife life; if (mode) life = mode.Players().Get(playerId); if (life && life.UsedLife) Observe(playerId, ERR_ObserverReason.SPENT_LIFE); +#ifdef WORKBENCH + ERR_DeathDiagnostic.Event("ObserveAfterLoss completed"); +#endif } override void OnPlayerSpawned_S(int playerId, IEntity entity) { --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c @@ -568,6 +568,9 @@ // Transition(). Close the successor before vanilla publishes/restarts the round. protected override void OnGameModeEnd(SCR_GameModeEndData endData) { +#ifdef WORKBENCH + ERR_DeathDiagnostic.Event("OnGameModeEnd entry"); +#endif if (m_NativeEndHandled) return; m_NativeEndHandled = true; StopLocalViewDistance(); @@ -619,6 +622,9 @@ void ~ERR_GameMode() { +#ifdef WORKBENCH + ERR_DeathDiagnostic.Event("GameMode destructor entry"); +#endif if (GetGame()) { GetGame().GetCallqueue().Remove(StartScenario);