Files
EscapistsReintroduced/docs/evidence/truck-hunters-2026-10-05/change.patch
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

608 lines
34 KiB
Diff

--- addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c (before this task)
+++ addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c
@@ -32,6 +32,7 @@
if (end < 0) return Short(message);
string result = message.Substring(0, end);
result.Replace("aggression-uaz-", "UAZ patrol ");
+ result.Replace("aggression-truck-", "Troop-truck patrol ");
return result;
}
static string YesNo(string value)
@@ -45,7 +46,7 @@
if (kind == "REGISTER") return "Group " + Field(message, "group") + " joined HQ: " + Field(message, "members") + " soldiers, faction " + Field(message, "faction") + ". Assignment: " + Field(message, "force") + ".";
if (kind == "SPAWN MEMBER") return "Group " + Field(message, "group") + ": soldier spawned at " + Field(message, "position") + ".";
if (kind == "CONTACT LOCAL") return "Group " + Field(message, "group") + " spotted a target at " + Field(message, "observed") + ". Local combat has started; the report still needs to reach HQ.";
- if (kind == "AGGRESSION") return "HQ has identified " + Field(message, "bodies") + " bodies. Aggression: " + Field(message, "aggression") + ". Total hunter UAZ allowance: " + Field(message, "allowance") + ".";
+ if (kind == "AGGRESSION") return "HQ has identified " + Field(message, "bodies") + " bodies. Aggression: " + Field(message, "aggression") + ". Total hunting vehicle allowance: " + Field(message, "allowance") + ".";
if (kind == "HUNTER SPAWN") return Subject(message) + " deployed with " + Field(message, "crew") + " crew at " + Field(message, "position") + ". Deployment source: " + Field(message, "base") + ".";
if (kind == "HUNTER READY") return Subject(message) + ": driver and gunner are seated; registered with HQ.";
if (kind == "HUNTER ORDER") return Subject(message) + " received its radio order. Drive to " + Field(message, "destination") + ".";
--- addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c
@@ -4,8 +4,8 @@
int Id;
string SourceId;
bool Consumed;
- int OutstandingBodies = 19;
- int OutstandingGroups = 6;
+ int OutstandingBodies = 10;
+ int OutstandingGroups = 5;
ref array<int> Records = {-1, -1, -1, -1, -1, -1};
}
@@ -20,32 +20,29 @@
void ERR_ConvoyPool(ERR_GameMode mode, int maxConvoys, bool supply = false)
{
m_Mode = mode; m_Supply = supply; m_Limit = Math.Clamp(maxConvoys, 0, 20);
+ // Keep shared headroom for the infantry reassigned to hunters.
if (supply) { m_BodyLimit = 60 + 25 * m_Limit; m_GroupLimit = 256; }
}
static int CrewCount(int vehicle)
{
if (vehicle == 1) return 1;
- if (vehicle == 2) return 9;
+ if (vehicle == 2) return 0;
if (vehicle == 4) return 3;
if (vehicle == 0 || vehicle == 3 || vehicle == 5) return 2;
return 0;
}
int PlannedCount(int index)
{
- if (m_Supply && index == 2) return 1;
- if (m_Supply && (index == 6 || index == 7)) return 7;
return CrewCount(index);
}
int BodyLimit() { return m_BodyLimit; }
int PlannedBodies()
{
- if (m_Supply) return 25;
- return 19;
+ return 10;
}
int PlannedGroups()
{
- if (m_Supply) return 8;
- return 6;
+ return 5;
}
protected bool Current()
{
@@ -89,7 +86,6 @@
foreach (ERR_ConvoyReceipt known : m_Receipts) if (known.SourceId == sourceId) return known;
if (m_Receipts.Count() >= m_Limit || CommittedBodies() + PlannedBodies() > m_BodyLimit || CommittedGroups() + PlannedGroups() > m_GroupLimit) return null;
ERR_ConvoyReceipt receipt = new ERR_ConvoyReceipt(); receipt.Id = m_Receipts.Count() + 1; receipt.SourceId = sourceId;
- if (m_Supply) { receipt.OutstandingBodies = 25; receipt.OutstandingGroups = 8; receipt.Records.Insert(-1); receipt.Records.Insert(-1); }
m_Receipts.Insert(receipt);
return receipt;
}
@@ -101,7 +97,7 @@
}
bool BindRecord(ERR_ConvoyReceipt receipt, int vehicle, ERR_ForceRecord record)
{
- if (!Current() || !Owns(receipt) || !receipt.Consumed || vehicle < 0 || vehicle >= PlannedGroups() || !record) return false;
+ if (!Current() || !Owns(receipt) || !receipt.Consumed || vehicle < 0 || vehicle >= receipt.Records.Count() || PlannedCount(vehicle) <= 0 || !record) return false;
if (receipt.Records[vehicle] >= 0) return receipt.Records[vehicle] == record.Id;
if (m_Mode.Forces().Find(record.Id) != record || record.Kind != ERR_ForceKind.CONVOY || !record.Group || !record.Physical || record.Eliminated) return false;
if (record.SourceId != receipt.SourceId + "-vehicle-" + vehicle.ToString() || record.InitialStrength != PlannedCount(vehicle) || record.Strength != record.InitialStrength) return false;
--- addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c
@@ -5,7 +5,8 @@
static ResourceName VehiclePrefab(int index)
{
if (index == 1) return "{3336BE330C4F355B}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_ammo.et";
- if (index == 2) return "{16C1F16C9B053801}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et";
+ // Slot 2 is retired: troop transports now belong to the hunter roster.
+ // Keep other canonical slot IDs stable for receipts, cargo and HQ bindings.
if (index == 4) return "{1DC01AAACFFA8B63}Prefabs/Vehicles/Tracked/BMP1/BMP1_Base.et";
if (index == 0 || index == 3 || index == 5) return "{0B4DEA8078B78A9B}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et";
return "";
@@ -21,7 +22,7 @@
static bool Available()
{
array<ResourceName> required = {GroupPrefab(), Soldier(), Rifle(), Magazine(), ERR_Resources.MoveWaypoint()};
- for (int i = 0; i < 6; i++) required.Insert(VehiclePrefab(i));
+ for (int i = 0; i < 6; i++) if (i != 2) required.Insert(VehiclePrefab(i));
foreach (ResourceName prefab : required)
{
Resource resource = Resource.Load(prefab);
@@ -54,7 +55,7 @@
{
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6) return false;
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
- for (int i = 0; i < positions.Count(); i++) if (!placement.Clear(positions[i], angles[i][0])) return false;
+ for (int i = 0; i < positions.Count(); i++) if (i != 2 && !placement.Clear(positions[i], angles[i][0])) return false;
return true;
}
static bool PartOf(IEntity entity, IEntity root)
@@ -371,6 +372,7 @@
}
protected bool FillCrew(ERR_ConvoyVehicle unit)
{
+ if (unit.Index == 2) return false;
array<BaseCompartmentSlot> all = {};
if (!ERR_ConvoyRules.CollectSeats(unit.Entity, all)) return false;
array<BaseCompartmentSlot> drivers = {}, turrets = {}, cargo = {};
@@ -382,12 +384,11 @@
else if (slot.GetType() == ECompartmentType.CARGO) cargo.Insert(slot);
}
int requiredTurrets = 1;
- if (unit.Index == 1 || unit.Index == 2) requiredTurrets = 0;
+ if (unit.Index == 1) requiredTurrets = 0;
if (unit.Index == 4) requiredTurrets = 2;
- if (drivers.Count() != 1 || turrets.Count() < requiredTurrets || (unit.Index == 2 && cargo.Count() < 8)) return false;
+ if (drivers.Count() != 1 || turrets.Count() < requiredTurrets) return false;
unit.Seats.Insert(drivers[0]);
for (int i = 0; i < requiredTurrets; i++) unit.Seats.Insert(turrets[i]);
- if (unit.Index == 2) for (int i = 0; i < 8; i++) unit.Seats.Insert(cargo[i]);
// Exact planned population is known before any character is created.
if (unit.Seats.Count() != ERR_ConvoyPool.CrewCount(unit.Index)) return false;
AIGroup nativeGroup = unit.Group;
@@ -410,6 +411,7 @@
for (int index = 0; index < 6; index++)
{
ERR_ConvoyVehicle unit = NewVehicle(); unit.Index = index; task.Vehicles.Insert(unit);
+ if (index == 2) { unit.Released = true; continue; }
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
if (unit.Group) m_Mode.Own(unit.Group);
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(index), positions[index], angles[index]));
@@ -421,7 +423,7 @@
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
if (movement) movement.SetCruiseSpeed(35);
}
- ERR_HQ.Event("CONVOY CREATED", "six physical vehicles; finite crew created; native readiness and ledger admission pending");
+ ERR_HQ.Event("CONVOY CREATED", "five physical vehicles; troop transport reassigned to hunters; finite crew created; native readiness and ledger admission pending");
return task;
}
protected string ReadinessReason(ERR_ConvoyVehicle unit)
@@ -473,9 +475,10 @@
}
protected bool Admit(ERR_ConvoyTask task, float now)
{
- foreach (ERR_ConvoyVehicle observed : task.Vehicles) InspectReadiness(observed, now);
+ foreach (ERR_ConvoyVehicle observed : task.Vehicles) if (observed.Index != 2) InspectReadiness(observed, now);
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
{
+ if (unit.Index == 2) continue;
if (unit.Registered) continue;
if (!unit.CrewValid(m_Mode)) return false;
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
--- addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c
@@ -1,5 +1,24 @@
+// One troop truck joins every pair of armed UAZs, within the existing finite
+// vehicle allowance. Trucks can flank/dismount but cannot provide turret cover.
+class ERR_HunterFleetRules
+{
+ static bool Transport(int ordinal) { return ordinal >= 0 && ordinal < 15 && ordinal % 3 == 2; }
+ static ResourceName VehiclePrefab(int ordinal)
+ {
+ if (Transport(ordinal)) return "{16C1F16C9B053801}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et";
+ return ERR_ConvoyResources.VehiclePrefab(0);
+ }
+ static bool Seats(bool transport, int drivers, int turrets, int cargo, int total)
+ {
+ if (drivers != 1 || cargo < 1) return false;
+ if (transport) return turrets == 0 && cargo == 14 && total == 15;
+ return turrets > 0 && total <= 12;
+ }
+}
+
class ERR_HunterVehicle : ERR_ConvoyVehicle
{
+ bool TroopTransport;
bool RadioRequested;
ref ERR_EnemyEquipmentCallback RadioOperation;
string Source;
@@ -52,7 +71,7 @@
}
}
-// An escalation allowance of fifteen physical hunter UAZs, separate from supply
+// An escalation allowance of fifteen physical hunting vehicles (UAZs and troop trucks), separate from supply
// convoys. Losses remain physical; aggression never replenishes killed crews.
class ERR_HunterService : ERR_ConvoyService
{
@@ -129,7 +148,7 @@
foreach (ERR_HunterVehicle unit : m_Hunters) if (!unit.Released) count++;
return count;
}
- override string Status() { return string.Format("HQ UAZ patrols: %1 active; %2 deployed from a total allowance of %3 (maximum 15). Lost vehicles are not replaced.", ActiveHunters(), Deployed(), m_Target); }
+ override string Status() { return string.Format("HQ hunting patrols (UAZs + troop trucks): %1 active; %2 deployed from a total allowance of %3 (maximum 15). Lost vehicles are not replaced.", ActiveHunters(), Deployed(), m_Target); }
protected bool StartHunter(ERR_HQ hq, float now)
{
if (m_Hunters.Count() >= m_Target || m_Hunters.Count() >= 15 || m_Mode.Forces().Count() >= 250) return false;
@@ -169,7 +188,8 @@
ERR_HQ.Event("HUNTER WAIT", "deployment deferred: friendly road obstructed, occupied, budget full or players within 400m");
return false;
}
- // Hunter UAZs carry a full response team, unlike the two-person convoy escort.
+ // Each hunter fills its actual seats, including one medic; the transport
+ // carries a driver and fourteen infantry in the same HQ-controlled group.
override protected bool FillCrew(ERR_ConvoyVehicle unit)
{
ResourceName soldier = ERR_EnemyMedicalCatalog.Crew(m_Mode, false);
@@ -185,7 +205,8 @@
else if (seat.GetType() == ECompartmentType.TURRET) turrets.Insert(seat);
else if (seat.GetType() == ECompartmentType.CARGO) cargo.Insert(seat);
}
- if (drivers.Count() != 1 || turrets.IsEmpty() || cargo.IsEmpty() || all.Count() > 12) return false;
+ ERR_HunterVehicle hunter = ERR_HunterVehicle.Cast(unit);
+ if (!hunter || !ERR_HunterFleetRules.Seats(hunter.TroopTransport, drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
unit.Seats.Insert(drivers[0]);
foreach (BaseCompartmentSlot turret : turrets) unit.Seats.Insert(turret);
int medicIndex = unit.Seats.Count();
@@ -215,10 +236,13 @@
return false;
}
ERR_HunterVehicle unit = new ERR_HunterVehicle();
+ unit.TroopTransport = ERR_HunterFleetRules.Transport(m_Hunters.Count());
unit.Source = "aggression-uaz-" + (m_Hunters.Count() + 1).ToString();
+ if (unit.TroopTransport) unit.Source = "aggression-truck-" + (m_Hunters.Count() + 1).ToString();
+ ResourceName vehiclePrefab = ERR_HunterFleetRules.VehiclePrefab(m_Hunters.Count());
unit.Deadline = now + 120000; m_Hunters.Insert(unit);
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), position));
- unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(0), position, angles));
+ unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(vehiclePrefab, position, angles));
if (unit.Group) m_Mode.Own(unit.Group);
if (unit.Entity) m_Mode.Own(unit.Entity);
if (!unit.Group || !unit.Entity || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
@@ -228,7 +252,11 @@
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
- if (movement) movement.SetCruiseSpeed(45);
+ if (movement)
+ {
+ if (unit.TroopTransport) movement.SetCruiseSpeed(35);
+ else movement.SetCruiseSpeed(45);
+ }
ERR_HQ.Event("HUNTER SPAWN", string.Format("%1 identified bodies=%2 aggression=%3/30 allowance=%4/15 base=%5 position=%6 crew=%7", unit.Source, hq.Watch().IdentifiedBodies(), hq.Watch().Aggression(), m_Target, source, position, unit.Members.Count()));
return true;
}
--- addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c
@@ -15,37 +15,7 @@
protected override bool FillCrew(ERR_ConvoyVehicle unit)
{
if (m_Creating) m_Creating.Starts.Insert(unit.Entity.GetOrigin());
- if (unit.Index != 2) return super.FillCrew(unit);
- array<BaseCompartmentSlot> all = {}, drivers = {}, cargo = {};
- if (!m_Creating || !ERR_ConvoyRules.CollectSeats(unit.Entity, all)) return false;
- foreach (BaseCompartmentSlot seat : all)
- {
- if (!seat || seat.IsOccupied() || !seat.IsCompartmentAccessible()) continue;
- if (seat.GetType() == ECompartmentType.PILOT) drivers.Insert(seat);
- if (seat.GetType() == ECompartmentType.CARGO) cargo.Insert(seat);
- }
- if (drivers.Count() != 1 || cargo.Count() != 14) return false;
- AIGroup nativeGroup = unit.Group;
- IEntity driver = drivers[0].SpawnCharacterInCompartment(ERR_ConvoyResources.Soldier(), nativeGroup, ERR_ConvoyResources.GroupPrefab());
- if (driver) { m_Mode.Own(driver); unit.Members.Insert(driver); unit.Seats.Insert(drivers[0]); unit.Driver = driver; }
- if (!driver || nativeGroup != unit.Group) return false;
- for (int index = 0; index < 2; index++)
- {
- ERR_SupplyTeam team = new ERR_SupplyTeam(); m_Creating.Teams.Insert(team);
- team.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), unit.Entity.GetOrigin()));
- if (team.Group) m_Mode.Own(team.Group);
- if (!ERR_ConvoyRules.EmptyCrewTemplate(team.Group)) return false;
- AIFormationComponent formation = AIFormationComponent.Cast(team.Group.FindComponent(AIFormationComponent));
- if (formation) formation.SetFormation("Line");
- nativeGroup = team.Group;
- for (int slot = index; slot < 14; slot += 2)
- {
- IEntity actor = cargo[slot].SpawnCharacterInCompartment(ERR_ConvoyResources.Soldier(), nativeGroup, ERR_ConvoyResources.GroupPrefab());
- if (actor) { m_Mode.Own(actor); team.Members.Insert(actor); team.Seats.Insert(cargo[slot]); }
- if (!actor || nativeGroup != team.Group) return false;
- }
- }
- return true;
+ return super.FillCrew(unit);
}
protected void Wake(SCR_AIGroup group)
{
@@ -57,39 +27,16 @@
protected override bool Admit(ERR_ConvoyTask task, float now)
{
ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(task);
- if (!supply || supply.Teams.Count() != 2) return false;
+ if (!supply || !supply.Teams.IsEmpty()) return false;
// Distant native agents must wake before compartment membership can settle.
foreach (ERR_ConvoyVehicle vehicle : supply.Vehicles)
{
+ if (vehicle.Released) continue;
Wake(vehicle.Group);
foreach (IEntity member : vehicle.Members) ERR_ConvoyMountedCrew.Hold(member, vehicle.Entity);
}
foreach (ERR_SupplyTeam infantry : supply.Teams) Wake(infantry.Group);
if (!super.Admit(supply, now)) return false;
- SCR_AIVehicleUsageComponent truck = SCR_AIVehicleUsageComponent.Cast(supply.Vehicles[2].Entity.FindComponent(SCR_AIVehicleUsageComponent));
- for (int index = 0; index < supply.Teams.Count(); index++)
- {
- ERR_SupplyTeam team = supply.Teams[index];
- if (team.Registered) continue;
- if (!team.Group || team.Group.GetFaction() != m_Mode.EnemyFaction() || team.Members.Count() != 7) return false;
- array<AIAgent> agents = {}; team.Group.GetAgents(agents);
- if (agents.Count() != 7) return false;
- foreach (AIAgent agent : agents)
- if (!agent || !team.Members.Contains(agent.GetControlledEntity()) || !ERR_Actors.CanFight(agent.GetControlledEntity()) || ERR_Actors.Player(agent.GetControlledEntity())) return false;
- for (int slot = 0; slot < 7; slot++)
- if (team.Seats[slot].GetOccupant() != team.Members[slot] || ERR_Actors.Group(team.Members[slot]) != team.Group) return false;
- SCR_AIGroupUtilityComponent utility = team.Group.GetGroupUtilityComponent();
- if (!utility || !truck) return false;
- utility.AddUsableVehicle(truck);
- ERR_ForceRecord record = m_Mode.Forces().Register(supply.Receipt.SourceId + "-vehicle-" + (index + 6).ToString(), ERR_ForceKind.CONVOY, team.Group.GetOrigin(), 7, null, team.Group, now);
- if (!record || !m_Pool.BindRecord(supply.Receipt, index + 6, record)) { supply.Stop(m_Mode, "infantry ledger admission failed; no replacement"); return false; }
- record.Pinned = true; team.Registered = true;
- m_Mode.RegisterGroup(team.Group);
- ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
- if (hq) hq.ReserveGroup(team.Group);
- team.Group.SetPermanentLOD(0); team.Group.ActivateAI();
- foreach (AIAgent active : agents) { active.SetPermanentLOD(0); active.ActivateAI(); }
- }
supply.Column = new ERR_SupplyConvoyColumn(m_Mode, supply);
supply.Tactics = new ERR_SupplyConvoyTactics(m_Mode, supply);
supply.TravelDeadline = now + 600000;
--- addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c
@@ -470,12 +470,14 @@
intercept.ClearIntercept(intercept.InterceptOrder);
if (intercept.InterceptToken != 0) return;
}
- Clear(); m_State = 1; m_Contact = contact; m_TruckWithdrawn = false; m_OrdersIssued = false;
+ Clear(); m_State = 1; m_Contact = contact; m_TruckWithdrawn = m_Task.Teams.IsEmpty(); m_OrdersIssued = false;
m_NextSearch = now + 45000;
foreach (ERR_SupplyTeam team : m_Task.Teams) { team.SearchPlanned = false; team.SearchStep = 0; }
if (m_Task.Column) m_Task.Column.Reset(now);
m_Task.Waiting = false;
- vector origin = m_Task.Vehicles[2].Entity.GetOrigin();
+ vector origin = contact;
+ foreach (ERR_ConvoyVehicle stagingVehicle : m_Task.Vehicles)
+ if (!stagingVehicle.Released && stagingVehicle.Entity) { origin = stagingVehicle.Entity.GetOrigin(); break; }
foreach (ERR_ConvoyVehicle vehicle : m_Task.Vehicles)
{
vehicle.ClearOrder();
@@ -486,7 +488,7 @@
m_Forward.Normalize(); m_Left = Vector(-m_Forward[2], 0, m_Forward[0]);
Orders(true, now);
m_DismountAttempts = 0; m_NextDismount = now + 10000;
- ERR_HQ.Event("CONVOY COMBAT", m_Task.Receipt.SourceId + ": infantry flank left/right; escorts cover; troop truck holds for dismount");
+ ERR_HQ.Event("CONVOY COMBAT", m_Task.Receipt.SourceId + ": armed escorts take covering positions; ammo truck withdraws");
}
protected void Finish(float now)
{
--- addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c (before this task)
+++ addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c
@@ -169,6 +169,7 @@
m_NextMovementDiagnostic = now + 30000;
for (int i = 0; i < m_Task.Vehicles.Count() && i < 6; i++)
{
+ if (i == 2) continue;
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",
@@ -236,12 +237,13 @@
}
protected bool Population(ERR_HQ hq)
{
- if (!Check(m_Task.Vehicles.Count() == 6, "six original vehicles remain tracked")) return false;
+ if (!Check(m_Task.Vehicles.Count() == 6, "canonical slots remain tracked with retired transport")) return false;
array<IEntity> vehicles = {}, crew = {};
array<SCR_AIGroup> groups = {};
array<BaseCompartmentSlot> slots = {};
for (int i = 0; i < 6; i++)
{
+ if (i == 2) continue;
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),
@@ -279,9 +281,9 @@
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
+ return Check(crew.Count() == 10 && hq.ConvoyPool().CommittedBodies() <= 60
&& m_Task.Receipt.OutstandingBodies == 0 && m_Task.Receipt.OutstandingGroups == 0,
- "nineteen unique crew admitted once within shared sixty-body cap");
+ "ten unique crew admitted once within shared sixty-body cap");
}
protected int Cargo(float now)
{
@@ -353,9 +355,10 @@
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;
+ "canonical receipt owns five attempted physical vehicles and retired slot")) return -1;
foreach (ERR_ConvoyVehicle vehicle : m_Task.Vehicles)
{
+ if (vehicle.Index == 2) { m_OriginalVehicles.Insert(null); m_Origins.Insert(vector.Zero); continue; }
if (!Check(vehicle.Entity != null, "original physical vehicle created")) return -1;
m_OriginalVehicles.Insert(vehicle.Entity); m_Origins.Insert(vehicle.Entity.GetOrigin());
}
@@ -367,7 +370,7 @@
}
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 (candidate.Index != 2 && !candidate.Arrived) completed = false;
if (!m_Task || (m_Task.Stopped && !completed))
{
string reason = "missing task";
@@ -387,8 +390,8 @@
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;
+ if (!Check(m_Mode.Forces().Count() == m_InitialLedger + 5,
+ "exactly five new Core records after native readiness")) return -1;
m_Step = 2;
}
if (m_Step == 2)
@@ -400,6 +403,7 @@
float minimum = 1000000;
for (int i = 0; i < 6; i++)
{
+ if (i == 2) continue;
ERR_ConvoyVehicle vehicle = m_Task.Vehicles[i];
if (!vehicle || !vehicle.Entity || (vehicle.Released && !vehicle.Arrived))
{ LossDiagnostic(vehicle, i, now); Check(false, "vehicle released before road movement proof"); return -1; }
@@ -418,9 +422,9 @@
"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,
+ && m_Mode.Forces().Count() == m_InitialLedger + 5 && 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");
+ Check(true, "all five native vehicles drove at least 20m and leader at least 100m without fixture movement");
if (ERR_NativeMountedInterceptProbe.Enabled())
{
m_Intercept = new ERR_NativeMountedInterceptProbe(m_Mode, hq, m_Task, now); m_Step = 5; return 0;
--- addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyConvoyProbe.c (before this task)
+++ addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyConvoyProbe.c
@@ -1,6 +1,6 @@
#ifdef WORKBENCH
// Explicit opt-in fixture. Startup, seating, cargo and driving remain production-owned.
-// The final phase injects one clearly labelled contact to exercise native dismount/reboard.
+// The final phase injects one clearly labelled contact to exercise mounted combat/resume.
class ERR_NativeSupplyConvoyProbe : ERR_NativeConvoyProbe
{
protected ref array<vector> m_StartPositions = {};
@@ -10,27 +10,28 @@
protected float m_PhaseBegan;
protected int m_Phase;
protected ref ERR_SupplyConvoyTask m_CombatTask;
- protected bool m_SawDismount;
- protected bool m_SawBoarding;
+ protected bool m_SawCombat;
protected int m_TestLegs;
protected vector m_RouteResumePosition;
protected int ObserveMotion(ERR_HQ hq)
{
- if (m_StartPositions.Count() != 18) return 0;
+ if (m_StartPositions.Count() != 15) return 0;
int cursor, moved;
for (int index = 0; index < 3; index++)
{
foreach (ERR_ConvoyVehicle vehicle : hq.Convoys().At(index).Vehicles)
{
+ if (vehicle.Index == 2) continue;
+ if (!vehicle.Entity) return -1;
if (vector.DistanceXZ(vehicle.Entity.GetOrigin(), m_StartPositions[cursor]) >= 20) m_Moved[cursor] = true;
if (m_Moved[cursor]) moved++;
cursor++;
}
}
- if (moved == 18 && !m_AllMoved)
+ if (moved == 15 && !m_AllMoved)
{
m_AllMoved = true;
- Print("ERR SUPPLY PASS: all eighteen original vehicles drove at least twenty metres from their recorded spawn positions through native AI");
+ Print("ERR SUPPLY PASS: all fifteen original vehicles drove at least twenty metres from their recorded spawn positions through native AI");
}
return moved;
}
@@ -42,7 +43,7 @@
int moved = ObserveMotion(hq);
if (now >= m_NextLog)
{
- PrintFormat("ERR SUPPLY MOTION: %1/18 original vehicles moved at least20m", moved);
+ PrintFormat("ERR SUPPLY MOTION: %1/15 original vehicles moved at least20m", moved);
PrintFormat("ERR SUPPLY STATE: phase=%1 elapsed=%2 %3", m_Phase, now - m_Began, hq.ConvoyStatus());
for (int logIndex = 0; logIndex < hq.Convoys().Count(); logIndex++)
{
@@ -55,11 +56,12 @@
for (int car = 0; car < m_CombatTask.Vehicles.Count(); car++)
{
ERR_ConvoyVehicle diagnosticCar = m_CombatTask.Vehicles[car];
+ if (!diagnosticCar.Entity) continue;
vector target; if (diagnosticCar.Waypoint) target = diagnosticCar.Waypoint.GetOrigin();
PrintFormat("ERR SUPPLY COLUMN: car=%1 released=%2 arrived=%3 position=%4 target=%5", car + 1, diagnosticCar.Released, diagnosticCar.Arrived, diagnosticCar.Entity.GetOrigin(), target);
if (diagnosticCar.Waypoint) PrintFormat("ERR SUPPLY WAYPOINT: car=%1 distance=%2 nativeCurrent=%3 nativeComplete=%4 routeIndex=%5", car + 1, vector.DistanceXZ(diagnosticCar.Entity.GetOrigin(), target), diagnosticCar.Group.GetCurrentWaypoint() == diagnosticCar.Waypoint, diagnosticCar.Waypoint.IsWithinCompletionRadius(diagnosticCar.Group), diagnosticCar.RouteIndex);
}
- ERR_ConvoyVehicle truck = m_CombatTask.Vehicles[2];
+ ERR_ConvoyVehicle truck = m_CombatTask.Vehicles[1];
SCR_VehicleDamageManagerComponent health = SCR_VehicleDamageManagerComponent.Cast(truck.Entity.FindComponent(SCR_VehicleDamageManagerComponent));
PrintFormat("ERR SUPPLY TRUCK: released=%1 driverAlive=%2 driverGroup=%3 driverAboard=%4 health=%5 retained=%6 position=%7", truck.Released, ERR_Actors.CanFight(truck.Driver), ERR_Actors.Group(truck.Driver) == truck.Group, CompartmentAccessComponent.GetVehicleIn(truck.Driver) == truck.Entity, health.GetHealthScaled(), ERR_ConvoyMountedCrew.Retain(truck.Driver, truck.Entity), truck.Entity.GetOrigin());
SCR_AIGroupUtilityComponent driverGroup = truck.Group.GetGroupUtilityComponent();
@@ -98,10 +100,8 @@
if (!task || task.Stopped) return -1;
if (!task.Ready || !task.MagazinesIssued) return 0;
foreach (ERR_ConvoyCargoCallback pending : task.Cargo) if (!pending.Finished) return 0;
- if (task.Vehicles.Count() != 6 || task.Teams.Count() != 2 || task.Receipt.OutstandingBodies != 0 || task.Receipt.OutstandingGroups != 0) return -1;
- foreach (ERR_SupplyTeam team : task.Teams)
- if (!team.Registered || team.Group == task.Vehicles[2].Group || team.Members.Count() != 7) return -1;
- if (task.Teams[0].Group == task.Teams[1].Group) return -1;
+ if (task.Vehicles.Count() != 6 || !task.Teams.IsEmpty() || task.Receipt.OutstandingBodies != 0 || task.Receipt.OutstandingGroups != 0) return -1;
+ if (!task.Vehicles[2].Released || task.Vehicles[2].Entity || task.Vehicles[2].Group) return -1;
int successful;
foreach (ERR_ConvoyCargoCallback load : task.Cargo) if (load.Succeeded) successful++;
if (successful < 14) { Print("ERR SUPPLY FAIL: weapon and ammunition storage operations missing"); return -1; }
@@ -124,37 +124,31 @@
for (int convoy = 0; convoy < 3; convoy++)
{
ERR_SupplyConvoyTask original = ERR_SupplyConvoyTask.Cast(hq.Convoys().At(convoy));
- if (original.Starts.Count() != 6) return -1;
+ if (original.Starts.Count() != 5) return -1;
foreach (vector start : original.Starts) { m_StartPositions.Insert(start); m_Moved.Insert(false); }
}
m_CombatTask = ERR_SupplyConvoyTask.Cast(hq.Convoys().At(0));
- Print("ERR SUPPLY PASS: production auto-started three six-vehicle convoys, exact ledger admission, two separate seated teams each, finite native cargo");
+ Print("ERR SUPPLY PASS: production auto-started three five-vehicle convoys, exact ledger admission, no troop transport, finite native cargo");
m_Phase = 1; m_PhaseBegan = now;
}
if (m_Phase == 1)
{
if (!m_AllMoved) return 0;
m_CombatTask = ERR_SupplyConvoyTask.Cast(hq.Convoys().At(0));
- vector contact = m_CombatTask.Vehicles[2].Entity.GetOrigin() + "0 0 200";
+ vector contact = m_CombatTask.Vehicles[1].Entity.GetOrigin() + "0 0 200";
m_CombatTask.Tactics.Report(contact, now, now);
- Print("ERR SUPPLY FIXTURE: injected one contact for native dismount/reboard validation; this phase does not prove perception");
+ Print("ERR SUPPLY FIXTURE: injected one contact for mounted combat/resume validation; this phase does not prove perception");
m_Phase = 2; m_PhaseBegan = now;
}
if (m_Phase == 2)
{
if (m_CombatTask.Stopped) return -1;
- bool dismounted = true;
- foreach (ERR_SupplyTeam foot : m_CombatTask.Teams)
- if (!foot.Aboard(m_CombatTask.Vehicles[2].Entity, false)) dismounted = false;
- if (dismounted && !m_SawDismount) { m_SawDismount = true; Print("ERR SUPPLY PASS: both original infantry teams physically dismounted"); }
- if (m_CombatTask.Tactics.State() == 2) m_SawBoarding = true;
- if (now - m_PhaseBegan > 60000 && m_CombatTask.Tactics.State() == 0)
+ if (m_CombatTask.Tactics.State() == 1) m_SawCombat = true;
+ if (now - m_PhaseBegan > 180000 && m_CombatTask.Tactics.State() == 0)
{
- if (!m_SawDismount || !m_SawBoarding) return -1;
- foreach (ERR_SupplyTeam boarded : m_CombatTask.Teams)
- if (boarded.Released || !boarded.Aboard(m_CombatTask.Vehicles[2].Entity, true)) return -1;
- Print("ERR SUPPLY PASS: both original teams reboarded and production travel resumed after quiet interval");
- m_TestLegs = m_CombatTask.Legs; m_RouteResumePosition = m_CombatTask.Vehicles[2].Entity.GetOrigin();
+ if (!m_SawCombat) return -1;
+ Print("ERR SUPPLY PASS: escorts covered and production travel resumed after quiet interval without absent infantry blocking readiness");
+ m_TestLegs = m_CombatTask.Legs; m_RouteResumePosition = m_CombatTask.Vehicles[1].Entity.GetOrigin();
Print("ERR SUPPLY FIXTURE: waiting for physical column arrival, production pause and next base leg; no forced timeout arrival");
m_Phase = 3;
}
@@ -164,15 +158,15 @@
}
protected int Departure()
{
- if (m_CombatTask.Stopped || m_CombatTask.Vehicles[2].Released || m_CombatTask.Bases.Count() < 2) return -1;
+ if (m_CombatTask.Stopped || m_CombatTask.Vehicles[1].Released || m_CombatTask.Bases.Count() < 2) return -1;
if (m_Phase == 3 && m_CombatTask.Legs > m_TestLegs)
{
- m_RouteResumePosition = m_CombatTask.Vehicles[2].Entity.GetOrigin();
+ m_RouteResumePosition = m_CombatTask.Vehicles[1].Entity.GetOrigin();
m_Phase = 4;
Print("ERR SUPPLY FIXTURE: next leg started; record original truck position now, excluding all earlier regroup movement");
return 0;
}
- if (m_Phase == 4 && vector.DistanceXZ(m_CombatTask.Vehicles[2].Entity.GetOrigin(), m_RouteResumePosition) >= 20)
+ if (m_Phase == 4 && vector.DistanceXZ(m_CombatTask.Vehicles[1].Entity.GetOrigin(), m_RouteResumePosition) >= 20)
{
Print("ERR SUPPLY PASS: original truck drives twenty metres AFTER departure on the new native base leg; no replacement convoy");
m_Phase = 5;