// Local knowledge of mines laid by this checkpoint service. No mine immunity,
// no hostile/player detection, no changes to native pressure/explosion logic.
class ERR_MineAvoidanceRules
{
 static bool Clear(vector start, vector end, vector mine, float radius)
 {
  if (!ERR_ForceRules.FiniteVector(start) || !ERR_ForceRules.FiniteVector(end) || !ERR_ForceRules.FiniteVector(mine)
   || !ERR_ForceRules.Finite(radius) || radius < 0) return false;
  vector delta = end - start; delta[1] = 0;
  float squared = delta.LengthSq();
  if (!ERR_ForceRules.Finite(squared)) return false;
  float fraction;
  if (squared > 0.001) fraction = Math.Clamp(vector.Dot(mine - start, delta) / squared, 0, 1);
  return vector.DistanceXZ(mine, start + delta * fraction) > radius;
 }
 // A short chord round the danger circle, chosen toward the original route.
 // Used repeatedly until that route is clear; never order a straight crossing.
 static vector Detour(vector start, vector goal, vector mine, int side)
 {
  vector away = start - mine; away[1] = 0;
  if (away.LengthSq() < 0.01) return start;
  away.Normalize();
  if (vector.DistanceXZ(start, mine) < 10) return mine + away * 16;
  vector tangent = Vector(away[2], 0, -away[0]);
  if (side < 0) tangent = -tangent;
  return mine + (away * 0.707107 + tangent * 0.707107) * 16;
 }
}
class ERR_KnownMine
{
 IEntity Item;
 vector Position;
 bool Live()
 {
  if (!ERR_Actors.Alive(Item) || Item.GetParent() || vector.Distance(Item.GetOrigin(), Position) > 1) return false;
  SCR_PressureTriggerComponent trigger = SCR_PressureTriggerComponent.Cast(Item.FindComponent(SCR_PressureTriggerComponent));
  return trigger && trigger.IsActivated();
 }
}
class ERR_MineDetour
{
 IEntity Actor;
 SCR_AIUtilityComponent Utility;
 ref SCR_AIBehaviorBase Action;
 vector Goal;
 vector Step;
 float Expires;
 float NextStep;
 SCR_AIGroup Group;
 AIWaypoint Waypoint;
 bool ResumeMove;
 bool FinalLeg;
 void Clear()
 {
  SCR_ChimeraAIAgent agent;
  if (Actor) agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(Actor));
  if (agent && agent.m_UtilityComponent == Utility && Action)
  {
   array<ref AIActionBase> actions = {}; Utility.GetActions(actions);
   if (actions.Contains(Action)) Action.Complete();
  }
  Action = null;
 }
}
class ERR_KnownMines
{
 protected ERR_GameMode m_Mode;
 protected ref array<ref ERR_KnownMine> m_Mines = {};
 protected ref array<ref ERR_MineDetour> m_Detours = {};
 protected ref array<IEntity> m_Near = {};
 protected bool m_Running, m_Stopped;
 void ERR_KnownMines(ERR_GameMode mode) { m_Mode = mode; }
 void Add(IEntity item, vector position)
 {
  if (m_Stopped || !item || m_Mines.Count() >= 48) return;
  foreach (ERR_KnownMine old : m_Mines) if (old.Item == item) return;
  ERR_KnownMine mine = new ERR_KnownMine(); mine.Item = item; mine.Position = position;
  if (!mine.Live()) return;
  m_Mines.Insert(mine);
  if (!m_Running) { m_Running = true; GetGame().GetCallqueue().CallLater(Tick, 250, true); }
 }
 protected bool FriendlyActor(IEntity actor)
 {
  if (!ChimeraCharacter.Cast(actor) || !ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor) || CompartmentAccessComponent.GetVehicleIn(actor)) return false;
  Faction faction = SCR_FactionManager.SGetFaction(actor);
  return faction && m_Mode.EnemyFaction() && !m_Mode.EnemyFaction().IsFactionEnemy(faction);
 }
 protected bool Collect(IEntity actor)
 {
  if (m_Near.Count() >= 128) return false;
  if (FriendlyActor(actor) && !m_Near.Contains(actor)) m_Near.Insert(actor);
  return true;
 }
 protected ERR_KnownMine Blocking(vector start, vector goal, float radius)
 {
  ERR_KnownMine nearest;
  float best = float.MAX;
  foreach (ERR_KnownMine mine : m_Mines)
  {
   if (ERR_MineAvoidanceRules.Clear(start, goal, mine.Position, radius)) continue;
   float distance = vector.DistanceXZ(start, mine.Position);
   if (distance < best) { best = distance; nearest = mine; }
  }
  return nearest;
 }
 protected bool SafeStep(vector start, vector end, ERR_KnownMine escaping, IEntity actor)
 {
  foreach (ERR_KnownMine mine : m_Mines)
  {
   // Inside the safety circle only a strictly outward move is admissible.
   if (mine == escaping && vector.DistanceXZ(start, mine.Position) < 10)
   {
    vector travel = end - start, away = start - mine.Position;
    if (vector.DotXZ(travel, away) <= 0) return false;
   }
   else if (!ERR_MineAvoidanceRules.Clear(start, end, mine.Position, 8)) return false;
  }
  if (!ERR_HQRoadIntercept.Standable(end)) return false;
  // Do not hand native navigation an obstructed straight chord whose actual
  // detour could bend back through the mine. Wait when this corridor is blocked.
  TraceBox path = new TraceBox(); path.Start = start + "0 1 0"; path.End = end + "0 1 0";
  path.Mins = "-0.35 -0.8 -0.35"; path.Maxs = "0.35 0.8 0.35";
  path.Exclude = actor; path.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
  return GetGame().GetWorld().TraceMove(path, null) == 1;
 }
 protected void Steer(ERR_MineDetour detour, ERR_KnownMine mine)
 {
  vector start = detour.Actor.GetOrigin();
  vector first = ERR_Actors.Ground(ERR_MineAvoidanceRules.Detour(start, detour.Goal, mine.Position, 1));
  vector second = ERR_Actors.Ground(ERR_MineAvoidanceRules.Detour(start, detour.Goal, mine.Position, -1));
  if (vector.DistanceXZ(second, detour.Goal) < vector.DistanceXZ(first, detour.Goal))
  { vector swap = first; first = second; second = swap; }
  bool safe = SafeStep(start, first, mine, detour.Actor);
  if (!safe && SafeStep(start, second, mine, detour.Actor)) { first = second; safe = true; }
  detour.Clear();
  detour.FinalLeg = false;
  detour.NextStep = GetGame().GetWorld().GetWorldTime() + 1000;
  if (safe)
  {
   detour.Step = first;
   detour.Action = new SCR_AIMoveIndividuallyBehavior(detour.Utility, null, first, 1100, 0, null, 1);
  }
  else
  {
   // Never deliberately send a guard across a mine when terrain offers no step.
   detour.Step = start;
   detour.Action = new SCR_AIWaitBehavior(detour.Utility, null); detour.Action.SetPriority(1100);
  }
  detour.Utility.AddAction(detour.Action);
 }
 protected void Tick()
 {
  if (m_Stopped || !Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) { Stop(); return; }
  for (int i = m_Mines.Count() - 1; i >= 0; i--) if (!m_Mines[i].Live()) m_Mines.Remove(i);
  float now = GetGame().GetWorld().GetWorldTime();
  for (int i = m_Detours.Count() - 1; i >= 0; i--)
  {
   ERR_MineDetour detour = m_Detours[i];
   SCR_ChimeraAIAgent agent;
   if (detour.Actor) agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(detour.Actor));
   if (!FriendlyActor(detour.Actor) || !agent || agent.m_UtilityComponent != detour.Utility || now >= detour.Expires || m_Mines.IsEmpty() || !detour.Group || ERR_Actors.Group(detour.Actor) != detour.Group || detour.Group.GetCurrentWaypoint() != detour.Waypoint)
   { detour.Clear(); m_Detours.Remove(i); continue; }
   vector start = detour.Actor.GetOrigin();
   ERR_KnownMine obstruction = Blocking(start, detour.Goal, 10);
   if (!obstruction)
   {
    // Native individual moves can be cancelled when the avoidance action wins.
    // Finish the captured move explicitly; never lose an engineer/HQ foot order
    // merely because its route needed a short detour. A new group order cancels.
    if (!detour.ResumeMove || vector.DistanceXZ(start, detour.Goal) < 3)
    { detour.Clear(); m_Detours.Remove(i); continue; }
    if (!detour.FinalLeg)
    {
     detour.Clear(); detour.FinalLeg = true; detour.Step = detour.Goal;
     detour.Action = new SCR_AIMoveIndividuallyBehavior(detour.Utility, null, detour.Goal, 1100, 0, null, 1);
     detour.Utility.AddAction(detour.Action);
    }
    continue;
   }
   if (now >= detour.NextStep && (detour.FinalLeg || vector.DistanceXZ(start, detour.Step) < 4 || Blocking(start, start, 7))) Steer(detour, obstruction);
  }
  if (m_Mines.IsEmpty()) { GetGame().GetCallqueue().Remove(Tick); m_Running = false; return; }
  m_Near.Clear();
  foreach (ERR_KnownMine mine : m_Mines)
   GetGame().GetWorld().QueryEntitiesBySphere(mine.Position, 22, Collect, queryFlags: EQueryEntitiesFlags.DYNAMIC);
  foreach (IEntity actor : m_Near)
  {
   bool busy;
   foreach (ERR_MineDetour existing : m_Detours) if (existing.Actor == actor) { busy = true; break; }
   if (busy || m_Detours.Count() >= 64) continue;
   SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(actor));
   if (!agent || !agent.m_UtilityComponent || !agent.m_UtilityComponent.m_AIInfo) continue;
   vector start = actor.GetOrigin(), goal = start;
   SCR_AIMoveBehaviorBase movement = SCR_AIMoveBehaviorBase.Cast(agent.m_UtilityComponent.GetCurrentAction());
   if (movement) goal = movement.m_vPosition.m_Value;
   else
   {
    Physics physics = actor.GetPhysics();
    if (physics) goal = start + physics.GetVelocity() * 4;
   }
   ERR_KnownMine obstruction = Blocking(start, goal, 10);
   if (!obstruction) continue;
   ERR_MineDetour detour = new ERR_MineDetour();
   detour.Actor = actor; detour.Utility = agent.m_UtilityComponent; detour.Goal = goal; detour.Expires = now + 45000;
   detour.Group = ERR_Actors.Group(actor);
   if (!detour.Group) continue;
   detour.Waypoint = detour.Group.GetCurrentWaypoint(); detour.ResumeMove = movement != null;
   m_Detours.Insert(detour); Steer(detour, obstruction);
  }
 }
 void Stop()
 {
  m_Stopped = true;
  if (GetGame()) GetGame().GetCallqueue().Remove(Tick);
  foreach (ERR_MineDetour detour : m_Detours) detour.Clear();
  m_Detours.Clear(); m_Mines.Clear(); m_Near.Clear();
 }
}
