UnityTowerDefense/Assets/_Project/Scripts/Gameplay/EnemyAbility.cs

84 lines
3.6 KiB
C#

// Assets/_Project/Scripts/Gameplay/EnemyAbility.cs
using Unity.Netcode;
using UnityEngine;
using TD.Core;
using TD.Gameplay.EnemyAbilities;
namespace TD.Gameplay
{
/// <summary>
/// Per-enemy ability slot. Holds the <see cref="EnemyAbilityDefinition"/> rolled for this
/// instance (if any) and drives its server-only hooks.
/// </summary>
/// <remarks>
/// <b>Initialization:</b> Call <see cref="InitializeServer"/> on the server immediately after
/// <c>Instantiate</c> and before <c>NetworkObject.Spawn()</c>, following the same pattern as
/// <c>EnemyHealth.InitializeServer</c> / <c>EnemyMovement.InitializeServer</c>.
///
/// <b>Optional component:</b> Not required by <c>EnemyHealth</c> or <c>EnemyMovement</c> —
/// <c>WaveManager.SpawnEnemy</c> 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.
///
/// <b>On-death hook is not event-driven:</b> <c>WaveManager.HandleEnemyKilled</c> calls
/// <see cref="EnemyAbilityDefinition.ServerOnDeath"/> directly (via <see cref="Definition"/>)
/// rather than this component subscribing to <c>EnemyHealth.OnDied</c> itself — that keeps a
/// split-spawn's <c>activeEnemyCount</c> increment ordered before the triggering kill's
/// decrement, regardless of NetworkBehaviour subscription order.
/// </remarks>
[RequireComponent(typeof(NetworkObject))]
public class EnemyAbility : NetworkBehaviour
{
private readonly NetworkVariable<byte> kind = new NetworkVariable<byte>(
(byte)EnemyAbilityKind.None,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);
// ----- Pre-spawn init (server-local) ----------------------------------
private EnemyAbilityDefinition pendingDefinition;
private bool hasPendingInit;
// ----- Public state -----------------------------------------------------
/// <summary>The ability rolled for this enemy, or null if none was rolled.</summary>
public EnemyAbilityDefinition Definition { get; private set; }
/// <summary>Replicated kind of the rolled ability. <see cref="EnemyAbilityKind.None"/> if
/// none was rolled.</summary>
public EnemyAbilityKind Kind => (EnemyAbilityKind)kind.Value;
// ----- Server-only pre-spawn init -------------------------------------
/// <summary>
/// Called by <c>WaveManager</c> on the server after <c>Instantiate</c> and before
/// <c>NetworkObject.Spawn()</c>. <paramref name="definition"/> may be null, meaning no
/// ability was rolled for this enemy.
/// </summary>
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);
}
}
}