Fix stalled hunter recovery and document intel cleanup
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -14,6 +14,20 @@ class ERR_VehicleBehaviorCleanup
|
||||
|
||||
// Preserve original hunter actors. Reboarding uses native AI tasks, never new
|
||||
// crew, vehicle teleports, health restoration, or ammunition replacement.
|
||||
// Readiness failures share one deadline even if the reason changes or combat
|
||||
// briefly interrupts reboarding. Native combat and moving vehicles defer exit.
|
||||
class ERR_HunterRecoveryWait
|
||||
{
|
||||
float Since = -1;
|
||||
void Reset() { Since = -1; }
|
||||
bool Expired(bool registered, bool ready, bool combat, float speed, float now)
|
||||
{
|
||||
if (!registered || ready) { Reset(); return false; }
|
||||
if (Since < 0 || now < Since) Since = now;
|
||||
return now - Since >= 120000 && !combat && speed >= 0 && speed <= 3;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_HunterCrewRecovery
|
||||
{
|
||||
protected ERR_HunterVehicle m_Unit;
|
||||
@@ -22,6 +36,7 @@ class ERR_HunterCrewRecovery
|
||||
protected float m_NextAttempt;
|
||||
protected string m_LastReason;
|
||||
protected bool m_Stopped;
|
||||
ref ERR_HunterRecoveryWait Wait = new ERR_HunterRecoveryWait();
|
||||
string Reason;
|
||||
void ERR_HunterCrewRecovery(ERR_HunterVehicle unit) { m_Unit = unit; }
|
||||
void SuspendBoarding()
|
||||
|
||||
@@ -81,6 +81,7 @@ class ERR_HunterVehicle : ERR_ConvoyVehicle
|
||||
bool DeliveredContact;
|
||||
ref ERR_HunterCrewRecovery Recovery;
|
||||
ref ERR_HunterTactics Tactics;
|
||||
ref ERR_HunterApproachMemory FailedApproaches = new ERR_HunterApproachMemory();
|
||||
ref ERR_HunterCloseContact CloseContact;
|
||||
bool CloseContactHeld;
|
||||
bool CombatHeld;
|
||||
@@ -770,10 +771,22 @@ class ERR_HunterService : ERR_ConvoyService
|
||||
// A delivered manoeuvre owns dismount/reboarding. The ordinary recovery
|
||||
// must not re-add its vehicle while the team is moving on foot.
|
||||
if (!unit.CloseContact) unit.CloseContact = new ERR_HunterCloseContact(unit);
|
||||
if (unit.CloseContact.Tick(m_Mode, now)) continue;
|
||||
if (unit.Tactics && unit.Tactics.Tick(hq, now)) continue;
|
||||
if (unit.CloseContact.Tick(m_Mode, now))
|
||||
{ if (unit.Recovery) unit.Recovery.Wait.Reset(); continue; }
|
||||
if (unit.Tactics && unit.Tactics.Tick(hq, now))
|
||||
{ if (unit.Recovery) unit.Recovery.Wait.Reset(); continue; }
|
||||
UpdateCombat(unit, now);
|
||||
if (unit.Recovery) unit.Recovery.Update(m_Mode, now);
|
||||
// Crew failures bypass the route-progress observer. Give them their own
|
||||
// bounded fallback, including a casualty occupying the driver's seat.
|
||||
if (unit.Recovery)
|
||||
{
|
||||
float recoverySpeed = -1;
|
||||
Physics recoveryPhysics = unit.Entity.GetPhysics();
|
||||
if (recoveryPhysics) recoverySpeed = recoveryPhysics.GetVelocity().Length();
|
||||
if (unit.Recovery.Wait.Expired(unit.Registered, unit.CrewValid(m_Mode), unit.CombatHeld, recoverySpeed, now))
|
||||
{ AbandonStuckVehicle(unit, "driver recovery timed out after two minutes"); continue; }
|
||||
}
|
||||
if (unit.CombatHeld) continue;
|
||||
if (!unit.Registered)
|
||||
{
|
||||
@@ -860,7 +873,7 @@ class ERR_HunterService : ERR_ConvoyService
|
||||
if (unit.Waypoint) { unit.Waypoint.SetCompletionRadius(50); unit.Group.AddWaypoint(unit.Waypoint); }
|
||||
ERR_HQ.Event("HUNTER REPATH", unit.Source + ": no movement for 60 seconds; native route requested again, attempt " + unit.Repaths.ToString());
|
||||
}
|
||||
protected void AbandonStuckVehicle(ERR_HunterVehicle unit)
|
||||
protected void AbandonStuckVehicle(ERR_HunterVehicle unit, string reason = "three route retries failed")
|
||||
{
|
||||
if (unit.Released || unit.PlayerCaptureObserved || ERR_ConvoyRules.Captured(unit.Entity, unit.Group)) return;
|
||||
// Stop reboarding/tactics before detaching the vehicle. Preserve the original
|
||||
@@ -878,7 +891,7 @@ class ERR_HunterService : ERR_ConvoyService
|
||||
if (!agent || !agent.m_UtilityComponent || !agent.m_UtilityComponent.m_AIInfo) continue;
|
||||
agent.m_UtilityComponent.AddAction(new SCR_AIGetOutVehicle(agent.m_UtilityComponent, null, unit.Entity, priority: 1000));
|
||||
}
|
||||
ERR_HQ.Event("HUNTER RELEASE", unit.Source + ": three route retries failed; original crew ordered out for on-foot HQ duties, vehicle retained");
|
||||
ERR_HQ.Event("HUNTER RELEASE", unit.Source + ": " + reason + "; original crew ordered out for on-foot HQ duties, vehicle retained");
|
||||
}
|
||||
override void Stop()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
// A bounded response by original hunter crews. Enemy positions are immutable
|
||||
// radio-delivered snapshots; native perception still owns aiming and firing.
|
||||
// Failures belong to the physical crew, not a disposable tactical assignment.
|
||||
// Bound both memory and lifetime so changed terrain/contact can be tried later.
|
||||
class ERR_HunterApproachMemory
|
||||
{
|
||||
protected ref array<vector> m_Positions = {};
|
||||
protected ref array<float> m_Expires = {};
|
||||
float RetryAfter;
|
||||
void Prune(float now)
|
||||
{
|
||||
for (int i = m_Expires.Count() - 1; i >= 0; i--)
|
||||
if (now >= m_Expires[i]) { m_Expires.Remove(i); m_Positions.Remove(i); }
|
||||
}
|
||||
void Reject(vector position, float now)
|
||||
{
|
||||
Prune(now); RetryAfter = now + 60000;
|
||||
for (int i = 0; i < m_Positions.Count(); i++)
|
||||
if (vector.DistanceXZ(position, m_Positions[i]) < 85)
|
||||
{ m_Expires[i] = now + 300000; return; }
|
||||
if (m_Positions.Count() >= 8) { m_Positions.Remove(0); m_Expires.Remove(0); }
|
||||
m_Positions.Insert(position); m_Expires.Insert(now + 300000);
|
||||
}
|
||||
void Append(array<vector> excluded, float now)
|
||||
{
|
||||
Prune(now);
|
||||
foreach (vector position : m_Positions) excluded.Insert(position);
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_HunterAttackRules
|
||||
{
|
||||
static float ManeuverBudget(float metres) { return Math.Clamp(30 + metres / 2, 90, 240) * 1000; }
|
||||
@@ -328,12 +356,22 @@ class ERR_HunterTactics
|
||||
bool Plan(array<vector> occupied)
|
||||
{
|
||||
if (!Living()) return false;
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
if (!Delivered && now < Unit.FailedApproaches.RetryAfter) return false;
|
||||
array<vector> excluded = {};
|
||||
foreach (vector reservedPosition : occupied) excluded.Insert(reservedPosition);
|
||||
Unit.FailedApproaches.Append(excluded, now);
|
||||
SCR_AIGroupUtilityComponent utility = Unit.Group.GetGroupUtilityComponent();
|
||||
if (!utility) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
float currentRange = vector.DistanceXZ(Unit.Entity.GetOrigin(), Attack.ReportPosition);
|
||||
if (Role == 0 && currentRange >= 140 && currentRange <= 500 && VisualCover())
|
||||
{ Stage = Unit.Entity.GetOrigin(); return true; }
|
||||
{
|
||||
bool rejected;
|
||||
foreach (vector failedPosition : excluded)
|
||||
if (vector.DistanceXZ(Unit.Entity.GetOrigin(), failedPosition) < 85) rejected = true;
|
||||
if (!rejected) { Stage = Unit.Entity.GetOrigin(); return true; }
|
||||
}
|
||||
// Approach from this crew's side of the contact. A common cover axis used
|
||||
// to send the opposite-side truck around the entire fight before dismount.
|
||||
vector approachForward = ERR_MountedCover.Approach(Unit.Entity.GetOrigin(), Attack.ReportPosition, Attack.Forward);
|
||||
@@ -348,7 +386,7 @@ class ERR_HunterTactics
|
||||
if (attempt % 2 == 0) lateral = -lateral;
|
||||
vector seed = desired - approachForward * (attempt * 15) + approachLeft * lateral;
|
||||
if (!ERR_SupplyConvoySystem.ApproachDirected(seed, point, angles, 100)) continue;
|
||||
if (!ERR_HunterAttackRules.Stage(point, Attack.ReportPosition, approachForward, occupied) || !utility.IsPositionAllowed(point) || !placement.Clear(point, angles[0], 2.5)) continue;
|
||||
if (!ERR_HunterAttackRules.Stage(point, Attack.ReportPosition, approachForward, excluded) || !utility.IsPositionAllowed(point) || !placement.Clear(point, angles[0], 2.5)) continue;
|
||||
if (Role == 0 && !ERR_MountedCover.ClearLane(Unit.Entity, point, Attack.ReportPosition)) continue;
|
||||
Stage = point; found = true; break;
|
||||
}
|
||||
@@ -359,7 +397,7 @@ class ERR_HunterTactics
|
||||
vector point = desired;
|
||||
if (offroad > 0) point += Vector(Math.RandomFloat(-90, 90), 0, Math.RandomFloat(-90, 90));
|
||||
point = ERR_Actors.Ground(point);
|
||||
if (!ERR_HunterAttackRules.Stage(point, Attack.ReportPosition, approachForward, occupied) || !utility.IsPositionAllowed(point)) continue;
|
||||
if (!ERR_HunterAttackRules.Stage(point, Attack.ReportPosition, approachForward, excluded) || !utility.IsPositionAllowed(point)) continue;
|
||||
if (!placement.Clear(point, ERR_ForceRules.Heading(point, Attack.ReportPosition, 0), 2.5)) continue;
|
||||
if (Role == 0 && !ERR_MountedCover.ClearLane(Unit.Entity, point, Attack.ReportPosition)) continue;
|
||||
Stage = point; found = true;
|
||||
@@ -371,7 +409,7 @@ class ERR_HunterTactics
|
||||
vector parked = Unit.Entity.GetOrigin();
|
||||
float range = vector.DistanceXZ(parked, Attack.ReportPosition);
|
||||
bool separate = true;
|
||||
foreach (vector reserved : occupied) if (vector.DistanceXZ(parked, reserved) < 85) separate = false;
|
||||
foreach (vector reserved : excluded) if (vector.DistanceXZ(parked, reserved) < 85) separate = false;
|
||||
if (range >= 140 && range <= 500 && separate) { Stage = parked; found = true; }
|
||||
}
|
||||
if (!found) { Note("no separated safe approach; keeping current orders"); return false; }
|
||||
@@ -410,6 +448,7 @@ class ERR_HunterTactics
|
||||
}
|
||||
protected bool AlternateStage(float now)
|
||||
{
|
||||
RejectStage(now);
|
||||
// Reserve failed positions as well as the other teams. Do not repeatedly
|
||||
// choose the same screened corner or send two vehicles to one firing point.
|
||||
array<vector> occupied = {};
|
||||
@@ -426,9 +465,13 @@ class ERR_HunterTactics
|
||||
Note("alternate approach assigned=" + Stage.ToString());
|
||||
return true;
|
||||
}
|
||||
protected void RejectStage(float now)
|
||||
{
|
||||
Unit.FailedApproaches.Reject(Stage, now);
|
||||
}
|
||||
protected bool RecoverApproach(float now)
|
||||
{
|
||||
if (m_ApproachRetries >= 2) { Note("approach still blocked after two recoveries; releasing assignment"); Stop(); return false; }
|
||||
if (m_ApproachRetries >= 2) { Note("approach still blocked after two recoveries; releasing assignment"); RejectStage(now); Stop(); return false; }
|
||||
m_ApproachRetries++;
|
||||
bool assigned;
|
||||
if (m_ApproachRetries == 1) assigned = Move(Stage, 35);
|
||||
@@ -444,7 +487,7 @@ class ERR_HunterTactics
|
||||
if (VisualCover()) { m_BlindSince = -1; return true; }
|
||||
if (m_BlindSince < 0) m_BlindSince = now;
|
||||
if (now - m_BlindSince < 20000) return true;
|
||||
if (m_CoverRetries >= 3) { Note("no visual cover after three alternate positions; releasing assignment"); Stop(); return false; }
|
||||
if (m_CoverRetries >= 3) { Note("no visual cover after three alternate positions; releasing assignment"); RejectStage(now); Stop(); return false; }
|
||||
m_CoverRetries++; m_BlindSince = now;
|
||||
Note("cover position has no visual contact; looking for another firing angle");
|
||||
AlternateStage(now);
|
||||
@@ -500,7 +543,7 @@ class ERR_HunterTactics
|
||||
{
|
||||
if (ERR_HunterAttackRules.ExtendApproach(m_ApproachStarted, m_ApproachProgress, now))
|
||||
{ Deadline = Math.Min(now + 30000, m_ApproachStarted + 300000); Note("approach still progressing; allowing another bounded route interval"); }
|
||||
else { Note("approach made no recent progress or exhausted five-minute budget; release this assignment"); Stop(); return false; }
|
||||
else { Note("approach made no recent progress or exhausted five-minute budget; release this assignment"); RejectStage(now); Stop(); return false; }
|
||||
}
|
||||
else if (now - m_ApproachProgress >= 30000 && !RecoverApproach(now)) return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user