252 lines
12 KiB
C
252 lines
12 KiB
C
#ifdef WORKBENCH
|
|
// Opt-in controlled arrangement of the existing local prisoner only. Production
|
|
// perception, radio intake, planning and downlink execution remain unchanged.
|
|
modded class ERR_HQ
|
|
{
|
|
protected override void Observe(ERR_HQUnit unit, float now)
|
|
{
|
|
ERR_HQReport before = unit.PendingReport;
|
|
super.Observe(unit, now);
|
|
ERR_NativeConvoyCoverProbe proof = ERR_NativeConvoyCoverProbe.Active();
|
|
if (proof && unit.PendingReport && unit.PendingReport != before) proof.Observed(unit, now);
|
|
}
|
|
protected override void Accept(ERR_HQReport report, float now)
|
|
{
|
|
super.Accept(report, now);
|
|
ERR_NativeConvoyCoverProbe proof = ERR_NativeConvoyCoverProbe.Active();
|
|
if (!proof || !report) return;
|
|
foreach (ERR_HQReport accepted : m_Contacts)
|
|
if (accepted.MessageId == report.MessageId) { proof.Accepted(accepted, now); break; }
|
|
}
|
|
protected override void DeliverConvoyOrder(ERR_HQUnit unit, float now)
|
|
{
|
|
ERR_ConvoyCombatOrder order = unit.PendingConvoyOrder;
|
|
ERR_HQRoute route = unit.Downlink;
|
|
bool hadWaypoint;
|
|
ERR_ConvoyTask task = ConvoyTask();
|
|
if (task && !task.Vehicles.IsEmpty()) hadWaypoint = task.Vehicles[0].Waypoint != null;
|
|
super.DeliverConvoyOrder(unit, now);
|
|
ERR_NativeConvoyCoverProbe proof = ERR_NativeConvoyCoverProbe.Active();
|
|
if (proof && order && route) proof.Delivered(order, route, hadWaypoint, now);
|
|
}
|
|
}
|
|
class ERR_NativeConvoyCoverReport
|
|
{
|
|
ref ERR_HQReport Report;
|
|
ref ERR_HQRoute Uplink;
|
|
bool Accepted;
|
|
}
|
|
class ERR_NativeConvoyCoverProbe
|
|
{
|
|
protected static ERR_NativeConvoyCoverProbe s_Active;
|
|
protected ERR_GameMode m_Mode;
|
|
protected ERR_HQ m_HQ;
|
|
protected ERR_ConvoyTask m_Task;
|
|
protected ERR_ConvoyVehicle m_Lead;
|
|
protected IEntity m_Player;
|
|
protected SCR_PlayerController m_Controller;
|
|
protected ERR_PlayerLife m_Life;
|
|
protected DamageManagerComponent m_Damage;
|
|
protected bool m_DamageWasEnabled;
|
|
protected bool m_Protected;
|
|
protected bool m_Stopped;
|
|
protected bool m_Failed;
|
|
protected bool m_Held;
|
|
protected bool m_Resumed;
|
|
protected int m_RouteIndex;
|
|
protected int m_LedgerCount;
|
|
protected float m_Deadline;
|
|
protected float m_HeldAt;
|
|
protected float m_ResumedAt;
|
|
protected float m_NextLog;
|
|
protected vector m_HoldPosition;
|
|
protected vector m_ResumePosition;
|
|
protected vector m_HidePosition;
|
|
protected ref array<IEntity> m_Forces = {};
|
|
protected ref array<ref ERR_NativeConvoyCoverReport> m_Reports = {};
|
|
protected int m_Step;
|
|
static bool Enabled()
|
|
{
|
|
string flag;
|
|
return ERR_NativeConvoyProbe.Enabled() && System.GetCLIParam("errConvoyCover", flag) && flag == "1";
|
|
}
|
|
static ERR_NativeConvoyCoverProbe Active() { return s_Active; }
|
|
void ERR_NativeConvoyCoverProbe(ERR_GameMode mode) { m_Mode = mode; }
|
|
protected bool Check(bool condition, string detail)
|
|
{
|
|
if (condition) { Print("ERR SMOKE PASS: convoy cover " + detail); return true; }
|
|
m_Failed = true; Print("ERR SMOKE FAIL: convoy cover " + detail, LogLevel.ERROR); Stop(); return false;
|
|
}
|
|
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 UnchangedState()
|
|
{
|
|
if (!Canonical() || !m_Task || m_Task.Stopped || m_Task.Vehicles.Count() != 6
|
|
|| m_Mode.Forces().Count() != m_LedgerCount || m_Task.Cargo.Count() != 56) return false;
|
|
foreach (ERR_ConvoyVehicle vehicle : m_Task.Vehicles)
|
|
if (!vehicle || vehicle.Released || !vehicle.CrewValid(m_Mode)) return false;
|
|
array<IEntity> current = {}; m_HQ.PhysicalForces(current);
|
|
if (current.Count() != m_Forces.Count()) return false;
|
|
foreach (IEntity entity : current) if (!m_Forces.Contains(entity)) return false;
|
|
return true;
|
|
}
|
|
protected bool Unchanged()
|
|
{
|
|
if (UnchangedState()) return true;
|
|
// Read-only, failure-only evidence. Preserve the original invariant result.
|
|
PrintFormat("ERR CONVOY COVER INVARIANT: canonical=%1 player=%2 controller=%3 life=%4 ledger=%5 expectedLedger=%6",
|
|
Canonical(), m_Player != null, m_Controller != null, m_Life != null, m_Mode.Forces().Count(), m_LedgerCount);
|
|
if (!m_Task) { Print("ERR CONVOY COVER INVARIANT: missing original task"); return false; }
|
|
PrintFormat("ERR CONVOY COVER INVARIANT: stopped=%1 reason=%2 ready=%3 vehicles=%4 cargoCallbacks=%5",
|
|
m_Task.Stopped, m_Task.Reason, m_Task.Ready, m_Task.Vehicles.Count(), m_Task.Cargo.Count());
|
|
for (int index = 0; index < m_Task.Vehicles.Count() && index < 6; index++)
|
|
{
|
|
ERR_ConvoyVehicle vehicle = m_Task.Vehicles[index];
|
|
if (!vehicle) { PrintFormat("ERR CONVOY COVER INVARIANT: missing vehicle record=%1", index); continue; }
|
|
bool crewValid = vehicle.CrewValid(m_Mode);
|
|
PrintFormat("ERR CONVOY COVER INVARIANT: vehicle=%1 released=%2 arrived=%3 crewValid=%4 entityAlive=%5 group=%6 routeIndex=%7 waypoint=%8",
|
|
index, vehicle.Released, vehicle.Arrived, crewValid, ERR_Actors.Alive(vehicle.Entity), vehicle.Group, vehicle.RouteIndex, vehicle.Waypoint != null);
|
|
if (crewValid) continue;
|
|
IEntity driverVehicle;
|
|
if (vehicle.Driver) driverVehicle = CompartmentAccessComponent.GetVehicleIn(vehicle.Driver);
|
|
PrintFormat("ERR CONVOY COVER INVARIANT: vehicle=%1 driverConscious=%2 driverPlayer=%3 driverAboardOriginal=%4",
|
|
index, ERR_Actors.CanFight(vehicle.Driver), ERR_Actors.Player(vehicle.Driver),
|
|
ERR_ConvoyRules.PartOf(driverVehicle, vehicle.Entity));
|
|
for (int memberIndex = 0; memberIndex < vehicle.Members.Count() && memberIndex < 19; memberIndex++)
|
|
{
|
|
IEntity member = vehicle.Members[memberIndex];
|
|
PrintFormat("ERR CONVOY COVER INVARIANT: vehicle=%1 member=%2 actor=%3 alive=%4 player=%5 sameGroup=%6",
|
|
index, memberIndex, member, ERR_Actors.Alive(member), ERR_Actors.Player(member), ERR_Actors.Group(member) == vehicle.Group);
|
|
}
|
|
}
|
|
array<IEntity> current = {}; m_HQ.PhysicalForces(current);
|
|
PrintFormat("ERR CONVOY COVER INVARIANT: currentForces=%1 initialForces=%2", current.Count(), m_Forces.Count());
|
|
int differences;
|
|
foreach (IEntity added : current)
|
|
if (!m_Forces.Contains(added) && differences++ < 8) PrintFormat("ERR CONVOY COVER INVARIANT: newly listed physical entity=%1", added);
|
|
differences = 0;
|
|
foreach (IEntity missing : m_Forces)
|
|
if (!current.Contains(missing) && differences++ < 8) PrintFormat("ERR CONVOY COVER INVARIANT: no longer listed physical entity=%1", missing);
|
|
return false;
|
|
}
|
|
protected void MovePrisoner(vector position)
|
|
{
|
|
vector transform[4]; m_Player.GetWorldTransform(transform);
|
|
transform[3] = ERR_Actors.Ground(position);
|
|
ChimeraCharacter.Cast(m_Player).Teleport(transform);
|
|
}
|
|
protected ERR_NativeConvoyCoverReport Find(int id)
|
|
{
|
|
foreach (ERR_NativeConvoyCoverReport entry : m_Reports)
|
|
if (entry.Report.MessageId == id) return entry;
|
|
return null;
|
|
}
|
|
void Observed(ERR_HQUnit unit, float now)
|
|
{
|
|
if (m_Stopped || !unit.PendingReport || unit.PendingReport.Target != m_Player || !unit.Uplink) return;
|
|
IEntity observer = unit.Utility.ERR_ResponseVisualObserver(unit.PendingReport, now);
|
|
if (!observer || !m_Forces.Contains(observer) || m_Reports.Count() >= 64) return;
|
|
ERR_NativeConvoyCoverReport entry = new ERR_NativeConvoyCoverReport();
|
|
entry.Report = unit.PendingReport.Snapshot(); entry.Uplink = unit.Uplink; m_Reports.Insert(entry);
|
|
}
|
|
void Accepted(ERR_HQReport report, float now)
|
|
{
|
|
if (m_Stopped) return;
|
|
ERR_NativeConvoyCoverReport entry = Find(report.MessageId);
|
|
if (!entry) return;
|
|
entry.Accepted = !entry.Uplink.Failed && entry.Uplink.StartedAt >= 0
|
|
&& entry.Uplink.Cursor >= entry.Uplink.Nodes.Count();
|
|
}
|
|
void Delivered(ERR_ConvoyCombatOrder order, ERR_HQRoute route, bool hadWaypoint, float now)
|
|
{
|
|
if (m_Stopped || !m_Lead || order.Group != m_Lead.Group
|
|
|| m_Lead.Combat.LastCommandId != order.CommandId || route.Failed) return;
|
|
if (!Check(route.Downlink && route.StartedAt >= 0 && route.Cursor == route.Nodes.Count() - 1,
|
|
"original crew received its real completed downlink")) return;
|
|
if (order.Hold)
|
|
{
|
|
ERR_NativeConvoyCoverReport entry = Find(order.ReportId);
|
|
if (!Check(entry && entry.Accepted && entry.Report.Target == m_Player,
|
|
"HOLD traces to actual original observer perception and delivered sighting")) return;
|
|
if (m_Held) return;
|
|
if (!Check(hadWaypoint && m_Lead.Combat.Held && !m_Lead.Waypoint,
|
|
"delivered HOLD removed the owned native road waypoint")) return;
|
|
m_Held = true; m_HeldAt = now; m_RouteIndex = m_Lead.RouteIndex;
|
|
m_HoldPosition = m_Lead.Entity.GetOrigin();
|
|
}
|
|
else
|
|
{
|
|
if (!Check(m_Held && !hadWaypoint && !m_Lead.Combat.Held && m_Lead.Waypoint
|
|
&& m_Lead.RouteIndex == m_RouteIndex && now - order.ObservedAt >= 60000,
|
|
"delivered RESUME restores one road waypoint at the preserved index after quiet evidence")) return;
|
|
m_Resumed = true; m_ResumedAt = now; m_ResumePosition = m_Lead.Entity.GetOrigin();
|
|
}
|
|
}
|
|
int Tick(ERR_HQ hq, float now)
|
|
{
|
|
if (!Enabled() || !hq || m_Failed || m_Stopped) return -1;
|
|
if (m_Step == 0)
|
|
{
|
|
m_HQ = hq; m_Task = hq.ConvoyTask();
|
|
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
|
if (m_Controller) m_Player = m_Controller.GetControlledEntity();
|
|
m_Life = m_Mode.Players().CurrentActor(m_Player);
|
|
if (!Check(Canonical() && m_Task && m_Task.Ready && m_Task.Vehicles.Count() == 6,
|
|
"canonical local prisoner and previously proven physical convoy selected")) return -1;
|
|
m_Lead = m_Task.Vehicles[0];
|
|
ERR_SiteRuntime hide = m_Mode.Sites().Find("everon-extraction-1");
|
|
if (!Check(hide && hide.Definition && !m_Lead.Combat.Held && m_Lead.Waypoint
|
|
&& vector.DistanceXZ(hide.Definition.Position, m_Lead.Entity.GetOrigin()) >= 800,
|
|
"lead has a live road order and authored distant concealment destination")) return -1;
|
|
m_HidePosition = hide.Definition.Position;
|
|
m_Damage = DamageManagerComponent.Cast(m_Player.FindComponent(DamageManagerComponent));
|
|
if (!Check(m_Damage != null, "existing prisoner native damage component found")) return -1;
|
|
m_DamageWasEnabled = m_Damage.IsDamageHandlingEnabled(); m_Damage.EnableDamageHandling(false); m_Protected = true;
|
|
m_HQ.PhysicalForces(m_Forces); m_LedgerCount = m_Mode.Forces().Count();
|
|
vector vehicleTransform[4]; m_Lead.Entity.GetWorldTransform(vehicleTransform);
|
|
s_Active = this; m_Deadline = now + 300000; m_Step = 1;
|
|
MovePrisoner(m_Lead.Entity.GetOrigin() + vehicleTransform[0] * 20);
|
|
Print("ERR CONVOY COVER arrangement: existing prisoner moved beside original lead; temporary damage protection; no AI, inventory, reports, timers or radio routes changed.");
|
|
}
|
|
if (!Unchanged()) { Check(false, "original physical forces or canonical player changed"); return -1; }
|
|
if (now >= m_Deadline) { Check(false, "bounded sighting/hold/resume deadline"); return -1; }
|
|
if (now >= m_NextLog)
|
|
{
|
|
m_NextLog = now + 10000;
|
|
PrintFormat("ERR CONVOY COVER: step=%1 held=%2 resumed=%3 report=%4 observed=%5 routeIndex=%6 waypoint=%7 reports=%8",
|
|
m_Step, m_Lead.Combat.Held, m_Resumed, m_Lead.Combat.ReportId, m_Lead.Combat.ObservedAt, m_Lead.RouteIndex, m_Lead.Waypoint != null, m_Reports.Count());
|
|
}
|
|
if (m_Step == 1 && m_Held)
|
|
{
|
|
if (m_Lead.Waypoint || m_Lead.RouteIndex != m_RouteIndex) { Check(false, "held native road order unexpectedly advanced"); return -1; }
|
|
if (now - m_HeldAt < 5000) return 0;
|
|
if (!Check(true, "owned road waypoint stayed absent and route index stable for five seconds")) return -1;
|
|
PrintFormat("ERR CONVOY COVER held native movement=%1m (native combat movement is not suppressed)", vector.DistanceXZ(m_HoldPosition, m_Lead.Entity.GetOrigin()));
|
|
MovePrisoner(m_HidePosition); m_Step = 2;
|
|
Print("ERR CONVOY COVER arrangement: same prisoner moved to distant authored extraction site; native visibility and radio must expire nearby evidence.");
|
|
}
|
|
if (m_Step == 2 && m_Resumed)
|
|
{
|
|
if (vector.DistanceXZ(m_ResumePosition, m_Lead.Entity.GetOrigin()) < 20) return 0;
|
|
if (!Check(!m_Lead.Combat.Held && now > m_ResumedAt && Unchanged(),
|
|
"same native lead vehicle drove twenty metres after delivered RESUME; forces and cargo unchanged")) 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_NativeConvoyCoverProbe() { Stop(); }
|
|
}
|
|
#endif
|