Validate independent packed addons and complete physical mortar salvos
This commit is contained in:
@@ -34,10 +34,15 @@ class ERR_MortarBattery
|
||||
ref ERR_HQRoute RadioOrder;
|
||||
ref ERR_HQReport PendingRadioReport;
|
||||
ref ERR_HQReport PendingSupplyReport;
|
||||
ref ERR_MortarRefresh Refresh;
|
||||
bool RefreshRejected; // latched through an already committed projectile
|
||||
protected ref ERR_MortarReadiness m_Readiness;
|
||||
protected ref ERR_MortarSupplyTransfer m_Transfer;
|
||||
protected ref array<IEntity> m_AttemptedShells = {};
|
||||
protected bool m_Stopped;
|
||||
protected SCR_AIStaticArtilleryActivity m_ObservedActivity;
|
||||
protected int m_ObservedActivityOrder;
|
||||
protected EAIActionState m_ActivityTerminal = EAIActionState.EVALUATED;
|
||||
protected ref ERR_MortarReportLedger m_ReportLedger = new ERR_MortarReportLedger();
|
||||
vector Position;
|
||||
vector Impact;
|
||||
@@ -45,6 +50,7 @@ class ERR_MortarBattery
|
||||
int AimedShot = -1;
|
||||
float FlightTime;
|
||||
float AimedAt = -1;
|
||||
float AimValidatedAt = -1;
|
||||
SCR_AIGetArtilleryAimDistanceCompensation AimSolver;
|
||||
ref ERR_MortarAimSolution AimSolution;
|
||||
float AirspaceUntil;
|
||||
@@ -135,6 +141,28 @@ class ERR_MortarBattery
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CanRefresh(ERR_HQReport report, float now)
|
||||
{
|
||||
if (m_Stopped || !CommandOpen || !Activity || Refresh || RefreshRejected || Rejected || !Operational()) return false;
|
||||
return ERR_MortarRefreshRules.Newer(Report, report, now) && m_ReportLedger.Available(report.MessageId, report.ObservedAt, now);
|
||||
}
|
||||
bool BeginRefresh(ERR_HQReport report, ERR_HQRoute route, float now)
|
||||
{
|
||||
if (!route || !CanRefresh(report, now) || !m_ReportLedger.Claim(report.MessageId, report.ObservedAt, now)) return false;
|
||||
Refresh = new ERR_MortarRefresh(this, report, route);
|
||||
Audit("UPDATE QUEUED", string.Format("same order; update radio=M%1 newer observation=M%2 observed=%3", route.MessageId, report.MessageId, report.ObservedAt));
|
||||
return true;
|
||||
}
|
||||
void RejectRefresh(string reason)
|
||||
{
|
||||
// Never clear the commit latch or steal the physical projectile callback.
|
||||
// AllowShot refuses further loads now; HQ cancels after it is observed.
|
||||
RefreshRejected = true;
|
||||
Refresh = null;
|
||||
LastFailure = reason;
|
||||
Audit("UPDATE REJECTED", reason);
|
||||
}
|
||||
|
||||
void ObserveProjectile(IEntity projectileEntity, IEntity firingCharacter, bool playerGunner)
|
||||
{
|
||||
if (!projectileEntity)
|
||||
@@ -149,6 +177,7 @@ class ERR_MortarBattery
|
||||
// Only a physical projectile unlocks the next load. It must be aimed again.
|
||||
Released = false;
|
||||
AimedAt = -1;
|
||||
AimValidatedAt = -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));
|
||||
@@ -383,6 +412,52 @@ class ERR_MortarBattery
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void WatchActivity()
|
||||
{
|
||||
DetachActivity();
|
||||
if (!Activity || !CommandOpen || m_Stopped) return;
|
||||
m_ObservedActivity = Activity;
|
||||
m_ObservedActivityOrder = OrderId;
|
||||
Activity.m_OnActionCompleted.Insert(OnActivityCompleted);
|
||||
Activity.m_OnActionFailed.Insert(OnActivityFailed);
|
||||
}
|
||||
protected void DetachActivity()
|
||||
{
|
||||
if (m_ObservedActivity)
|
||||
{
|
||||
m_ObservedActivity.m_OnActionCompleted.Remove(OnActivityCompleted);
|
||||
m_ObservedActivity.m_OnActionFailed.Remove(OnActivityFailed);
|
||||
}
|
||||
m_ObservedActivity = null;
|
||||
m_ObservedActivityOrder = 0;
|
||||
m_ActivityTerminal = EAIActionState.EVALUATED;
|
||||
}
|
||||
protected void OnActivityCompleted(SCR_AIActionBase action)
|
||||
{
|
||||
ObserveActivityTerminal(action, EAIActionState.COMPLETED);
|
||||
}
|
||||
protected void OnActivityFailed(SCR_AIActionBase action)
|
||||
{
|
||||
ObserveActivityTerminal(action, EAIActionState.FAILED);
|
||||
}
|
||||
protected void ObserveActivityTerminal(SCR_AIActionBase action, EAIActionState terminal)
|
||||
{
|
||||
if (m_Stopped || !CommandOpen || !Activity || action != Activity || action != m_ObservedActivity || OrderId != m_ObservedActivityOrder) return;
|
||||
if (m_ActivityTerminal != EAIActionState.EVALUATED) return;
|
||||
if (terminal != EAIActionState.COMPLETED && terminal != EAIActionState.FAILED) return;
|
||||
// Vanilla deselection can overwrite COMPLETED/FAILED with EVALUATED
|
||||
// before the next HQ tick. Record its actual event, never infer completion
|
||||
// from projectile totals, and never cancel from inside the native callback.
|
||||
m_ActivityTerminal = terminal;
|
||||
Audit("ACTIVITY EVENT", string.Format("native terminal event=%1 current state=%2 captured order=%3 physical order shots=%4 total shots=%5 committed=%6", terminal, Activity.GetActionState(), m_ObservedActivityOrder, OrderShots, TotalShots, Released));
|
||||
}
|
||||
protected EAIActionState ActivityTerminal()
|
||||
{
|
||||
if (Activity == m_ObservedActivity && OrderId == m_ObservedActivityOrder)
|
||||
if (m_ActivityTerminal == EAIActionState.COMPLETED || m_ActivityTerminal == EAIActionState.FAILED) return m_ActivityTerminal;
|
||||
return Activity.GetActionState();
|
||||
}
|
||||
|
||||
void Cancel(string reason = "cancel requested")
|
||||
{
|
||||
if (CommandOpen)
|
||||
@@ -391,12 +466,18 @@ class ERR_MortarBattery
|
||||
Audit("ORDER END", string.Format("reason=%1 observed shots=%2 shell committed=%3 elapsed=%4s last failure=%5", reason, OrderShots, Released, (GetGame().GetWorld().GetWorldTime() - OrderedAt) / 1000, LastFailure));
|
||||
}
|
||||
CommandOpen = false;
|
||||
// Remove before our own cleanup calls Complete; that is not a native
|
||||
// successful fire mission and must not manufacture an observed event.
|
||||
DetachActivity();
|
||||
RadioOrder = null;
|
||||
PendingRadioReport = null;
|
||||
PendingSupplyReport = null;
|
||||
Refresh = null;
|
||||
RefreshRejected = false;
|
||||
if (m_Transfer) m_Transfer.Invalidate();
|
||||
Report = null;
|
||||
AimedAt = -1;
|
||||
AimValidatedAt = -1;
|
||||
AimSolver = null;
|
||||
AimSolution = null;
|
||||
if (Activity)
|
||||
@@ -451,8 +532,9 @@ class ERR_MortarBattery
|
||||
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));
|
||||
if (Activity.GetActionState() == EAIActionState.FAILED) Cancel("native artillery activity FAILED");
|
||||
else if (Activity.GetActionState() == EAIActionState.COMPLETED) Cancel("native artillery activity COMPLETED (shot count is separate)");
|
||||
EAIActionState terminal = ActivityTerminal();
|
||||
if (terminal == EAIActionState.FAILED) Cancel("native artillery activity FAILED");
|
||||
else if (terminal == EAIActionState.COMPLETED) Cancel("native artillery activity COMPLETED (shot count is separate)");
|
||||
else if (now - OrderedAt > ERR_MortarSalvo.TIMEOUT_MS) Cancel("120s artillery salvo timeout");
|
||||
}
|
||||
if (StartupActivated)
|
||||
@@ -468,6 +550,7 @@ class ERR_MortarBattery
|
||||
Released = false;
|
||||
UpdateCrewDetail(now, true);
|
||||
Activity = new SCR_AIStaticArtilleryActivity(Group.GetGroupUtilityComponent(), null, report.Position, SCR_EAIArtilleryAmmoType.HIGH_EXPLOSIVE, ERR_MortarSalvo.ROUNDS);
|
||||
WatchActivity();
|
||||
Group.GetGroupUtilityComponent().AddAction(Activity);
|
||||
Audit("AIM", string.Format("native activity created; latest source=M%1 observed=%2", report.MessageId, report.Description(now)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user