// Assets/_Project/Scripts/Gameplay/EnemyUpgrades/AbilityEnemyUpgradeOption.cs using UnityEngine; using TD.Gameplay.EnemyAbilities; using TD.Gameplay.Waves; namespace TD.Gameplay.EnemyUpgrades { /// /// The workhorse enemy-buff card: grants an to a wave /// slot. Every enemy spawned by that wave, in every later cycle of the phase, carries the /// ability. /// /// /// There is nothing to do at vote-resolution time — recording the option id on the slot IS the /// effect, because the spawn path builds each enemy's ability set from the slot's recorded /// options. is what that lookup resolves to. /// [CreateAssetMenu(fileName = "EnemyUpgrade_Ability", menuName = "TD/Enemy Upgrades/Ability Card", order = 20)] public class AbilityEnemyUpgradeOption : EnemyUpgradeOption { [Header("Payload")] [Tooltip("The ability every enemy in this wave gains. Must also be present in the scene's " + "EnemyAbilityPool so it can be resolved at spawn time.")] public EnemyAbilityDefinition Ability; public override bool IsValidForSlot(RunState run, int slot) { if (Ability == null) return false; // Don't offer an ability the slot already has by another card's hand. Exact-id repeats // are filtered by the caller, but two different cards can grant the same ability, and // stacking one twice is a no-op the players would rightly feel cheated by. return !SlotAlreadyHasAbility(run, slot); } // Shared scratch: validation runs on the server during vote generation, which is // single-threaded, so one reused list beats allocating per candidate per slot. private static readonly System.Collections.Generic.List s_idScratch = new System.Collections.Generic.List(); private bool SlotAlreadyHasAbility(RunState run, int slot) { var pool = EnemyUpgradePool.Instance; if (run == null || pool == null) return false; var ids = s_idScratch; run.GetUpgradesForSlot(slot, ids); foreach (int id in ids) { if (pool.Get(id) is AbilityEnemyUpgradeOption other && other.Ability == Ability) return true; } return false; } /// Inherits nothing automatically — carries no /// icon, so the card's own is the only source. public override Sprite ResolveIcon() => Icon; } }