Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
158 lines
7.7 KiB
C
158 lines
7.7 KiB
C
// Original checkpoint duty, not an invented HQ sighting/order. One carried mine
|
|
// per job; common engineer custody/callback/withdrawal rules own all mutations.
|
|
class ERR_RoadblockMine : ERR_EngineerMine
|
|
{
|
|
protected AIWaypoint m_Hold;
|
|
protected ERR_KnownMines m_Known;
|
|
protected AIAgent m_WorkAgent;
|
|
protected int m_PreviousLOD;
|
|
protected bool m_LODLease;
|
|
protected override bool OrderValid()
|
|
{
|
|
if (!m_Hold || !m_Known || m_Unit.Task || m_Unit.Assignment || m_Unit.PendingTask || m_Unit.PendingOrder || m_Unit.Downlink) return false;
|
|
if (m_Group.GetCurrentWaypoint() != m_Hold || vector.Distance(m_Hold.GetOrigin(), m_Rally) > 0.01) return false;
|
|
// The mine must stay outside the entire ordinary guard/cover-search area.
|
|
return vector.DistanceXZ(m_Rally, m_Position) >= m_Hold.GetCompletionRadius() + 20;
|
|
}
|
|
override void Stop()
|
|
{
|
|
super.Stop();
|
|
// Release only this job's temporary simulation lease; preserve GM changes.
|
|
if (m_LODLease && m_WorkAgent && m_WorkAgent.GetPermanentLOD() == 0) m_WorkAgent.SetPermanentLOD(m_PreviousLOD);
|
|
m_LODLease = false;
|
|
}
|
|
protected override void OnArmed() { m_Known.Add(m_Mine, m_Position); }
|
|
bool BeginPost(ERR_GameMode mode, ERR_HQUnit unit, AIWaypoint hold, vector position, float now, array<vector> priorSites, ERR_KnownMines known)
|
|
{
|
|
if (m_Begun || m_Stopped) return false;
|
|
m_Begun = true;
|
|
if (!Replication.IsServer() || !mode || mode != ERR_GameMode.Get() || !mode.Active()) return Refuse();
|
|
if (!unit || !unit.Group || !unit.StatusSampler || !hold || !known || !mode.Forces()) return Refuse();
|
|
if (!ERR_ForceRules.Finite(now) || now < 0 || !ERR_ForceRules.FiniteVector(position)) return Refuse();
|
|
if (!m_Attempted || m_Attempted.Count() >= 256 || !priorSites || priorSites.Count() > 32) return Refuse();
|
|
m_Mode = mode; m_Unit = unit; m_Group = unit.Group; m_Hold = hold; m_Known = known;
|
|
m_SourceId = unit.ForceSourceId; m_Strength = unit.StatusSampler.InitialStrength(); m_Record = mode.Forces().FindGroup(m_Group);
|
|
m_Position = position; m_Rally = hold.GetOrigin();
|
|
float distance = vector.DistanceXZ(m_Position, m_Rally);
|
|
if (!ERR_ForceRules.FiniteVector(m_Rally) || distance < 45 || distance > 60) return Refuse();
|
|
foreach (vector prior : priorSites)
|
|
{
|
|
if (!ERR_ForceRules.FiniteVector(prior)) return Refuse();
|
|
m_PriorSites.Insert(prior);
|
|
}
|
|
if (!SelectActor() || !Owned() || !Quiet()) return Refuse();
|
|
if (!ERR_EngineerMineRules.CorridorClear(m_Actor.GetOrigin(), m_Position, m_PriorSites)
|
|
|| !ERR_EngineerMineRules.CorridorClear(m_Position, m_Rally, m_PriorSites)) return Refuse();
|
|
m_Attempted.Insert(m_Mine);
|
|
if (!Carried()) return Refuse();
|
|
if (m_Mode.MineIntel()) m_Mode.MineIntel().Candidates(m_Actor, m_IntelFolders);
|
|
m_WorkAgent = SCR_AIUtils.GetAIAgent(m_Actor);
|
|
if (!m_WorkAgent) return Refuse();
|
|
m_PreviousLOD = m_WorkAgent.GetPermanentLOD(); m_LODLease = true;
|
|
m_WorkAgent.SetPermanentLOD(0);
|
|
if (!m_WorkAgent.ActivateAI()) return Refuse();
|
|
m_Started = now; m_LastTick = now; m_State = 0;
|
|
Move(m_Position);
|
|
ERR_HQ.Event("ROADBLOCK MINE", "guard carrying original PMN4 to off-road site; original hold order retained");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class ERR_RoadblockMinePost
|
|
{
|
|
SCR_AIGroup Group;
|
|
AIWaypoint Hold;
|
|
vector Right;
|
|
int Attempts;
|
|
float NextAttempt;
|
|
ref array<vector> Sites = {};
|
|
ref array<ref ERR_RoadblockMine> Jobs = {};
|
|
}
|
|
|
|
class ERR_RoadblockMining
|
|
{
|
|
protected ERR_GameMode m_Mode;
|
|
protected ref array<ref ERR_RoadblockMinePost> m_Posts = {};
|
|
protected ref ERR_KnownMines m_Known;
|
|
protected bool m_Stopped;
|
|
void ERR_RoadblockMining(ERR_GameMode mode) { m_Mode = mode; m_Known = new ERR_KnownMines(mode); }
|
|
void Register(SCR_AIGroup group, AIWaypoint hold, vector right)
|
|
{
|
|
if (m_Stopped || !group || !hold || m_Posts.Count() >= 12) return;
|
|
ERR_RoadblockMinePost post = new ERR_RoadblockMinePost();
|
|
post.Group = group; post.Hold = hold; post.Right = right;
|
|
// Equipment insertion and original roster sampling must settle first.
|
|
post.NextAttempt = GetGame().GetWorld().GetWorldTime() + 30000;
|
|
m_Posts.Insert(post);
|
|
}
|
|
// Four separated flanks, with open road and a clear inner guard area. Every
|
|
// nearby road's *edge* is checked, including intersections and narrow tracks.
|
|
static bool Site(vector post, vector right, int slot, out vector position)
|
|
{
|
|
if (slot < 0 || slot >= 4 || !ERR_ForceRules.FiniteVector(post) || !ERR_ForceRules.FiniteVector(right)) return false;
|
|
vector forward = Vector(-right[2], 0, right[0]);
|
|
int side = 1, row = 1;
|
|
if (slot % 2 == 1) side = -1;
|
|
if (slot >= 2) row = -1;
|
|
position = ERR_Actors.Ground(post + right * (side * 47) + forward * (row * 20));
|
|
if (!ERR_EngineerMineSite.InBand(post, position) || !ERR_HQRoadIntercept.Standable(position)) return false;
|
|
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
|
if (!settlements.OutsideSettlements(position)) return false;
|
|
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
|
if (!world || !world.GetRoadNetworkManager()) return false;
|
|
array<BaseRoad> roads = {};
|
|
world.GetRoadNetworkManager().GetRoadsInAABB(position - "100 1000 100", position + "100 1000 100", roads);
|
|
if (roads.IsEmpty() || roads.Count() > 128) return false;
|
|
int segments;
|
|
foreach (BaseRoad road : roads)
|
|
{
|
|
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 0 || road.GetWidth() > 160) return false;
|
|
array<vector> points = {}; road.GetPoints(points);
|
|
if (points.Count() < 2 || points.Count() > 1024) return false;
|
|
segments += points.Count() - 1;
|
|
if (segments > 4096) return false;
|
|
for (int i = 1; i < points.Count(); i++)
|
|
if (!ERR_MineAvoidanceRules.Clear(points[i - 1], points[i], position, road.GetWidth() * 0.5 + 15)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
void Tick(array<ref ERR_HQUnit> units, array<IEntity> attempted, float now)
|
|
{
|
|
if (m_Stopped || !Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return;
|
|
int active;
|
|
foreach (ERR_RoadblockMinePost post : m_Posts)
|
|
foreach (ERR_RoadblockMine job : post.Jobs)
|
|
{ job.Tick(now); if (!job.Finished()) active++; }
|
|
if (active >= 2) return;
|
|
foreach (ERR_RoadblockMinePost post : m_Posts)
|
|
{
|
|
if (!post.Group || !post.Hold || post.Attempts >= 4 || now < post.NextAttempt) continue;
|
|
if (post.Group.GetCurrentWaypoint() != post.Hold) { post.Attempts = 4; continue; }
|
|
ERR_HQUnit owner;
|
|
foreach (ERR_HQUnit unit : units) if (unit.Group == post.Group) { owner = unit; break; }
|
|
if (!owner || !owner.Eligible(m_Mode) || !owner.StatusSampler || !owner.StatusSampler.OwnsRoster(post.Group)) continue;
|
|
if (owner.Task || owner.Assignment || owner.PendingTask || owner.PendingOrder || owner.Downlink || ERR_HunterCombat.Active(post.Group)) continue;
|
|
bool blocked;
|
|
ERR_ForceRecord record = m_Mode.Forces().FindGroup(post.Group);
|
|
foreach (ERR_RoadblockMine previous : post.Jobs) if (previous.BlocksNextPost(record)) { blocked = true; break; }
|
|
if (blocked) continue;
|
|
int slot = post.Attempts++;
|
|
post.NextAttempt = now + 30000;
|
|
vector position;
|
|
if (!Site(post.Hold.GetOrigin(), post.Right, slot, position))
|
|
{ ERR_HQ.Event("ROADBLOCK MINE", "unsafe off-road site skipped; finite attempt consumed"); return; }
|
|
ERR_RoadblockMine next = new ERR_RoadblockMine(attempted);
|
|
post.Jobs.Insert(next); owner.EngineerMine = next;
|
|
if (next.BeginPost(m_Mode, owner, post.Hold, position, now, post.Sites, m_Known)) post.Sites.Insert(position);
|
|
else { next.Stop(); ERR_HQ.Event("ROADBLOCK MINE", "placement refused: no eligible carried mine, quiet guard or clear withdrawal route"); }
|
|
return; // One bounded site scan/admission per HQ tick.
|
|
}
|
|
}
|
|
void Stop()
|
|
{
|
|
m_Stopped = true;
|
|
foreach (ERR_RoadblockMinePost post : m_Posts) foreach (ERR_RoadblockMine job : post.Jobs) job.Stop();
|
|
m_Known.Stop();
|
|
}
|
|
}
|