Adding Match State controller

This commit is contained in:
Matt F 2026-05-12 10:31:23 -07:00
parent c100db52e5
commit abcefcd7f1
13 changed files with 445 additions and 99 deletions

View file

@ -0,0 +1,138 @@
// Assets/_Project/Scripts/Gameplay/MatchState.cs
using Unity.Netcode;
using UnityEngine;
using TD.Core;
namespace TD.Gameplay
{
/// <summary>
/// Singleton NetworkBehaviour that tracks global match data.
/// Lives on a persistent scene object.
///
/// All state is server-authoritative (Server-write NetworkVariables).
/// Clients react to OnValueChanged events on individual fields.
/// Phase transitions (Lobby → CountDown → Playing → Victory/Defeat)
/// and wave/lives logic are wired in Phases 1.41.6 and 1.8.
/// </summary>
public class MatchState : NetworkBehaviour
{
// --- Singleton ---------------------------------------------------
public static MatchState Instance { get; private set; }
// --- Networked state ---------------------------------------------
private readonly NetworkVariable<MatchPhase> phase = new NetworkVariable<MatchPhase>(
value: MatchPhase.Playing,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// <summary>Current match phase. Authoritative on the server, replicated to all clients.</summary>
public MatchPhase Phase => phase.Value;
// STUBBED — written by the wave system (Phase 1.5/1.6). Zero until waves begin.
[Tooltip("STUBBED — not written until the wave system lands (Phase 1.5/1.6).")]
private readonly NetworkVariable<int> currentWave = new NetworkVariable<int>(
value: 0,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// <summary>STUBBED. Current wave index. Zero until the wave system (Phase 1.5/1.6) writes it.</summary>
public int CurrentWave => currentWave.Value;
// STUBBED — written by the combat/leak system (Phase 1.4). Zero until then.
[Tooltip("STUBBED — not written until the combat/leak system lands (Phase 1.4).")]
private readonly NetworkVariable<int> lives = new NetworkVariable<int>(
value: 0,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// <summary>STUBBED. Shared lives pool. Zero until the combat/leak system (Phase 1.4) writes it.</summary>
public int Lives => lives.Value;
// STUBBED — driven by the race-pick UI (Phase 1.8). Zero when no pick is in progress.
[Tooltip("STUBBED — not driven until the race-pick system lands (Phase 1.8).")]
private readonly NetworkVariable<float> racePickTimer = new NetworkVariable<float>(
value: 0f,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// <summary>STUBBED. Seconds remaining in the race-pick countdown. Zero when no pick is in progress.</summary>
public float RacePickTimer => racePickTimer.Value;
/// <summary>
/// Fired on every peer (server and clients) when the phase changes.
/// Subscribe to react to phase transitions without polling.
/// </summary>
public event System.Action<MatchPhase, MatchPhase> OnPhaseChanged;
// --- Lifecycle ---------------------------------------------------
public override void OnNetworkSpawn()
{
if (Instance != null && Instance != this)
{
Debug.LogError("[MatchState] Duplicate MatchState detected — only one may exist per scene. " +
"Despawning the duplicate.");
NetworkObject.Despawn();
return;
}
Instance = this;
phase.OnValueChanged += HandlePhaseChanged;
Debug.Log($"[MatchState] Spawned. Phase={phase.Value}");
}
public override void OnNetworkDespawn()
{
phase.OnValueChanged -= HandlePhaseChanged;
if (Instance == this) Instance = null;
}
// --- Server API --------------------------------------------------
/// <summary>
/// Transitions to a new phase. Server-only; no-op on clients with a log warning.
/// </summary>
public void SetPhase(MatchPhase next)
{
if (!IsServer) { Debug.LogWarning("[MatchState] SetPhase called on a client — ignored."); return; }
if (phase.Value == next) return;
phase.Value = next;
}
/// <summary>STUBBED. Sets the current wave index. Called by the wave system (Phase 1.5/1.6).</summary>
public void SetCurrentWave(int wave)
{
if (!IsServer) { Debug.LogWarning("[MatchState] SetCurrentWave called on a client — ignored."); return; }
currentWave.Value = wave;
}
/// <summary>STUBBED. Sets the shared lives pool. Called by the combat/leak system (Phase 1.4).</summary>
public void SetLives(int value)
{
if (!IsServer) { Debug.LogWarning("[MatchState] SetLives called on a client — ignored."); return; }
lives.Value = Mathf.Max(0, value);
}
/// <summary>STUBBED. Sets the race-pick countdown timer. Called by the race-pick system (Phase 1.8).</summary>
public void SetRacePickTimer(float seconds)
{
if (!IsServer) { Debug.LogWarning("[MatchState] SetRacePickTimer called on a client — ignored."); return; }
racePickTimer.Value = Mathf.Max(0f, seconds);
}
// --- Handlers ----------------------------------------------------
private void HandlePhaseChanged(MatchPhase previous, MatchPhase current)
{
Debug.Log($"[MatchState] Phase: {previous} → {current}");
OnPhaseChanged?.Invoke(previous, current);
}
}
}