Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

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();
}
}