138 lines
5.8 KiB
C#
138 lines
5.8 KiB
C#
// 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.4–1.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);
|
||
}
|
||
}
|
||
}
|