// Assets/_Project/Scripts/Gameplay/EnemyAbilities/EnemyAbilityPool.cs using System; using UnityEngine; using TD.Core; namespace TD.Gameplay.EnemyAbilities { /// /// Scene singleton holding every available this match, /// authored as a flat array in the inspector for convenience. Internally it builds a /// fixed-size table indexed by so lookups are a single array /// access, not a scan. Mirrors exactly. /// /// /// Plain MonoBehaviour: identical on every peer (same assets), so there is nothing to sync. /// Only the server calls (at enemy spawn time, in /// WaveManager.SpawnEnemy). /// public class EnemyAbilityPool : MonoBehaviour { public static EnemyAbilityPool Instance { get; private set; } [Tooltip("Every EnemyAbilityDefinition asset available this match. One entry per " + "EnemyAbilityKind — order doesn't matter, Kind on the asset itself decides " + "its slot.")] [SerializeField] private EnemyAbilityDefinition[] abilities; [Tooltip("Relative weight of an enemy rolling no ability at all, vs. the combined " + "weight of every entry in 'abilities'. Higher = abilities are rarer.")] [Min(0f)] [SerializeField] private float noAbilityWeight = 20f; // Fixed-size, enum-indexed lookup built once in Awake. Sized to the enum's entry count, // not authored count, so an out-of-range Kind is a compile-time impossibility rather // than a bounds check we'd otherwise need on every Get(). private EnemyAbilityDefinition[] byKind; private void Awake() { if (Instance != null && Instance != this) { Debug.LogError("[EnemyAbilityPool] Multiple instances detected. Only one per scene."); return; } Instance = this; int kindCount = Enum.GetValues(typeof(EnemyAbilityKind)).Length; byKind = new EnemyAbilityDefinition[kindCount]; if (abilities == null) return; for (int i = 0; i < abilities.Length; i++) { var def = abilities[i]; if (def == null) continue; byKind[(int)def.Kind] = def; } } private void OnDestroy() { if (Instance == this) Instance = null; } /// Returns the ability asset for , or null if none is /// authored in this pool. public EnemyAbilityDefinition Get(EnemyAbilityKind kind) { int i = (int)kind; return (byKind != null && i >= 0 && i < byKind.Length) ? byKind[i] : null; } /// /// Server-only: weighted random draw across every authored ability plus the /// no-ability bucket. Returns null when "no ability" is drawn, or when the pool has /// nothing authored. /// public EnemyAbilityDefinition RollRandom() { if (abilities == null || abilities.Length == 0) return null; float total = noAbilityWeight; for (int i = 0; i < abilities.Length; i++) if (abilities[i] != null) total += abilities[i].Weight; if (total <= 0f) return null; float roll = UnityEngine.Random.Range(0f, total); if (roll < noAbilityWeight) return null; roll -= noAbilityWeight; for (int i = 0; i < abilities.Length; i++) { var def = abilities[i]; if (def == null) continue; if (roll < def.Weight) return def; roll -= def.Weight; } return null; } } }