// Assets/_Project/Scripts/Gameplay/EnemyAbility.cs using Unity.Netcode; using UnityEngine; using TD.Core; using TD.Gameplay.EnemyAbilities; namespace TD.Gameplay { /// /// Per-enemy ability slot. Holds the rolled for this /// instance (if any) and drives its server-only hooks. /// /// /// Initialization: Call on the server immediately after /// Instantiate and before NetworkObject.Spawn(), following the same pattern as /// EnemyHealth.InitializeServer / EnemyMovement.InitializeServer. /// /// Optional component: Not required by EnemyHealth or EnemyMovement — /// WaveManager.SpawnEnemy only rolls and assigns an ability when this component is /// present on the prefab, so existing enemy prefabs are unaffected until it's added to them. /// /// On-death hook is not event-driven: WaveManager.HandleEnemyKilled calls /// directly (via ) /// rather than this component subscribing to EnemyHealth.OnDied itself — that keeps a /// split-spawn's activeEnemyCount increment ordered before the triggering kill's /// decrement, regardless of NetworkBehaviour subscription order. /// [RequireComponent(typeof(NetworkObject))] public class EnemyAbility : NetworkBehaviour { private readonly NetworkVariable kind = new NetworkVariable( (byte)EnemyAbilityKind.None, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); // ----- Pre-spawn init (server-local) ---------------------------------- private EnemyAbilityDefinition pendingDefinition; private bool hasPendingInit; // ----- Public state ----------------------------------------------------- /// The ability rolled for this enemy, or null if none was rolled. public EnemyAbilityDefinition Definition { get; private set; } /// Replicated kind of the rolled ability. if /// none was rolled. public EnemyAbilityKind Kind => (EnemyAbilityKind)kind.Value; // ----- Server-only pre-spawn init ------------------------------------- /// /// Called by WaveManager on the server after Instantiate and before /// NetworkObject.Spawn(). may be null, meaning no /// ability was rolled for this enemy. /// public void InitializeServer(EnemyAbilityDefinition definition) { pendingDefinition = definition; hasPendingInit = true; } // ----- NGO lifecycle -------------------------------------------------- public override void OnNetworkSpawn() { if (!IsServer || !hasPendingInit) return; Definition = pendingDefinition; kind.Value = (byte)(Definition != null ? Definition.Kind : EnemyAbilityKind.None); hasPendingInit = false; Definition?.ServerOnSpawn(this); } // ----- Server tick ------------------------------------------------ private void Update() { if (!IsServer || Definition == null) return; Definition.ServerTick(this, Time.deltaTime); } } }