First pass at full refactor to 2.0 design
Restructures the game around the cyclical run loop from Game Design Doc V2: 5 waves = a cycle, 3 cycles = a phase, each phase ends in a boss. - New TD.Gameplay.Waves: WaveGroup / PhaseDefinition / RunDefinition author the run as draggable weighted pools; RunState owns phase/cycle position, the drawn wave slots, and the per-slot enemy buff sets. WaveManager's flat wave array is gone -- it now only runs the encounter RunState points at. - New TD.Gameplay.EnemyUpgrades: the post-wave enemy-buff vote, with public live-replicated ballots so the HUD can show who voted for what. - Inter-wave flow is now strictly sequential: draft -> vote -> build, each stage ending early once every player has acted. - Enemy abilities inverted from per-instance random rolls to deterministic, stacking per-wave-slot sets. Six cards ship: Split (reworked), Flight, Blink, No Bounty, Gold Theft, Double Up. - Tower upgrades are a two-step tree: a draft pick unlocks a node, gold converts an already-placed tower in place. - Boss encounters flag their enemies and drive a boss HP bar. - Player cap reduced to 3 via MatchRules.MaxPlayers. - GoldConfig is now keyed by global encounter number rather than wave index. Compiles clean; NOT yet verified in-engine. Editor wiring still required -- see Docs/2.0_Setup_Checklist.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
7e5c3a8279
commit
4892d7253d
64 changed files with 4023 additions and 344 deletions
|
|
@ -1,5 +1,6 @@
|
|||
// Assets/_Project/Scripts/Gameplay/EnemyHealth.cs
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using TD.Core;
|
||||
|
|
@ -67,6 +68,7 @@ namespace TD.Gameplay
|
|||
private int pendingLivesCost = 1;
|
||||
private bool pendingIsFlying;
|
||||
private bool pendingIsHeld;
|
||||
private bool pendingIsBoss;
|
||||
private bool hasPendingInit;
|
||||
|
||||
// ----- Server-local runtime state -------------------------------------
|
||||
|
|
@ -103,6 +105,13 @@ namespace TD.Gameplay
|
|||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
// Set for enemies spawned by a phase's boss encounter. Replicated because the HUD's boss
|
||||
// bar is client-side and has to recognise a boss without asking the server.
|
||||
private readonly NetworkVariable<bool> isBoss = new NetworkVariable<bool>(
|
||||
false,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
// ----- Public state ---------------------------------------------------
|
||||
|
||||
public float CurrentHp => hp.Value;
|
||||
|
|
@ -117,6 +126,19 @@ namespace TD.Gameplay
|
|||
/// </summary>
|
||||
public bool IsFlying => isFlying.Value;
|
||||
|
||||
/// <summary>True if this enemy was spawned by a phase's boss encounter. Replicated.</summary>
|
||||
public bool IsBoss => isBoss.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Every live boss, on every peer. Maintained by spawn/despawn so the HUD can render a
|
||||
/// boss bar without polling the scene.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A list rather than a single reference because the boss encounter spawns one boss per
|
||||
/// player zone — there are as many simultaneous bosses as there are players.
|
||||
/// </remarks>
|
||||
public static readonly List<EnemyHealth> ActiveBosses = new List<EnemyHealth>();
|
||||
|
||||
// ----- Events ---------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -136,12 +158,14 @@ namespace TD.Gameplay
|
|||
/// and before <c>NetworkObject.Spawn()</c>. Mirrors the
|
||||
/// <c>TowerInstance.InitializeServer</c> pattern.
|
||||
/// </summary>
|
||||
public void InitializeServer(float maxHp, int livesCost, bool flying, bool held = false)
|
||||
public void InitializeServer(float maxHp, int livesCost, bool flying, bool held = false,
|
||||
bool boss = false)
|
||||
{
|
||||
pendingMaxHp = maxHp;
|
||||
pendingLivesCost = livesCost;
|
||||
pendingIsFlying = flying;
|
||||
pendingIsHeld = held;
|
||||
pendingIsBoss = boss;
|
||||
hasPendingInit = true;
|
||||
|
||||
// Cache locally on the server immediately — clients resolve via NV.
|
||||
|
|
@ -158,6 +182,7 @@ namespace TD.Gameplay
|
|||
hp.Value = pendingMaxHp;
|
||||
isFlying.Value = pendingIsFlying;
|
||||
isHeld.Value = pendingIsHeld;
|
||||
isBoss.Value = pendingIsBoss;
|
||||
hasPendingInit = false;
|
||||
}
|
||||
|
||||
|
|
@ -165,6 +190,9 @@ namespace TD.Gameplay
|
|||
if (!IsServer)
|
||||
MaxHp = hp.Value;
|
||||
|
||||
if (isBoss.Value && !ActiveBosses.Contains(this))
|
||||
ActiveBosses.Add(this);
|
||||
|
||||
// Apply initial held state on all peers and watch for future changes.
|
||||
// isHeld.OnValueChanged doesn't fire for the initial replication, so we
|
||||
// apply it explicitly here as well.
|
||||
|
|
@ -174,6 +202,8 @@ namespace TD.Gameplay
|
|||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
ActiveBosses.Remove(this);
|
||||
|
||||
// If this enemy was the locally-selected ISelectable, clear the
|
||||
// selection so the HUD doesn't keep displaying a stale corpse.
|
||||
// SelectionState is a local UI singleton, safe to query on any peer.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue