Advance standalone admission, finite artillery and native validation
This commit is contained in:
@@ -33,12 +33,20 @@ class ERR_MortarBattery
|
||||
ref ERR_HQReport Report;
|
||||
ref ERR_HQRoute RadioOrder;
|
||||
ref ERR_HQReport PendingRadioReport;
|
||||
ref ERR_HQReport PendingSupplyReport;
|
||||
protected ref ERR_MortarReadiness m_Readiness;
|
||||
protected ref ERR_MortarSupplyTransfer m_Transfer;
|
||||
protected ref array<IEntity> m_AttemptedShells = {};
|
||||
protected bool m_Stopped;
|
||||
protected ref ERR_MortarReportLedger m_ReportLedger = new ERR_MortarReportLedger();
|
||||
vector Position;
|
||||
vector Impact;
|
||||
vector AimOffset;
|
||||
int AimedShot = -1;
|
||||
float FlightTime;
|
||||
float AimedAt = -1;
|
||||
SCR_AIGetArtilleryAimDistanceCompensation AimSolver;
|
||||
ref ERR_MortarAimSolution AimSolution;
|
||||
float AirspaceUntil;
|
||||
vector AirspaceImpact;
|
||||
float AirspaceRadius = 600;
|
||||
@@ -94,6 +102,7 @@ class ERR_MortarBattery
|
||||
|
||||
string OperationalReason()
|
||||
{
|
||||
if (m_Stopped) return "battery stopped";
|
||||
if (!Gun) return "gun missing";
|
||||
if (!SpawnFailure.IsEmpty()) return SpawnFailure;
|
||||
if (!Group || !Group.GetGroupUtilityComponent()) return "crew group missing";
|
||||
@@ -105,8 +114,13 @@ class ERR_MortarBattery
|
||||
return "";
|
||||
}
|
||||
|
||||
void BeginRequest(ERR_HQReport report, float now)
|
||||
bool CanRequest(ERR_HQReport report, float now)
|
||||
{
|
||||
return !m_Stopped && ERR_MortarPrediction.Fresh(report, now) && m_ReportLedger.Available(report.MessageId, report.ObservedAt, now);
|
||||
}
|
||||
bool BeginRequest(ERR_HQReport report, float now)
|
||||
{
|
||||
if (!CanRequest(report, now) || !m_ReportLedger.Claim(report.MessageId, report.ObservedAt, now)) return false;
|
||||
Cancel("superseded by new HQ order");
|
||||
FlushTraces();
|
||||
OrderId++;
|
||||
@@ -118,6 +132,7 @@ class ERR_MortarBattery
|
||||
OrderedAt = now;
|
||||
LastFailure = "";
|
||||
Audit("COMMAND", string.Format("requested rounds=%1 HE radius=%2m; %3", ERR_MortarSalvo.ROUNDS, ERR_MortarSalvo.RADIUS_METRES, report.Description(now)));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObserveProjectile(IEntity projectileEntity, IEntity firingCharacter, bool playerGunner)
|
||||
@@ -134,6 +149,8 @@ class ERR_MortarBattery
|
||||
// Only a physical projectile unlocks the next load. It must be aimed again.
|
||||
Released = false;
|
||||
AimedAt = -1;
|
||||
AimSolver = null;
|
||||
AimSolution = null;
|
||||
Audit("FIRED", string.Format("physical projectile=%1 origin=%2 order shots=%3 total shots=%4 predicted impact=%5 predicted flight=%6s gunner=%7 (impact not observed)", projectileEntity, projectileEntity.GetOrigin(), OrderShots, TotalShots, Impact, FlightTime, firingCharacter));
|
||||
}
|
||||
else
|
||||
@@ -234,10 +251,11 @@ class ERR_MortarBattery
|
||||
Position = gun.GetOrigin();
|
||||
SpawnedAt = now;
|
||||
Claimed = true;
|
||||
m_Readiness = new ERR_MortarReadiness(this);
|
||||
}
|
||||
|
||||
|
||||
protected int InventoryShells(InventoryStorageManagerComponent inventory)
|
||||
int InventoryShells(InventoryStorageManagerComponent inventory)
|
||||
{
|
||||
if (!inventory)
|
||||
return 0;
|
||||
@@ -252,36 +270,41 @@ class ERR_MortarBattery
|
||||
return count;
|
||||
}
|
||||
|
||||
protected void Equip(IEntity soldier)
|
||||
bool Stopped() { return m_Stopped; }
|
||||
bool SupplyPending() { return m_Transfer && m_Transfer.Unresolved(); }
|
||||
bool SupplyAuthorized(float now)
|
||||
{
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(soldier.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory)
|
||||
{
|
||||
Audit("EQUIP FAILED", string.Format("crew=%1 inventory missing", soldier));
|
||||
return;
|
||||
}
|
||||
if (soldier == RadioOperator)
|
||||
{
|
||||
int capability = ERR_HQRadio.Capability(soldier);
|
||||
Audit("RADIO EQUIP", string.Format("operator=%1 capability=%2 (2=physical manpack); next HQ radio index verifies route", soldier, capability));
|
||||
return;
|
||||
}
|
||||
if (ERR_HQRadio.Capability(soldier) == 0 && !inventory.TrySpawnPrefabToStorage("{E1A5D4B878AA8980}Prefabs/Items/Equipment/Radios/Radio_R148.et"))
|
||||
Print("Rereintroduced AI [mortar]: crew radio did not fit; HQ handoff required", LogLevel.WARNING);
|
||||
// Native mortar-crew backpacks carry three EMPTY three-round boxes.
|
||||
// Fill them once. No infinite ammunition or replacement after casualties.
|
||||
for (int i = InventoryShells(inventory); i < 9; i++)
|
||||
{
|
||||
if (!inventory.TrySpawnPrefabToStorage(SHELL, purpose: EStoragePurpose.PURPOSE_DEPOSIT))
|
||||
break;
|
||||
}
|
||||
Audit("EQUIP", string.Format("crew=%1 inventory HE=%2 requested=9 radio capability=%3 (0=none)", soldier, InventoryShells(inventory), ERR_HQRadio.Capability(soldier)));
|
||||
if (!Replication.IsServer() || m_Stopped || !StartupActivated || !Operational() || !CommandOpen || !ERR_HQ.MissionActive()) return false;
|
||||
ERR_MortarSystem system = ERR_MortarSystem.Get();
|
||||
if (!system || system.FindGun(Gun) != this) return false;
|
||||
ERR_HQReport suppliedReport = Report;
|
||||
if (!suppliedReport) suppliedReport = PendingSupplyReport;
|
||||
return ERR_MortarPrediction.Fresh(suppliedReport, now);
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
Cancel("HQ stopped");
|
||||
if (m_Readiness) m_Readiness.Stop();
|
||||
}
|
||||
|
||||
bool SupplyGunner()
|
||||
ERR_MortarSupplyState SupplyState(float now)
|
||||
{
|
||||
if (!Operational())
|
||||
return SupplyFailed(OperationalReason());
|
||||
// Observe an existing native operation even after its order was canceled.
|
||||
// No second request may race an unresolved transfer on this battery.
|
||||
if (m_Transfer)
|
||||
{
|
||||
ERR_MortarSupplyState observed = m_Transfer.Poll(now);
|
||||
if (m_Transfer.CapturedOrder() == OrderId)
|
||||
{
|
||||
if (observed == ERR_MortarSupplyState.READY) m_Transfer = null;
|
||||
return observed;
|
||||
}
|
||||
if (m_Transfer.Unresolved()) return ERR_MortarSupplyState.FAILED;
|
||||
m_Transfer = null;
|
||||
}
|
||||
if (!SupplyAuthorized(now)) return SupplyStateFailed("current HQ order, crew or report is unavailable");
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(Gun.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
IEntity gunner;
|
||||
if (usage && usage.GetTurretCompartmentSlot())
|
||||
@@ -303,15 +326,15 @@ class ERR_MortarBattery
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!gunner) return SupplyFailed("no fighting gunner found");
|
||||
if (ERR_Actors.Group(gunner) != Group) return SupplyFailed("turret occupant belongs to another group");
|
||||
if (!ERR_Actors.CanFight(gunner)) return SupplyFailed("gunner cannot fight");
|
||||
if (!gunner) return SupplyStateFailed("no fighting gunner found");
|
||||
if (ERR_Actors.Group(gunner) != Group) return SupplyStateFailed("turret occupant belongs to another group");
|
||||
if (!ERR_Actors.CanFight(gunner)) return SupplyStateFailed("gunner cannot fight");
|
||||
InventoryStorageManagerComponent destination = InventoryStorageManagerComponent.Cast(gunner.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!destination)
|
||||
return SupplyFailed("gunner inventory missing");
|
||||
return SupplyStateFailed("gunner inventory missing");
|
||||
if (InventoryShells(destination) > 0)
|
||||
return true;
|
||||
int nearbyDonors, donorShells, storageRejected, moveRejected;
|
||||
return ERR_MortarSupplyState.READY;
|
||||
int nearbyDonors, donorShells, storageRejected;
|
||||
array<IEntity> consideredShells = {};
|
||||
foreach (AIAgent bearer : agents)
|
||||
{
|
||||
@@ -330,20 +353,27 @@ class ERR_MortarBattery
|
||||
{
|
||||
if (!item || !item.GetPrefabData() || item.GetPrefabData().GetPrefabName() != SHELL)
|
||||
continue;
|
||||
if (consideredShells.Contains(item)) continue;
|
||||
if (consideredShells.Contains(item) || m_AttemptedShells.Contains(item)) continue;
|
||||
consideredShells.Insert(item);
|
||||
donorShells++;
|
||||
BaseInventoryStorageComponent storage = destination.FindStorageForItem(item, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (!storage) { storageRejected++; continue; }
|
||||
if (inventory.TryMoveItemToStorage(item, storage))
|
||||
{
|
||||
Audit("AMMO TRANSFER", string.Format("request accepted donor=%1 gunner=%2 shell=%3; inventory move is not a fired shot", donor, gunner, item));
|
||||
return true;
|
||||
}
|
||||
moveRejected++;
|
||||
m_AttemptedShells.Insert(item); // even rejected/ambiguous requests are never retried
|
||||
m_Transfer = new ERR_MortarSupplyTransfer(this, donor, gunner, item, storage, now);
|
||||
if (!m_Transfer.Begin(now)) return SupplyStateFailed("exact shell transfer request rejected; no retry");
|
||||
Audit("AMMO TRANSFER", string.Format("one exact-item move pending donor=%1 gunner=%2 shell=%3 order=%4; awaiting callback AND physical destination", donor, gunner, item, OrderId));
|
||||
return ERR_MortarSupplyState.PENDING;
|
||||
}
|
||||
}
|
||||
return SupplyFailed(string.Format("gunner empty; fighting donors with inventory within 12m=%1 donor HE=%2 destination storage rejected=%3 native move rejected=%4", nearbyDonors, donorShells, storageRejected, moveRejected));
|
||||
return SupplyStateFailed(string.Format("gunner empty; fighting donors within 12m=%1 unattempted HE=%2 destination storage rejected=%3", nearbyDonors, donorShells, storageRejected));
|
||||
}
|
||||
|
||||
// Compatibility for existing native callers; request acceptance is NOT ready.
|
||||
bool SupplyGunner() { return SupplyState(GetGame().GetWorld().GetWorldTime()) == ERR_MortarSupplyState.READY; }
|
||||
protected ERR_MortarSupplyState SupplyStateFailed(string reason)
|
||||
{
|
||||
SupplyFailed(reason);
|
||||
return ERR_MortarSupplyState.FAILED;
|
||||
}
|
||||
|
||||
bool SupplyFailed(string reason)
|
||||
@@ -363,8 +393,12 @@ class ERR_MortarBattery
|
||||
CommandOpen = false;
|
||||
RadioOrder = null;
|
||||
PendingRadioReport = null;
|
||||
PendingSupplyReport = null;
|
||||
if (m_Transfer) m_Transfer.Invalidate();
|
||||
Report = null;
|
||||
AimedAt = -1;
|
||||
AimSolver = null;
|
||||
AimSolution = null;
|
||||
if (Activity)
|
||||
Activity.Complete();
|
||||
Activity = null;
|
||||
@@ -399,42 +433,21 @@ class ERR_MortarBattery
|
||||
|
||||
void Update(float now)
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
if (!StartupActivated)
|
||||
{
|
||||
if (!SpawnFailure.IsEmpty()) return;
|
||||
if (!m_Readiness) { SpawnFailed("initial support readiness owner missing"); return; }
|
||||
ERR_MortarSupplyState startup = m_Readiness.Poll(now);
|
||||
if (startup != ERR_MortarSupplyState.READY) return;
|
||||
StartupActivated = true;
|
||||
}
|
||||
if (m_Transfer) m_Transfer.Poll(now);
|
||||
if (!Operational())
|
||||
{
|
||||
Cancel(OperationalReason());
|
||||
return;
|
||||
}
|
||||
if (!StartupActivated && now - SpawnedAt >= 10000)
|
||||
{
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(Gun.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage || !Group.GetGroupUtilityComponent().m_VehicleMgr || !Group.GetGroupUtilityComponent().m_VehicleMgr.TryAddVehicle(usage))
|
||||
{
|
||||
SpawnFailure = "native gun registration failed";
|
||||
Cancel(SpawnFailure);
|
||||
return;
|
||||
}
|
||||
array<AIAgent> initialCrew = {};
|
||||
Group.GetAgents(initialCrew);
|
||||
foreach (AIAgent initialAgent : initialCrew)
|
||||
{
|
||||
IEntity initialActor = initialAgent.GetControlledEntity();
|
||||
if (initialActor && initialActor.GetPrefabData() && initialActor.GetPrefabData().GetPrefabName() == RADIO_OPERATOR) RadioOperator = initialActor;
|
||||
}
|
||||
StartupActivated = true;
|
||||
Audit("CREW ACTIVATE", "startup delay elapsed; activating and equipping crew");
|
||||
Group.ActivateAI();
|
||||
array<AIAgent> agents = {};
|
||||
Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (agent && ERR_Actors.CanFight(agent.GetControlledEntity()))
|
||||
{
|
||||
agent.SetPermanentLOD(0);
|
||||
agent.ActivateAI();
|
||||
Equip(agent.GetControlledEntity());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Activity)
|
||||
{
|
||||
Trace("ACTIVITY", string.Format("native state=%1", Activity.GetActionState()), string.Format("age=%1s aim age=%2s committed=%3 shots=%4", (now - OrderedAt) / 1000, (now - AimedAt) / 1000, Released, OrderShots));
|
||||
@@ -448,6 +461,7 @@ class ERR_MortarBattery
|
||||
|
||||
void Order(ERR_HQReport report, float now)
|
||||
{
|
||||
if (m_Stopped || !StartupActivated || !Operational() || !CommandOpen || Activity || !ERR_MortarPrediction.Fresh(report, now)) return;
|
||||
Report = report.Snapshot();
|
||||
OrderedAt = now;
|
||||
NextOrder = now + 30000;
|
||||
|
||||
Reference in New Issue
Block a user