Files
EscapistsReintroduced/addons/chat/Scripts/Game/ERR/ERR_NativeSmoke.c
T

350 lines
16 KiB
C

// 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<IEntity> 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<BaseUserAction> 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 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<IEntity> players = {};
ERR_Actors.Players(players, true);
if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return;
m_ERRSmokePlayer = players[0];
if (!ERR_CheckRoster()) 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_FactionCatalog.IntelFolder()) 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;
SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_InventoryStorageManagerComponent));
if (!ERR_Check(inventory && inventory.TryInsertItem(m_ERRCacheFolders[m_ERRSmokeStep - 1]), "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<AIAgent> agents = {};
battery.Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(agent.GetControlledEntity().FindComponent(InventoryStorageManagerComponent));
array<IEntity> 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