Validate independent packed addons and complete physical mortar salvos
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
||||
// Companion-specific commands extend Core's authenticated transport.
|
||||
modded class ERR_AdminService
|
||||
{
|
||||
protected override 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";
|
||||
}
|
||||
return super.Setting(section, option);
|
||||
}
|
||||
protected override bool Extension(ERR_GameMode mode, string arguments, array<string> replies)
|
||||
{
|
||||
if (arguments != "status" && arguments != "hq show" && arguments != "hq log") return super.Extension(mode, arguments, replies);
|
||||
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
|
||||
if (!hq) { replies.Insert("HQ unavailable"); return true; }
|
||||
if (arguments == "hq show" || arguments == "status") replies.Insert(hq.Status());
|
||||
else
|
||||
{
|
||||
int cursor;
|
||||
hq.Journal().Read(Math.Max(0, hq.Journal().Sequence() - 8), replies, cursor);
|
||||
if (replies.IsEmpty()) replies.Insert("HQ journal is empty.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected override string Help()
|
||||
{
|
||||
return super.Help() + " | hq show|log | hq interval|squads|radius show|set N | intel carrying show|set N";
|
||||
}
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
#ifdef WORKBENCH
|
||||
// Read-only evidence hooks. Every production method delegates unchanged.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
void ERR_ResponseUnits(out array<ERR_HQUnit> units)
|
||||
{
|
||||
units.Clear();
|
||||
if (!ERR_NativeHQResponseProof.Enabled()) return;
|
||||
foreach (ERR_HQUnit unit : m_Units) units.Insert(unit);
|
||||
}
|
||||
protected override void Observe(ERR_HQUnit unit, float now)
|
||||
{
|
||||
ERR_HQReport previous = unit.PendingReport;
|
||||
super.Observe(unit, now);
|
||||
ERR_NativeHQResponseProof proof = ERR_NativeHQResponseProof.Active();
|
||||
if (proof && unit.PendingReport && unit.PendingReport != previous) proof.Observed(this, unit, now);
|
||||
}
|
||||
protected override void Accept(ERR_HQReport report, float now)
|
||||
{
|
||||
super.Accept(report, now);
|
||||
ERR_NativeHQResponseProof proof = ERR_NativeHQResponseProof.Active();
|
||||
if (!proof || !report) return;
|
||||
foreach (ERR_HQReport accepted : m_Contacts)
|
||||
if (accepted.MessageId == report.MessageId) { proof.Accepted(this, accepted, now); break; }
|
||||
}
|
||||
protected override void DeliverOrder(ERR_HQUnit unit, float now)
|
||||
{
|
||||
ERR_HQReport order = unit.PendingOrder;
|
||||
ERR_HQRoute route = unit.Downlink;
|
||||
super.DeliverOrder(unit, now);
|
||||
ERR_NativeHQResponseProof proof = ERR_NativeHQResponseProof.Active();
|
||||
if (proof && order && route && unit.Assignment == order && unit.Waypoint) proof.Delivered(this, unit, route, now);
|
||||
}
|
||||
}
|
||||
modded class ERR_HQJournal
|
||||
{
|
||||
override void Write(string kind, string message, int messageId = 0)
|
||||
{
|
||||
super.Write(kind, message, messageId);
|
||||
ERR_NativeHQResponseProof proof = ERR_NativeHQResponseProof.Active();
|
||||
if (proof) proof.JournalEvent(this, kind, message, messageId);
|
||||
}
|
||||
}
|
||||
modded class SCR_AIGroupUtilityComponent
|
||||
{
|
||||
IEntity ERR_ResponseVisualObserver(ERR_HQReport report, float now)
|
||||
{
|
||||
foreach (SCR_AIInfoComponent info : m_aInfoComponents)
|
||||
{
|
||||
if (!info || !info.m_Perception) continue;
|
||||
AIAgent agent = AIAgent.Cast(info.GetOwner());
|
||||
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) continue;
|
||||
array<BaseTarget> targets = {}; info.m_Perception.GetTargetsList(targets, ETargetCategory.ENEMY);
|
||||
foreach (BaseTarget target : targets)
|
||||
{
|
||||
ERR_HQVisualContact sample = ERR_HQObservation.Capture(target, now);
|
||||
if (sample && sample.Entity == report.Target && sample.Escapist && Math.AbsFloat(sample.ObservedAt - report.ObservedAt) < 1 && vector.Distance(sample.Position, report.Position) < 0.1) return agent.GetControlledEntity();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
class ERR_NativeResponseUnit
|
||||
{
|
||||
ERR_HQUnit Unit;
|
||||
SCR_AIGroup Group;
|
||||
IEntity Radio;
|
||||
ref array<IEntity> Actors = {};
|
||||
}
|
||||
class ERR_NativeResponseJournal
|
||||
{
|
||||
bool Report;
|
||||
bool Transmit;
|
||||
bool HQReceive;
|
||||
bool HQTransmit;
|
||||
bool UnitReceive;
|
||||
bool Delivered;
|
||||
}
|
||||
class ERR_NativeResponseLineage
|
||||
{
|
||||
ref ERR_HQReport Report;
|
||||
ERR_HQUnit Source;
|
||||
IEntity Observer;
|
||||
IEntity SourceRadio;
|
||||
ref ERR_HQRoute Uplink;
|
||||
bool Accepted;
|
||||
float AcceptedAt;
|
||||
ERR_HQUnit Recipient;
|
||||
IEntity RecipientRadio;
|
||||
int DeliveryId;
|
||||
AIWaypoint Waypoint;
|
||||
IEntity MovingActor;
|
||||
vector DeliveredOrigin;
|
||||
float DeliveredAt;
|
||||
}
|
||||
class ERR_NativeHQResponseProof
|
||||
{
|
||||
protected static ERR_NativeHQResponseProof s_Active;
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_HQ m_HQ;
|
||||
protected IEntity m_Player;
|
||||
protected SCR_PlayerController m_Controller;
|
||||
protected ERR_PlayerLife m_Life;
|
||||
protected ERR_HQUnit m_Source;
|
||||
protected DamageManagerComponent m_Damage;
|
||||
protected bool m_DamageWasEnabled;
|
||||
protected bool m_Protected;
|
||||
protected bool m_Started;
|
||||
protected bool m_Stopped;
|
||||
protected bool m_Failed;
|
||||
protected string m_Failure;
|
||||
protected float m_Deadline;
|
||||
protected float m_NextDiagnostic;
|
||||
protected ref array<ref ERR_NativeResponseUnit> m_Units = {};
|
||||
protected ref array<IEntity> m_Forces = {};
|
||||
protected ref array<ref ERR_NativeResponseLineage> m_Reports = {};
|
||||
protected ref map<int, ref ERR_NativeResponseJournal> m_Journal = new map<int, ref ERR_NativeResponseJournal>();
|
||||
static bool Enabled()
|
||||
{
|
||||
string enabled, outcome;
|
||||
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "hq-response";
|
||||
}
|
||||
static ERR_NativeHQResponseProof Active() { return s_Active; }
|
||||
void ERR_NativeHQResponseProof(ERR_GameMode mode, IEntity player)
|
||||
{
|
||||
m_Mode = mode; m_Player = player; m_HQ = ERR_HQ.Get(); m_Deadline = GetGame().GetWorld().GetWorldTime() + 140000;
|
||||
if (Enabled() && !s_Active) s_Active = this;
|
||||
}
|
||||
protected bool Fail(string reason) { m_Failed = true; m_Failure = reason; return false; }
|
||||
protected bool Check(bool condition, string detail)
|
||||
{
|
||||
if (condition) { Print("ERR SMOKE PASS: HQ response " + detail); return true; }
|
||||
Print("ERR SMOKE FAIL: HQ response " + detail, LogLevel.ERROR); Stop(); return false;
|
||||
}
|
||||
protected ERR_NativeResponseUnit Original(ERR_HQUnit unit)
|
||||
{
|
||||
foreach (ERR_NativeResponseUnit original : m_Units) if (original.Unit == unit && original.Group == unit.Group) return original;
|
||||
return null;
|
||||
}
|
||||
protected ERR_NativeResponseLineage Report(int id)
|
||||
{
|
||||
foreach (ERR_NativeResponseLineage report : m_Reports) if (report.Report.MessageId == id) return report;
|
||||
return null;
|
||||
}
|
||||
protected bool Canonical()
|
||||
{
|
||||
return m_Player && m_Controller && m_Life && m_Mode.Players().CurrentActor(m_Player) == m_Life && m_Controller == GetGame().GetPlayerController() && m_Controller.GetControlledEntity() == m_Player && m_Life.Actor == m_Player && m_Life.Connected && m_Life.UsedLife;
|
||||
}
|
||||
protected bool UnchangedForces()
|
||||
{
|
||||
array<ERR_HQUnit> current = {}; m_HQ.ERR_ResponseUnits(current);
|
||||
if (current.Count() != m_Units.Count()) return false;
|
||||
foreach (ERR_HQUnit unit : current)
|
||||
{
|
||||
ERR_NativeResponseUnit original = Original(unit);
|
||||
if (!original) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != original.Actors.Count()) return false;
|
||||
foreach (AIAgent agent : agents) if (!agent || !original.Actors.Contains(agent.GetControlledEntity())) return false;
|
||||
}
|
||||
array<IEntity> forces = {}; m_HQ.PhysicalForces(forces);
|
||||
if (forces.Count() != m_Forces.Count()) return false;
|
||||
foreach (IEntity entity : forces) if (!m_Forces.Contains(entity)) return false;
|
||||
return true;
|
||||
}
|
||||
protected bool Arrange()
|
||||
{
|
||||
array<ERR_HQUnit> units = {}; m_HQ.ERR_ResponseUnits(units);
|
||||
foreach (ERR_HQUnit unit : units)
|
||||
{
|
||||
if (!unit.Group || !unit.Utility || !unit.Radio || ERR_HQRadio.Capability(unit.Radio) != 2) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.IsEmpty() || !unit.Group.IsExpandComplete()) return false;
|
||||
ERR_NativeResponseUnit original = new ERR_NativeResponseUnit(); original.Unit = unit; original.Group = unit.Group; original.Radio = unit.Radio;
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) return false;
|
||||
original.Actors.Insert(agent.GetControlledEntity());
|
||||
}
|
||||
m_Units.Insert(original);
|
||||
if (!m_Source && !unit.ReservedForSupport) m_Source = unit;
|
||||
}
|
||||
if (!m_Source || m_Units.Count() < 2) return false;
|
||||
m_HQ.PhysicalForces(m_Forces);
|
||||
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController()); m_Life = m_Mode.Players().CurrentActor(m_Player);
|
||||
if (!Canonical()) return false;
|
||||
m_Damage = DamageManagerComponent.Cast(m_Player.FindComponent(DamageManagerComponent));
|
||||
if (!m_Damage) return false;
|
||||
m_DamageWasEnabled = m_Damage.IsDamageHandlingEnabled(); m_Damage.EnableDamageHandling(false); m_Protected = true;
|
||||
// Preserve the established fixture arrangement: move only the real prisoner.
|
||||
// Native perception must discover him; neither targets nor reports are seeded.
|
||||
vector position = ERR_Actors.Ground(m_Source.Group.GetOrigin() + "0 0 20");
|
||||
vector transform[4]; m_Player.GetWorldTransform(transform); transform[3] = position;
|
||||
ChimeraCharacter.Cast(m_Player).Teleport(transform);
|
||||
m_Started = true;
|
||||
PrintFormat("ERR HQ RESPONSE ARRANGEMENT: canonical prisoner moved to=%1 near original source group=%2; temporary player damage protection=%3; no AI/report/radio cadence changes", position, m_Source.Id, m_Protected);
|
||||
return true;
|
||||
}
|
||||
void JournalEvent(ERR_HQJournal journal, string kind, string message, int messageId)
|
||||
{
|
||||
if (!m_Started || m_Stopped || !m_HQ || journal != m_HQ.Journal() || messageId <= 0) return;
|
||||
ERR_NativeResponseJournal entry = m_Journal.Get(messageId);
|
||||
if (!entry)
|
||||
{
|
||||
if (m_Journal.Count() >= 128) { Fail("bounded journal evidence map exhausted"); return; }
|
||||
entry = new ERR_NativeResponseJournal(); m_Journal.Insert(messageId, entry);
|
||||
}
|
||||
if (kind == "REPORT") entry.Report = true;
|
||||
if (kind == "TRANSMIT") { entry.Transmit = true; if (message.StartsWith("HQ -> ")) entry.HQTransmit = true; }
|
||||
if (kind == "RECEIVE") { if (message.StartsWith("HQ received from ")) entry.HQReceive = true; else if (message.StartsWith("unit ")) entry.UnitReceive = true; }
|
||||
if (kind == "ORDER DELIVERED") entry.Delivered = true;
|
||||
}
|
||||
void Observed(ERR_HQ hq, ERR_HQUnit unit, float now)
|
||||
{
|
||||
if (!m_Started || m_Stopped || hq != m_HQ || unit != m_Source || unit.PendingReport.Target != m_Player) return;
|
||||
ERR_HQReport report = unit.PendingReport;
|
||||
ERR_NativeResponseUnit original = Original(unit);
|
||||
IEntity observer = unit.Utility.ERR_ResponseVisualObserver(report, now);
|
||||
if (!Canonical() || !original || !original.Actors.Contains(observer) || !unit.Uplink || unit.Uplink.Nodes.IsEmpty() || unit.Uplink.Nodes[0].Carrier != unit.Radio || unit.Uplink.MessageId != report.MessageId || unit.Radio != original.Radio || !original.Actors.Contains(unit.Radio) || report.Source != original.Group || report.ObservedAt > now || Report(report.MessageId)) { Fail("real observed report lacks canonical original observer/radio/source lineage"); return; }
|
||||
if (m_Reports.Count() >= 32) { Fail("bounded report evidence exhausted"); return; }
|
||||
ERR_NativeResponseLineage proof = new ERR_NativeResponseLineage(); proof.Report = report.Snapshot(); proof.Source = unit; proof.Observer = observer; proof.SourceRadio = unit.Radio; proof.Uplink = unit.Uplink; m_Reports.Insert(proof);
|
||||
PrintFormat("ERR HQ RESPONSE SIGHTING: source=%1 report=M%2 observer=%3 radio=%4 target=%5 observedAt=%6; actual native visual sample", unit.Id, report.MessageId, observer, unit.Radio, m_Player, report.ObservedAt);
|
||||
}
|
||||
void Accepted(ERR_HQ hq, ERR_HQReport report, float now)
|
||||
{
|
||||
if (!m_Started || m_Stopped || hq != m_HQ) return;
|
||||
ERR_NativeResponseLineage proof = Report(report.MessageId); if (!proof) return;
|
||||
if (report.Target != m_Player || report.Source != proof.Report.Source || report.ObservedAt != proof.Report.ObservedAt || vector.Distance(report.Position, proof.Report.Position) > 0.01 || !proof.Uplink || proof.Uplink.Failed || proof.Uplink.Sending || proof.Uplink.Cursor < proof.Uplink.Nodes.Count()) { Fail("accepted report lacks completed actual uplink or immutable observed time"); return; }
|
||||
proof.Accepted = true; proof.AcceptedAt = now;
|
||||
}
|
||||
void Delivered(ERR_HQ hq, ERR_HQUnit unit, ERR_HQRoute route, float now)
|
||||
{
|
||||
if (!m_Started || m_Stopped || hq != m_HQ || !unit.Assignment) return;
|
||||
ERR_NativeResponseLineage proof = Report(unit.Assignment.MessageId); if (!proof || proof.Recipient) return;
|
||||
ERR_NativeResponseUnit original = Original(unit);
|
||||
if (!proof.Accepted || !original || unit == proof.Source || !unit.Assignment || !unit.Waypoint) { Fail("delivered response lacks accepted original recipient/order"); return; }
|
||||
if (unit.Assignment.Target != m_Player || unit.Assignment.Source != proof.Report.Source || unit.Assignment.ObservedAt != proof.Report.ObservedAt || vector.Distance(unit.Assignment.Position, proof.Report.Position) > 0.01) { Fail("delivered response changed immutable report"); return; }
|
||||
if (unit.Radio != original.Radio || !original.Actors.Contains(unit.Radio) || !route.Downlink || route.Nodes.IsEmpty()) { Fail("delivered response lacks original radio/downlink"); return; }
|
||||
ERR_HQRadioNode recipientNode = route.Nodes[route.Nodes.Count() - 1];
|
||||
if (recipientNode.Carrier != unit.Radio || route.Failed || route.Sending || route.Cursor != route.Nodes.Count() - 1) { Fail("delivered response lacks completed actual recipient downlink"); return; }
|
||||
proof.Recipient = unit; proof.RecipientRadio = unit.Radio; proof.DeliveryId = route.MessageId; proof.Waypoint = unit.Waypoint; proof.DeliveredAt = now;
|
||||
foreach (IEntity actor : original.Actors) if (ERR_Actors.CanFight(actor)) { proof.MovingActor = actor; break; }
|
||||
if (proof.MovingActor) proof.DeliveredOrigin = proof.MovingActor.GetOrigin();
|
||||
PrintFormat("ERR HQ RESPONSE DELIVERED: report=M%1 downlink=M%2 source=%3 recipient=%4 radio=%5 waypoint=%6 observedAt=%7", proof.Report.MessageId, route.MessageId, proof.Source.Id, unit.Id, unit.Radio, unit.Waypoint, unit.Assignment.ObservedAt);
|
||||
}
|
||||
int Tick(float now)
|
||||
{
|
||||
if (m_Stopped) return -1;
|
||||
if (!Enabled() || s_Active != this || !m_Mode || !m_Mode.Active() || !m_HQ || now > m_Deadline || m_Failed) { Check(false, "bounded active evidence context: " + m_Failure); return -1; }
|
||||
if (!m_Started)
|
||||
{
|
||||
if (!m_HQ.ERR_ProbeSquadsReady()) return 0;
|
||||
array<IEntity> guns = {}; m_HQ.Mortars().AppendGuns(guns);
|
||||
foreach (IEntity gun : guns) { ERR_MortarBattery battery = m_HQ.Mortars().FindGun(gun); if (!battery || !battery.StartupActivated || !battery.Operational() || !m_HQ.MortarConnected(battery.Group)) return 0; }
|
||||
if (!Check(Arrange(), "original physical force, RTOs and canonical prisoner captured before arranged sighting")) return -1;
|
||||
return 0;
|
||||
}
|
||||
if (!Canonical() || !UnchangedForces()) { Check(false, "canonical player and all original physical group/actor identities retained without added force"); return -1; }
|
||||
if (now >= m_NextDiagnostic)
|
||||
{
|
||||
PrintFormat("ERR HQ RESPONSE PROOF: observed source reports=%1; %2", m_Reports.Count(), m_HQ.ERR_ProbePerception()); m_NextDiagnostic = now + 10000;
|
||||
}
|
||||
foreach (ERR_NativeResponseLineage proof : m_Reports)
|
||||
{
|
||||
if (!proof.Recipient || !proof.MovingActor) continue;
|
||||
if (proof.Recipient.Waypoint != proof.Waypoint || !proof.Recipient.Assignment || proof.Recipient.Assignment.MessageId != proof.Report.MessageId) continue;
|
||||
ERR_NativeResponseJournal uplink = m_Journal.Get(proof.Report.MessageId);
|
||||
ERR_NativeResponseJournal downlink = m_Journal.Get(proof.DeliveryId);
|
||||
if (!uplink || !downlink || !uplink.Report || !uplink.Transmit || !uplink.HQReceive || !downlink.HQTransmit || !downlink.UnitReceive || !downlink.Delivered) continue;
|
||||
if (proof.Recipient.Group.GetCurrentWaypoint() != proof.Waypoint || vector.DistanceXZ(proof.MovingActor.GetOrigin(), proof.DeliveredOrigin) < 3) continue;
|
||||
if (vector.DistanceXZ(proof.MovingActor.GetOrigin(), proof.Report.Position) >= vector.DistanceXZ(proof.DeliveredOrigin, proof.Report.Position) - 1) continue;
|
||||
if (!Check(proof.SourceRadio && proof.RecipientRadio && m_HQ.ERR_ProbeUniqueAssignments(), "actual sighting/report/uplink/HQ receipt/downlink journal lineage reaches distinct original infantry with bounded unique waypoints")) return -1;
|
||||
if (!Check(UnchangedForces(), "original recipient physically moves toward its native Search waypoint with no replacement or additional force")) return -1;
|
||||
Stop(); return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
if (m_Protected && m_Damage) m_Damage.EnableDamageHandling(m_DamageWasEnabled);
|
||||
m_Protected = false;
|
||||
if (s_Active == this) s_Active = null;
|
||||
}
|
||||
void ~ERR_NativeHQResponseProof() { Stop(); }
|
||||
}
|
||||
#endif
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#ifdef WORKBENCH
|
||||
modded class ERR_MortarSystem
|
||||
{
|
||||
ERR_MortarBattery ERR_ProbeBattery()
|
||||
{
|
||||
if (m_Batteries.Count() != 1) return null;
|
||||
return m_Batteries[0];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
// Opt-in engine fixture: physical radios and production delivery routes only.
|
||||
#ifdef WORKBENCH
|
||||
modded class ERR_HQ
|
||||
{
|
||||
vector ERR_ProbeResponseOrigin()
|
||||
{
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (!unit.ReservedForSupport && unit.Group) return unit.Group.GetOrigin() + "0 0 20";
|
||||
return vector.Zero;
|
||||
}
|
||||
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 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()
|
||||
{
|
||||
int infantry;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.ReservedForSupport) continue;
|
||||
infantry++;
|
||||
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 infantry == 6;
|
||||
}
|
||||
}
|
||||
|
||||
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: six 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
|
||||
+506
@@ -0,0 +1,506 @@
|
||||
// Explicit, local Workbench diagnostic only. Never compiled into game/server builds.
|
||||
#ifdef WORKBENCH
|
||||
modded class ERR_GameMode
|
||||
{
|
||||
protected bool m_ERRTestPatrolHQ;
|
||||
protected ref ERR_NativePatrolProbe m_ERRHQPatrolMovement;
|
||||
protected ref ERR_NativePatrolHQProbe m_ERRHQPatrolOrder;
|
||||
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 bool m_ERRTestMortarFire;
|
||||
protected bool m_ERRTestMortarSalvo;
|
||||
protected ref ERR_NativeMortarSalvoProbe m_ERRMortarSalvo;
|
||||
protected bool m_ERRTestMortarCourier;
|
||||
protected ref ERR_NativeMortarCourierProbe m_ERRMortarCourier;
|
||||
protected bool m_ERRTestMortarTransfer;
|
||||
protected ref ERR_NativeMortarTransferProbe m_ERRMortarTransfer;
|
||||
protected ref ERR_NativeMortarFiringProbe m_ERRMortarFire;
|
||||
protected float m_ERRNextProbeLog;
|
||||
protected ref ERR_NativeRadioProbe m_ERRRadioProbe;
|
||||
protected ref ERR_NativeHQResponseProof m_ERRHQResponseProof;
|
||||
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_ERRTestMortarFire = outcome == "mortar-fire" || outcome == "mortar-expiry";
|
||||
m_ERRTestMortarCourier = outcome == "mortar-courier";
|
||||
m_ERRTestMortarSalvo = outcome == "mortar-salvo";
|
||||
m_ERRTestMortarTransfer = outcome == "mortar-transfer";
|
||||
m_ERRTestPatrolHQ = outcome == "patrol-hq";
|
||||
m_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse || m_ERRTestMortar;
|
||||
m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 150000;
|
||||
if (m_ERRTestMortarCourier) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 450000;
|
||||
if (m_ERRTestMortarTransfer) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
|
||||
if (m_ERRTestMortarSalvo) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
|
||||
if (m_ERRTestMortarFire) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
|
||||
if (m_ERRTestPatrolHQ) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 420000;
|
||||
OnStop().Insert(ERR_StopHQPatrol);
|
||||
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; }
|
||||
ERR_StopHQPatrol();
|
||||
Print("ERR SMOKE FAIL: " + name, LogLevel.ERROR);
|
||||
m_ERRSmokeDone = true;
|
||||
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
|
||||
return false;
|
||||
}
|
||||
protected void ERR_StopHQPatrol()
|
||||
{
|
||||
if (m_ERRHQResponseProof) m_ERRHQResponseProof.Stop();
|
||||
if (m_ERRMortarSalvo) m_ERRMortarSalvo.Stop();
|
||||
if (m_ERRMortarCourier) m_ERRMortarCourier.Stop();
|
||||
if (m_ERRMortarTransfer) m_ERRMortarTransfer.Stop();
|
||||
if (m_ERRMortarFire) m_ERRMortarFire.Stop();
|
||||
if (m_ERRHQPatrolMovement) m_ERRHQPatrolMovement.Stop();
|
||||
if (m_ERRHQPatrolOrder) m_ERRHQPatrolOrder.Stop();
|
||||
}
|
||||
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_ERRTestMortarSalvo)
|
||||
{
|
||||
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 191, "native configured salvo and unchanged-report replay observation complete before reset")) return;
|
||||
m_ERRSmokeDone = true;
|
||||
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
|
||||
ERR_StopHQPatrol();
|
||||
Print("ERR SMOKE COMPLETE: native full configured mortar salvo, exact original ammunition and unchanged-report replay refusal; fixture reset requested");
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestMortarCourier)
|
||||
{
|
||||
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 181, "native finite courier reached an exhausted battery before explicit reset")) return;
|
||||
m_ERRSmokeDone = true;
|
||||
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
|
||||
ERR_StopHQPatrol();
|
||||
Print("ERR SMOKE COMPLETE: native finite courier, real supply uplink/downlink, original patrol walking and exact physical shell delivery; original 27 shells sequestered in fixture boxes; reset requested");
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestMortarTransfer)
|
||||
{
|
||||
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 161, "native exact shell transfers and original-owner restoration complete before explicit reset")) return;
|
||||
m_ERRSmokeDone = true;
|
||||
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
|
||||
ERR_StopHQPatrol();
|
||||
Print("ERR SMOKE COMPLETE: native exact mortar inventory transfer, accepted-operation cancellation and original-owner restoration; radio bypassed; fixture reset requested");
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestMortarFire)
|
||||
{
|
||||
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 151, "native mortar first shot and stop complete before explicit reset")) return;
|
||||
m_ERRSmokeDone = true;
|
||||
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
|
||||
ERR_StopHQPatrol();
|
||||
Print("ERR SMOKE COMPLETE: native HQ radio mortar projectile and configured cancellation/expiry; fixture reset requested");
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestPatrolHQ)
|
||||
{
|
||||
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 133, "HQ patrol preemption and physical resumption before explicit reset")) return;
|
||||
m_ERRSmokeDone = true;
|
||||
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
|
||||
Print("ERR SMOKE COMPLETE: native HQ patrol preemption and physical resumption");
|
||||
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<string> replies = {};
|
||||
array<string> commands = {"hq interval show", "hq squads show", "hq radius show", "intel carrying show", "intel reveal show"};
|
||||
array<string> 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 == 190)
|
||||
{
|
||||
int result = m_ERRMortarSalvo.Tick(now);
|
||||
if (result < 0) { ERR_Check(false, "native complete mortar salvo fixture; inspect source diagnostics"); return; }
|
||||
if (result == 1) { m_ERRSmokeStep = 191; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRSmokeStep == 180)
|
||||
{
|
||||
int result = m_ERRMortarCourier.Tick(now);
|
||||
if (result < 0) { ERR_Check(false, "native finite courier fixture"); return; }
|
||||
if (result == 1) { m_ERRSmokeStep = 181; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRSmokeStep == 160)
|
||||
{
|
||||
int result = m_ERRMortarTransfer.Tick(now);
|
||||
if (result < 0) { ERR_Check(false, "native mortar exact-shell transfer fixture"); return; }
|
||||
if (result == 1) { m_ERRSmokeStep = 161; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRSmokeStep == 150)
|
||||
{
|
||||
int result = m_ERRMortarFire.Tick(now);
|
||||
if (result < 0) { ERR_Check(false, "native mortar firing probe failed; see diagnostics"); return; }
|
||||
if (result == 1) { m_ERRSmokeStep = 151; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRSmokeStep == 130 || m_ERRSmokeStep == 132)
|
||||
{
|
||||
int result = m_ERRHQPatrolMovement.Tick(now);
|
||||
if (result < 0) { ERR_StopHQPatrol(); GetGame().GetCallqueue().Remove(ERR_SmokeTick); return; }
|
||||
if (result == 1)
|
||||
{
|
||||
if (m_ERRSmokeStep == 130) { m_ERRHQPatrolOrder = new ERR_NativePatrolHQProbe(this); m_ERRSmokeStep = 131; }
|
||||
else { m_ERRSmokeStep = 133; Transition(ERR_Phase.DEFEAT); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_ERRSmokeStep == 131)
|
||||
{
|
||||
int result = m_ERRHQPatrolOrder.Tick(now);
|
||||
if (result < 0) { ERR_StopHQPatrol(); GetGame().GetCallqueue().Remove(ERR_SmokeTick); return; }
|
||||
if (result == 1) { m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this); m_ERRSmokeStep = 132; }
|
||||
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() || !ERR_NativeContractsProbe.Run(this, m_ERRSmokePlayer) || !ERR_CheckCompanionCommands()) return;
|
||||
if (m_ERRTestPatrolHQ)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this);
|
||||
m_ERRSmokeStep = 130;
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestMortarSalvo)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRMortarSalvo = new ERR_NativeMortarSalvoProbe(this);
|
||||
m_ERRSmokeStep = 190;
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestMortarCourier)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRMortarCourier = new ERR_NativeMortarCourierProbe(this);
|
||||
m_ERRSmokeStep = 180;
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestMortarTransfer)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRMortarTransfer = new ERR_NativeMortarTransferProbe(this);
|
||||
m_ERRSmokeStep = 160;
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestMortarFire)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRMortarFire = new ERR_NativeMortarFiringProbe(this);
|
||||
m_ERRSmokeStep = 150;
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestMortar)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRSmokeStep = 120;
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestHQResponse)
|
||||
{
|
||||
m_ERRHQResponseProof = new ERR_NativeHQResponseProof(this, m_ERRSmokePlayer);
|
||||
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(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;
|
||||
array<IEntity> cacheStock = {};
|
||||
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(Sites().Find("everon-supply-1").Container.FindComponent(InventoryStorageManagerComponent)), cacheStock);
|
||||
foreach (IEntity tracked : cacheStock)
|
||||
{
|
||||
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(DefenceOccupied(), "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(!DefenceOccupied() && 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(ExtractionVehicle());
|
||||
return;
|
||||
}
|
||||
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_ERRSmokePlayer.FindComponent(CompartmentAccessComponent));
|
||||
BaseCompartmentSlot slot = access.FindFreeCompartment(ExtractionVehicle(), ECompartmentType.PILOT);
|
||||
if (!ERR_Check(slot && access.GetInVehicle(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 = {};
|
||||
ERR_Inventory.Items(inventory, 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)
|
||||
{
|
||||
int result = m_ERRHQResponseProof.Tick(now);
|
||||
if (result < 0) { ERR_Check(false, "native HQ response evidence failed; inspect report lineage diagnostics"); return; }
|
||||
if (result == 0) 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) == ExtractionVehicle(), "physical extraction occupancy")) return;
|
||||
// Controlled transport tests the boundary detector, not piloting skill.
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#ifndef WORKBENCH
|
||||
modded class ArmaReforgerScripted
|
||||
{
|
||||
override protected ResourceName ERR_DiagnosticHeader(string project)
|
||||
{
|
||||
if (project == "chat") return "{C1E20667DCE324ED}Missions/ERR_Everon.conf";
|
||||
return super.ERR_DiagnosticHeader(project);
|
||||
}
|
||||
|
||||
override protected string ERR_DiagnosticAddon(string project)
|
||||
{
|
||||
if (project == "chat") return "FA804F411BDDF2C6";
|
||||
return super.ERR_DiagnosticAddon(project);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+51
@@ -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