modded class ERR_GameMode { protected int m_ERRStatsStage; protected IEntity m_ERRStatsActor; protected IEntity m_ERRStatsFriend; protected UUID m_ERRStatsAccount; protected SCR_CharacterDamageManagerComponent m_ERRStatsDamage; protected float m_ERRStatsWait; protected override void EOnInit(IEntity owner) { super.EOnInit(owner); if (Replication.IsServer() && !SCR_Global.IsEditMode()) GetGame().GetCallqueue().CallLater(ERR_StatsPoll, 500, true); } protected bool ERR_StatsCheck(bool ok, string detail) { if (ok) { Print("ERR SMOKE PASS: statistics " + detail); return true; } Print("ERR SMOKE FAIL: statistics " + detail, LogLevel.ERROR); GetGame().GetCallqueue().Remove(ERR_StatsPoll); return false; } protected void ERR_StatsPoll() { if (!PreparationError().IsEmpty()) { ERR_StatsCheck(false, PreparationError()); return; } if (GetWorld().GetWorldTime() < m_ERRStatsWait) return; if (m_ERRStatsStage == 0) { if (!Active()) return; SCR_PlayerController pc = SCR_PlayerController.Cast(GetGame().GetPlayerController()); if (!pc || !pc.GetControlledEntity() || !Players().CurrentActor(pc.GetControlledEntity())) return; m_ERRStatsActor = pc.GetControlledEntity(); m_ERRStatsAccount = Players().Get(pc.GetPlayerId()).AccountId; m_ERRStatsDamage = SCR_CharacterDamageManagerComponent.Cast(m_ERRStatsActor.FindComponent(SCR_CharacterDamageManagerComponent)); ERR_SiteRuntime site = Sites().Find("everon-prison-1"); array agents = {}; site.Group.GetAgents(agents); IEntity enemy = agents[0].GetControlledEntity(); SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(enemy.FindComponent(SCR_CharacterDamageManagerComponent)); damage.EnableDamageHandling(true); damage.Kill(Instigator.CreateInstigator(m_ERRStatsActor)); m_ERRStatsDamage.EnableDamageHandling(true); m_ERRStatsDamage.ForceUnconsciousness(); m_ERRStatsStage = 1; m_ERRStatsWait = GetWorld().GetWorldTime() + 2000; return; } ERR_RoundPlayerStats row = ERR_Statistics().Find(m_ERRStatsAccount); if (!ERR_StatsCheck(row != null, "participant record exists")) return; if (m_ERRStatsStage == 1) { PrintFormat("ERR STATS VALUES: kills=%1 friendly=%2 deaths=%3 downed=%4 recoveries=%5", row.Kills, row.FriendlyKills, row.Deaths, row.Downed, row.Recoveries); if (!ERR_StatsCheck(row.Kills == 1 && row.Deaths == 0 && row.Downed == 1, "native enemy kill and unconscious callback counted separately")) return; m_ERRStatsDamage.FullHeal(); m_ERRStatsStage = 2; m_ERRStatsWait = GetWorld().GetWorldTime() + 2000; return; } if (m_ERRStatsStage == 2) { if (row.Recoveries < 1) return; if (!ERR_StatsCheck(row.Recoveries == 1, "native recovery counted once")) return; m_ERRStatsFriend = ERR_Actors.Spawn("{5436629450D8387A}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_SL.et", ERR_Actors.Ground(m_ERRStatsActor.GetOrigin() + "10 0 10")); if (!ERR_StatsCheck(m_ERRStatsFriend != null, "friendly NPC created")) return; SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRStatsFriend.FindComponent(SCR_CharacterDamageManagerComponent)); damage.EnableDamageHandling(true); damage.Kill(Instigator.CreateInstigator(m_ERRStatsActor)); m_ERRStatsStage = 3; m_ERRStatsWait = GetWorld().GetWorldTime() + 1000; return; } if (m_ERRStatsStage == 3) { if (!ERR_StatsCheck(row.FriendlyKills == 1 && row.Kills == 1, "friendly kill separated from normal kills")) return; ERR_Statistics().Killed(m_ERRStatsFriend, m_ERRStatsActor, Instigator.CreateInstigator(m_ERRStatsActor)); if (!ERR_StatsCheck(row.FriendlyKills == 1, "duplicate native callback does not double count")) return; if (!ERR_StatsCheck(m_ERRStatsDamage.ERR_ForceFatalLossForTest(Instigator.CreateInstigator(m_ERRStatsActor), m_ERRStatsActor), "fatal player fixture requested")) return; m_ERRStatsStage = 4; m_ERRStatsWait = GetWorld().GetWorldTime() + 1000; return; } if (m_ERRStatsStage == 4) { if (!ERR_StatsCheck(row.Deaths == 1 && row.Kills == 1 && row.FriendlyKills == 1, "actual fatal player loss counted once without self-kill credit")) return; if (!ERR_StatsCheck(Transition(ERR_Phase.DEFEAT), "normal mission end requested")) return; m_ERRStatsStage = 5; m_ERRStatsWait = GetWorld().GetWorldTime() + 4000; return; } if (m_ERRStatsStage == 5) { if (!ERR_StatsCheck(!ERR_RoundSummary().IsEmpty() && ERR_RoundSummary().Contains("Death") && ERR_RoundSummary().Contains("Dead"), "frozen end summary contains participant death outcome")) return; MenuBase menu = GetGame().GetMenuManager().GetTopMenu(); if (!menu) return; TextWidget text = TextWidget.Cast(menu.GetRootWidget().FindAnyWidget("RoundStatistics")); if (!ERR_StatsCheck(text && text.GetText() == ERR_RoundSummary(), "real native game-over screen displays replicated summary")) return; float x, y, width, height; text.GetScreenPos(x, y); text.GetScreenSize(width, height); PrintFormat("ERR STATS UI READY: text x=%1 y=%2 width=%3 height=%4 visible=%5", x, y, width, height, text.IsVisible()); System.MakeScreenshot("$logs:err-round-statistics"); m_ERRStatsStage = 6; m_ERRStatsWait = GetWorld().GetWorldTime() + 15000; return; } Print("ERR SMOKE COMPLETE: round statistics native death/knockdown/recovery/friendly-kill and end-screen integration"); GetGame().GetCallqueue().Remove(ERR_StatsPoll); } }