// Owner-authorized group startup radio fitting; provenance in equipment migration docs. // Existing HQ registration selects the group. Physical inventory alone enables routes. class ERR_EnemyRadio { static const ResourceName BASIC = "{E1A5D4B878AA8980}Prefabs/Items/Equipment/Radios/Radio_R148.et"; static const ResourceName MANPACK = "{54C68E438DD34265}Prefabs/Items/Equipment/Radios/Radio_R107M.et"; protected SCR_AIGroup m_Group; protected bool m_RelaySelected; protected bool m_BasicDone; protected bool m_RelayDone; protected bool m_Captured; protected bool m_Stopped; protected int m_Checks; protected int m_Requests; protected int m_Completed; protected ref array m_Members = {}; protected ref array m_Allowed = {}; protected ref array m_BasicAttempts = {}; protected ref array m_RelayAttempts = {}; protected IEntity m_PendingActor; protected int m_PendingType; protected bool m_Submitted; protected ref ERR_EnemyEquipmentCallback m_Operation; void ERR_EnemyRadio(SCR_AIGroup group, int radioId) { m_Group = group; m_RelaySelected = radioId > 0 && radioId % 4 == 1; m_RelayDone = !m_RelaySelected; if (radioId <= 0) m_Stopped = true; } // Only the equipment coordinator may add newly seen original actors. Its // retained actor tombstones exclude transferred bodies from another grant. void Register(IEntity actor) { if (m_Stopped || m_Captured || !actor || m_Allowed.Count() >= 32 || m_Allowed.Contains(actor)) return; m_Allowed.Insert(actor); } // Negative means inventory is not yet inspectable, never permission to issue. static int Capability(IEntity actor) { if (!actor) return -1; InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent)); if (!inventory) return -1; array items = {}; inventory.GetItems(items); if (items.Count() > 256) return -1; int capability; foreach (IEntity item : items) { if (!item || !item.FindComponent(BaseRadioComponent)) continue; capability = 1; SCR_RadioComponent radio = SCR_RadioComponent.Cast(item.FindComponent(SCR_RadioComponent)); if (radio && radio.ERR_IsHQManpack()) return 2; } return capability; } protected void Finish(string reason) { if (m_Stopped) return; m_Stopped = true; ERR_HQ.Event("RADIO EQUIPMENT", reason); } void Stop() { Finish("startup fitting stopped; accepted native insert is not cancelled or replaced"); } protected bool OriginalCrew(ERR_GameMode mode) { array agents = {}; m_Group.GetAgents(agents); if (agents.IsEmpty() || agents.Count() > 32) return false; array current = {}; foreach (AIAgent agent : agents) { if (!agent) return false; IEntity actor = agent.GetControlledEntity(); if (!m_Allowed.Contains(actor) || !ERR_EnemyEquipmentRules.Eligible(mode, m_Group, actor) || current.Contains(actor)) return false; current.Insert(actor); } if (!m_Captured) { foreach (IEntity original : current) m_Members.Insert(original); m_Captured = true; } if (current.Count() != m_Members.Count()) return false; foreach (IEntity member : m_Members) if (!current.Contains(member)) return false; return true; } protected bool Submit(ERR_GameMode mode, int capability) { if (m_Stopped || m_Submitted) return false; ResourceName prefab = BASIC; if (capability == 2) prefab = MANPACK; foreach (IEntity actor : m_Members) { if (!ERR_EnemyEquipmentRules.Eligible(mode, m_Group, actor)) { Stop(); return false; } if (capability == 1 && m_BasicAttempts.Contains(actor)) continue; if (capability == 2 && m_RelayAttempts.Contains(actor)) continue; InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent)); if (!inventory || !inventory.CanInsertResource(prefab, EStoragePurpose.PURPOSE_ANY)) continue; if (capability == 1) m_BasicAttempts.Insert(actor); else m_RelayAttempts.Insert(actor); m_PendingActor = actor; m_PendingType = capability; m_Operation = new ERR_EnemyEquipmentCallback(); m_Submitted = true; m_Requests++; bool accepted = inventory.TrySpawnPrefabToStorage(prefab, null, -1, EStoragePurpose.PURPOSE_ANY, m_Operation); if (!accepted && !(m_Operation.Finished && m_Operation.Succeeded)) m_Submitted = false; return true; // At most one native request across the entire group per check. } return false; } bool Check(ERR_GameMode mode) { if (m_Stopped) return true; if (!Replication.IsServer() || SCR_Global.IsEditMode() || !mode || mode != ERR_GameMode.Get() || !mode.Active() || !m_Group || m_Group.IsSlave() || m_Group.GetPlayerCount(true) > 0 || m_Group.GetFaction() != mode.EnemyFaction()) { Stop(); return true; } m_Checks++; if (!m_Group.IsExpandComplete()) { if (m_Checks >= 5) Finish("native startup crew did not complete before radio deadline"); return m_Stopped; } if (!OriginalCrew(mode)) { if (m_Captured || m_Checks >= 5) Finish("original radio crew unavailable or changed; no replacement"); return m_Stopped; } bool known = true; int best; foreach (IEntity actor : m_Members) { int capability = Capability(actor); if (capability < 0) known = false; else if (capability > best) best = capability; } if (best >= 1) m_BasicDone = true; if (best >= 2) m_RelayDone = true; if (m_Submitted) { if (!ERR_EnemyEquipmentRules.Eligible(mode, m_Group, m_PendingActor)) { Stop(); return true; } if (m_Operation && m_Operation.Finished) { if (m_Operation.Succeeded) { m_Completed++; m_BasicDone = true; if (m_PendingType == 2) m_RelayDone = true; } m_Submitted = false; } } if (m_BasicDone && m_RelayDone) { if (m_Requests == 0) Finish("original group already equipped; no native radio request submitted"); else if (m_Completed > 0) Finish("native radio completion recorded; original group allocation terminal, no replenishment"); else Finish("physical group radio observed after request; insertion provenance unconfirmed, no replenishment"); return true; } if (m_Checks >= 5) { if (m_Submitted) Finish("startup radio deadline; accepted unresolved insertion quarantined"); else Finish("startup radio deadline; required compatible equipment not confirmed"); return true; } if (m_Submitted || !known) return false; // A relay satisfies the basic need too; never submit both in parallel. if (!m_RelayDone && Submit(mode, 2)) return m_Stopped; if (!m_BasicDone) Submit(mode, 1); return m_Stopped; } }