// 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); } } }