// Explicit, local Workbench diagnostic only. Never compiled into game/server builds. #ifdef WORKBENCH modded class ERR_GameMode { protected int m_ERRSmokeStep; protected float m_ERRSmokeDeadline; protected float m_ERRSmokeWait; protected IEntity m_ERRSmokePlayer; protected BaseDoorComponent m_ERRGate; protected ERR_LocationComponent m_ERRSmokeRadio; protected bool m_ERRSmokeDone; protected bool m_ERRTestLostHelicopter; protected bool m_ERRTestPlayerDeath; protected bool m_ERRTestRadioNetwork; protected bool m_ERRTestHQResponse; protected bool m_ERRTestMortar; protected float m_ERRNextProbeLog; protected ref ERR_NativeRadioProbe m_ERRRadioProbe; protected ref array m_ERRCacheFolders = {}; protected override void EOnInit(IEntity owner) { super.EOnInit(owner); string enabled; if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errSmoke", enabled) || enabled != "1") return; string outcome; m_ERRTestLostHelicopter = System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "lost-helicopter"; m_ERRTestRadioNetwork = outcome == "radio-network"; m_ERRTestHQResponse = outcome == "hq-response"; m_ERRTestMortar = outcome == "mortar-inventory"; m_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse || m_ERRTestMortar; m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 150000; OnPhaseChanged().Insert(ERR_SmokePhase); GetGame().GetCallqueue().CallLater(ERR_SmokeTick, 500, true); Print("ERR SMOKE: scripted integration test enabled; gameplay acceptance remains separate"); } protected bool ERR_Check(bool condition, string name) { if (condition) { Print("ERR SMOKE PASS: " + name); return true; } Print("ERR SMOKE FAIL: " + name, LogLevel.ERROR); m_ERRSmokeDone = true; GetGame().GetCallqueue().Remove(ERR_SmokeTick); return false; } protected void ERR_MovePlayer(vector position) { vector transform[4]; m_ERRSmokePlayer.GetWorldTransform(transform); transform[3] = ERR_Actors.Ground(position); ChimeraCharacter.Cast(m_ERRSmokePlayer).Teleport(transform); } protected ERR_UnlockPrisonAction ERR_FindLock(IEntity entity) { BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(entity.FindComponent(BaseActionsManagerComponent)); if (manager) { array actions = {}; manager.GetActionsList(actions); foreach (BaseUserAction action : actions) { ERR_UnlockPrisonAction lock = ERR_UnlockPrisonAction.Cast(action); if (lock) return lock; } } IEntity child = entity.GetChildren(); while (child) { ERR_UnlockPrisonAction lock = ERR_FindLock(child); if (lock) return lock; child = child.GetSibling(); } return null; } protected void ERR_SmokePhase(ERR_Phase previous, ERR_Phase current) { if (m_ERRSmokeDone || !ERR_MissionRules.Terminal(current)) return; if (m_ERRTestPlayerDeath) { if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 91, "death exhausts round life and reaches defeat after spectator grace")) return; } else if (m_ERRTestLostHelicopter) { if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 8, "lost extraction helicopter causes defeat")) return; } else if (!ERR_Check(current == ERR_Phase.VICTORY && m_ERRSmokeStep == 7, "scripted escape reaches victory after physical vehicle occupancy")) return; m_ERRSmokeDone = true; GetGame().GetCallqueue().Remove(ERR_SmokeTick); PrintFormat("ERR SMOKE COMPLETE: terminal=%1 lostHelicopterCase=%2 playerDeathCase=%3", current, m_ERRTestLostHelicopter, m_ERRTestPlayerDeath); GetGame().GetCallqueue().CallLater(ERR_EndScreenshot, 4000); } protected bool ERR_CheckRoster() { ERR_PlayerRoster roster = new ERR_PlayerRoster(); UUID first = "00e77100-0000-0000-0000-000000000101"; UUID second = "00e77100-0000-0000-0000-000000000202"; if (!ERR_Check(roster.Bind(101, first) && roster.CanEnter(101, ERR_Phase.PRISON), "new identity may enter prison")) return false; roster.Spawned(101, m_ERRSmokePlayer); if (!ERR_Check(!roster.CanEnter(101, ERR_Phase.PRISON), "spawn consumes identity life")) return false; roster.Disconnect(101); if (!ERR_Check(roster.HasSurvivor() && !roster.Get(101), "disconnect retains living actor and clears connection")) return false; if (!ERR_Check(roster.Bind(202, first) && !roster.CanEnter(202, ERR_Phase.PRISON), "reconnected identity retains spent life")) return false; if (!ERR_Check(!roster.Bind(303, first), "duplicate active identity rejected")) return false; if (!ERR_Check(roster.Bind(101, second) && roster.CanEnter(101, ERR_Phase.RADIO_DEFENCE) && !roster.CanEnter(101, ERR_Phase.EXTRACTION), "recycled connection receives independent life and extraction admission policy")) return false; return true; } protected bool ERR_CheckCompanionCommands() { array replies = {}; array commands = {"hq interval show", "hq squads show", "hq radius show", "intel carrying show", "intel reveal show"}; array keys = {"reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent"}; for (int i = 0; i < commands.Count(); i++) { Admin().Execute(this, true, commands[i], replies); if (!ERR_Check(replies.Count() == 1 && replies[0] == keys[i] + "=" + Settings().Get(keys[i]).ToString(), "companion command retained: " + commands[i])) return false; } Admin().Execute(this, false, "hq log", replies); if (!ERR_Check(replies.Count() == 1 && replies[0] == "Administrator access required.", "companion journal is behind Core admin authorization")) return false; Admin().Execute(this, true, "hq show", replies); ERR_HQ hq = ERR_HQ.Cast(Command()); if (!ERR_Check(hq && replies.Count() == 1 && replies[0] == hq.Status(), "companion HQ status uses the shared command service")) return false; return true; } protected void ERR_PrisonScreenshot() { System.MakeScreenshot("$logs:err-prison"); } protected void ERR_EndScreenshot() { System.MakeScreenshot("$logs:err-result"); } protected void ERR_SmokeTick() { float now = GetWorld().GetWorldTime(); if (m_ERRSmokeDone) return; if (now > m_ERRSmokeDeadline) { ERR_Check(false, "timeout at step " + m_ERRSmokeStep.ToString()); return; } if (now < m_ERRSmokeWait) return; if (m_ERRSmokeStep == 0) { array players = {}; ERR_Actors.Players(players, true); if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return; m_ERRSmokePlayer = players[0]; if (!ERR_CheckRoster() || !ERR_NativeContractsProbe.Run(this, m_ERRSmokePlayer) || !ERR_CheckCompanionCommands()) return; if (m_ERRTestMortar) { DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false); m_ERRSmokeStep = 120; return; } if (m_ERRTestHQResponse) { DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false); m_ERRSmokeStep = 110; return; } if (m_ERRTestRadioNetwork) { m_ERRRadioProbe = new ERR_NativeRadioProbe(); DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false); m_ERRSmokeStep = 100; return; } if (m_ERRTestPlayerDeath) { m_ERRSmokeStep = 9; SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent)).Kill(Instigator.CreateInstigator(m_ERRSmokePlayer)); m_ERRSmokeWait = now + 4000; return; } DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false); if (!ERR_Check(vector.DistanceXZ(m_ERRSmokePlayer.GetOrigin(), PrisonPosition()) < 5, "initial prisoner position")) return; foreach (ERR_LocationComponent location : m_Locations) if (location.Kind() == ERR_LocationKind.RADIO) m_ERRSmokeRadio = location; if (!ERR_Check(m_ERRSmokeRadio && ERR_Actors.Alive(m_ExtractionVehicle), "radio and extraction vehicle exist")) return; ERR_UnlockPrisonAction lock; foreach (IEntity tracked : m_OwnedEntities) { if (tracked) lock = ERR_FindLock(tracked); if (lock) break; } if (!ERR_Check(lock != null, "authored gate exposes lock action")) return; ERR_MovePlayer(lock.GetOwner().GetOrigin() + "1 0 1"); vector gateLow, gateHigh; lock.GetOwner().GetWorldBounds(gateLow, gateHigh); PrintFormat("ERR GEOMETRY: gate low=%1 high=%2", gateLow, gateHigh); IEntity wall = lock.GetOwner().GetParent().GetChildren(); while (wall) { vector wallLow, wallHigh; wall.GetWorldBounds(wallLow, wallHigh); PrintFormat("ERR GEOMETRY: child origin=%1 low=%2 high=%3", wall.GetOrigin(), wallLow, wallHigh); wall = wall.GetSibling(); } GetGame().GetCallqueue().CallLater(ERR_PrisonScreenshot, 2000); if (!ERR_Check(lock.CanBePerformedScript(m_ERRSmokePlayer), "lock permits prisoner")) return; m_ERRGate = BaseDoorComponent.Cast(lock.GetOwner().FindComponent(BaseDoorComponent)); lock.PerformAction(lock.GetOwner(), m_ERRSmokePlayer); if (!ERR_Check(Phase() == ERR_Phase.RADIO_SEARCH, "lock action advances mission")) return; foreach (IEntity tracked : m_OwnedEntities) { if (tracked && tracked.GetPrefabData() && tracked.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRCacheFolders.Insert(tracked); } if (!ERR_Check(m_ERRCacheFolders.Count() == 2, "two guaranteed physical cache documents")) return; if (!ERR_Check(m_Settings.Set("aiIntelChancePercent", 0), "test does not rely on random intel")) return; if (!ERR_Check(m_Settings.Set("radioDefenceSeconds", 30), "isolated test profile defence duration")) return; m_ERRSmokeStep = 1; } else if (m_ERRSmokeStep == 1 || m_ERRSmokeStep == 2) { if (m_ERRSmokeStep == 2 && !ERR_Check(m_ERRGate && m_ERRGate.IsOpen() && m_ERRGate.CanCharacterPass(0.6), "unlocked gate physically permits passage")) return; ERR_MovePlayer(Sites().Find("everon-supply-1").Container.GetOrigin() + "0 0 2"); SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_InventoryStorageManagerComponent)); InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(Sites().Find("everon-supply-1").Container.FindComponent(InventoryStorageManagerComponent)); BaseInventoryStorageComponent destination = inventory.FindStorageForItem(m_ERRCacheFolders[m_ERRSmokeStep - 1]); if (!ERR_Check(destination && source.TryMoveItemToStorage(m_ERRCacheFolders[m_ERRSmokeStep - 1], destination), "authored cache document recovered into inventory")) return; m_ERRSmokeStep++; m_ERRSmokeWait = now + 2500; } else if (m_ERRSmokeStep == 3) { if (!ERR_Check(m_ERRSmokeRadio.Knowledge() == 2, "carried intel identifies radio through intel service")) return; if (!ERR_Check(!TuneRadio(m_ERRSmokePlayer, m_ERRSmokeRadio), "radio rejects distant player")) return; ERR_MovePlayer(m_ERRSmokeRadio.Position() + "2 0 0"); m_ERRSmokeStep = 30; m_ERRSmokeWait = now + 1000; } else if (m_ERRSmokeStep == 30) { PrintFormat("ERR SMOKE: radio=%1 player=%2", m_ERRSmokeRadio.Position(), m_ERRSmokePlayer.GetOrigin()); if (!ERR_Check(TuneRadio(m_ERRSmokePlayer, m_ERRSmokeRadio), "identified radio starts defence")) return; m_ERRSmokeStep = 4; m_ERRSmokeWait = now + 3000; } else if (m_ERRSmokeStep == 4) { if (!ERR_Check(m_DefenceSince >= 0, "occupied defence timer running")) return; ERR_MovePlayer(m_ERRSmokeRadio.Position() + "90 0 0"); m_ERRSmokeStep = 5; m_ERRSmokeWait = now + 2500; } else if (m_ERRSmokeStep == 5) { if (!ERR_Check(m_DefenceSince < 0 && Phase() == ERR_Phase.RADIO_DEFENCE, "leaving area resets defence")) return; ERR_MovePlayer(m_ERRSmokeRadio.Position() + "2 0 0"); m_ERRSmokeStep = 6; m_ERRSmokeWait = now + 33000; } else if (m_ERRSmokeStep == 6) { if (!ERR_Check(Phase() == ERR_Phase.EXTRACTION, "continuous occupied defence completes")) return; if (m_ERRTestLostHelicopter) { m_ERRSmokeStep = 8; ERR_Actors.DeleteSpawned(m_ExtractionVehicle); return; } CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_ERRSmokePlayer.FindComponent(CompartmentAccessComponent)); BaseCompartmentSlot slot = access.FindFreeCompartment(m_ExtractionVehicle, ECompartmentType.PILOT); if (!ERR_Check(slot && access.GetInVehicle(m_ExtractionVehicle, slot, true, -1, ECloseDoorAfterActions.INVALID, false), "boarding extraction vehicle")) return; m_ERRSmokeStep = 7; m_ERRSmokeWait = now + 3000; } else if (m_ERRSmokeStep == 120) { ERR_MortarBattery battery = ERR_MortarSystem.Get().ERR_ProbeBattery(); if (!battery || !battery.StartupActivated) return; if (!ERR_Check(battery.Operational() && battery.Group.GetAgentsCount() == 4, "authored physical mortar and four-person crew initialize")) return; if (!ERR_Check(ERR_HQRadio.Capability(battery.RadioOperator) == 2, "mortar has a dedicated physical manpack operator")) return; if (!ERR_Check(battery.CountShells() == 27, "mortar starts with exactly 27 physical HE shells")) return; array agents = {}; battery.Group.GetAgents(agents); foreach (AIAgent agent : agents) { InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(agent.GetControlledEntity().FindComponent(InventoryStorageManagerComponent)); array items = {}; inventory.GetItems(items); foreach (IEntity item : items) if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL) SCR_EntityHelper.DeleteEntityAndChildren(item); } m_ERRSmokeStep = 121; m_ERRSmokeWait = now + 15000; } else if (m_ERRSmokeStep == 121) { ERR_MortarBattery battery = ERR_MortarSystem.Get().ERR_ProbeBattery(); if (!ERR_Check(battery.CountShells() == 0 && battery.TotalShots == 0, "depleted mortar inventory stays empty without fabricated ammunition")) return; ERR_Actors.DeleteSpawned(battery.Gun); ERR_Actors.DeleteSpawned(battery.Group); m_ERRSmokeStep = 122; m_ERRSmokeWait = now + 12000; } else if (m_ERRSmokeStep == 122) { ERR_MortarBattery battery = ERR_MortarSystem.Get().ERR_ProbeBattery(); if (!ERR_Check(battery && !battery.Gun && !battery.Group && !battery.Operational(), "lost mortar and crew remain lost without replacement spawning")) return; m_ERRSmokeStep = 9; SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent)); damage.EnableDamageHandling(true); damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer)); m_ERRSmokeWait = now + 4000; } else if (m_ERRSmokeStep == 110) { ERR_HQ hq = ERR_HQ.Cast(Command()); if (!hq || !hq.ERR_ProbeSquadsReady()) return; ERR_MovePlayer(hq.ERR_ProbeResponseOrigin()); m_ERRSmokeStep = 111; m_ERRSmokeWait = now + 1000; } else if (m_ERRSmokeStep == 111) { ERR_HQ hq = ERR_HQ.Cast(Command()); if (now >= m_ERRNextProbeLog) { Print("ERR HQ PROBE: " + hq.ERR_ProbePerception()); m_ERRNextProbeLog = now + 10000; } if (!hq.ERR_ProbeDeliveredResponse()) return; if (!ERR_Check(true, "native visual contact reaches HQ and orders a different existing squad")) return; if (!ERR_Check(hq.ERR_ProbeUniqueAssignments(), "HQ uses distinct waypoints within the existing force and order limits")) return; m_ERRSmokeStep = 9; SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent)); damage.EnableDamageHandling(true); damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer)); m_ERRSmokeWait = now + 4000; } else if (m_ERRSmokeStep == 100) { int result = m_ERRRadioProbe.Tick(ERR_HQ.Cast(Command()), now); if (result == 0) return; if (!ERR_Check(result == 1, "native radio delivery and interruption sequence")) return; m_ERRSmokeStep = 9; SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent)); damage.EnableDamageHandling(true); damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer)); m_ERRSmokeWait = now + 4000; } else if (m_ERRSmokeStep == 9) { SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController()); if (!ERR_Check(!ERR_Actors.Alive(m_ERRSmokePlayer), "native damage kills prisoner")) return; if (!ERR_Check(controller && controller.ERR_ObserverState() == ERR_ObserverReason.SPENT_LIFE, "death selects spectator without another life")) return; if (!ERR_Check(controller.ERR_HasObserverCamera() && SCR_ManualCamera.Cast(GetGame().GetCameraManager().CurrentCamera()), "spectator camera activates after native death")) return; m_ERRSmokeStep = 91; } else if (m_ERRSmokeStep == 7) { if (!ERR_Check(CompartmentAccessComponent.GetVehicleIn(m_ERRSmokePlayer) == m_ExtractionVehicle, "physical extraction occupancy")) return; // Controlled transport tests the boundary detector, not piloting skill. m_ExtractionVehicle.SetOrigin(m_EscapeCenter + Vector(m_EscapeRadius + 100, 300, 0)); m_ERRSmokeWait = now + 5000; } } void ~ERR_GameMode() { if (GetGame()) { GetGame().GetCallqueue().Remove(ERR_SmokeTick); GetGame().GetCallqueue().Remove(ERR_PrisonScreenshot); GetGame().GetCallqueue().Remove(ERR_EndScreenshot); } } } // Diagnose inherited teardown failures without changing their behavior. modded class SCR_BaseItemSupportStationComponent { override void OnDelete(IEntity owner) { if (owner && !m_EntityCatalogManager) PrintFormat("ERR TEARDOWN: uninitialized item support station on %1", owner); super.OnDelete(owner); } } #endif