Zabiju je
This commit is contained in:
@@ -1,71 +1,346 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Subsystems;
|
||||
using GeoSus.Client;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using TMPro;
|
||||
|
||||
namespace Subsystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages UI for the GameManager. Canvas references are only valid in Client.unity;
|
||||
/// Art-menu scenes use their own lightweight UI scripts that read from GameManager.Instance.
|
||||
/// </summary>
|
||||
public class GameManager_UI
|
||||
{
|
||||
private GameClient _gameClient;
|
||||
private Canvas _CreateJoinLobby;
|
||||
private Canvas _InLobby;
|
||||
private Canvas _LoadingScreen;
|
||||
private Canvas _GameScreen;
|
||||
public GameManager_UI(GameClient gameClient, Canvas CreateJoinLobby, Canvas InLobby, Canvas LoadingScreen, Canvas GameScreen)
|
||||
|
||||
// Set by GameManager after Client.unity loads (called from GameManager.OnSceneLoaded)
|
||||
public Canvas ClientCreateJoinLobby; // fallback join-code canvas in Client.unity
|
||||
public Canvas ClientInLobby; // InLobby canvas in Client.unity (unused now, kept compat)
|
||||
public Canvas ClientLoadingScreen;
|
||||
public Canvas ClientGameScreen; // parent of all HUD elements
|
||||
|
||||
// HUD elements (children of ClientGameScreen, resolved at runtime)
|
||||
private TMP_Text _roleText;
|
||||
private TMP_Text _taskListText;
|
||||
private TMP_Text _taskProgressText;
|
||||
private Button _actionButton;
|
||||
private TMP_Text _actionButtonText;
|
||||
private TMP_Text _killCooldownText;
|
||||
private GameObject _sabotagePanel;
|
||||
private TMP_Text _sabotageTimerText;
|
||||
private GameObject _meetingPanel;
|
||||
private GameObject _gameEndPanel;
|
||||
private TMP_Text _gameEndText;
|
||||
|
||||
// Runtime state
|
||||
private bool _isDead;
|
||||
private bool _commsBlackout;
|
||||
private DateTime _sabotageMeltdownDeadline;
|
||||
private bool _sabotageTimerActive;
|
||||
|
||||
// Lobby-changed flag — set from network thread, consumed in Update
|
||||
private volatile bool _lobbyDirty;
|
||||
|
||||
public GameManager_UI(GameClient gameClient)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
_CreateJoinLobby = CreateJoinLobby;
|
||||
_LoadingScreen = LoadingScreen;
|
||||
_GameScreen = GameScreen;
|
||||
_InLobby = InLobby;
|
||||
_CreateJoinLobby.enabled = true;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>Called by Network subsystem when lobby player list changes.</summary>
|
||||
public void NotifyLobbyChanged() => _lobbyDirty = true;
|
||||
|
||||
// ── Called from GameManager after Client.unity loads ──────────────────
|
||||
|
||||
public void BindClientScene(Canvas createJoin, Canvas inLobby, Canvas loading, Canvas game)
|
||||
{
|
||||
ClientCreateJoinLobby = createJoin;
|
||||
ClientInLobby = inLobby;
|
||||
ClientLoadingScreen = loading;
|
||||
ClientGameScreen = game;
|
||||
|
||||
EnsureCanvasReady(createJoin);
|
||||
EnsureCanvasReady(inLobby);
|
||||
EnsureCanvasReady(loading);
|
||||
EnsureCanvasReady(game);
|
||||
|
||||
if (createJoin) createJoin.gameObject.SetActive(false);
|
||||
if (inLobby) inLobby.gameObject.SetActive(false);
|
||||
if (loading) loading.gameObject.SetActive(false);
|
||||
if (game) game.gameObject.SetActive(false);
|
||||
|
||||
if (game != null)
|
||||
{
|
||||
_roleText = FindTMP(game.transform, "Role");
|
||||
_taskListText = FindTMP(game.transform, "TaskList");
|
||||
_taskProgressText = FindTMP(game.transform, "TaskProgress");
|
||||
_killCooldownText = FindTMP(game.transform, "KillCooldown");
|
||||
_sabotageTimerText = FindTMP(game.transform, "SabotageTimer");
|
||||
_gameEndText = FindTMP(game.transform, "GameEndText");
|
||||
|
||||
var actionGO = game.transform.Find("ActionButton");
|
||||
if (actionGO != null)
|
||||
{
|
||||
_actionButton = actionGO.GetComponent<Button>();
|
||||
_actionButtonText = actionGO.GetComponentInChildren<TMP_Text>();
|
||||
}
|
||||
|
||||
var sabGO = game.transform.Find("SabotagePanel");
|
||||
_sabotagePanel = sabGO?.gameObject;
|
||||
|
||||
var meetGO = game.transform.Find("MeetingPanel");
|
||||
_meetingPanel = meetGO?.gameObject;
|
||||
if (_meetingPanel) _meetingPanel.SetActive(false);
|
||||
|
||||
var endGO = game.transform.Find("GameEndPanel");
|
||||
_gameEndPanel = endGO?.gameObject;
|
||||
if (_gameEndPanel) _gameEndPanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Main update (called every frame from GameManager.Update) ──────────
|
||||
|
||||
public void UpdateLobbyUI()
|
||||
{
|
||||
if (_gameClient.CurrentLobbyState == null)
|
||||
var state = _gameClient.CurrentLobbyState;
|
||||
if (state == null) return;
|
||||
|
||||
// Update any LobbyDisplayUI listeners in the current scene
|
||||
if (_lobbyDirty)
|
||||
{
|
||||
_CreateJoinLobby.enabled = true;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = false;
|
||||
return;
|
||||
_lobbyDirty = false;
|
||||
LobbyDisplayUI.RefreshAll(state);
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Loading)
|
||||
|
||||
// Only do canvas switches if we are in Client.unity (canvases assigned)
|
||||
if (ClientGameScreen == null) return;
|
||||
|
||||
switch (state.Phase)
|
||||
{
|
||||
_CreateJoinLobby.enabled = false;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = true;
|
||||
return;
|
||||
case GamePhase.Loading:
|
||||
SetCanvases(false, false, true, false);
|
||||
break;
|
||||
|
||||
case GamePhase.Lobby:
|
||||
SetCanvases(false, true, false, false);
|
||||
break;
|
||||
|
||||
case GamePhase.Playing:
|
||||
case GamePhase.Meeting:
|
||||
case GamePhase.Voting:
|
||||
SetCanvases(false, false, false, true);
|
||||
UpdateGameHUD();
|
||||
break;
|
||||
|
||||
case GamePhase.Ended:
|
||||
// GameEndPanel shown by HandleGameEnded
|
||||
break;
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Lobby)
|
||||
}
|
||||
|
||||
private void UpdateGameHUD()
|
||||
{
|
||||
if (_roleText != null) _roleText.text = _gameClient.MyRole?.ToString() ?? "";
|
||||
|
||||
// Task list
|
||||
if (_taskListText != null)
|
||||
{
|
||||
_InLobby.enabled = true;
|
||||
_CreateJoinLobby.enabled = false;
|
||||
var playerList = _InLobby.transform.Find("PlayerList").GetComponent<TMPro.TMP_Text>();
|
||||
playerList.text = "";
|
||||
foreach (var player in _gameClient.CurrentLobbyState.Players)
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var t in _gameClient.MyTasks)
|
||||
sb.AppendLine(t.Name);
|
||||
_taskListText.text = sb.ToString();
|
||||
}
|
||||
|
||||
// Kill cooldown (managed by GameManager_Tasks via Update)
|
||||
// Sabotage timer
|
||||
if (_sabotageTimerActive && _sabotageTimerText != null)
|
||||
{
|
||||
double remaining = (_sabotageMeltdownDeadline - DateTime.UtcNow).TotalSeconds;
|
||||
_sabotageTimerText.text = remaining > 0 ? $"MELTDOWN: {remaining:F0}s" : "MELTDOWN!";
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers called by Network handlers ────────────────────────────────
|
||||
|
||||
public void SetKillCooldownText(string text)
|
||||
{
|
||||
if (_killCooldownText != null)
|
||||
{
|
||||
_killCooldownText.text = text;
|
||||
_killCooldownText.gameObject.SetActive(!string.IsNullOrEmpty(text));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateTaskProgress(int completed, int total)
|
||||
{
|
||||
if (_taskProgressText != null)
|
||||
_taskProgressText.text = $"Tasks: {completed}/{total}";
|
||||
}
|
||||
|
||||
public void SetActionButton(string label, bool visible, UnityEngine.Events.UnityAction onClick = null)
|
||||
{
|
||||
if (_actionButton == null) return;
|
||||
_actionButton.gameObject.SetActive(visible);
|
||||
if (_actionButtonText != null) _actionButtonText.text = label;
|
||||
if (onClick != null)
|
||||
{
|
||||
_actionButton.onClick.RemoveAllListeners();
|
||||
_actionButton.onClick.AddListener(onClick);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnLocalPlayerDied()
|
||||
{
|
||||
_isDead = true;
|
||||
if (_roleText != null) _roleText.text = "GHOST";
|
||||
}
|
||||
|
||||
public void ShowMeetingAlert()
|
||||
{
|
||||
Debug.Log("Meeting called! Run to meeting point.");
|
||||
}
|
||||
|
||||
public void ShowMeetingPanel(List<PlayerInfo> players, MeetingStartedPayload payload)
|
||||
{
|
||||
if (_meetingPanel == null) return;
|
||||
_meetingPanel.SetActive(true);
|
||||
|
||||
var header = FindTMP(_meetingPanel.transform, "MeetingHeader");
|
||||
if (header != null)
|
||||
header.text = payload.Type == MeetingType.BodyReport ? "BODY REPORTED!" : "EMERGENCY MEETING!";
|
||||
|
||||
// Build simple text list of players — full vote buttons need prefabs in Unity Editor
|
||||
var playerList = FindTMP(_meetingPanel.transform, "MeetingPlayerList");
|
||||
if (playerList != null && players != null)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var p in players)
|
||||
sb.AppendLine($"{p.DisplayName} [{p.State}]");
|
||||
playerList.text = sb.ToString();
|
||||
}
|
||||
|
||||
// Wire skip button if it exists
|
||||
var skipBtn = _meetingPanel.transform.Find("SkipButton")?.GetComponent<Button>();
|
||||
if (skipBtn != null)
|
||||
{
|
||||
skipBtn.onClick.RemoveAllListeners();
|
||||
skipBtn.onClick.AddListener(() => GameManager.Instance?.CastVote(null));
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendVoteInstruction()
|
||||
{
|
||||
var playerList = FindTMP(_meetingPanel?.transform, "MeetingPlayerList");
|
||||
if (playerList != null)
|
||||
playerList.text += "\n[Tap a name] then press VOTE — or press SKIP";
|
||||
}
|
||||
|
||||
public void ShowVoteResult(VotingClosedPayload payload, List<PlayerInfo> players)
|
||||
{
|
||||
if (_meetingPanel == null) return;
|
||||
|
||||
var resultText = FindTMP(_meetingPanel.transform, "VoteResult");
|
||||
if (resultText != null)
|
||||
{
|
||||
if (payload.WasTie)
|
||||
{
|
||||
playerList.text += player.DisplayName + "\n";
|
||||
resultText.text = "TIE — nobody ejected.";
|
||||
}
|
||||
else if (string.IsNullOrEmpty(payload.EjectedPlayerId))
|
||||
{
|
||||
resultText.text = "Skip — nobody ejected.";
|
||||
}
|
||||
else
|
||||
{
|
||||
var ejected = players?.Find(p => p.ClientUuid == payload.EjectedPlayerId);
|
||||
resultText.text = $"{ejected?.DisplayName ?? payload.EjectedPlayerId} ejected!";
|
||||
}
|
||||
_InLobby.transform.Find("JoinCode").GetComponent<TMPro.TMP_Text>().text = _gameClient.CurrentLobbyState.JoinCode;
|
||||
return;
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Playing)
|
||||
|
||||
// Close panel after 5 seconds — use coroutine via GameManager
|
||||
GameManager.Instance?.StartCoroutine(CloseMeetingPanelAfterDelay(5f));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator CloseMeetingPanelAfterDelay(float delay)
|
||||
{
|
||||
yield return new UnityEngine.WaitForSeconds(delay);
|
||||
if (_meetingPanel != null) _meetingPanel.SetActive(false);
|
||||
}
|
||||
|
||||
public void ShowGameEndPanel(GameEndedPayload payload, string myUuid)
|
||||
{
|
||||
if (_gameEndPanel != null) _gameEndPanel.SetActive(true);
|
||||
bool won = payload.Winners != null && payload.Winners.Contains(myUuid);
|
||||
if (_gameEndText != null)
|
||||
_gameEndText.text = $"{(won ? "VICTORY" : "DEFEAT")}\n{payload.WinningFaction} wins!\n{payload.Reason}";
|
||||
}
|
||||
|
||||
public void ShowSabotageTimer(DateTime deadline)
|
||||
{
|
||||
_sabotageMeltdownDeadline = deadline;
|
||||
_sabotageTimerActive = true;
|
||||
if (_sabotageTimerText != null) _sabotageTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void HideSabotageTimer()
|
||||
{
|
||||
_sabotageTimerActive = false;
|
||||
if (_sabotageTimerText != null) _sabotageTimerText.gameObject.SetActive(false);
|
||||
SetCommsBlackout(false);
|
||||
}
|
||||
|
||||
public void SetCommsBlackout(bool active)
|
||||
{
|
||||
_commsBlackout = active;
|
||||
}
|
||||
|
||||
public bool IsCommsBlackout => _commsBlackout;
|
||||
public bool IsPlayerDead => _isDead;
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────
|
||||
|
||||
private void SetCanvases(bool createJoin, bool inLobby, bool loading, bool game)
|
||||
{
|
||||
EnsureCanvasReady(ClientCreateJoinLobby);
|
||||
EnsureCanvasReady(ClientInLobby);
|
||||
EnsureCanvasReady(ClientLoadingScreen);
|
||||
EnsureCanvasReady(ClientGameScreen);
|
||||
|
||||
if (ClientCreateJoinLobby) ClientCreateJoinLobby.gameObject.SetActive(createJoin);
|
||||
if (ClientInLobby) ClientInLobby.gameObject.SetActive(inLobby);
|
||||
if (ClientLoadingScreen) ClientLoadingScreen.gameObject.SetActive(loading);
|
||||
if (ClientGameScreen) ClientGameScreen.gameObject.SetActive(game);
|
||||
}
|
||||
|
||||
private static void EnsureCanvasReady(Canvas canvas)
|
||||
{
|
||||
if (canvas == null) return;
|
||||
|
||||
if (!canvas.enabled)
|
||||
canvas.enabled = true;
|
||||
|
||||
// Some scene canvases are saved with zero scale, which makes UI invisible.
|
||||
var t = canvas.transform;
|
||||
if (t != null)
|
||||
{
|
||||
_CreateJoinLobby.enabled = false;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = true;
|
||||
_LoadingScreen.enabled = false;
|
||||
_GameScreen.transform.Find("Role").GetComponent<TMPro.TMP_Text>().text = _gameClient.MyRole.ToString() ;
|
||||
return;
|
||||
var s = t.localScale;
|
||||
if (Mathf.Abs(s.x) < 0.001f || Mathf.Abs(s.y) < 0.001f || Mathf.Abs(s.z) < 0.001f)
|
||||
t.localScale = Vector3.one;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static TMP_Text FindTMP(Transform root, string name)
|
||||
{
|
||||
if (root == null) return null;
|
||||
foreach (var tmp in root.GetComponentsInChildren<TMP_Text>(true))
|
||||
{
|
||||
if (tmp != null && tmp.name == name)
|
||||
return tmp;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user