class ERR_IntelRequest { IEntity Actor; int Attempts; bool Accepted; } // Mission-owned inventory service. Physical folder identities are consumed once, // even when transferred between players; owned inventory entities are not deleted. class ERR_IntelService { protected ERR_GameMode m_Mode; protected ref array m_Requests = {}; protected ref set m_Rolled = new set(); protected ref set m_Spent = new set(); void ERR_IntelService(ERR_GameMode mode) { m_Mode = mode; } void RegisterGroup(SCR_AIGroup group) { if (!Replication.IsServer() || !group) return; array members = {}; group.GetAgents(members); foreach (AIAgent member : members) { if (!member) continue; IEntity actor = member.GetControlledEntity(); if (!ERR_Actors.Alive(actor) || ERR_Actors.Player(actor) || m_Rolled.Contains(actor)) continue; m_Rolled.Insert(actor); if (Math.RandomInt(0, 100) >= m_Mode.Settings().Get("aiIntelChancePercent")) continue; if (m_Requests.Count() >= 256) continue; ERR_IntelRequest request = new ERR_IntelRequest(); request.Actor = actor; m_Requests.Insert(request); } } protected void Folders(InventoryStorageManagerComponent inventory, out array folders) { folders.Clear(); SCR_PrefabNamePredicate predicate = new SCR_PrefabNamePredicate(); predicate.prefabName = ERR_FactionCatalog.IntelFolder(); inventory.FindItems(folders, predicate, EStoragePurpose.PURPOSE_ANY); } void Tick(float now) { if (!Replication.IsServer()) return; int budget = 8; for (int i = m_Requests.Count() - 1; i >= 0 && budget > 0; i--) { budget--; ERR_IntelRequest request = m_Requests[i]; if (!ERR_Actors.Alive(request.Actor) || ERR_Actors.Player(request.Actor)) { m_Requests.Remove(i); continue; } SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(request.Actor.FindComponent(SCR_InventoryStorageManagerComponent)); array folders = {}; if (inventory) Folders(inventory, folders); if (!folders.IsEmpty()) { m_Requests.Remove(i); continue; } request.Attempts++; if (request.Attempts > 30) { Print("Rereintroduced [intel]: bounded assignment failed", LogLevel.WARNING); m_Requests.Remove(i); continue; } if (inventory && !request.Accepted) request.Accepted = inventory.TrySpawnPrefabToStorage(ERR_FactionCatalog.IntelFolder(), purpose: EStoragePurpose.PURPOSE_ANY); } array players = {}; ERR_Actors.Players(players); foreach (IEntity player : players) Recover(player); } protected void Recover(IEntity player) { InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(player.FindComponent(InventoryStorageManagerComponent)); if (!inventory) return; array folders = {}; Folders(inventory, folders); foreach (IEntity folder : folders) { if (!folder || m_Spent.Contains(folder) || m_Spent.Count() >= 4096) continue; array locations = {}; m_Mode.Locations(locations); array eligible = {}; foreach (ERR_LocationComponent location : locations) { if (location && location.Kind() != ERR_LocationKind.PRISON && location.Kind() != ERR_LocationKind.EXTRACTION && location.Knowledge() < 2) eligible.Insert(location); } if (eligible.IsEmpty()) continue; ERR_LocationComponent chosen = eligible.GetRandomElement(); if (!chosen.Discover(Math.RandomInt(0, 100), m_Mode.Settings().Get("intelRevealChancePercent"))) continue; m_Spent.Insert(folder); PrintFormat("Rereintroduced [intel]: player %1 recovered folder; location=%2 knowledge=%3", GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(player), chosen.Id(), chosen.Knowledge()); } } void Stop() { m_Requests.Clear(); m_Rolled.Clear(); m_Spent.Clear(); } } modded class ERR_GameMode { protected ref ERR_IntelService m_Intel; protected override void EOnInit(IEntity owner) { super.EOnInit(owner); if (SCR_Global.IsEditMode() || !Replication.IsServer()) return; m_Intel = new ERR_IntelService(this); OnGroup().Insert(m_Intel.RegisterGroup); OnTick().Insert(m_Intel.Tick); OnStop().Insert(m_Intel.Stop); } }