181 lines
8.7 KiB
C
181 lines
8.7 KiB
C
enum ERR_MortarSupplyState { FAILED = -1, PENDING, READY }
|
|
|
|
class ERR_MortarSupplyRules
|
|
{
|
|
static int CycleGate(bool commandOpen, int confirmedShots, int behaviorShots, int limit, int supplyState)
|
|
{
|
|
if (!commandOpen) return -1;
|
|
if (confirmedShots <= behaviorShots) return 0;
|
|
if (confirmedShots < limit) return supplyState;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
class ERR_MortarInitialItem : ScriptedInventoryOperationCallback
|
|
{
|
|
bool Finished;
|
|
bool Succeeded;
|
|
protected void Finish(bool success) { if (!Finished) { Finished = true; Succeeded = success; } }
|
|
protected override void OnComplete() { Finish(true); }
|
|
protected override void OnFailed() { Finish(false); }
|
|
protected override void OnAllComplete() { Finish(true); }
|
|
protected override void OnAllFailed() { Finish(false); }
|
|
void Rejected() { Finish(false); }
|
|
}
|
|
|
|
// One finite allocation, after the ENTIRE original support crew exists. Failed
|
|
// or stopped setup retains physical assets and retires only its pending spawn intent.
|
|
class ERR_MortarReadiness
|
|
{
|
|
protected ERR_MortarBattery m_Battery;
|
|
protected ref array<IEntity> m_Members = {};
|
|
protected ref array<ref ERR_MortarInitialItem> m_Operations = {};
|
|
protected bool m_Allocated;
|
|
protected bool m_Ready;
|
|
protected bool m_Stopped;
|
|
protected bool m_Retired;
|
|
protected int m_RequestedShells;
|
|
void ERR_MortarReadiness(ERR_MortarBattery battery) { m_Battery = battery; }
|
|
static ERR_MortarSupplyState Population(int physical, int gunners, int operators, bool complete, bool expired)
|
|
{
|
|
if (physical > 4 || gunners > 3 || operators > 1) return ERR_MortarSupplyState.FAILED;
|
|
if (physical == 4 && gunners == 3 && operators == 1 && complete && !expired) return ERR_MortarSupplyState.READY;
|
|
if (expired) return ERR_MortarSupplyState.FAILED;
|
|
return ERR_MortarSupplyState.PENDING;
|
|
}
|
|
static bool AllocationValid(int shells) { return shells >= 0 && shells <= 9; }
|
|
int RequestedShells() { return m_RequestedShells; }
|
|
bool Ready() { return m_Ready && !m_Stopped; }
|
|
void Stop()
|
|
{
|
|
if (m_Stopped) return;
|
|
m_Stopped = true;
|
|
if (m_Ready || m_Retired || !m_Battery || !m_Battery.Group || !Replication.IsServer()) return;
|
|
m_Retired = true;
|
|
// As in Core initial guard cleanup: stopping the current request alone does
|
|
// not retire the native group's still-authored future expansion intent.
|
|
RetireGroup(m_Battery.Group);
|
|
}
|
|
static void RetireGroup(SCR_AIGroup group)
|
|
{
|
|
if (!group || !Replication.IsServer()) return;
|
|
group.SetSpawnImmediately(false);
|
|
group.m_aUnitPrefabSlots = new array<ResourceName>();
|
|
if (!GetGame()) return;
|
|
ChimeraAIWorld world = ChimeraAIWorld.Cast(GetGame().GetAIWorld());
|
|
if (world) world.PurgeSpawnRequestsForGroup(group);
|
|
}
|
|
|
|
protected ERR_MortarSupplyState Fail(string reason)
|
|
{
|
|
m_Battery.SpawnFailed(reason);
|
|
Stop();
|
|
return ERR_MortarSupplyState.FAILED;
|
|
}
|
|
protected bool Allocate()
|
|
{
|
|
m_Allocated = true; // before native requests; never refill or retry a failure
|
|
foreach (IEntity actor : m_Members)
|
|
{
|
|
if (actor == m_Battery.RadioOperator) continue;
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
|
int existing = m_Battery.InventoryShells(inventory);
|
|
if (!AllocationValid(existing)) return false;
|
|
if (ERR_HQRadio.Capability(actor) == 0 && !inventory.TrySpawnPrefabToStorage("{E1A5D4B878AA8980}Prefabs/Items/Equipment/Radios/Radio_R148.et"))
|
|
m_Battery.Audit("RADIO EQUIP", "optional crew handset did not fit; dedicated manpack remains required");
|
|
for (int i = existing; i < 9; i++)
|
|
{
|
|
ERR_MortarInitialItem operation = new ERR_MortarInitialItem();
|
|
m_Operations.Insert(operation);
|
|
m_RequestedShells++;
|
|
if (!inventory.TrySpawnPrefabToStorage(ERR_MortarBattery.SHELL, purpose: EStoragePurpose.PURPOSE_DEPOSIT, cb: operation))
|
|
{
|
|
operation.Rejected();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
ERR_MortarSupplyState Poll(float now)
|
|
{
|
|
if (m_Stopped || !Replication.IsServer() || !m_Battery || m_Battery.Stopped()) return ERR_MortarSupplyState.FAILED;
|
|
if (m_Ready) return ERR_MortarSupplyState.READY;
|
|
SCR_AIGroup group = m_Battery.Group;
|
|
ERR_GameMode mode = ERR_GameMode.Get();
|
|
if (!mode || !group || !m_Battery.Gun || group.GetFaction() != mode.EnemyFaction() || group.GetPlayerCount(true) > 0) return Fail("initial support group/gun/faction changed");
|
|
DamageManagerComponent damage = DamageManagerComponent.Cast(m_Battery.Gun.FindComponent(DamageManagerComponent));
|
|
if (!damage || damage.IsDestroyed()) return Fail("initial support gun missing damage manager or destroyed");
|
|
bool expired = now < m_Battery.SpawnedAt || now - m_Battery.SpawnedAt >= 90000;
|
|
array<AIAgent> agents = {}; group.GetAgents(agents);
|
|
array<IEntity> physical = {};
|
|
int gunners, operators;
|
|
IEntity operatorActor;
|
|
foreach (AIAgent agent : agents)
|
|
{
|
|
if (!agent || !agent.GetControlledEntity()) continue;
|
|
IEntity actor = agent.GetControlledEntity();
|
|
if (physical.Contains(actor)) return Fail("initial support group reports duplicate physical actor");
|
|
physical.Insert(actor);
|
|
if (!ERR_Actors.CanFight(actor) || SCR_FactionManager.SGetFaction(actor) != mode.EnemyFaction() || !actor.GetPrefabData()) return Fail("initial support actor unavailable or has wrong faction/resource");
|
|
ResourceName prefab = actor.GetPrefabData().GetPrefabName();
|
|
if (prefab == ERR_MortarBattery.CREW) gunners++;
|
|
else if (prefab == ERR_MortarBattery.RADIO_OPERATOR) { operators++; operatorActor = actor; }
|
|
else return Fail("initial support actor does not belong to authored crew catalog");
|
|
}
|
|
ERR_MortarSupplyState population = Population(physical.Count(), gunners, operators, group.IsExpandComplete(), expired);
|
|
if (population == ERR_MortarSupplyState.FAILED) return Fail("initial four-person support population incomplete/invalid within 90 seconds");
|
|
if (population == ERR_MortarSupplyState.PENDING) return population;
|
|
if (m_Allocated)
|
|
{
|
|
foreach (IEntity original : m_Members)
|
|
if (!physical.Contains(original)) return Fail("initial support member replaced after finite allocation");
|
|
}
|
|
foreach (IEntity member : physical)
|
|
{
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(member.FindComponent(InventoryStorageManagerComponent));
|
|
if (!inventory) return ERR_MortarSupplyState.PENDING;
|
|
if (!m_Allocated && member != operatorActor && m_Battery.InventoryShells(inventory) < 9 && !inventory.CanInsertResource(ERR_MortarBattery.SHELL, EStoragePurpose.PURPOSE_DEPOSIT)) return ERR_MortarSupplyState.PENDING;
|
|
}
|
|
if (ERR_HQRadio.Capability(operatorActor) != 2) return ERR_MortarSupplyState.PENDING;
|
|
// Preserve the existing earliest activation delay, but it is not readiness.
|
|
if (now - m_Battery.SpawnedAt < 10000) return ERR_MortarSupplyState.PENDING;
|
|
if (!m_Allocated)
|
|
{
|
|
if (!group.GetGroupUtilityComponent() || !group.GetGroupUtilityComponent().m_VehicleMgr) return ERR_MortarSupplyState.PENDING;
|
|
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Battery.Gun.FindComponent(SCR_AIVehicleUsageComponent));
|
|
if (!usage || !group.GetGroupUtilityComponent().m_VehicleMgr.TryAddVehicle(usage)) return Fail("native support gun registration failed");
|
|
m_Members.Copy(physical); m_Battery.RadioOperator = operatorActor;
|
|
if (!Allocate()) return Fail("finite initial support shell allocation rejected; no retry");
|
|
return ERR_MortarSupplyState.PENDING; // never accept mutation-frame readiness
|
|
}
|
|
foreach (ERR_MortarInitialItem operation : m_Operations)
|
|
{
|
|
if (!operation.Finished) return ERR_MortarSupplyState.PENDING;
|
|
if (!operation.Succeeded) return Fail("finite initial support shell operation failed; no retry");
|
|
}
|
|
foreach (IEntity equipped : m_Members)
|
|
{
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(equipped.FindComponent(InventoryStorageManagerComponent));
|
|
int shells = m_Battery.InventoryShells(inventory);
|
|
if (equipped == operatorActor)
|
|
{
|
|
if (shells != 0) return Fail("dedicated initial manpack operator unexpectedly carries mortar shells");
|
|
}
|
|
else if (shells != 9) return ERR_MortarSupplyState.PENDING;
|
|
}
|
|
if (m_Battery.CountShells() != 27) return ERR_MortarSupplyState.PENDING;
|
|
m_Ready = true;
|
|
// One physical-ready handoff: HQ already owns/reserves this group, while
|
|
// Intel must see its actual late-spawned members after finite stock settles.
|
|
mode.RegisterGroup(group);
|
|
group.ActivateAI();
|
|
foreach (AIAgent readyAgent : agents)
|
|
{
|
|
if (readyAgent) { readyAgent.SetPermanentLOD(0); readyAgent.ActivateAI(); }
|
|
}
|
|
m_Battery.Audit("CREW READY", string.Format("four original physical members, one manpack, 27 confirmed HE; new shell requests=%1; allocation permanently closed", m_RequestedShells));
|
|
return ERR_MortarSupplyState.READY;
|
|
}
|
|
}
|