Advance standalone admission, finite artillery and native validation
This commit is contained in:
@@ -36,6 +36,8 @@ modded class SCR_AIGetArtilleryAimDistanceCompensation
|
||||
if (!battery)
|
||||
return super.EOnTaskSimulate(owner, dt);
|
||||
battery.AimedAt = -1;
|
||||
battery.AimSolver = null;
|
||||
battery.AimSolution = null;
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
ERR_HQReport report = system.Latest(battery, now);
|
||||
if (!ERR_HQ.MissionActive()) { system.Withhold(battery, "aim: mission inactive"); return ENodeResult.FAIL; }
|
||||
@@ -43,13 +45,56 @@ modded class SCR_AIGetArtilleryAimDistanceCompensation
|
||||
if (battery.Released) { system.Withhold(battery, "aim: waiting for committed projectile"); return ENodeResult.FAIL; }
|
||||
if (!report) { system.Withhold(battery, "aim: " + system.ContactFailure(battery)); return ENodeResult.FAIL; }
|
||||
battery.Trace("AIM START", "solving moving intercept", string.Format("latest source=M%1; %2", report.MessageId, report.Description(now)));
|
||||
ERR_MortarAimSolution solution = ERR_Solve(battery, system, report, now);
|
||||
if (!solution) return ENodeResult.FAIL;
|
||||
battery.Impact = solution.Impact;
|
||||
battery.AimOffset = solution.SalvoOffset;
|
||||
battery.AimedShot = battery.OrderShots;
|
||||
battery.FlightTime = solution.FlightTime;
|
||||
battery.AimSolution = solution;
|
||||
battery.AimSolver = this; // weak: do not retain a destroyed native behavior tree
|
||||
battery.AimedAt = now;
|
||||
battery.LastFailure = "";
|
||||
battery.Trace("AIM SOLUTION", "ready", string.Format("predicted impact=%1 flight=%2s charge index=%3 aim target=%4 latest source=M%5", solution.Impact, solution.FlightTime, solution.Charge, solution.AimTarget, report.MessageId));
|
||||
SetVariableOut(PORT_AIM_POS, solution.AimTarget);
|
||||
SetVariableOut(PORT_INIT_SPEED_ID, solution.Charge);
|
||||
return ENodeResult.SUCCESS;
|
||||
}
|
||||
|
||||
// Reuse this exact native node's coefficient/elevation APIs. A slow native
|
||||
// barrel can keep its command only if a NEW solve still matches that command.
|
||||
bool ERR_RevalidateAim(ERR_MortarBattery battery, ERR_MortarSystem system, ERR_HQReport report, float now)
|
||||
{
|
||||
IEntity gun;
|
||||
ResourceName ammo;
|
||||
GetVariableIn(PORT_ARTILLERY_ENTITY, gun);
|
||||
GetVariableIn(PORT_AMMO_PREFAB, ammo);
|
||||
ERR_MortarAimSolution commanded = battery.AimSolution;
|
||||
if (battery.AimSolver != this || !commanded || !commanded.Matches(battery, report) || gun != battery.Gun || ammo != ERR_MortarBattery.SHELL)
|
||||
return system.Withhold(battery, "original aim solver or command no longer available");
|
||||
if (!ERR_MortarPrediction.Fresh(report, now)) return system.Withhold(battery, system.ContactFailure(battery));
|
||||
ERR_MortarAimSolution updated = ERR_Solve(battery, system, report, now);
|
||||
if (!updated) return false;
|
||||
if (!ERR_MortarAimRules.Equivalent(commanded.Charge, updated.Charge, commanded.GunOrigin, updated.GunOrigin, commanded.Impact, updated.Impact, commanded.AimTarget, updated.AimTarget, commanded.FlightTime, updated.FlightTime))
|
||||
return system.Withhold(battery, "new ballistic solution requires native re-aim", string.Format("gun drift=%1m impact drift=%2m aim drift=%3m charge=%4/%5 flight=%6/%7s", vector.Distance(commanded.GunOrigin, updated.GunOrigin), vector.Distance(commanded.Impact, updated.Impact), vector.Distance(commanded.AimTarget, updated.AimTarget), commanded.Charge, updated.Charge, commanded.FlightTime, updated.FlightTime));
|
||||
// Keep the ORIGINAL command/impact/flight. Comparing successive refreshed
|
||||
// solutions would allow a moving target to drift away in small increments.
|
||||
battery.AimedAt = now;
|
||||
battery.Trace("AIM REVALIDATED", "same native command", string.Format("fresh ballistic solve matched original; source=M%1 observed age=%2s", report.MessageId, (now - report.ObservedAt) / 1000));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected ERR_MortarAimSolution ERR_Solve(ERR_MortarBattery battery, ERR_MortarSystem system, ERR_HQReport report, float now)
|
||||
{
|
||||
IEntity gun = battery.Gun;
|
||||
vector gunOrigin = gun.GetOrigin();
|
||||
ResourceName ammo;
|
||||
GetVariableIn(PORT_AMMO_PREFAB, ammo);
|
||||
if (ammo != ERR_MortarBattery.SHELL) { system.Withhold(battery, "unsupported ammunition prefab", ammo); return ENodeResult.FAIL; }
|
||||
if (ammo != ERR_MortarBattery.SHELL) { system.Withhold(battery, "unsupported ammunition prefab", ammo); return null; }
|
||||
Resource resource = Resource.Load(ammo);
|
||||
if (!resource || !resource.IsValid() || !resource.GetResource()) { system.Withhold(battery, "shell resource load failed", ammo); return ENodeResult.FAIL; }
|
||||
if (!resource || !resource.IsValid() || !resource.GetResource()) { system.Withhold(battery, "shell resource load failed", ammo); return null; }
|
||||
IEntitySource source = resource.GetResource().ToEntitySource();
|
||||
if (!source) { system.Withhold(battery, "shell resource has no entity source", ammo); return ENodeResult.FAIL; }
|
||||
if (!source) { system.Withhold(battery, "shell resource has no entity source", ammo); return null; }
|
||||
array<float> coefficients = {};
|
||||
GetAmmoInitialSpeedCoefficients(ammo, coefficients);
|
||||
if (coefficients.IsEmpty())
|
||||
@@ -66,13 +111,13 @@ modded class SCR_AIGetArtilleryAimDistanceCompensation
|
||||
// Reject non-convergence rather than fire with an arbitrary lead time.
|
||||
for (int iteration = 0; iteration < 4; iteration++)
|
||||
{
|
||||
if (!ERR_MortarPrediction.Predict(report, now, flight + 2, impact)) { system.Withhold(battery, "aim prediction: " + ERR_MortarPrediction.FailureReason, string.Format("flight lead=%1s; %2", flight + 2, report.Description(now))); return ENodeResult.FAIL; }
|
||||
if (!system.OutsideSettlements(impact)) { system.Withhold(battery, "aim target inside settlement exclusion", system.SettlementReason); return ENodeResult.FAIL; }
|
||||
if (!ERR_MortarPrediction.Predict(report, now, flight + 2, impact)) { system.Withhold(battery, "aim prediction: " + ERR_MortarPrediction.FailureReason, string.Format("flight lead=%1s; %2", flight + 2, report.Description(now))); return null; }
|
||||
if (!system.OutsideSettlements(impact)) { system.Withhold(battery, "aim target inside settlement exclusion", system.SettlementReason); return null; }
|
||||
impact += salvoOffset;
|
||||
impact[1] = SCR_TerrainHelper.GetTerrainY(impact);
|
||||
float distance = vector.DistanceXZ(gun.GetOrigin(), impact);
|
||||
float altitude = impact[1] - gun.GetOrigin()[1];
|
||||
if (distance < 350 || distance > 4000) { system.Withhold(battery, "predicted impact out of range", string.Format("iteration=%1 distance=%2m impact=%3 allowed=350..4000m", iteration, distance, impact)); return ENodeResult.FAIL; }
|
||||
float distance = vector.DistanceXZ(gunOrigin, impact);
|
||||
float altitude = impact[1] - gunOrigin[1];
|
||||
if (distance < 350 || distance > 4000) { system.Withhold(battery, "predicted impact out of range", string.Format("iteration=%1 distance=%2m impact=%3 allowed=350..4000m", iteration, distance, impact)); return null; }
|
||||
bool reachable;
|
||||
float calculated;
|
||||
for (int i = 0; i < coefficients.Count(); i++)
|
||||
@@ -86,25 +131,26 @@ modded class SCR_AIGetArtilleryAimDistanceCompensation
|
||||
reachable = true;
|
||||
break;
|
||||
}
|
||||
if (!reachable) { system.Withhold(battery, "no native ballistic solution within elevation/flight-time limits", string.Format("iteration=%1 distance=%2 altitude=%3 charges=%4 elevation radians=%5..%6 flight limit=75s", iteration, distance, altitude, coefficients.Count(), minimum, maximum)); return ENodeResult.FAIL; }
|
||||
if (!reachable) { system.Withhold(battery, "no native ballistic solution within elevation/flight-time limits", string.Format("iteration=%1 distance=%2 altitude=%3 charges=%4 elevation radians=%5..%6 flight limit=75s", iteration, distance, altitude, coefficients.Count(), minimum, maximum)); return null; }
|
||||
converged = Math.AbsFloat(calculated - flight) < 1;
|
||||
flight = calculated;
|
||||
if (converged)
|
||||
break;
|
||||
}
|
||||
if (!converged) { system.Withhold(battery, "moving intercept did not converge in four iterations"); return ENodeResult.FAIL; }
|
||||
if (!system.Safe(battery, impact, flight + 2)) { system.Withhold(battery, "aim impact safety rejected", system.SafetyReason); return ENodeResult.FAIL; }
|
||||
battery.Impact = impact;
|
||||
battery.AimOffset = salvoOffset;
|
||||
battery.AimedShot = battery.OrderShots;
|
||||
battery.FlightTime = flight;
|
||||
battery.AimedAt = now;
|
||||
battery.LastFailure = "";
|
||||
battery.Trace("AIM SOLUTION", "ready", string.Format("predicted impact=%1 flight=%2s charge index=%3 aim height offset=%4m latest source=M%5", impact, flight, charge, offset, report.MessageId));
|
||||
vector aim = impact + Vector(0, offset, 0);
|
||||
SetVariableOut(PORT_AIM_POS, aim);
|
||||
SetVariableOut(PORT_INIT_SPEED_ID, charge);
|
||||
return ENodeResult.SUCCESS;
|
||||
if (!converged) { system.Withhold(battery, "moving intercept did not converge in four iterations"); return null; }
|
||||
if (!system.Safe(battery, impact, flight + 2)) { system.Withhold(battery, "aim impact safety rejected", system.SafetyReason); return null; }
|
||||
ERR_MortarAimSolution solution = new ERR_MortarAimSolution();
|
||||
solution.Gun = gun;
|
||||
solution.Report = report;
|
||||
solution.OrderId = battery.OrderId;
|
||||
solution.Shot = battery.OrderShots;
|
||||
solution.GunOrigin = gunOrigin;
|
||||
solution.Impact = impact;
|
||||
solution.AimTarget = impact + Vector(0, offset, 0);
|
||||
solution.SalvoOffset = salvoOffset;
|
||||
solution.Charge = charge;
|
||||
solution.FlightTime = flight;
|
||||
return solution;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,17 +250,27 @@ modded class SCR_AIStaticArtilleryBehavior_ShootingLogic
|
||||
}
|
||||
if (battery)
|
||||
{
|
||||
if (!battery.CommandOpen) return ENodeResult.FAIL;
|
||||
// Loading can outlast the native pause. Count confirmed shells only.
|
||||
if (battery.OrderShots <= behavior.GetShotCount()) return ENodeResult.RUNNING;
|
||||
int gate = ERR_MortarSupplyRules.CycleGate(battery.CommandOpen, battery.OrderShots, behavior.GetShotCount(), ERR_MortarSalvo.ROUNDS, ERR_MortarSupplyState.READY);
|
||||
if (gate < 0) return ENodeResult.FAIL;
|
||||
if (gate == 0) return ENodeResult.RUNNING;
|
||||
// Native super increments the behavior counter. Wait BEFORE calling it,
|
||||
// so repeated pending ticks cannot count the same projectile twice.
|
||||
if (battery.OrderShots < ERR_MortarSalvo.ROUNDS)
|
||||
{
|
||||
ERR_MortarSupplyState supply = battery.SupplyState(GetGame().GetWorld().GetWorldTime());
|
||||
if (supply == ERR_MortarSupplyState.PENDING) return ENodeResult.RUNNING;
|
||||
if (supply == ERR_MortarSupplyState.FAILED)
|
||||
{
|
||||
ERR_MortarSystem system = ERR_MortarSystem.Get();
|
||||
if (system) system.Withhold(battery, "next native load lacks confirmed physical gunner ammunition");
|
||||
return ENodeResult.FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
ENodeResult result = super.EOnTaskSimulate(owner, dt);
|
||||
if (battery && result == ENodeResult.SUCCESS)
|
||||
{
|
||||
SetVariableOut(PORT_AIMING_NEEDED, true);
|
||||
// Transfer existing nearby crew ammunition before the next native load.
|
||||
if (battery.CommandOpen && battery.OrderShots < ERR_MortarSalvo.ROUNDS)
|
||||
battery.SupplyGunner();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user