Build independent Everon successor foundation and native regressions
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
class ERR_CommandArguments
|
||||
{
|
||||
static bool Integer(string text, out int value)
|
||||
{
|
||||
if (text.IsEmpty() || text.Length() > 6) return false;
|
||||
for (int i = 0; i < text.Length(); i++)
|
||||
if ("0123456789".IndexOf(text.Substring(i, 1)) < 0) return false;
|
||||
value = text.ToInt();
|
||||
return true;
|
||||
}
|
||||
static string Setting(string section, string option)
|
||||
{
|
||||
if (section == "hq")
|
||||
{
|
||||
if (option == "interval") return "reportIntervalSeconds";
|
||||
if (option == "squads") return "supportSquadLimit";
|
||||
if (option == "radius") return "responseRadiusMetres";
|
||||
}
|
||||
if (section == "intel")
|
||||
{
|
||||
if (option == "carrying") return "aiIntelChancePercent";
|
||||
if (option == "reveal") return "intelRevealChancePercent";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_PlayerController
|
||||
{
|
||||
protected ChatCommandInvoker m_ERRCommand;
|
||||
protected float m_ERRLastRequest = -1000;
|
||||
protected int m_ERRRegisterAttempts;
|
||||
|
||||
override void OnControlledEntityChanged(IEntity from, IEntity to)
|
||||
{
|
||||
super.OnControlledEntityChanged(from, to);
|
||||
if (this == GetGame().GetPlayerController() && ERR_GameMode.Get()) ERR_Register();
|
||||
}
|
||||
|
||||
protected void ERR_Register()
|
||||
{
|
||||
if (m_ERRCommand || m_ERRRegisterAttempts++ > 40) return;
|
||||
SCR_ChatPanelManager manager = SCR_ChatPanelManager.GetInstance();
|
||||
if (!manager) { GetGame().GetCallqueue().CallLater(ERR_Register, 250, false); return; }
|
||||
m_ERRCommand = manager.GetCommandInvoker("esc");
|
||||
if (m_ERRCommand) m_ERRCommand.Insert(ERR_OnCommand);
|
||||
}
|
||||
protected override void ERR_OnObserverMonitorStarted()
|
||||
{
|
||||
super.ERR_OnObserverMonitorStarted();
|
||||
ERR_Register();
|
||||
}
|
||||
protected void ERR_OnCommand(SCR_ChatPanel panel, string arguments)
|
||||
{
|
||||
string localCommand = arguments.Trim(); localCommand.ToLower();
|
||||
if (localCommand == "spectate next" || localCommand == "spectate previous")
|
||||
{
|
||||
int direction = 1;
|
||||
if (localCommand == "spectate previous") direction = -1;
|
||||
ERR_CycleObserver(direction);
|
||||
return;
|
||||
}
|
||||
Rpc(ERR_Request, arguments);
|
||||
}
|
||||
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
|
||||
protected void ERR_Request(string arguments)
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!Replication.IsServer() || !mode || !SCR_Global.IsAdmin(GetPlayerId())) return;
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
if (arguments.Length() > 160 || now - m_ERRLastRequest < 250) return;
|
||||
m_ERRLastRequest = now;
|
||||
arguments = arguments.Trim(); arguments.ToLower();
|
||||
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
|
||||
if (arguments == "hq show" || arguments == "status")
|
||||
{
|
||||
string status = "HQ unavailable";
|
||||
if (hq) status = hq.Status();
|
||||
ERR_Reply(string.Format("Mission phase=%1; %2", mode.Phase(), status)); return;
|
||||
}
|
||||
if (arguments == "hq log")
|
||||
{
|
||||
if (!hq) { ERR_Reply("HQ unavailable"); return; }
|
||||
array<string> lines = {}; int cursor;
|
||||
hq.Journal().Read(Math.Max(0, hq.Journal().Sequence() - 8), lines, cursor);
|
||||
foreach (string line : lines) ERR_Reply(line);
|
||||
return;
|
||||
}
|
||||
array<string> parts = {};
|
||||
arguments.Split(" ", parts, true);
|
||||
if (parts.Count() == 3 || parts.Count() == 4)
|
||||
{
|
||||
string key = ERR_CommandArguments.Setting(parts[0], parts[1]);
|
||||
if (!key.IsEmpty())
|
||||
{
|
||||
if (parts.Count() == 3 && parts[2] == "show") { ERR_Reply(key + "=" + mode.Settings().Get(key).ToString()); return; }
|
||||
int value;
|
||||
if (parts.Count() == 4 && parts[2] == "set" && ERR_CommandArguments.Integer(parts[3], value) && mode.Settings().Set(key, value))
|
||||
ERR_Reply(key + "=" + value.ToString() + " saved");
|
||||
else ERR_Reply("Invalid value or settings could not be saved; previous setting retained.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
ERR_Reply("/esc status | hq show | hq log | hq interval|squads|radius show|set N | intel carrying|reveal show|set N");
|
||||
}
|
||||
protected void ERR_Reply(string text) { Rpc(ERR_Response, text); }
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
|
||||
protected void ERR_Response(string text) { SCR_ChatComponent.RadioProtocolMessage(text); }
|
||||
|
||||
void ~SCR_PlayerController()
|
||||
{
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(ERR_Register);
|
||||
if (m_ERRCommand) m_ERRCommand.Remove(ERR_OnCommand);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Opt-in engine fixture: physical radios and production delivery routes only.
|
||||
#ifdef WORKBENCH
|
||||
modded class ERR_HQ
|
||||
{
|
||||
vector ERR_ProbeResponseOrigin()
|
||||
{
|
||||
if (m_Units.IsEmpty() || !m_Units[0].Group) return vector.Zero;
|
||||
return m_Units[0].Group.GetOrigin() + "0 0 20";
|
||||
}
|
||||
bool ERR_ProbeDeliveredResponse()
|
||||
{
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Assignment && unit.Waypoint && unit.Group != unit.Assignment.Source) return true;
|
||||
return false;
|
||||
}
|
||||
bool ERR_ProbeUniqueAssignments()
|
||||
{
|
||||
array<AIWaypoint> waypoints = {};
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (!unit.Waypoint) continue;
|
||||
if (!unit.Assignment || waypoints.Contains(unit.Waypoint)) return false;
|
||||
waypoints.Insert(unit.Waypoint);
|
||||
}
|
||||
return m_Units.Count() == 3 && waypoints.Count() > 0 && Assigned() <= m_Mode.Settings().Get("supportSquadLimit");
|
||||
}
|
||||
string ERR_ProbePerception()
|
||||
{
|
||||
int targets, sightings, radios;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Radio) radios++;
|
||||
if (!unit.Utility) continue;
|
||||
array<ref ERR_HQVisualContact> samples = {};
|
||||
ERR_HQObservation.Gather(unit.Utility, GetGame().GetWorld().GetWorldTime(), samples);
|
||||
sightings += samples.Count();
|
||||
if (unit.Utility.m_Perception) targets += unit.Utility.m_Perception.m_aTargets.Count();
|
||||
}
|
||||
return string.Format("%1; radios=%2 targets=%3 fresh visual samples=%4", Status(), radios, targets, sightings);
|
||||
}
|
||||
ERR_HQRoute ERR_ProbeRoute(IEntity carrier = null, bool downlink = false)
|
||||
{
|
||||
if (carrier) return m_Network.Route(carrier, downlink);
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Radio) return m_Network.Route(unit.Radio, downlink);
|
||||
return null;
|
||||
}
|
||||
bool ERR_ProbeSquadsReady()
|
||||
{
|
||||
if (m_Units.Count() != 3) return false;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (!unit.Group || ERR_HQRadio.Capability(unit.Radio) != 2) return false;
|
||||
array<AIAgent> agents = {};
|
||||
unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != 6) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_NativeRadioProbe
|
||||
{
|
||||
protected ref ERR_HQRoute m_Route;
|
||||
protected IEntity m_Carrier;
|
||||
protected int m_Step;
|
||||
|
||||
int Tick(ERR_HQ hq, float now)
|
||||
{
|
||||
if (!hq) return -1;
|
||||
if (m_Step == 0)
|
||||
{
|
||||
if (!hq.ERR_ProbeSquadsReady()) return 0;
|
||||
Print("ERR SMOKE PASS: three physical six-person squads each have a manpack operator");
|
||||
m_Route = hq.ERR_ProbeRoute();
|
||||
if (!m_Route || m_Route.Nodes.IsEmpty()) return -1;
|
||||
m_Carrier = m_Route.Nodes[0].Carrier;
|
||||
m_Route.MessageId = hq.Journal().Message();
|
||||
m_Step = 1;
|
||||
}
|
||||
int result = m_Route.Advance(now, hq.Journal());
|
||||
if (result == 0) return 0;
|
||||
if (m_Step == 3)
|
||||
{
|
||||
if (result >= 0) return -1;
|
||||
Print("ERR SMOKE PASS: removing receiving manpack interrupts an in-flight HQ order");
|
||||
return 1;
|
||||
}
|
||||
if (result < 0) return -1;
|
||||
if (m_Step == 1) Print("ERR SMOKE PASS: physical manpack uplink delivers to HQ");
|
||||
if (m_Step == 2) Print("ERR SMOKE PASS: HQ downlink delivers to a physical receiver");
|
||||
m_Route = hq.ERR_ProbeRoute(m_Carrier, true);
|
||||
if (!m_Route || m_Route.Nodes.IsEmpty()) return -1;
|
||||
m_Route.MessageId = hq.Journal().Message();
|
||||
m_Step++;
|
||||
if (m_Step == 3)
|
||||
{
|
||||
// Start HQ transmission, then remove the actual receiving equipment.
|
||||
if (m_Route.Advance(now, hq.Journal()) != 0) return -1;
|
||||
IEntity receiver = m_Route.Nodes[0].Carrier;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(receiver.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) return -1;
|
||||
array<IEntity> items = {};
|
||||
inventory.GetItems(items);
|
||||
foreach (IEntity item : items)
|
||||
if (item && item.FindComponent(BaseRadioComponent)) SCR_EntityHelper.DeleteEntityAndChildren(item);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,302 @@
|
||||
// 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 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_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse;
|
||||
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_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 == 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
|
||||
@@ -0,0 +1,51 @@
|
||||
[WorkbenchPluginAttribute(name: "Validate Rereintroduced scenario", wbModules: {"WorldEditor"})]
|
||||
class ERR_ValidateWorldPlugin : WorldEditorPlugin
|
||||
{
|
||||
protected bool m_Obstructed;
|
||||
protected bool CheckClearance(IEntity entity)
|
||||
{
|
||||
if (entity && entity.GetVObject()) m_Obstructed = true;
|
||||
return !m_Obstructed;
|
||||
}
|
||||
override void Run() { Validate(); }
|
||||
override void RunCommandline() { Validate(); }
|
||||
protected void Validate()
|
||||
{
|
||||
WorldEditor editor = Workbench.GetModule(WorldEditor);
|
||||
if (!editor) { Print("ERR VALIDATION: World Editor missing", LogLevel.ERROR); return; }
|
||||
WorldEditorAPI api = editor.GetApi();
|
||||
if (!api) { Print("ERR VALIDATION: world API missing", LogLevel.ERROR); return; }
|
||||
PrintFormat("ERR VALIDATION: prison terrain=%1, radio terrain=%2, extraction terrain=%3", api.GetTerrainSurfaceY(6090,7160), api.GetTerrainSurfaceY(6500,7200), api.GetTerrainSurfaceY(7000,7500));
|
||||
for (int dx = -6; dx <= 6; dx += 3)
|
||||
for (int dz = -6; dz <= 6; dz += 3)
|
||||
PrintFormat("ERR TERRAIN: x=%1 z=%2 y=%3", 6090 + dx, 7160 + dz, api.GetTerrainSurfaceY(6090 + dx, 7160 + dz));
|
||||
float bestSlope = 1000;
|
||||
vector bestPosition;
|
||||
for (int x = 5800; x <= 6200; x += 10)
|
||||
{
|
||||
for (int z = 6800; z <= 7200; z += 10)
|
||||
{
|
||||
float low = 10000;
|
||||
float high = -10000;
|
||||
for (int sx = -9; sx <= 9; sx += 3)
|
||||
for (int sz = -15; sz <= 9; sz += 3)
|
||||
{
|
||||
float height = api.GetTerrainSurfaceY(x + sx, z + sz);
|
||||
low = Math.Min(low, height);
|
||||
high = Math.Max(high, height);
|
||||
}
|
||||
if (low > 5 && high - low < bestSlope)
|
||||
{
|
||||
m_Obstructed = false;
|
||||
api.GetWorld().QueryEntitiesByAABB(Vector(x - 10, low + 0.1, z - 16), Vector(x + 10, high + 5, z + 10), CheckClearance);
|
||||
if (m_Obstructed) continue;
|
||||
bestSlope = high - low;
|
||||
bestPosition = Vector(x, api.GetTerrainSurfaceY(x, z), z);
|
||||
}
|
||||
}
|
||||
}
|
||||
PrintFormat("ERR SITE CANDIDATE: position=%1 elevationRange=%2", bestPosition, bestSlope);
|
||||
string play;
|
||||
if (System.GetCLIParam("errPlay", play) && play == "1") editor.SwitchToGameMode();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user