using System; using System.Collections.Generic; using UnityEngine; using Newtonsoft.Json; using CommanderAILab.Models; namespace CommanderAILab.UI { /// /// Singleton event hub for the live battleground game state. /// All UI widgets subscribe to these events instead of polling. /// Receives parsed server events from GameWebSocketClient. /// public class GameStateManager : MonoBehaviour { public static GameStateManager Instance { get; private set; } // ── Events ──────────────────────────────────────────────── public event Action OnTurnChanged; // activeSeat public event Action OnPhaseChanged; // phase, seat, turnNumber public event Action OnPriorityChanged; // seatIndex public event Action OnLifeChanged; // seatIndex, newLife public event Action OnPlayerEliminated; // seatIndex, reason public event Action> OnStackUpdated; public event Action OnFullStateSync; // ── State ──────────────────────────────────────────────── public int TurnNumber { get; private set; } = 1; private GameState _state; // ── Lifecycle ───────────────────────────────────────────── private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } // ── Server event dispatch ─────────────────────────────────── /// Called by GameWebSocketClient on the Unity main thread. public void ApplyServerEvent(string type, string payload) { try { switch (type) { case "state_update": _state = JsonConvert.DeserializeObject(payload); if (_state != null) OnFullStateSync?.Invoke(_state); break; case "turn_start": var ts = JsonConvert.DeserializeObject(payload); if (ts != null) { TurnNumber = ts.turnNumber; OnTurnChanged?.Invoke(ts.seat); } break; case "phase_change": var pc = JsonConvert.DeserializeObject(payload); if (pc != null) OnPhaseChanged?.Invoke((PhaseTrackerWidget.Phase)pc.phaseIndex, pc.seat, TurnNumber); break; case "priority": var pv = JsonConvert.DeserializeObject(payload); if (pv != null) OnPriorityChanged?.Invoke(pv.seat); break; case "life_change": var lc = JsonConvert.DeserializeObject(payload); if (lc != null) OnLifeChanged?.Invoke(lc.seat, lc.newLife); break; case "stack_update": var su = JsonConvert.DeserializeObject(payload); if (su?.items != null) OnStackUpdated?.Invoke(su.items); break; case "player_elim": var pe = JsonConvert.DeserializeObject(payload); if (pe != null) EliminatePlayer(pe.seat, pe.reason); break; default: Debug.LogWarning($"[GameStateManager] Unknown event type: {type}"); break; } } catch (Exception ex) { Debug.LogError($"[GameStateManager] ApplyServerEvent failed ({type}): {ex.Message}"); } } /// Trigger elimination (called by LifeTrackerWidget, CommanderDamageRow, or server event). public void EliminatePlayer(int seatIndex, string reason) => OnPlayerEliminated?.Invoke(seatIndex, reason); // ── Debug helpers (editor only) ───────────────────────────── #if UNITY_EDITOR private void OnGUI() { GUILayout.BeginArea(new Rect(10, 10, 220, 200)); GUILayout.Label("[GSM Debug]"); if (GUILayout.Button("Fake Turn 1 Start")) ApplyServerEvent("turn_start", "{\"seat\":0,\"turnNumber\":1}"); if (GUILayout.Button("Advance Phase Main1")) ApplyServerEvent("phase_change","{\"phaseIndex\":3,\"seat\":0}"); if (GUILayout.Button("Priority Seat 0")) ApplyServerEvent("priority", "{\"seat\":0}"); if (GUILayout.Button("Elim Seat 2")) EliminatePlayer(2, "life"); GUILayout.EndArea(); } #endif // ── Private event POCOs ─────────────────────────────────── [Serializable] private class TurnStartEvent { public int seat; public int turnNumber; } [Serializable] private class PhaseChangeEvent { public int phaseIndex; public int seat; } [Serializable] private class PriorityEvent { public int seat; } [Serializable] private class LifeChangeEvent { public int seat; public int newLife; } [Serializable] private class StackUpdateEvent { public List items; } [Serializable] private class EliminationEvent { public int seat; public string reason; } } }