--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c +++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c @@ -1,6 +1,39 @@ // Decisions consumed by the finite original-item controller below. class ERR_EngineerMineRules { + // Straight XZ corridors are conservative local checks, not native route proofs. + static bool CorridorClear(vector start, vector end, array priorSites) + { + if (!ERR_ForceRules.FiniteVector(start) || !ERR_ForceRules.FiniteVector(end)) return false; + if (!priorSites) return true; + if (priorSites.Count() > 32) return false; + float dx = end[0] - start[0], dz = end[2] - start[2]; + float squared = dx * dx + dz * dz; + if (!ERR_ForceRules.Finite(squared)) return false; + foreach (vector site : priorSites) + { + if (!ERR_ForceRules.FiniteVector(site)) return false; + float along = 0; + if (squared > 0) + { + float projection = (site[0] - start[0]) * dx + (site[2] - start[2]) * dz; + if (!ERR_ForceRules.Finite(projection)) return false; + along = projection / squared; + if (!ERR_ForceRules.Finite(along)) return false; + along = Math.Clamp(along, 0, 1); + } + float x = start[0] + along * dx - site[0], z = start[2] + along * dz - site[2]; + float clearance = x * x + z * z; + if (!ERR_ForceRules.Finite(clearance) || clearance <= 400) return false; + } + return true; + } + static bool WithinWorkArea(vector post, vector actor) + { + if (!ERR_ForceRules.FiniteVector(post) || !ERR_ForceRules.FiniteVector(actor)) return false; + float distance = vector.DistanceXZ(post, actor); + return ERR_ForceRules.Finite(distance) && distance <= 60; + } static bool Within(float started, float previous, float now) { return ERR_ForceRules.Finite(started) && ERR_ForceRules.Finite(previous) && ERR_ForceRules.Finite(now) @@ -56,6 +89,7 @@ protected ref ERR_EngineerMineRemoval m_Removal; protected ref array m_Attempted; protected ref array m_IntelFolders = {}; + protected ref array m_PriorSites = {}; protected vector m_Position; protected vector m_Rally; protected float m_Started = -1; @@ -70,6 +104,14 @@ void ERR_EngineerMine(array attempted) { m_Attempted = attempted; } bool Finished() { return m_Stopped || m_State == 4; } + bool BlocksNextPost(ERR_ForceRecord record) + { + if (!record || record != m_Record) return false; + if (m_Begun && !Finished()) return true; + // A late terminal callback or exact deletion settles only the old job. + // Destroyed but surviving items and uncertain native refusals remain pinned. + return m_Removal && !m_Removal.Result && m_Mine; + } protected bool Refuse() { Stop(); return false; } protected bool Quiet() { @@ -90,6 +132,11 @@ if (m_Record.SourceId != m_SourceId || m_Unit.ForceSourceId != m_SourceId || m_Record.InitialStrength != m_Strength) return false; if (!m_Unit.StatusSampler.OwnsRoster(m_Group) || m_Unit.StatusSampler.InitialStrength() != m_Strength) return false; if (!m_Actor || !ERR_Actors.CanFight(m_Actor) || ERR_Actors.Player(m_Actor) || CompartmentAccessComponent.GetVehicleIn(m_Actor)) return false; + vector actorPosition = m_Actor.GetOrigin(); + if (!ERR_EngineerMineRules.WithinWorkArea(m_Rally, actorPosition) || !ERR_EngineerMineRules.WithinWorkArea(m_Rally, m_Position)) return false; + if (!ERR_EngineerMineRules.CorridorClear(actorPosition, actorPosition, m_PriorSites) + || !ERR_EngineerMineRules.CorridorClear(m_Position, m_Position, m_PriorSites) + || !ERR_EngineerMineRules.CorridorClear(m_Rally, m_Rally, m_PriorSites)) return false; if (!m_Unit.StatusSampler.CanAttribute(m_Slot, m_Actor, false) || ERR_Actors.Group(m_Actor) != m_Group) return false; if (SCR_FactionManager.SGetFaction(m_Actor) != m_Mode.EnemyFaction()) return false; SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(m_Actor)); @@ -190,7 +237,7 @@ } return false; } - bool Begin(ERR_GameMode mode, ERR_HQUnit unit, ERR_HQTask task, vector minePosition, float now) + bool Begin(ERR_GameMode mode, ERR_HQUnit unit, ERR_HQTask task, vector minePosition, float now, array priorSites = null) { if (m_Begun || m_Stopped) return false; m_Begun = true; @@ -203,7 +250,18 @@ m_Position = minePosition; m_Rally = task.Destination; float distance = vector.DistanceXZ(m_Position, m_Rally); if (!ERR_ForceRules.FiniteVector(m_Rally) || distance < 25 || distance > 60 || now >= task.ExpiresAt) return Refuse(); + if (priorSites) + { + if (priorSites.Count() > 32) 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(); // The shared exact-item tombstone survives pickup, transfer and late callbacks. m_Attempted.Insert(m_Mine); if (!Carried()) return Refuse();