Zabiju je

This commit is contained in:
2026-04-26 13:30:33 +02:00
parent 208696487e
commit 700e6bfbfc
143 changed files with 11027 additions and 1298 deletions

View File

@@ -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;
}
}
}