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

388 lines
20 KiB
C

#ifdef WORKBENCH
// Failure diagnostics only. Every query is read-only and uses the production seed/volume rules.
class ERR_NativeConvoyRoadSurvey
{
void Run(vector seed)
{
if (!ERR_NativeConvoyProbe.Enabled()) return;
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
if (!world || !world.GetRoadNetworkManager())
{
Print("ERR CONVOY SURVEY: native AI world or road-network manager unavailable"); return;
}
array<BaseRoad> roads = {};
vector low = seed + "-500 -1000 -500", high = seed + "500 1000 500";
world.GetRoadNetworkManager().GetRoadsInAABB(low, high, roads);
PrintFormat("ERR CONVOY SURVEY: seed=%1 AABB=%2..%3 candidates=%4; inspecting at most16, production cap128",
seed, low, high, roads.Count());
for (int r = 0; r < roads.Count() && r < 16; r++)
{
BaseRoad road = roads[r];
if (!road) { Print("ERR CONVOY SURVEY: null candidate"); continue; }
array<vector> points = {}; road.GetPoints(points);
float length, nearest = 1e20;
for (int i = 1; i < points.Count() && i <= 1024; i++)
{
vector delta = points[i] - points[i - 1]; delta[1] = 0;
float segment = delta.Length(); length += segment;
if (segment < 0.01) continue;
vector offset = seed - points[i - 1]; offset[1] = 0;
float fraction = Math.Clamp(vector.Dot(offset, delta) / (segment * segment), 0, 1);
nearest = Math.Min(nearest, vector.DistanceXZ(seed, vector.Lerp(points[i - 1], points[i], fraction)));
}
vector first, last;
if (!points.IsEmpty()) { first = points[0]; last = points[points.Count() - 1]; }
PrintFormat("ERR CONVOY SURVEY: road=%1 width=%2 points=%3 XZlength=%4 nearest=%5 first=%6 last=%7",
r, road.GetWidth(), points.Count(), length, nearest, first, last);
array<vector> positions, angles;
ERR_ForceRoute route;
float proximity;
bool polyline = ERR_ConvoyRoadPlan.FromPolyline(points, seed, positions, angles, route, proximity);
PrintFormat("ERR CONVOY SURVEY: road=%1 widthAllowed=%2 FromPolyline=%3 proximity=%4",
r, road.GetWidth() >= 4, polyline, proximity);
if (!polyline) continue;
for (int i = 0; i < positions.Count(); i++) positions[i] = ERR_Actors.Ground(positions[i]);
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
for (int i = 0; i < positions.Count() && i < 6; i++)
{
bool clear = placement.Clear(positions[i], angles[i][0]);
PrintFormat("ERR CONVOY SURVEY: road=%1 vehicle=%2 ground=%3 heading=%4 GroundClear=%5",
r, i, positions[i], angles[i][0], clear);
Print("ERR CONVOY SURVEY: " + placement.Diagnostic());
}
}
}
}
// Opt-in native fixture: production road survey, canonical HQ dispatch and native driving.
// The fixture does not create vehicles, seat crew, move entities or advance convoy service.
class ERR_NativeConvoyProbe
{
protected ERR_GameMode m_Mode;
protected ref ERR_NativeConvoyCoverProbe m_Cover;
protected ref ERR_ConvoyTask m_Task;
protected ref array<vector> m_Origins = {};
protected ref array<IEntity> m_OriginalVehicles = {};
protected ref array<IEntity> m_CargoBefore = {};
protected float m_Deadline;
protected float m_ReadyAt;
protected float m_NextLog;
protected float m_NextMovementDiagnostic;
protected int m_InitialLedger;
protected int m_Step;
protected bool m_Failed;
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1"
&& System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "convoy";
}
void ERR_NativeConvoyProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string description)
{
if (condition) { Print("ERR SMOKE PASS: convoy " + description); return true; }
m_Failed = true; Print("ERR SMOKE FAIL: convoy " + description, LogLevel.ERROR); return false;
}
protected InventoryStorageManagerComponent TruckInventory()
{
if (!m_Task || m_Task.Vehicles.Count() != 6 || !m_Task.Vehicles[1].Entity) return null;
return InventoryStorageManagerComponent.Cast(m_Task.Vehicles[1].Entity.FindComponent(InventoryStorageManagerComponent));
}
protected void PopulationDiagnostic(ERR_ConvoyVehicle vehicle, array<AIAgent> agents, int expected)
{
PrintFormat("ERR CONVOY POPULATION: vehicle=%1 registered=%2 members=%3 seats=%4 agents=%5 expected=%6",
vehicle.Index, vehicle.Registered, vehicle.Members.Count(), vehicle.Seats.Count(), agents.Count(), expected);
if (vehicle.Index != 0) return;
for (int i = 0; i < agents.Count() && i < 19; i++)
{
AIAgent agent = agents[i];
IEntity controlled;
AIGroup parent;
if (agent) { controlled = agent.GetControlledEntity(); parent = agent.GetParentGroup(); }
string prefab = "none";
if (controlled && controlled.GetPrefabData()) prefab = controlled.GetPrefabData().GetPrefabName();
PrintFormat("ERR CONVOY POPULATION: returned agent=%1 controlled=%2 prefab=%3 parent=%4 expectedGroup=%5",
agent, controlled, prefab, parent, vehicle.Group);
}
for (int i = 0; i < vehicle.Members.Count() && i < 19; i++)
{
IEntity actor = vehicle.Members[i];
AIControlComponent control;
AIAgent agent;
AIGroup parent;
if (actor) control = AIControlComponent.Cast(actor.FindComponent(AIControlComponent));
if (control) agent = control.GetControlAIAgent();
if (agent) parent = agent.GetParentGroup();
PrintFormat("ERR CONVOY POPULATION: intended member=%1 actor=%2 controlAgent=%3 parent=%4 expectedGroup=%5",
i, actor, agent, parent, vehicle.Group);
}
}
protected string MovementAction(AIActionBase action)
{
if (!action) return "none";
SCR_AIActionBase scripted = SCR_AIActionBase.Cast(action);
string detail = action.ToString();
if (scripted) detail = scripted.GetActionDebugInfo();
if (detail.Length() > 200) detail = detail.Substring(0, 200);
return string.Format("%1 state=%2 suspended=%3", detail, action.GetActionState(), action.GetActionIsSuspended());
}
protected void MovementDiagnostic(float now)
{
if (!m_Task || now < m_NextMovementDiagnostic || m_Origins.Count() != 6) return;
m_NextMovementDiagnostic = now + 30000;
for (int i = 0; i < m_Task.Vehicles.Count() && i < 6; i++)
{
ERR_ConvoyVehicle unit = m_Task.Vehicles[i];
if (!unit || !unit.Entity || !unit.Group) { PrintFormat("ERR CONVOY MOTION: vehicle=%1 missing original identity", i); continue; }
PrintFormat("ERR CONVOY MOTION: vehicle=%1 held=%2 command=%3 report=%4 observed=%5",
i, unit.Combat.Held, unit.Combat.LastCommandId, unit.Combat.ReportId, unit.Combat.ObservedAt);
AIWaypoint current = unit.Group.GetCurrentWaypoint();
vector target, nativeTarget;
float targetDistance = -1;
bool nativeWithin;
if (unit.Waypoint)
{
target = unit.Waypoint.GetOrigin();
targetDistance = vector.DistanceXZ(unit.Entity.GetOrigin(), target);
nativeWithin = unit.Waypoint.IsWithinCompletionRadius(unit.Group);
}
if (current) nativeTarget = current.GetOrigin();
PrintFormat("ERR CONVOY MOTION: vehicle=%1 travel=%2 pos=%3 routeIndex=%4 released=%5 arrived=%6 groupOrigin=%7",
i, vector.DistanceXZ(m_Origins[i], unit.Entity.GetOrigin()), unit.Entity.GetOrigin(), unit.RouteIndex,
unit.Released, unit.Arrived, unit.Group.GetOrigin());
PrintFormat("ERR CONVOY MOTION: vehicle=%1 ownWaypoint=%2 currentWaypoint=%3 same=%4 target=%5 distance=%6 nativeWithin=%7 nativeTarget=%8",
i, unit.Waypoint != null, current != null, current == unit.Waypoint, target, targetDistance, nativeWithin, nativeTarget);
SCR_AIGroupUtilityComponent groupUtility = unit.Group.GetGroupUtilityComponent();
string groupAction = "no group utility", driverBehavior = "no driver utility";
if (groupUtility) groupAction = MovementAction(groupUtility.GetCurrentAction());
AIControlComponent control;
if (unit.Driver) control = AIControlComponent.Cast(unit.Driver.FindComponent(AIControlComponent));
AIAgent driverAgent;
if (control) driverAgent = control.GetControlAIAgent();
SCR_AIUtilityComponent driverUtility;
if (driverAgent) driverUtility = SCR_AIUtilityComponent.Cast(driverAgent.FindComponent(SCR_AIUtilityComponent));
if (driverUtility) driverBehavior = MovementAction(driverUtility.GetCurrentBehavior());
PrintFormat("ERR CONVOY MOTION: vehicle=%1 groupAction=%2 driverBehavior=%3", i, groupAction, driverBehavior);
}
}
// Failure-only readout distinguishes incapacitation, ownership, native seat
// switching and dismounts without changing the exact original-seat assertion.
protected void SeatDiagnostic(ERR_ConvoyVehicle vehicle, int index, IEntity actor, BaseCompartmentSlot intended, bool duplicateActor, bool duplicateSeat)
{
IEntity aboard, occupant;
SCR_AIGroup actualGroup;
BaseCompartmentSlot actual;
ChimeraCharacter character = ChimeraCharacter.Cast(actor);
if (actor) { aboard = CompartmentAccessComponent.GetVehicleIn(actor); actualGroup = ERR_Actors.Group(actor); }
if (intended) occupant = intended.GetOccupant();
if (character && character.GetCompartmentAccessComponent()) actual = character.GetCompartmentAccessComponent().GetCompartment();
int intendedType = -1, actualType = -1, actualManager = -1, actualSlot = -1;
if (intended) intendedType = intended.GetType();
if (actual) { actualType = actual.GetType(); actualManager = actual.GetCompartmentMgrID(); actualSlot = actual.GetCompartmentSlotID(); }
PrintFormat("ERR CONVOY SEAT: vehicle=%1 member=%2 actor=%3 alive=%4 conscious=%5 groupMatches=%6 duplicateActor=%7 duplicateSeat=%8",
vehicle.Index, index, actor, ERR_Actors.Alive(actor), ERR_Actors.CanFight(actor), actualGroup == vehicle.Group, duplicateActor, duplicateSeat);
PrintFormat("ERR CONVOY SEAT: intended=%1 occupant=%2 type=%3 actual=%4 actualType=%5 manager=%6 slot=%7 sameSeat=%8",
intended, occupant, intendedType, actual, actualType, actualManager, actualSlot, actual == intended);
PrintFormat("ERR CONVOY SEAT: aboard=%1 originalVehicle=%2 originalVehicleTree=%3",
aboard, vehicle.Entity, ERR_ConvoyRules.PartOf(aboard, vehicle.Entity));
AIControlComponent control;
AIAgent agent;
SCR_AIUtilityComponent utility;
if (actor) control = AIControlComponent.Cast(actor.FindComponent(AIControlComponent));
if (control) agent = control.GetControlAIAgent();
if (agent) utility = SCR_AIUtilityComponent.Cast(agent.FindComponent(SCR_AIUtilityComponent));
string behavior = "no actor utility", groupAction = "no group utility";
if (utility) behavior = MovementAction(utility.GetCurrentBehavior());
SCR_AIGroupUtilityComponent groupUtility = vehicle.Group.GetGroupUtilityComponent();
if (groupUtility) groupAction = MovementAction(groupUtility.GetCurrentAction());
PrintFormat("ERR CONVOY SEAT: actorBehavior=%1 groupAction=%2", behavior, groupAction);
}
protected bool Population(ERR_HQ hq)
{
if (!Check(m_Task.Vehicles.Count() == 6, "six original vehicles remain tracked")) return false;
array<IEntity> vehicles = {}, crew = {};
array<SCR_AIGroup> groups = {};
array<BaseCompartmentSlot> slots = {};
for (int i = 0; i < 6; i++)
{
ERR_ConvoyVehicle vehicle = m_Task.Vehicles[i];
if (!Check(vehicle.Entity && vehicle.Entity == m_OriginalVehicles[i] && ERR_Actors.Alive(vehicle.Entity)
&& !vehicles.Contains(vehicle.Entity) && vehicle.Group && !groups.Contains(vehicle.Group),
"vehicle and group identity " + i.ToString())) return false;
vehicles.Insert(vehicle.Entity); groups.Insert(vehicle.Group);
int expected = ERR_ConvoyPool.CrewCount(i);
array<AIAgent> agents = {}; vehicle.Group.GetAgents(agents);
PopulationDiagnostic(vehicle, agents, expected);
if (!Check(vehicle.Registered && vehicle.Members.Count() == expected && vehicle.Seats.Count() == expected
&& agents.Count() == expected, "native group membership matches exact crew plan " + i.ToString())) return false;
ERR_ForceRecord record = m_Mode.Forces().FindGroup(vehicle.Group);
if (!Check(record && record.Kind == ERR_ForceKind.CONVOY && record.Physical && !record.Eliminated
&& record.Strength == expected && record.InitialStrength == expected
&& record.Id == m_Task.Receipt.Records[i], "Core ledger owns physical convoy group " + i.ToString())) return false;
for (int seatIndex = 0; seatIndex < expected; seatIndex++)
{
IEntity actor = vehicle.Members[seatIndex];
BaseCompartmentSlot seat = vehicle.Seats[seatIndex];
bool occupied = actor && ERR_Actors.CanFight(actor) && !crew.Contains(actor)
&& ERR_Actors.Group(actor) == vehicle.Group && seat && !slots.Contains(seat) && seat.GetOccupant() == actor
&& ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(actor), vehicle.Entity);
if (!occupied) SeatDiagnostic(vehicle, seatIndex, actor, seat, crew.Contains(actor), slots.Contains(seat));
if (!Check(occupied, "original crew occupies intended native compartment " + crew.Count().ToString())) return false;
crew.Insert(actor); slots.Insert(seat);
bool nativeMember;
foreach (AIAgent agent : agents) if (agent && agent.GetControlledEntity() == actor) nativeMember = true;
if (!Check(nativeMember, "seated actor is a real native group member")) return false;
}
if (!Check(vehicle.Driver == vehicle.Members[0]
&& vehicle.Seats[0].GetType() == ECompartmentType.PILOT, "actual driver seat " + i.ToString())) return false;
}
return Check(crew.Count() == 19 && hq.ConvoyPool().CommittedBodies() <= 60
&& m_Task.Receipt.OutstandingBodies == 0 && m_Task.Receipt.OutstandingGroups == 0,
"nineteen unique crew admitted once within shared sixty-body cap");
}
protected int Cargo(float now)
{
if (!m_Task.CargoIssued) return 0;
if (m_Task.Cargo.Count() != 56) { Check(false, "cargo request count differs from56"); return -1; }
int rifles, magazines;
foreach (ERR_ConvoyCargoCallback operation : m_Task.Cargo)
{
if (!operation || !operation.Finished)
{
if (now - m_ReadyAt > 60000) { Check(false, "cargo callback deadline"); return -1; }
return 0;
}
if (!operation.Succeeded) { Check(false, "native cargo insertion failed"); return -1; }
if (operation.Prefab == ERR_ConvoyResources.Rifle()) rifles++;
if (operation.Prefab == ERR_ConvoyResources.Magazine()) magazines++;
}
if (!Check(rifles == 8 && magazines == 48, "cargo requests are eight rifles and forty-eight magazines")) return -1;
InventoryStorageManagerComponent inventory = TruckInventory();
if (!Check(inventory != null, "real ammunition truck inventory exists")) return -1;
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
rifles = 0; magazines = 0;
foreach (IEntity item : items)
{
if (!item || m_CargoBefore.Contains(item) || !item.GetPrefabData()) continue;
ResourceName prefab = item.GetPrefabData().GetPrefabName();
if (prefab == ERR_ConvoyResources.Rifle()) rifles++;
if (prefab == ERR_ConvoyResources.Magazine()) magazines++;
}
if (!Check(rifles == 8 && magazines >= 48,
"eight new physical rifles and at least forty-eight magazines found in truck")) return -1;
return 1;
}
static bool MovementComplete(float leaderMetres, float minimumMetres)
{
return ERR_ForceRules.Finite(leaderMetres) && ERR_ForceRules.Finite(minimumMetres)
&& leaderMetres >= 100 && minimumMetres >= 20;
}
int Tick(ERR_HQ hq, float now)
{
if (!Enabled() || !hq || !m_Mode || m_Mode != ERR_GameMode.Get() || m_Failed) return -1;
if (m_Step == 4) return m_Cover.Tick(hq, now);
if (m_Deadline == 0) m_Deadline = now + 480000;
if (now >= m_Deadline) { Check(false, "native fixture deadline"); return -1; }
if (m_Step == 0)
{
// Existing finite support must finish ordinary readiness before measuring the ledger baseline.
array<ref ERR_MortarBattery> batteries = {}; hq.Mortars().Batteries(batteries);
foreach (ERR_MortarBattery battery : batteries)
{
if (!battery || battery.Stopped()) { Check(false, "baseline mortar readiness failed"); return -1; }
if (!battery.StartupActivated) return 0;
}
// Canonical authored supply-site seed; a missing suitable real road is a test failure.
ERR_SiteRuntime site = m_Mode.Sites().Find("everon-supply-1");
if (!Check(site && site.Definition, "authored supply-site road survey seed exists")) return -1;
vector seed = site.Definition.Position;
m_InitialLedger = m_Mode.Forces().Count();
Print("ERR CONVOY NATIVE: road survey seed=" + seed.ToString());
if (!hq.PrepareConvoy(seed))
{
ERR_NativeConvoyRoadSurvey survey = new ERR_NativeConvoyRoadSurvey(); survey.Run(seed);
Check(false, "production road survey refused seed; read-only diagnostics above"); return -1;
}
Check(true, "production road survey accepted six real road positions");
if (!Check(hq.StartConvoy(), "canonical HQ dispatch accepted once")) return -1;
m_Task = hq.ConvoyTask();
if (!Check(m_Task && m_Task.Receipt && m_Task.Receipt.Consumed && m_Task.Vehicles.Count() == 6,
"canonical receipt owns six attempted physical vehicles")) return -1;
foreach (ERR_ConvoyVehicle vehicle : m_Task.Vehicles)
{
if (!Check(vehicle.Entity != null, "original physical vehicle created")) return -1;
m_OriginalVehicles.Insert(vehicle.Entity); m_Origins.Insert(vehicle.Entity.GetOrigin());
}
ERR_Inventory.Items(TruckInventory(), m_CargoBefore);
if (!Check(!hq.StartConvoy() && hq.ConvoyTask() == m_Task && hq.Convoys().Count() == 1,
"duplicate canonical dispatch creates no second native task")) return -1;
if (!Check(!hq.ConvoyPool().Reserve("native-over-cap"), "second convoy reservation rejected by finite cap")) return -1;
m_Step = 1;
}
bool completed = m_Task && m_Task.Vehicles.Count() == 6;
if (m_Task) foreach (ERR_ConvoyVehicle candidate : m_Task.Vehicles)
if (!candidate.Arrived) completed = false;
if (!m_Task || (m_Task.Stopped && !completed))
{
string reason = "missing task";
if (m_Task) reason = m_Task.Reason;
Check(false, "production convoy stopped before proof: " + reason); return -1;
}
if (m_Step == 1)
{
if (!m_Task.Ready) return 0;
m_ReadyAt = now;
if (!Population(hq)) return -1;
ERR_ConvoyPlacement occupied = new ERR_ConvoyPlacement();
Vehicle lead = m_Task.Vehicles[0].Entity;
bool empty = occupied.Clear(ERR_Actors.Ground(lead.GetOrigin()), lead.GetAngles()[0]);
float fraction = occupied.LastTraceFraction();
Print("ERR CONVOY NATIVE: existing lead vehicle blocker control; " + occupied.Diagnostic());
if (!Check(!empty && ERR_ForceRules.Finite(fraction) && fraction >= 0 && fraction < 1
&& occupied.Diagnostic().IndexOf("native filled volume collision") >= 0,
"native filled volume detects the actual lead vehicle as a positive blocker")) return -1;
if (!Check(m_Mode.Forces().Count() == m_InitialLedger + 6,
"exactly six new Core records after native readiness")) return -1;
m_Step = 2;
}
if (m_Step == 2)
{
int cargo = Cargo(now);
if (cargo <= 0) return cargo;
m_Step = 3;
}
float minimum = 1000000;
for (int i = 0; i < 6; i++)
{
ERR_ConvoyVehicle vehicle = m_Task.Vehicles[i];
if (!vehicle.Entity || (vehicle.Released && !vehicle.Arrived)) { Check(false, "vehicle released before road movement proof"); return -1; }
minimum = Math.Min(minimum, vector.DistanceXZ(m_Origins[i], vehicle.Entity.GetOrigin()));
}
float leader = vector.DistanceXZ(m_Origins[0], m_Task.Vehicles[0].Entity.GetOrigin());
if (now >= m_NextLog)
{
m_NextLog = now + 10000;
PrintFormat("ERR CONVOY NATIVE: leader travel=%1m minimum vehicle travel=%2m; age=%3s",
leader, minimum, (now - m_ReadyAt) / 1000);
}
MovementDiagnostic(now);
if (!MovementComplete(leader, minimum)) return 0;
if (!Check(m_Task.Route && m_Task.Route.Count() >= 2 && !m_Task.Route.Looped(),
"driving uses finite road-polyline route")) return -1;
if (!Population(hq)) return -1;
if (!Check(!hq.StartConvoy() && hq.Convoys().Count() == 1 && hq.ConvoyTask() == m_Task
&& m_Mode.Forces().Count() == m_InitialLedger + 6 && m_Task.Cargo.Count() == 56,
"later repeated dispatch preserves exact task, force and cargo counts")) return -1;
Check(true, "all six native vehicles drove at least 20m and leader at least 100m without fixture movement");
if (ERR_NativeConvoyCoverProbe.Enabled())
{
m_Cover = new ERR_NativeConvoyCoverProbe(m_Mode); m_Step = 4;
return 0;
}
return 1;
}
void Stop() { if (m_Cover) m_Cover.Stop(); }
}
#endif