236 lines
12 KiB
C
236 lines
12 KiB
C
#ifdef WORKBENCH
|
|
// Controlled native inventory boundary only: one existing actor is positioned
|
|
// beside the existing convoy truck. This does not prove HQ rendezvous/navigation.
|
|
modded class ERR_HQ
|
|
{
|
|
ERR_HQUnit ERR_InfantryFixtureRecipient(IEntity magazine)
|
|
{
|
|
if (!ERR_NativeInfantryTransferProbe.Enabled()) return null;
|
|
foreach (ERR_HQUnit unit : m_Units)
|
|
{
|
|
if (!unit.Eligible(m_Mode) || unit.HomeGuard || !unit.StatusSampler.OwnsRoster(unit.Group)) continue;
|
|
for (int slot = 0; slot < unit.StatusSampler.InitialStrength(); slot++)
|
|
{
|
|
IEntity actor = unit.StatusSampler.InitialMember(slot);
|
|
if (!actor || !unit.StatusSampler.CanAttribute(slot, actor) || !ERR_Actors.CanFight(actor)) continue;
|
|
if (!ERR_InfantryAmmoTransfer.Compatible(actor, magazine)) continue;
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
|
if (!inventory || !inventory.FindStorageForItem(magazine, EStoragePurpose.PURPOSE_DEPOSIT)) continue;
|
|
if (ReserveLogistics(unit.Group, ERR_NativeInfantryTransferProbe.TOKEN)) return unit;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
class ERR_NativeInfantryTransferProbe
|
|
{
|
|
static const int TOKEN = 200001;
|
|
protected ERR_GameMode m_Mode;
|
|
protected ERR_HQ m_HQ;
|
|
protected ERR_ConvoyVehicle m_Truck;
|
|
protected ERR_HQUnit m_Unit;
|
|
protected IEntity m_Actor;
|
|
protected IEntity m_Magazine;
|
|
protected ref ERR_InfantryAmmoTransfer m_Move;
|
|
protected vector m_OriginalPosition;
|
|
protected int m_OriginalLedger;
|
|
protected int m_DonorBefore;
|
|
protected int m_RecipientBefore;
|
|
protected int m_Rounds;
|
|
protected float m_Deadline;
|
|
protected int m_State;
|
|
protected bool m_Positioned;
|
|
protected bool m_PlacementStarted;
|
|
protected bool m_PlacementPending;
|
|
protected int m_PlacementIndex;
|
|
protected float m_PlacementReadyAt;
|
|
protected bool m_Stopped;
|
|
protected bool m_Failed;
|
|
static bool Enabled()
|
|
{
|
|
string smoke, outcome;
|
|
return Replication.IsServer() && System.GetCLIParam("errSmoke", smoke) && smoke == "1"
|
|
&& System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "infantry-transfer";
|
|
}
|
|
void ERR_NativeInfantryTransferProbe(ERR_GameMode mode) { m_Mode = mode; }
|
|
protected bool Check(bool condition, string detail)
|
|
{
|
|
if (condition) { Print("ERR SMOKE PASS: infantry transfer " + detail); return true; }
|
|
m_Failed = true; Print("ERR SMOKE FAIL: infantry transfer " + detail, LogLevel.ERROR); Stop(); return false;
|
|
}
|
|
protected int MagazineCount(IEntity owner)
|
|
{
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(owner.FindComponent(InventoryStorageManagerComponent));
|
|
if (!inventory) return -1;
|
|
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
|
if (items.Count() > 128) return -1;
|
|
int count;
|
|
foreach (IEntity item : items) if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_ConvoyResources.Magazine()) count++;
|
|
return count;
|
|
}
|
|
protected bool Has(IEntity owner, IEntity item)
|
|
{
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(owner.FindComponent(InventoryStorageManagerComponent));
|
|
if (!inventory) return false;
|
|
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
|
return items.Count() <= 128 && items.Contains(item);
|
|
}
|
|
protected bool RecipientOwned()
|
|
{
|
|
if (!m_Mode || !m_Unit || !m_Actor) return false;
|
|
if (!m_Unit.StatusSampler || !m_Unit.StatusSampler.OwnsRoster(m_Unit.Group)) return false;
|
|
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_Unit.Group, m_Actor) || CompartmentAccessComponent.GetVehicleIn(m_Actor)) return false;
|
|
int strength = m_Unit.StatusSampler.InitialStrength();
|
|
if (strength < 1 || strength > 32) return false;
|
|
for (int slot = 0; slot < strength; slot++)
|
|
if (m_Unit.StatusSampler.InitialMember(slot) == m_Actor) return m_Unit.StatusSampler.CanAttribute(slot, m_Actor);
|
|
return false;
|
|
}
|
|
protected void AccessDiagnostic()
|
|
{
|
|
ActionsManagerComponent manager = ActionsManagerComponent.Cast(m_Truck.Entity.FindComponent(ActionsManagerComponent));
|
|
PrintFormat("ERR INFANTRY ACCESS: root manager=%1", manager != null);
|
|
if (!manager) return;
|
|
array<UserActionContext> contexts = {}; manager.GetContextList(contexts);
|
|
PrintFormat("ERR INFANTRY ACCESS: enabled=%1 contexts=%2", manager.IsEnabled(), contexts.Count());
|
|
for (int i = 0; i < contexts.Count() && i < 32; i++)
|
|
{
|
|
UserActionContext context = contexts[i];
|
|
if (!context) { Print("ERR INFANTRY ACCESS: null context"); continue; }
|
|
vector matrix[4]; bool transformed = context.GetTransformationWorld(matrix);
|
|
array<BaseUserAction> actions = {}; context.GetActionsList(actions);
|
|
PrintFormat("ERR INFANTRY ACCESS: context=%1 transform=%2 point=%3 actions=%4", i, transformed, matrix[3], actions.Count());
|
|
for (int j = 0; j < actions.Count() && j < 16; j++)
|
|
{
|
|
BaseUserAction action = actions[j];
|
|
if (action) PrintFormat("ERR INFANTRY ACCESS: action=%1 owner=%2 rootOwned=%3 storage=%4", action.Type().ToString(), action.GetOwner(), action.GetOwner() == m_Truck.Entity, SCR_OpenVehicleStorageAction.Cast(action) != null);
|
|
}
|
|
}
|
|
}
|
|
// -1: exhausted/refused; 0: awaiting one native character update; 1: observed clear access.
|
|
protected int Place(float now)
|
|
{
|
|
if (!RecipientOwned()) return -1;
|
|
if (!m_PlacementStarted)
|
|
{
|
|
m_PlacementStarted = true; m_OriginalPosition = m_Actor.GetOrigin();
|
|
}
|
|
if (m_PlacementPending)
|
|
{
|
|
if (now < m_PlacementReadyAt) return 0;
|
|
m_PlacementPending = false;
|
|
ChimeraCharacter character = ChimeraCharacter.Cast(m_Actor);
|
|
if (!character) return -1;
|
|
PrintFormat("ERR INFANTRY TRANSFER: settledOrigin=%1 aiming=%2", m_Actor.GetOrigin(), character.AimingPosition());
|
|
if (ERR_InfantryAmmoAccess.Clear(m_Truck.Entity, m_Actor)) return 1;
|
|
m_Actor.SetOrigin(m_OriginalPosition);
|
|
}
|
|
array<vector> access;
|
|
if (!ERR_InfantryAmmoAccess.Points(m_Truck.Entity, access)) { AccessDiagnostic(); return -1; }
|
|
vector transform[4]; m_Truck.Entity.GetTransform(transform);
|
|
array<vector> offsets = {"2.5 0 -1.5", "-2.5 0 -1.5", "2.5 0 1.5", "-2.5 0 1.5"};
|
|
while (m_PlacementIndex < offsets.Count())
|
|
{
|
|
vector offset = offsets[m_PlacementIndex++];
|
|
vector point = ERR_Actors.Ground(m_Truck.Entity.GetOrigin() + transform[0] * offset[0] + transform[2] * offset[2]);
|
|
TraceBox body = new TraceBox(); body.Start = point + "0 0.95 0"; body.End = body.Start + "0 0.01 0";
|
|
body.Mins = "-0.3 -0.9 -0.3"; body.Maxs = "0.3 0.9 0.3"; body.Flags = TraceFlags.ENTS;
|
|
array<IEntity> exclude = {m_Actor}; body.ExcludeArray = exclude;
|
|
float fraction = GetGame().GetWorld().TraceMove(body, null);
|
|
PrintFormat("ERR INFANTRY TRANSFER: candidate=%1 bodyFraction=%2", point, fraction);
|
|
if (fraction != 1) continue;
|
|
m_Actor.SetOrigin(point);
|
|
// Mark any controlled move for Stop restoration, including an unsettled or
|
|
// subsequently rejected placement. Never replace the saved original position.
|
|
m_Positioned = true; m_PlacementPending = true; m_PlacementReadyAt = now + 250;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
int Tick(ERR_HQ hq, float now)
|
|
{
|
|
if (!Enabled() || m_Stopped || m_Failed || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return -1;
|
|
m_HQ = hq;
|
|
if (!hq) return -1;
|
|
if (m_Deadline == 0) m_Deadline = now + 180000;
|
|
if (now >= m_Deadline) { Check(false, "bounded fixture deadline"); return -1; }
|
|
if (m_State == 0)
|
|
{
|
|
ERR_ConvoyTask task = hq.ConvoyTask();
|
|
if (!task || !task.Ready) return 0;
|
|
if (!Check(!task.Stopped && task.Vehicles.Count() == 6 && task.CargoIssued && task.Cargo.Count() == 56,
|
|
"existing automatic convoy and finite startup cargo are ready")) return -1;
|
|
foreach (ERR_ConvoyCargoCallback operation : task.Cargo) if (!operation || !operation.Finished) return 0;
|
|
m_Truck = task.Vehicles[1];
|
|
if (!Check(m_Truck.CrewValid(m_Mode) && m_Truck.SupplyToken == 0 && !m_Truck.Combat.Held,
|
|
"original ammo truck is available for isolated inventory fixture")) return -1;
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(m_Truck.Entity.FindComponent(InventoryStorageManagerComponent));
|
|
array<IEntity> stock = {}; ERR_Inventory.Items(inventory, stock);
|
|
foreach (IEntity candidate : stock)
|
|
{
|
|
if (!candidate || !candidate.GetPrefabData() || candidate.GetPrefabData().GetPrefabName() != ERR_ConvoyResources.Magazine()) continue;
|
|
m_Unit = hq.ERR_InfantryFixtureRecipient(candidate);
|
|
if (!m_Unit) break;
|
|
m_Magazine = candidate;
|
|
for (int slot = 0; slot < m_Unit.StatusSampler.InitialStrength(); slot++)
|
|
{
|
|
IEntity actor = m_Unit.StatusSampler.InitialMember(slot);
|
|
if (!actor || !m_Unit.StatusSampler.CanAttribute(slot, actor) || !ERR_Actors.CanFight(actor)
|
|
|| !ERR_InfantryAmmoTransfer.Compatible(actor, candidate)) continue;
|
|
InventoryStorageManagerComponent recipient = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
|
if (!recipient || !recipient.FindStorageForItem(candidate, EStoragePurpose.PURPOSE_DEPOSIT)) continue;
|
|
m_Actor = actor; break;
|
|
}
|
|
break;
|
|
}
|
|
if (!Check(m_Unit && m_Actor && m_Magazine, "existing original patrol actor and compatible original truck magazine selected")) return -1;
|
|
m_OriginalLedger = m_Mode.Forces().Count();
|
|
m_Truck.SupplyToken = TOKEN; m_Truck.SupplyHeld = true; m_Truck.ClearOrder();
|
|
m_State = 1; return 0;
|
|
}
|
|
if (!Check(m_Truck && m_Truck.CrewValid(m_Mode) && m_Truck.SupplyToken == TOKEN
|
|
&& RecipientOwned() && hq.LogisticsToken(m_Unit.Group) == TOKEN, "fixture retains original physical ownership")) return -1;
|
|
if (m_State == 1)
|
|
{
|
|
Physics physics = m_Truck.Entity.GetPhysics();
|
|
if (!physics) return 0;
|
|
float speed = physics.GetVelocity().Length(), angular = physics.GetAngularVelocity().Length();
|
|
if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1)) return 0;
|
|
int placed = Place(now);
|
|
if (placed == 0) return 0;
|
|
if (!Check(placed == 1, "controlled existing actor placed at clear authored truck storage access after native pose update")) return -1;
|
|
m_DonorBefore = MagazineCount(m_Truck.Entity); m_RecipientBefore = MagazineCount(m_Actor);
|
|
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(m_Magazine.FindComponent(BaseMagazineComponent));
|
|
if (!Check(magazine && m_DonorBefore > 0 && m_RecipientBefore >= 0 && Has(m_Truck.Entity, m_Magazine) && !Has(m_Actor, m_Magazine),
|
|
"exact original magazine starts exclusively in original truck")) return -1;
|
|
m_Rounds = magazine.GetAmmoCount();
|
|
m_Move = new ERR_InfantryAmmoTransfer(m_Mode, m_Truck.Entity, m_Truck.Group, m_Actor, m_Unit.Group, m_Magazine);
|
|
m_State = 2;
|
|
}
|
|
int result = m_Move.Tick(now);
|
|
if (result == 0) return 0;
|
|
if (!Check(result == 1, "production native move: " + m_Move.Reason())) return -1;
|
|
if (!Check(m_Move.Submitted() && !m_Move.Unresolved() && !Has(m_Truck.Entity, m_Magazine) && Has(m_Actor, m_Magazine),
|
|
"native callback and exact destination ownership agree")) return -1;
|
|
BaseMagazineComponent moved = BaseMagazineComponent.Cast(m_Magazine.FindComponent(BaseMagazineComponent));
|
|
if (!Check(moved && moved.GetAmmoCount() == m_Rounds && MagazineCount(m_Truck.Entity) == m_DonorBefore - 1
|
|
&& MagazineCount(m_Actor) == m_RecipientBefore + 1 && m_Mode.Forces().Count() == m_OriginalLedger,
|
|
"one physical magazine moved without refill, duplicate or new forces")) return -1;
|
|
Stop(); return 1;
|
|
}
|
|
void Stop()
|
|
{
|
|
if (m_Stopped) return;
|
|
m_Stopped = true;
|
|
if (m_Move) m_Move.Stop();
|
|
bool restore = false;
|
|
if (m_HQ && m_Unit && m_HQ.LogisticsToken(m_Unit.Group) == TOKEN)
|
|
restore = m_Positioned && (!m_Move || !m_Move.Unresolved()) && RecipientOwned();
|
|
if (m_HQ && m_Unit) m_HQ.ReleaseLogistics(m_Unit.Group, TOKEN);
|
|
if (m_Truck && m_Truck.SupplyToken == TOKEN) { m_Truck.SupplyToken = 0; m_Truck.SupplyHeld = false; }
|
|
// Never race an accepted uncertain operation with a position restore.
|
|
if (restore) m_Actor.SetOrigin(m_OriginalPosition);
|
|
}
|
|
}
|
|
#endif
|