Zabiju je 2. Epicky thriller od tvurce Zabiju je
This commit is contained in:
@@ -9,168 +9,208 @@ 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.
|
||||
/// Reads from GameManager_Network.State (the authoritative GameState) and drives
|
||||
/// all in-game canvas panels. No business logic lives here.
|
||||
/// </summary>
|
||||
public class GameManager_UI
|
||||
{
|
||||
private GameClient _gameClient;
|
||||
private GameState _state => GameManager.Instance?.networkSubsystem?.State;
|
||||
|
||||
// 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)
|
||||
// ── Canvas refs (wired by BindClientScene from Client.unity) ──────────
|
||||
public Canvas ClientCreateJoinLobby;
|
||||
public Canvas ClientInLobby;
|
||||
public Canvas ClientLoadingScreen;
|
||||
public Canvas ClientGameScreen; // parent of all HUD elements
|
||||
public Canvas ClientGameScreen;
|
||||
|
||||
// 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;
|
||||
// ── HUD element refs (resolved once in BindClientScene) ───────────────
|
||||
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 TMP_Text _sabotageTimerText;
|
||||
private GameObject _meetingPanel;
|
||||
private TMP_Text _meetingHeader;
|
||||
private Transform _meetingScrollContent;
|
||||
private TMP_Text _meetingFallbackText;
|
||||
private GameObject _voteResultPanel;
|
||||
private TMP_Text _voteResultText;
|
||||
private GameObject _gameEndPanel;
|
||||
private TMP_Text _gameEndText;
|
||||
private TMP_Text _gameEndText;
|
||||
private RectTransform _returnToLobbyBtn;
|
||||
private TMP_Text _toastText;
|
||||
private GameObject _toastGO;
|
||||
|
||||
// Runtime state
|
||||
// ── Internal 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;
|
||||
}
|
||||
// Meeting vote-row references rebuilt each meeting
|
||||
private readonly List<GameObject> _voteRows = new List<GameObject>();
|
||||
private string _pendingVoteResultDisplay; // shown after voting
|
||||
|
||||
public GameManager_UI(GameClient gameClient) { _gameClient = gameClient; }
|
||||
|
||||
/// <summary>Called by Network subsystem when lobby player list changes.</summary>
|
||||
public void NotifyLobbyChanged() => _lobbyDirty = true;
|
||||
public bool IsCommsBlackout => _commsBlackout;
|
||||
public bool IsPlayerDead => _isDead;
|
||||
|
||||
// ── Called from GameManager after Client.unity loads ──────────────────
|
||||
// ── Scene binding ─────────────────────────────────────────────────────
|
||||
|
||||
public void BindClientScene(Canvas createJoin, Canvas inLobby, Canvas loading, Canvas game)
|
||||
{
|
||||
ClientCreateJoinLobby = createJoin;
|
||||
ClientInLobby = inLobby;
|
||||
ClientLoadingScreen = loading;
|
||||
ClientGameScreen = game;
|
||||
ClientInLobby = inLobby;
|
||||
ClientLoadingScreen = loading;
|
||||
ClientGameScreen = game;
|
||||
|
||||
EnsureCanvasReady(createJoin);
|
||||
EnsureCanvasReady(inLobby);
|
||||
EnsureCanvasReady(loading);
|
||||
EnsureCanvasReady(game);
|
||||
foreach (var c in new[] { createJoin, inLobby, loading, game })
|
||||
EnsureCanvasReady(c);
|
||||
|
||||
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 (inLobby) inLobby.gameObject.SetActive(false);
|
||||
if (loading) loading.gameObject.SetActive(false);
|
||||
if (game) game.gameObject.SetActive(false);
|
||||
|
||||
if (game != null)
|
||||
if (game == null) return;
|
||||
var t = game.transform;
|
||||
|
||||
_roleText = FindTMP(t, "Role");
|
||||
_taskListText = FindTMP(t, "TaskList");
|
||||
_taskProgressText = FindTMP(t, "TaskProgress");
|
||||
_killCooldownText = FindTMP(t, "KillCooldown");
|
||||
_sabotageTimerText = FindTMP(t, "SabotageTimer");
|
||||
_gameEndText = FindTMP(t, "GameEndText");
|
||||
_toastText = FindTMP(t, "Toast");
|
||||
_meetingHeader = FindTMP(t, "MeetingHeader");
|
||||
_meetingFallbackText = FindTMP(t, "MeetingPlayerList");
|
||||
_voteResultText = FindTMP(t, "VoteResult");
|
||||
_meetingScrollContent = FindTransform(t, "MeetingContent");
|
||||
|
||||
var actionGO = t.Find("ActionButton");
|
||||
if (actionGO != 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);
|
||||
_actionButton = actionGO.GetComponent<Button>();
|
||||
_actionButtonText = actionGO.GetComponentInChildren<TMP_Text>();
|
||||
}
|
||||
|
||||
_sabotagePanel = t.Find("SabotagePanel")?.gameObject;
|
||||
_meetingPanel = t.Find("MeetingPanel")?.gameObject;
|
||||
_gameEndPanel = t.Find("GameEndPanel")?.gameObject;
|
||||
_voteResultPanel = FindTransformGO(t, "VoteResultPanel");
|
||||
_toastGO = FindTransformGO(t, "Toast");
|
||||
|
||||
var retBtn = FindTransform(t, "ReturnToLobbyButton");
|
||||
if (retBtn != null) _returnToLobbyBtn = retBtn as RectTransform;
|
||||
|
||||
if (_meetingPanel) _meetingPanel.SetActive(false);
|
||||
if (_gameEndPanel) _gameEndPanel.SetActive(false);
|
||||
if (_voteResultPanel) _voteResultPanel.SetActive(false);
|
||||
if (_toastGO) _toastGO.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Main update (called every frame from GameManager.Update) ──────────
|
||||
// ── Update (called every frame from GameManager.Update) ───────────────
|
||||
|
||||
public void UpdateLobbyUI()
|
||||
{
|
||||
var state = _gameClient.CurrentLobbyState;
|
||||
if (state == null) return;
|
||||
var lobbyState = _gameClient.CurrentLobbyState;
|
||||
if (lobbyState == null) return;
|
||||
|
||||
// Update any LobbyDisplayUI listeners in the current scene
|
||||
if (_lobbyDirty)
|
||||
{
|
||||
_lobbyDirty = false;
|
||||
LobbyDisplayUI.RefreshAll(state);
|
||||
LobbyDisplayUI.RefreshAll(lobbyState);
|
||||
}
|
||||
|
||||
// Only do canvas switches if we are in Client.unity (canvases assigned)
|
||||
if (ClientGameScreen == null) return;
|
||||
|
||||
switch (state.Phase)
|
||||
switch (lobbyState.Phase)
|
||||
{
|
||||
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
|
||||
SetCanvases(false, false, false, true);
|
||||
break;
|
||||
}
|
||||
|
||||
TickToast();
|
||||
}
|
||||
|
||||
// ── Game HUD tick ─────────────────────────────────────────────────────
|
||||
|
||||
private void UpdateGameHUD()
|
||||
{
|
||||
if (_roleText != null) _roleText.text = _gameClient.MyRole?.ToString() ?? "";
|
||||
var s = _state;
|
||||
if (s == null) return;
|
||||
|
||||
// Task list
|
||||
// Role
|
||||
if (_roleText != null)
|
||||
{
|
||||
string ghostSuffix = s.IsDead ? " (GHOST)" : "";
|
||||
_roleText.text = $"{s.MyRole?.ToString() ?? "?"}{ghostSuffix}";
|
||||
_roleText.color = s.MyRole == PlayerRole.Impostor ? new Color(0.9f,0.2f,0.2f) : new Color(0.2f,0.8f,1f);
|
||||
}
|
||||
|
||||
// Task list with checkmarks
|
||||
if (_taskListText != null)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var t in _gameClient.MyTasks)
|
||||
sb.AppendLine(t.Name);
|
||||
foreach (var task in s.MyTasks)
|
||||
{
|
||||
bool done = s.MyCompletedTaskIds.Contains(task.TaskId);
|
||||
string mark = done ? "<color=#2DB84B>✓</color>" : "○";
|
||||
sb.AppendLine($"{mark} {task.Name}");
|
||||
}
|
||||
_taskListText.text = sb.ToString();
|
||||
}
|
||||
|
||||
// Kill cooldown (managed by GameManager_Tasks via Update)
|
||||
// Sabotage timer
|
||||
// Global task progress
|
||||
if (_taskProgressText != null && s.TotalRequired > 0)
|
||||
_taskProgressText.text = $"Tasks: {s.TotalCompleted}/{s.TotalRequired}";
|
||||
|
||||
// Kill cooldown
|
||||
if (_killCooldownText != null)
|
||||
{
|
||||
bool show = s.KillCooldownRemaining > 0;
|
||||
_killCooldownText.gameObject.SetActive(show);
|
||||
if (show) _killCooldownText.text = $"Kill: {Mathf.CeilToInt(s.KillCooldownRemaining)}s";
|
||||
}
|
||||
|
||||
// Sabotage banner
|
||||
if (_sabotageTimerActive && _sabotageTimerText != null)
|
||||
{
|
||||
double remaining = (_sabotageMeltdownDeadline - DateTime.UtcNow).TotalSeconds;
|
||||
_sabotageTimerText.text = remaining > 0 ? $"MELTDOWN: {remaining:F0}s" : "MELTDOWN!";
|
||||
_sabotageTimerText.text = remaining > 0 ? $"⚠ MELTDOWN: {remaining:F0}s" : "⚠ MELTDOWN!";
|
||||
}
|
||||
|
||||
// Keep meeting voted-indicator rows fresh each frame
|
||||
TickMeetingVoteIndicators();
|
||||
}
|
||||
|
||||
// ── Helpers called by Network handlers ────────────────────────────────
|
||||
// ── Kill cooldown helper (called from GameManager) ────────────────────
|
||||
|
||||
public void SetKillCooldownText(string text)
|
||||
{
|
||||
if (_killCooldownText != null)
|
||||
{
|
||||
_killCooldownText.text = text;
|
||||
_killCooldownText.gameObject.SetActive(!string.IsNullOrEmpty(text));
|
||||
}
|
||||
if (_killCooldownText == null) return;
|
||||
bool show = !string.IsNullOrEmpty(text);
|
||||
_killCooldownText.gameObject.SetActive(show);
|
||||
if (show) _killCooldownText.text = text;
|
||||
}
|
||||
|
||||
public void UpdateTaskProgress(int completed, int total)
|
||||
@@ -179,6 +219,8 @@ namespace Subsystems
|
||||
_taskProgressText.text = $"Tasks: {completed}/{total}";
|
||||
}
|
||||
|
||||
// ── Action button ─────────────────────────────────────────────────────
|
||||
|
||||
public void SetActionButton(string label, bool visible, UnityEngine.Events.UnityAction onClick = null)
|
||||
{
|
||||
if (_actionButton == null) return;
|
||||
@@ -191,15 +233,19 @@ namespace Subsystems
|
||||
}
|
||||
}
|
||||
|
||||
// ── Player state ──────────────────────────────────────────────────────
|
||||
|
||||
public void OnLocalPlayerDied()
|
||||
{
|
||||
_isDead = true;
|
||||
if (_roleText != null) _roleText.text = "GHOST";
|
||||
if (_state != null) _state.IsDead = true;
|
||||
}
|
||||
|
||||
// ── Meeting ───────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowMeetingAlert()
|
||||
{
|
||||
Debug.Log("Meeting called! Run to meeting point.");
|
||||
ShowToast("⚠ Meeting called! Head to the meeting point.");
|
||||
}
|
||||
|
||||
public void ShowMeetingPanel(List<PlayerInfo> players, MeetingStartedPayload payload)
|
||||
@@ -207,99 +253,217 @@ namespace Subsystems
|
||||
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!";
|
||||
if (_meetingHeader != null)
|
||||
_meetingHeader.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)
|
||||
if (_voteResultPanel) _voteResultPanel.SetActive(false);
|
||||
|
||||
BuildMeetingVoteRows(players);
|
||||
}
|
||||
|
||||
private void BuildMeetingVoteRows(List<PlayerInfo> players)
|
||||
{
|
||||
// Clear old rows
|
||||
foreach (var r in _voteRows) if (r) UnityEngine.Object.Destroy(r);
|
||||
_voteRows.Clear();
|
||||
|
||||
if (_meetingScrollContent == null || players == null)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var p in players)
|
||||
sb.AppendLine($"{p.DisplayName} [{p.State}]");
|
||||
playerList.text = sb.ToString();
|
||||
// Fall back to text list
|
||||
if (_meetingFallbackText != null)
|
||||
{
|
||||
_meetingFallbackText.gameObject.SetActive(true);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var p in players ?? new List<PlayerInfo>())
|
||||
sb.AppendLine($"{p.DisplayName} [{p.State}]");
|
||||
_meetingFallbackText.text = sb.ToString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Wire skip button if it exists
|
||||
var skipBtn = _meetingPanel.transform.Find("SkipButton")?.GetComponent<Button>();
|
||||
if (skipBtn != null)
|
||||
string myId = _gameClient.ClientUuid;
|
||||
bool canVote = !_isDead;
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
skipBtn.onClick.RemoveAllListeners();
|
||||
skipBtn.onClick.AddListener(() => GameManager.Instance?.CastVote(null));
|
||||
bool isMe = player.ClientUuid == myId;
|
||||
bool isAlive = player.State == PlayerState.Alive;
|
||||
var row = BuildVoteRow(player, isMe, isAlive, canVote && isAlive && !isMe);
|
||||
row.transform.SetParent(_meetingScrollContent, false);
|
||||
_voteRows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject BuildVoteRow(PlayerInfo player, bool isMe, bool isAlive, bool canVote)
|
||||
{
|
||||
const float ROW_H = 110f;
|
||||
var go = new GameObject($"VoteRow_{player.ClientUuid}");
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.sizeDelta = new Vector2(0, ROW_H);
|
||||
var le = go.AddComponent<LayoutElement>();
|
||||
le.minHeight = le.preferredHeight = ROW_H;
|
||||
|
||||
var bg = go.AddComponent<Image>();
|
||||
bg.color = isMe ? new Color(0.12f,0.18f,0.30f) : new Color(0.10f,0.12f,0.20f);
|
||||
|
||||
// Dead overlay
|
||||
if (!isAlive)
|
||||
{
|
||||
bg.color = new Color(0.08f,0.08f,0.10f,0.7f);
|
||||
}
|
||||
|
||||
// Name label
|
||||
var namRT = MakeChild("Name", rt);
|
||||
namRT.anchorMin = new Vector2(0,0); namRT.anchorMax = new Vector2(0.65f,1);
|
||||
namRT.offsetMin = new Vector2(16,6); namRT.offsetMax = new Vector2(0,-6);
|
||||
var namTmp = namRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
namTmp.text = (player.IsOwner ? "👑 " : "") + (player.DisplayName ?? "???");
|
||||
namTmp.fontSize = 36;
|
||||
namTmp.color = !isAlive ? Color.gray : (isMe ? Color.white : new Color(0.73f,0.8f,0.88f));
|
||||
namTmp.fontStyle = isMe ? FontStyles.Bold : FontStyles.Normal;
|
||||
namTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
|
||||
// Vote button
|
||||
var voteBtnRT = MakeChild("VoteBtn", rt);
|
||||
voteBtnRT.anchorMin = new Vector2(0.68f,0.10f); voteBtnRT.anchorMax = new Vector2(0.95f,0.90f);
|
||||
var voteBg = voteBtnRT.gameObject.AddComponent<Image>();
|
||||
voteBg.color = canVote ? new Color(0.2f,0.6f,1f) : new Color(0.2f,0.2f,0.2f,0.5f);
|
||||
var voteBtn = voteBtnRT.gameObject.AddComponent<Button>();
|
||||
voteBtn.targetGraphic = voteBg;
|
||||
voteBtn.interactable = canVote;
|
||||
string capturedId = player.ClientUuid;
|
||||
voteBtn.onClick.AddListener(() => GameManager.Instance?.CastVote(capturedId));
|
||||
var voteTxtRT = MakeChild("Txt", voteBtnRT);
|
||||
Stretch(voteTxtRT);
|
||||
var voteTmp = voteTxtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
voteTmp.text = isAlive ? "VOTE" : "DEAD";
|
||||
voteTmp.fontSize = 28;
|
||||
voteTmp.fontStyle = FontStyles.Bold;
|
||||
voteTmp.color = Color.white;
|
||||
voteTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Voted indicator (hidden by default; shown by TickMeetingVoteIndicators)
|
||||
var votedRT = MakeChild("VotedTick", rt);
|
||||
votedRT.anchorMin = new Vector2(0.95f,0.20f); votedRT.anchorMax = new Vector2(1f,0.80f);
|
||||
var vtTmp = votedRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
vtTmp.text = "✓"; vtTmp.fontSize = 34;
|
||||
vtTmp.color = new Color(0.18f,0.75f,0.30f); vtTmp.alignment = TextAlignmentOptions.Center;
|
||||
votedRT.gameObject.SetActive(false);
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
private void TickMeetingVoteIndicators()
|
||||
{
|
||||
var s = _state;
|
||||
if (s == null) return;
|
||||
foreach (var row in _voteRows)
|
||||
{
|
||||
if (row == null) continue;
|
||||
// Row name is "VoteRow_<uuid>"
|
||||
string uuid = row.name.Replace("VoteRow_", "");
|
||||
var tick = row.transform.Find("VotedTick")?.gameObject;
|
||||
if (tick != null)
|
||||
tick.SetActive(s.VotedPlayerIds.Contains(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendVoteInstruction()
|
||||
{
|
||||
var playerList = FindTMP(_meetingPanel?.transform, "MeetingPlayerList");
|
||||
if (playerList != null)
|
||||
playerList.text += "\n[Tap a name] then press VOTE — or press SKIP";
|
||||
// no-op – vote instructions are embedded in the row buttons
|
||||
}
|
||||
|
||||
public void ShowVoteResult(VotingClosedPayload payload, List<PlayerInfo> players)
|
||||
{
|
||||
if (_meetingPanel == null) return;
|
||||
|
||||
var resultText = FindTMP(_meetingPanel.transform, "VoteResult");
|
||||
if (resultText != null)
|
||||
if (_voteResultPanel != null) _voteResultPanel.SetActive(true);
|
||||
if (_voteResultText != null)
|
||||
{
|
||||
if (payload.WasTie)
|
||||
{
|
||||
resultText.text = "TIE — nobody ejected.";
|
||||
}
|
||||
_voteResultText.text = "⚖ TIE — nobody ejected.";
|
||||
else if (string.IsNullOrEmpty(payload.EjectedPlayerId))
|
||||
{
|
||||
resultText.text = "Skip — nobody ejected.";
|
||||
}
|
||||
_voteResultText.text = "Nobody ejected (skip).";
|
||||
else
|
||||
{
|
||||
var ejected = players?.Find(p => p.ClientUuid == payload.EjectedPlayerId);
|
||||
resultText.text = $"{ejected?.DisplayName ?? payload.EjectedPlayerId} ejected!";
|
||||
var ej = players?.Find(p => p.ClientUuid == payload.EjectedPlayerId);
|
||||
_voteResultText.text = $"🚪 {ej?.DisplayName ?? payload.EjectedPlayerId} ejected!";
|
||||
}
|
||||
}
|
||||
|
||||
// Close panel after 5 seconds — use coroutine via GameManager
|
||||
GameManager.Instance?.StartCoroutine(CloseMeetingPanelAfterDelay(5f));
|
||||
// Auto-close meeting panel after 5 s
|
||||
GameManager.Instance?.StartCoroutine(CloseMeetingAfterDelay(5f));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator CloseMeetingPanelAfterDelay(float delay)
|
||||
private System.Collections.IEnumerator CloseMeetingAfterDelay(float delay)
|
||||
{
|
||||
yield return new UnityEngine.WaitForSeconds(delay);
|
||||
if (_meetingPanel != null) _meetingPanel.SetActive(false);
|
||||
if (_meetingPanel) _meetingPanel.SetActive(false);
|
||||
if (_voteResultPanel) _voteResultPanel.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}";
|
||||
}
|
||||
// ── Sabotage ──────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowSabotageTimer(DateTime deadline)
|
||||
{
|
||||
_sabotageMeltdownDeadline = deadline;
|
||||
_sabotageTimerActive = true;
|
||||
if (_sabotageTimerText != null) _sabotageTimerText.gameObject.SetActive(true);
|
||||
_sabotageTimerActive = true;
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(true);
|
||||
if (_sabotageTimerText) _sabotageTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void HideSabotageTimer()
|
||||
{
|
||||
_sabotageTimerActive = false;
|
||||
if (_sabotageTimerText != null) _sabotageTimerText.gameObject.SetActive(false);
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(false);
|
||||
SetCommsBlackout(false);
|
||||
}
|
||||
|
||||
public void SetCommsBlackout(bool active)
|
||||
public void SetCommsBlackout(bool active) => _commsBlackout = active;
|
||||
|
||||
// ── Game end ──────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowGameEndPanel(GameEndedPayload payload, string myUuid)
|
||||
{
|
||||
_commsBlackout = active;
|
||||
if (_gameEndPanel) _gameEndPanel.SetActive(true);
|
||||
if (_gameEndText != null)
|
||||
{
|
||||
bool won = payload.Winners?.Contains(myUuid) ?? false;
|
||||
string title = won ? "<color=#FFB800>🏆 VICTORY</color>" : "<color=#C43232>💔 DEFEAT</color>";
|
||||
string faction = payload.WinningFaction == "Impostor" ? "Impostors win!" : "Crew wins!";
|
||||
_gameEndText.text = $"{title}\n{faction}\n<size=38>{payload.Reason}</size>";
|
||||
}
|
||||
|
||||
// Show "Return to Lobby" only for the host
|
||||
if (_returnToLobbyBtn != null)
|
||||
_returnToLobbyBtn.gameObject.SetActive(_gameClient.IsOwner);
|
||||
}
|
||||
|
||||
public bool IsCommsBlackout => _commsBlackout;
|
||||
public bool IsPlayerDead => _isDead;
|
||||
// ── Toast ─────────────────────────────────────────────────────────────
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────
|
||||
public void ShowToast(string message)
|
||||
{
|
||||
if (_state != null) { _state.ToastMessage = message; _state.ToastExpiry = UnityEngine.Time.time + 4f; }
|
||||
if (_toastGO == null) return;
|
||||
_toastGO.SetActive(true);
|
||||
if (_toastText != null) _toastText.text = message;
|
||||
}
|
||||
|
||||
private void TickToast()
|
||||
{
|
||||
var s = _state;
|
||||
if (_toastGO == null) return;
|
||||
|
||||
if (s != null && !string.IsNullOrEmpty(s.ToastMessage) && UnityEngine.Time.time < s.ToastExpiry)
|
||||
{
|
||||
_toastGO.SetActive(true);
|
||||
if (_toastText != null) _toastText.text = s.ToastMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
_toastGO.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Canvas switching ──────────────────────────────────────────────────
|
||||
|
||||
private void SetCanvases(bool createJoin, bool inLobby, bool loading, bool game)
|
||||
{
|
||||
@@ -314,14 +478,12 @@ namespace Subsystems
|
||||
if (ClientGameScreen) ClientGameScreen.gameObject.SetActive(game);
|
||||
}
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────
|
||||
|
||||
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.
|
||||
if (!canvas.enabled) canvas.enabled = true;
|
||||
var t = canvas.transform;
|
||||
if (t != null)
|
||||
{
|
||||
@@ -335,12 +497,34 @@ namespace Subsystems
|
||||
{
|
||||
if (root == null) return null;
|
||||
foreach (var tmp in root.GetComponentsInChildren<TMP_Text>(true))
|
||||
{
|
||||
if (tmp != null && tmp.name == name)
|
||||
return tmp;
|
||||
}
|
||||
if (tmp != null && tmp.name == name) return tmp;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Transform FindTransform(Transform root, string name)
|
||||
{
|
||||
if (root == null) return null;
|
||||
foreach (Transform t in root.GetComponentsInChildren<Transform>(true))
|
||||
if (t.name == name) return t;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GameObject FindTransformGO(Transform root, string name)
|
||||
=> FindTransform(root, name)?.gameObject;
|
||||
|
||||
private static RectTransform MakeChild(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
private static void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user