using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.AddressableAssets; namespace Jovian.EncounterSystem { /// id → encounter cache. Editor auto-repopulates on asset changes; runtime must call . [CreateAssetMenu(fileName = "EncounterRegistry", menuName = "Jovian/Encounter System/Encounter Registry")] public class EncounterRegistry : ScriptableObject { public EncountersCollection[] encounterCollections = Array.Empty(); private readonly Dictionary encounters = new(); public Dictionary GetEncounters() => encounters; public void RegisterEncounter(IEncounter encounter) { var id = encounter?.EncounterDefinition?.internalId; if(string.IsNullOrEmpty(id)) { return; } encounters.TryAdd(id, encounter); } public void UnregisterEncounter(IEncounter encounter) { var id = encounter?.EncounterDefinition?.internalId; if(string.IsNullOrEmpty(id)) { return; } encounters.Remove(id); } public void ClearEncounters() { encounters.Clear(); } public void PopulateEncounters() { if(encounterCollections == null) { return; } for(int c = 0; c < encounterCollections.Length; c++) { var collection = encounterCollections[c]; if(collection?.encounterTables == null) { continue; } for(int t = 0; t < collection.encounterTables.Length; t++) { var table = collection.encounterTables[t]; if(table?.encounters == null) { continue; } for(int i = 0; i < table.encounters.Count; i++) { RegisterEncounter(table.encounters[i]); } } } } public EncounterTable GetEncounterTable(string id) { foreach(var collection in encounterCollections) { foreach(var table in collection.encounterTables) { if(table.id == id) { return table; } } } return null; } public IEncounter GetRandomEncounter(string encounterTableId) { var table = GetEncounterTable(encounterTableId); if(!table) { return null; } var ec = table.encounters; return ec[UnityEngine.Random.Range(0, ec.Count)]; } /// Random pick restricted to encounters whose runtime type matches . /// Zero-alloc — uses reservoir sampling instead of building an intermediate filtered list. public IEncounter GetRandomEncounter(string encounterTableId, IEncounterKind encounterKind) { var table = GetEncounterTable(encounterTableId); if(!table || encounterKind == null) { return null; } IEncounter selected = null; var seen = 0; for(var i = 0; i < encounters.Count; i++) { var encounter = table.encounters[i]; if(encounter == null || encounter.GetType() != encounterKind.GetType()) { continue; } seen++; if(UnityEngine.Random.Range(0, seen) == 0) { selected = encounter; } } return selected; } /// Random pick restricted by . Used with /// to exclude gated encounters. Zero-alloc via reservoir sampling. public IEncounter GetRandomEncounter(Predicate filter, string encounterTableId) { var table = GetEncounterTable(encounterTableId); if(!table) { return null; } if(filter == null) { return GetRandomEncounter(encounterTableId); } IEncounter selected = null; var seen = 0; foreach(var encounter in table.encounters) { if(encounter == null || !filter(encounter)) { continue; } seen++; if(UnityEngine.Random.Range(0, seen) == 0) { selected = encounter; } } return selected; } } #if UNITY_EDITOR /// Rebuilds the registry (Addressables key "EncounterRegistry") on any asset import/move/delete. public class EncounterRegister : UnityEditor.AssetPostprocessor { private static EncounterRegistry registryCache; private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { registryCache ??= Addressables.LoadAssetAsync("EncounterRegistry").WaitForCompletion(); registryCache.ClearEncounters(); registryCache.PopulateEncounters(); } } #endif }