Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
608 lines
34 KiB
Diff
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;
|