// Decisions consumed by the finite original-item controller below. class ERR_EngineerMineRules { static bool Within(float started, float previous, float now) { return ERR_ForceRules.Finite(started) && ERR_ForceRules.Finite(previous) && ERR_ForceRules.Finite(now) && started >= 0 && previous >= started && now >= previous && now - started < 180000; } static float PrepareSince(float since, float now, bool near, bool engaged) { if (!ERR_ForceRules.Finite(now) || now < 0 || !near || engaged) return -1; if (!ERR_ForceRules.Finite(since) || since < 0 || since > now) return now; return since; } static bool Prepared(float since, float now) { return ERR_ForceRules.Finite(since) && ERR_ForceRules.Finite(now) && since >= 0 && now - since >= 10000; } static bool MayArm(bool grounded, bool ownerValid, float distance, bool friendlyNear) { return grounded && ownerValid && ERR_ForceRules.Finite(distance) && distance >= 20 && !friendlyNear; } } // Callback records only terminal evidence. It never moves, arms or retries items. class ERR_EngineerMineRemoval : ScriptedInventoryOperationCallback { int Result; protected void Finish(int result) { if (!Result) Result = result; } protected override void OnComplete() { Finish(1); } protected override void OnAllComplete() { Finish(1); } protected override void OnFailed() { Finish(-1); } protected override void OnAllFailed() { Finish(-1); } } // One original soldier, one carried PMN4, one accepted HQ road-interception post. // Loose cancelled items remain ordinary unarmed loot. Armed mines retain native // lifetime; a round's world reset owns their destruction, not this controller. class ERR_EngineerMine { protected ERR_GameMode m_Mode; protected ERR_HQUnit m_Unit; protected SCR_AIGroup m_Group; protected ref ERR_HQTask m_Task; protected ref ERR_HQReport m_Assignment; protected ref ERR_ForceRecord m_Record; protected string m_SourceId; protected int m_Strength; protected IEntity m_Actor; protected int m_Slot; protected IEntity m_Mine; protected SCR_PressureTriggerComponent m_Trigger; protected SCR_InventoryStorageManagerComponent m_Inventory; protected SCR_AIUtilityComponent m_Utility; protected ref SCR_AIBehaviorBase m_Action; protected ref ERR_EngineerMineRemoval m_Removal; protected ref array m_Attempted; protected ref array m_IntelFolders = {}; protected vector m_Position; protected vector m_Rally; protected float m_Started = -1; protected float m_LastTick = -1; protected float m_PreparedAt = -1; protected float m_NextClearance; protected int m_State; // 0 approach,1 continuous preparation,2 removal pending,3 withdraw,4 finished protected int m_Checked; protected bool m_FriendlyNear; protected bool m_Begun; protected bool m_Stopped; void ERR_EngineerMine(array attempted) { m_Attempted = attempted; } bool Finished() { return m_Stopped || m_State == 4; } protected bool Refuse() { Stop(); return false; } protected bool Quiet() { return m_Unit && !m_Unit.InfantryCombat.Held && !ERR_HunterCombat.Active(m_Group); } protected bool Owned() { if (m_Stopped || !Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return false; if (!m_Unit || !m_Group || m_Unit.Group != m_Group || !m_Task || m_Unit.Task != m_Task) return false; if (!m_Assignment || m_Unit.Assignment != m_Assignment || !m_Task.RoadIntercept || m_Task.ReleaseOnly) return false; if (m_Task.Investigation() || !m_Task.SecuritySite.IsEmpty() || m_Unit.PendingTask || m_Unit.PendingOrder) return false; if (m_Unit.ReservedForSupport || m_Unit.LogisticsReservation > 0 || m_Group.IsSlave() || m_Group.GetPlayerCount(true) > 0) return false; if (!m_Mode.EnemyFaction() || m_Group.GetFaction() != m_Mode.EnemyFaction() || !m_Unit.StatusSampler) return false; if (vector.Distance(m_Task.Destination, m_Rally) > 0.01 || !m_Mode.Forces() || !m_Record) return false; if (m_Mode.Forces().FindGroup(m_Group) != m_Record || m_Record.Group != m_Group || !m_Record.Physical || m_Record.Eliminated) return false; 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; 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)); if (!agent || !m_Utility || agent.m_UtilityComponent != m_Utility || !m_Utility.m_AIInfo) return false; if (!m_Inventory || m_Actor.FindComponent(SCR_InventoryStorageManagerComponent) != m_Inventory) return false; return true; } protected bool MineValid() { if (!ERR_Actors.Alive(m_Mine) || !m_Mine.GetPrefabData() || m_Mine.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) return false; if (!m_Trigger || m_Mine.FindComponent(SCR_PressureTriggerComponent) != m_Trigger || m_Trigger.IsActivated()) return false; return m_Attempted && m_Attempted.Contains(m_Mine); } protected bool Carried() { if (!MineValid() || !m_Inventory) return false; array items = {}; ERR_Inventory.Items(m_Inventory, items); return items.Count() <= 128 && items.Contains(m_Mine) && ERR_InfantryAmmoTransfer.StoredUnder(m_Mine, m_Actor); } protected bool Grounded() { if (!MineValid() || !m_Inventory) return false; InventoryItemComponent item = InventoryItemComponent.Cast(m_Mine.FindComponent(InventoryItemComponent)); if (!item || item.GetParentSlot() || m_Mine.GetParent()) return false; array items = {}; ERR_Inventory.Items(m_Inventory, items); return items.Count() <= 128 && !items.Contains(m_Mine); } protected bool Placed() { if (!Grounded() || vector.Distance(m_Mine.GetOrigin(), m_Position) > 1) return false; Physics physics = m_Mine.GetPhysics(); return physics && physics.GetVelocity().LengthSq() <= 0.25; } protected void ClearAction() { if (m_Action && m_Actor && m_Utility && m_Utility.m_AIInfo) { SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(m_Actor)); if (agent && agent.m_UtilityComponent == m_Utility) { array actions = {}; m_Utility.GetActions(actions); if (actions.Count() <= 128 && actions.Contains(m_Action)) m_Action.Complete(); } } m_Action = null; } protected void Move(vector destination) { ClearAction(); m_Action = new SCR_AIMoveIndividuallyBehavior(m_Utility, null, destination, 1000, 0, null, 1); m_Utility.AddAction(m_Action); } protected void WaitAtMine() { ClearAction(); m_Action = new SCR_AIWaitBehavior(m_Utility, null); m_Action.SetPriority(1000); m_Utility.AddAction(m_Action); } protected bool CheckFriendly(IEntity entity) { if (++m_Checked > 256) { m_FriendlyNear = true; return false; } if (!entity || (!ChimeraCharacter.Cast(entity) && !Vehicle.Cast(entity))) return true; DamageManagerComponent damage = DamageManagerComponent.Cast(entity.FindComponent(DamageManagerComponent)); if (damage && damage.IsDestroyed()) return true; FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(entity.FindComponent(FactionAffiliationComponent)); Faction faction; if (affiliation) faction = affiliation.GetAffiliatedFaction(); if (faction && m_Mode.EnemyFaction().IsFactionEnemy(faction)) return true; // Unconscious allies and unknown affiliations both prevent arming. m_FriendlyNear = true; return false; } protected bool SelectActor() { if (!m_Unit.StatusSampler.OwnsRoster(m_Group) || m_Strength < 1 || m_Strength > 32) return false; for (int slot = 0; slot < m_Strength; slot++) { IEntity actor = m_Unit.StatusSampler.InitialMember(slot); if (!actor || !ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor) || CompartmentAccessComponent.GetVehicleIn(actor)) continue; if (!m_Unit.StatusSampler.CanAttribute(slot, actor, false) || vector.DistanceXZ(actor.GetOrigin(), m_Rally) > 20) continue; SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(actor)); if (!agent || !agent.m_UtilityComponent || !agent.m_UtilityComponent.m_AIInfo) continue; SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(actor.FindComponent(SCR_InventoryStorageManagerComponent)); if (!inventory) continue; array items = {}; ERR_Inventory.Items(inventory, items); if (items.Count() > 128) continue; foreach (IEntity mine : items) { if (!ERR_Actors.Alive(mine) || m_Attempted.Contains(mine) || !mine.GetPrefabData() || mine.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) continue; if (!ERR_InfantryAmmoTransfer.StoredUnder(mine, actor)) continue; SCR_PressureTriggerComponent trigger = SCR_PressureTriggerComponent.Cast(mine.FindComponent(SCR_PressureTriggerComponent)); if (!trigger || trigger.IsActivated() || trigger.IsActivatedByDefault()) continue; m_Actor = actor; m_Slot = slot; m_Mine = mine; m_Trigger = trigger; m_Inventory = inventory; m_Utility = agent.m_UtilityComponent; return true; } } return false; } bool Begin(ERR_GameMode mode, ERR_HQUnit unit, ERR_HQTask task, vector minePosition, float now) { 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 || !task || unit.Task != task || !unit.Assignment) return Refuse(); if (!ERR_ForceRules.Finite(now) || now < 0 || !ERR_ForceRules.FiniteVector(minePosition)) return Refuse(); if (!m_Attempted || m_Attempted.Count() >= 256 || !mode.Forces()) return Refuse(); m_Mode = mode; m_Unit = unit; m_Task = task; m_Assignment = unit.Assignment; m_Group = unit.Group; m_SourceId = unit.ForceSourceId; m_Strength = unit.StatusSampler.InitialStrength(); m_Record = mode.Forces().FindGroup(m_Group); 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 (!SelectActor() || !Owned() || !Quiet()) return Refuse(); // The shared exact-item tombstone survives pickup, transfer and late callbacks. m_Attempted.Insert(m_Mine); if (!Carried()) return Refuse(); if (m_Mode.MineIntel()) m_Mode.MineIntel().Candidates(m_Actor, m_IntelFolders); m_Started = now; m_LastTick = now; m_State = 0; Move(m_Position); ERR_HQ.Event("ENGINEER MINE", "original carried PMN4 assigned; one approach and finite 180s deadline"); return true; } void Stop() { if (m_Stopped) return; m_Stopped = true; m_State = 4; ClearAction(); // Retain removal evidence and shared item quarantine. No late completion can arm. } void Tick(float now) { if (!m_Begun || Finished()) return; if (!ERR_EngineerMineRules.Within(m_Started, m_LastTick, now)) { Stop(); return; } m_LastTick = now; if (!Owned() || !Quiet() || !MineValid() || now >= m_Task.ExpiresAt) { Stop(); return; } if (m_State <= 1) { if (!Carried()) { Stop(); return; } bool near = vector.DistanceXZ(m_Actor.GetOrigin(), m_Position) <= 3; m_PreparedAt = ERR_EngineerMineRules.PrepareSince(m_PreparedAt, now, near, false); if (m_State == 0 && near) { m_State = 1; WaitAtMine(); } if (!ERR_EngineerMineRules.Prepared(m_PreparedAt, now)) return; // Set state and callback BEFORE native submission; never repeat the removal. m_Removal = new ERR_EngineerMineRemoval(); m_State = 2; bool accepted = m_Inventory.TryRemoveItemFromInventory(m_Mine, null, m_Removal); if (!accepted && !m_Removal.Result) { Stop(); return; } return; } if (m_State == 2) { if (!m_Removal || m_Removal.Result < 0) { Stop(); return; } // Observed transfer ends our custody; a later nearby drop cannot restore it. // Original inventory ownership may still settle after the native callback. InventoryItemComponent item = InventoryItemComponent.Cast(m_Mine.FindComponent(InventoryItemComponent)); bool attached = m_Mine.GetParent() || (item && item.GetParentSlot()); if (attached && !ERR_InfantryAmmoTransfer.StoredUnder(m_Mine, m_Actor)) { Stop(); return; } if (!m_Removal.Result || !Grounded()) return; if (vector.DistanceXZ(m_Actor.GetOrigin(), m_Position) > 3) { Stop(); return; } // A callback proves removal, not continued custody of a distant loose item. if (vector.Distance(m_Mine.GetOrigin(), m_Actor.GetOrigin()) > 3 || vector.Distance(m_Mine.GetOrigin(), m_Position) > 3) { Stop(); return; } m_Mine.SetOrigin(m_Position); m_State = 3; Move(m_Rally); return; } if (m_State != 3 || !Grounded() || vector.Distance(m_Mine.GetOrigin(), m_Position) > 1) { Stop(); return; } if (now < m_NextClearance || vector.DistanceXZ(m_Actor.GetOrigin(), m_Position) < 20) return; m_NextClearance = now + 1000; m_Checked = 0; m_FriendlyNear = false; if (!GetGame() || !GetGame().GetWorld()) { Stop(); return; } GetGame().GetWorld().QueryEntitiesBySphere(m_Position, 20, CheckFriendly, queryFlags: EQueryEntitiesFlags.ALL); if (!ERR_EngineerMineRules.MayArm(Placed(), Owned() && Quiet(), vector.DistanceXZ(m_Actor.GetOrigin(), m_Position), m_FriendlyNear)) return; m_Trigger.SetUser(m_Actor); m_Trigger.ActivateTrigger(); if (m_Trigger.IsActivated()) { if (m_Mode.MineIntel()) m_Mode.MineIntel().Seal(m_Actor, m_Mine, m_Position, m_IntelFolders); ERR_HQ.Event("ENGINEER MINE", "original PMN4 armed after engineer withdrawal and physical friendly clearance"); } Stop(); } }