84 lines
3.6 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|