// Assets/_Project/Scripts/Gameplay/MatchState.cs
using Unity.Netcode;
using UnityEngine;
using TD.Core;
namespace TD.Gameplay
{
///
/// 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.
///
public class MatchState : NetworkBehaviour
{
// --- Singleton ---------------------------------------------------
public static MatchState Instance { get; private set; }
// --- Networked state ---------------------------------------------
private readonly NetworkVariable phase = new NetworkVariable(
value: MatchPhase.Playing,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// Current match phase. Authoritative on the server, replicated to all clients.
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 currentWave = new NetworkVariable(
value: 0,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// STUBBED. Current wave index. Zero until the wave system (Phase 1.5/1.6) writes it.
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 lives = new NetworkVariable(
value: 0,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// STUBBED. Shared lives pool. Zero until the combat/leak system (Phase 1.4) writes it.
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 racePickTimer = new NetworkVariable(
value: 0f,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// STUBBED. Seconds remaining in the race-pick countdown. Zero when no pick is in progress.
public float RacePickTimer => racePickTimer.Value;
///
/// Fired on every peer (server and clients) when the phase changes.
/// Subscribe to react to phase transitions without polling.
///
public event System.Action 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 --------------------------------------------------
///
/// Transitions to a new phase. Server-only; no-op on clients with a log warning.
///
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;
}
/// STUBBED. Sets the current wave index. Called by the wave system (Phase 1.5/1.6).
public void SetCurrentWave(int wave)
{
if (!IsServer) { Debug.LogWarning("[MatchState] SetCurrentWave called on a client — ignored."); return; }
currentWave.Value = wave;
}
/// STUBBED. Sets the shared lives pool. Called by the combat/leak system (Phase 1.4).
public void SetLives(int value)
{
if (!IsServer) { Debug.LogWarning("[MatchState] SetLives called on a client — ignored."); return; }
lives.Value = Mathf.Max(0, value);
}
/// STUBBED. Sets the race-pick countdown timer. Called by the race-pick system (Phase 1.8).
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);
}
}
}