// Assets/_Project/Scripts/Gameplay/EnemyHealth.cs using Unity.Netcode; using UnityEngine; using TD.Core; namespace TD.Gameplay { /// /// Per-enemy HP component. Holds replicated HP and is the single point /// through which all damage flows, so resistance lookups (Phase 1.5+) can /// be added in one place without touching every damage source. /// /// /// Lives on the enemy prefab root alongside and the /// future EnemyMovement component (Phase 1.5/1.6). HP is server-written /// and replicated to all clients so health bars can render on any peer. /// /// Death flow (server-only): /// TakeDamage clamps HP to 0, fires , then calls /// NetworkObject.Despawn. Subscribers must not touch the NetworkObject /// after OnDied returns. /// [RequireComponent(typeof(NetworkObject))] public class EnemyHealth : NetworkBehaviour { [SerializeField] private float maxHp = 100f; private readonly NetworkVariable hp = new NetworkVariable( 0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); // ----- Public state ----------------------------------------------- public float CurrentHp => hp.Value; public float MaxHp => maxHp; public bool IsDead => hp.Value <= 0f; // Stub: set by EnemyMovement or spawner in Phase 1.5/1.6. // TowerCombat reads this to honour the GroundedOnly tower flag. public bool IsFlying => false; // ----- Events ----------------------------------------------------- /// /// Fired on the server immediately before the enemy NetworkObject is despawned. /// subscribes to clear its target reference. /// Do not access the NetworkObject after this event returns. /// public event System.Action OnDied; // ----- NGO lifecycle ---------------------------------------------- public override void OnNetworkSpawn() { if (IsServer) hp.Value = maxHp; } // ----- Server API ------------------------------------------------- /// /// Applies to this enemy. Server-only; no-op on clients. /// is recorded for future resistance/weakness lookups — /// all damage is full-value until the resistance table is implemented (Phase 1.5+). /// public void TakeDamage(float damage, DamageType type) { if (!IsServer) return; if (IsDead) return; // STUB — resistance table slot: // float modified = ResistanceTable.Apply(damage, type, this); float modified = damage; hp.Value = Mathf.Max(0f, hp.Value - modified); if (hp.Value <= 0f) HandleDeath(); } // ----- Private ---------------------------------------------------- private void HandleDeath() { OnDied?.Invoke(this); NetworkObject.Despawn(); } } }