We've got enemies and movement!!
This commit is contained in:
parent
42ee0bf65d
commit
3287e8ea43
26 changed files with 1409 additions and 161 deletions
|
|
@ -6,65 +6,129 @@ using TD.Core;
|
|||
namespace TD.Gameplay
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// Per-enemy HP component. Single point through which all damage flows so
|
||||
/// resistance lookups (Phase 1.5+) and kill attribution remain in one place.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Lives on the enemy prefab root alongside <see cref="EnemyStatus"/> and the
|
||||
/// future <c>EnemyMovement</c> component (Phase 1.5/1.6). HP is server-written
|
||||
/// and replicated to all clients so health bars can render on any peer.
|
||||
/// <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>TowerInstance.InitializeServer</c>.
|
||||
///
|
||||
/// <b>Death flow (server-only):</b>
|
||||
/// <c>TakeDamage</c> clamps HP to 0, fires <see cref="OnDied"/>, then calls
|
||||
/// <c>NetworkObject.Despawn</c>. Subscribers must not touch the NetworkObject
|
||||
/// after <c>OnDied</c> returns.
|
||||
/// <b>Kill attribution:</b> <see cref="LastHitOwner"/> tracks the
|
||||
/// <see cref="PlayerSlot"/> of the tower that most recently dealt direct damage.
|
||||
/// DoT ticks from <c>EnemyStatus</c> also carry an owner so the credit
|
||||
/// follows the source tower, not the DoT applicator.
|
||||
///
|
||||
/// <b>Death flow (server-only):</b> <see cref="TakeDamage"/> clamps HP to 0,
|
||||
/// fires <see cref="OnDied"/>, then calls <c>NetworkObject.Despawn</c>.
|
||||
/// Subscribers must not touch the NetworkObject after <see cref="OnDied"/> returns.
|
||||
/// </remarks>
|
||||
[RequireComponent(typeof(NetworkObject))]
|
||||
public class EnemyHealth : NetworkBehaviour
|
||||
{
|
||||
[SerializeField] private float maxHp = 100f;
|
||||
// ----- Pre-spawn init (server-local) ----------------------------------
|
||||
|
||||
private float pendingMaxHp = 100f;
|
||||
private int pendingGoldReward;
|
||||
private int pendingLivesCost = 1;
|
||||
private bool pendingIsFlying;
|
||||
private bool hasPendingInit;
|
||||
|
||||
// ----- Server-local runtime state -------------------------------------
|
||||
|
||||
/// <summary>Gold awarded to the killing player when this enemy dies.</summary>
|
||||
public int GoldReward { get; private set; }
|
||||
|
||||
/// <summary>Lives deducted from the shared pool when this enemy reaches the goal.</summary>
|
||||
public int LivesCost { get; private set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PlayerSlot"/> of the tower that last dealt direct damage.
|
||||
/// Used by <c>WaveManager</c> to award kill gold to the correct player.
|
||||
/// Updated on every <see cref="TakeDamage"/> call, including DoT ticks whose
|
||||
/// source owner is tracked on <see cref="EnemyStatus.StatusEffect"/>.
|
||||
/// </summary>
|
||||
public PlayerSlot LastHitOwner { get; private set; } = PlayerSlot.None;
|
||||
|
||||
// ----- Networked state ------------------------------------------------
|
||||
|
||||
private readonly NetworkVariable<float> hp = new NetworkVariable<float>(
|
||||
0f,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
// ----- Public state -----------------------------------------------
|
||||
private readonly NetworkVariable<bool> isFlying = new NetworkVariable<bool>(
|
||||
false,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
// ----- Public state ---------------------------------------------------
|
||||
|
||||
public float CurrentHp => hp.Value;
|
||||
public float MaxHp => maxHp;
|
||||
public float MaxHp { get; private set; } = 100f;
|
||||
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;
|
||||
/// <summary>
|
||||
/// True if this enemy flies over tower footprints.
|
||||
/// Replicated so client visuals can adjust altitude.
|
||||
/// Grounded towers with GroundedOnly=true will not target flying enemies.
|
||||
/// </summary>
|
||||
public bool IsFlying => isFlying.Value;
|
||||
|
||||
// ----- Events -----------------------------------------------------
|
||||
// ----- Events ---------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Fired on the server immediately before the enemy NetworkObject is despawned.
|
||||
/// <see cref="TD.Combat.TowerCombat"/> subscribes to clear its target reference.
|
||||
/// <c>WaveManager</c> subscribes to credit kill gold and decrement wave count.
|
||||
/// Do not access the NetworkObject after this event returns.
|
||||
/// </summary>
|
||||
public event System.Action<EnemyHealth> OnDied;
|
||||
|
||||
// ----- NGO lifecycle ----------------------------------------------
|
||||
// ----- Server-only pre-spawn init -------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Called by <c>WaveManager</c> on the server after <c>Instantiate</c>
|
||||
/// and before <c>NetworkObject.Spawn()</c>. Mirrors the
|
||||
/// <c>TowerInstance.InitializeServer</c> pattern.
|
||||
/// </summary>
|
||||
public void InitializeServer(float maxHp, int goldReward, int livesCost, bool flying)
|
||||
{
|
||||
pendingMaxHp = maxHp;
|
||||
pendingGoldReward = goldReward;
|
||||
pendingLivesCost = livesCost;
|
||||
pendingIsFlying = flying;
|
||||
hasPendingInit = true;
|
||||
|
||||
// Cache locally on the server immediately — clients resolve via NV.
|
||||
MaxHp = maxHp;
|
||||
GoldReward = goldReward;
|
||||
LivesCost = livesCost;
|
||||
}
|
||||
|
||||
// ----- NGO lifecycle --------------------------------------------------
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
if (IsServer)
|
||||
hp.Value = maxHp;
|
||||
if (IsServer && hasPendingInit)
|
||||
{
|
||||
hp.Value = pendingMaxHp;
|
||||
isFlying.Value = pendingIsFlying;
|
||||
hasPendingInit = false;
|
||||
}
|
||||
|
||||
// Non-server clients resolve MaxHp from the replicated hp initial value.
|
||||
if (!IsServer)
|
||||
MaxHp = hp.Value;
|
||||
}
|
||||
|
||||
// ----- Server API -------------------------------------------------
|
||||
// ----- Server API -----------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Applies <paramref name="damage"/> to this enemy. Server-only; no-op on clients.
|
||||
/// <paramref name="type"/> is recorded for future resistance/weakness lookups —
|
||||
/// all damage is full-value until the resistance table is implemented (Phase 1.5+).
|
||||
/// Applies damage to this enemy. Server-only; silently no-ops on clients.
|
||||
/// <paramref name="type"/> is accepted for future resistance lookups (Phase 1.5+).
|
||||
/// <paramref name="attackerSlot"/> identifies the tower owner for kill attribution.
|
||||
/// </summary>
|
||||
public void TakeDamage(float damage, DamageType type)
|
||||
public void TakeDamage(float damage, DamageType type, PlayerSlot attackerSlot)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
if (IsDead) return;
|
||||
|
|
@ -73,13 +137,14 @@ namespace TD.Gameplay
|
|||
// float modified = ResistanceTable.Apply(damage, type, this);
|
||||
float modified = damage;
|
||||
|
||||
hp.Value = Mathf.Max(0f, hp.Value - modified);
|
||||
LastHitOwner = attackerSlot;
|
||||
hp.Value = Mathf.Max(0f, hp.Value - modified);
|
||||
|
||||
if (hp.Value <= 0f)
|
||||
HandleDeath();
|
||||
}
|
||||
|
||||
// ----- Private ----------------------------------------------------
|
||||
// ----- Private --------------------------------------------------------
|
||||
|
||||
private void HandleDeath()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue