/*
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║
║ UŽIVATELSKÉ ROZHRANÍ - UnityTestClient_UI.cs ║
║ ║
║ Tento soubor obsahuje veškeré UI pomocí Unity IMGUI: ║
║ • Hlavní menu (připojení, vytvoření/vstup do lobby, statistiky) ║
║ • Lobby obrazovka (nastavení, seznam hráčů, chat) ║
║ • Loading obrazovka (progress bar načítání mapových dat) ║
║ • Herní HUD (role, úkoly, minimap, sabotáž timer) ║
║ • Meeting panel (hlasování, diskuze) ║
║ • Konec hry (výsledky, statistiky) ║
║ ║
║ HERNÍ HUD OBSAHUJE: ║
║ • Panel role (nahoře) - vaše role, jméno, stav ║
║ • Panel úkolů (vpravo) - globální progress bar + seznam VAŠICH úkolů ║
║ - Zelený checkmark = VY jste dokončili tento úkol ║
║ - Progress bar = celkový pokrok VŠECH hráčů ║
║ • Sabotáž timer (při aktivní sabotáži) - zbývající čas ║
║ • Akční tlačítka (dole) - USE, KILL, SABOTAGE, REPORT ║
║ ║
║ POZNÁMKA PRO STUDENTY: ║
║ IMGUI je vhodné pro prototypování a debug, ale pro produkční hru ║
║ doporučujeme použít Unity UI (Canvas) nebo UI Toolkit, které nabízejí: ║
║ • Lepší výkon (batching, atlasy) ║
║ • WYSIWYG editor v Unity ║
║ • Lepší podporu animací a přechodů ║
║ • Snadnější lokalizaci a škálování ║
║ • Lepší podporu dotykového ovládání ║
║ ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
*/
//Až budou designeri mit nejaky screen
using UnityEngine;
using System;
using System.Collections.Generic;
using GeoSus.Client;
public partial class UnityTestClient
{
#region ═══════════════════════════════════════════════════════════════════
// UI PROMĚNNÉ
// ════════════════════════════════════════════════════════════════════════
#endregion
// ─────────────────────────────────────────────────────────────────────────
// GUI Styly - inicializují se v InitializeUIStyles()
// ─────────────────────────────────────────────────────────────────────────
/// Styl pro velké nadpisy
protected GUIStyle titleStyle;
/// Styl pro podnadpisy
protected GUIStyle subtitleStyle;
/// Styl pro běžný text
protected GUIStyle labelStyle;
/// Styl pro tlačítka
protected GUIStyle buttonStyle;
/// Styl pro velká tlačítka
protected GUIStyle bigButtonStyle;
/// Styl pro textová pole
protected GUIStyle textFieldStyle;
/// Styl pro boxy/panely
protected GUIStyle boxStyle;
/// Styl pro rich text
protected GUIStyle richTextStyle;
/// Styl pro notifikace
protected GUIStyle notificationStyle;
// ─────────────────────────────────────────────────────────────────────────
// Vstupy pro formuláře
// ─────────────────────────────────────────────────────────────────────────
/// Vstupní pole pro jméno hráče
protected string inputPlayerName = "";
/// Vstupní pole pro kód lobby
protected string inputJoinCode = "";
/// Vstupní pole pro heslo
protected string inputPassword = "";
/// Vstupní pole pro zeměpisnou šířku
protected string inputLatitude = "50.7735892";
/// Vstupní pole pro zeměpisnou délku
protected string inputLongitude = "15.0721653";
/// Vstupní pole pro poloměr
protected string inputRadius = "300";
/// Počet impostorů
protected int inputImpostorCount = 1;
/// Počet úkolů
protected int inputTaskCount = 5;
// ─────────────────────────────────────────────────────────────────────────
// UI stav
// ─────────────────────────────────────────────────────────────────────────
/// Aktuální záložka v hlavním menu
protected int mainMenuTab = 0;
/// Scroll pozice pro seznam hráčů
protected Vector2 playerListScroll;
/// Scroll pozice pro statistiky
protected Vector2 statsScroll;
/// Škálovací faktor UI
protected float uiScale = 1f;
// ─────────────────────────────────────────────────────────────────────────
// Loading stav
// ─────────────────────────────────────────────────────────────────────────
/// Zpráva během načítání
protected string loadingMessage = "Načítání...";
/// Progress načítání (0-1)
protected float loadingProgress = 0f;
// ─────────────────────────────────────────────────────────────────────────
// Meeting stav
// ─────────────────────────────────────────────────────────────────────────
/// Aktuální meeting data
protected MeetingStartedPayload currentMeeting;
/// Kdo už hlasoval
protected Dictionary meetingVotes = new Dictionary();
/// Kdo dorazil na meeting
protected HashSet arrivedAtMeeting = new HashSet();
/// Dorazili jsme na meeting?
protected bool iArrivedAtMeeting = false;
/// Náš hlas (UUID nebo null pro skip)
protected string myVote;
/// Můžeme hlasovat?
protected bool canVote = false;
/// Můžeme hlasovat v tomto meetingu (jsme naživu)?
protected bool canVoteInMeeting = false;
/// Výsledky hlasování
protected VotingClosedPayload votingResults;
/// Zobrazit výsledky hlasování?
protected bool showVotingResults = false;
/// Čas konce zobrazení výsledků
protected float votingResultsEndTime;
// ─────────────────────────────────────────────────────────────────────────
// Herní HUD stav
// ─────────────────────────────────────────────────────────────────────────
/// Celkový počet dokončených úkolů (globálně pro všechny hráče)
protected int totalTasksCompleted = 0;
/// Celkový počet požadovaných úkolů (globálně)
protected int totalTasksRequired = 0;
/// ID mých splněných úkolů
protected HashSet myCompletedTaskIds = new HashSet();
/// Zobrazit sabotage panel?
protected bool showSabotagePanel = false;
#region ═══════════════════════════════════════════════════════════════════
// INICIALIZACE STYLŮ
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Inicializace GUI stylů.
/// DŮLEŽITÉ: Musí se volat v Start() nebo později, ne v Awake()!
/// GUISkin není dostupný v Awake.
///
protected void InitializeUIStyles()
{
// Styl pro velké nadpisy
titleStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 32,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter
};
titleStyle.normal.textColor = Color.white;
// Styl pro podnadpisy
subtitleStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 20,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter
};
subtitleStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f);
// Styl pro běžný text
labelStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 16,
alignment = TextAnchor.MiddleLeft
};
labelStyle.normal.textColor = Color.white;
// Styl pro tlačítka
buttonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 16,
fontStyle = FontStyle.Bold
};
// Styl pro velká tlačítka
bigButtonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 20,
fontStyle = FontStyle.Bold,
fixedHeight = 50
};
// Styl pro textová pole
textFieldStyle = new GUIStyle(GUI.skin.textField)
{
fontSize = 16,
fixedHeight = 30
};
// Styl pro boxy
boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(10, 10, 10, 10)
};
// Styl pro rich text
richTextStyle = new GUIStyle(GUI.skin.label)
{
richText = true,
fontSize = 14
};
// Styl pro notifikace
notificationStyle = new GUIStyle(GUI.skin.box)
{
fontSize = 18,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter
};
notificationStyle.normal.textColor = Color.white;
// Načteme uložené jméno
inputPlayerName = displayName;
}
///
/// Aplikace škálování UI pro různá rozlišení.
/// Základní rozlišení je 1920x1080.
///
protected void ApplyUIScaling()
{
// Výpočet škálovacího faktoru
float baseWidth = 1920f;
float baseHeight = 1080f;
// Použijeme menší z obou faktorů pro zachování poměru stran
float scaleX = Screen.width / baseWidth;
float scaleY = Screen.height / baseHeight;
uiScale = Mathf.Min(scaleX, scaleY);
// Minimální škálování
uiScale = Mathf.Max(uiScale, 0.5f);
// Aplikace matice pro škálování
GUI.matrix = Matrix4x4.TRS(
Vector3.zero,
Quaternion.identity,
new Vector3(uiScale, uiScale, 1f)
);
}
#region ═══════════════════════════════════════════════════════════════════
// HLAVNÍ MENU
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Vykreslení hlavního menu.
/// Obsahuje záložky pro připojení, vytvoření lobby a statistiky.
///
protected void DrawMainMenu()
{
// Reinicializace stylů pokud je potřeba (po změně skinu)
if (titleStyle == null) InitializeUIStyles();
// Centrovaný panel
float panelWidth = 500;
float panelHeight = 600;
float panelX = (Screen.width / uiScale - panelWidth) / 2;
float panelY = (Screen.height / uiScale - panelHeight) / 2;
GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight));
// Pozadí
GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle);
// Použijeme try-catch pro ošetření změn stavu během renderování
try
{
// ═══════════════════════════════════════════════════════════════════
// NADPIS
// ═══════════════════════════════════════════════════════════════════
GUILayout.Space(20);
GUILayout.Label("GeoSus", titleStyle);
GUILayout.Label("GPS Multiplayer Game", subtitleStyle);
GUILayout.Space(20);
// ═══════════════════════════════════════════════════════════════════
// STAV PŘIPOJENÍ
// ═══════════════════════════════════════════════════════════════════
// Cache stav na začátku renderování pro konzistenci
bool isConnected = client != null && client.IsConnected;
if (isConnected)
{
GUI.color = Color.green;
GUILayout.Label("● Připojeno k serveru", labelStyle);
GUI.color = Color.white;
}
else if (isConnecting)
{
GUI.color = Color.yellow;
GUILayout.Label("● Připojuji...", labelStyle);
GUI.color = Color.white;
}
else
{
GUI.color = Color.red;
GUILayout.Label("● Nepřipojeno", labelStyle);
GUI.color = Color.white;
}
GUILayout.Space(10);
// ═══════════════════════════════════════════════════════════════════
// JMÉNO HRÁČE
// ═══════════════════════════════════════════════════════════════════
GUILayout.Label("Vaše jméno:", labelStyle);
inputPlayerName = GUILayout.TextField(inputPlayerName, 20, textFieldStyle);
// Uložení jména
if (inputPlayerName != displayName && !string.IsNullOrWhiteSpace(inputPlayerName))
{
displayName = inputPlayerName;
PlayerPrefs.SetString("PlayerName", displayName);
if (client != null) client.DisplayName = displayName;
}
GUILayout.Space(10);
// ═══════════════════════════════════════════════════════════════════
// PŘIPOJENÍ K SERVERU
// ═══════════════════════════════════════════════════════════════════
if (!isConnected)
{
GUI.enabled = !isConnecting;
if (GUILayout.Button("Připojit k serveru", bigButtonStyle))
{
ConnectToServer();
}
GUI.enabled = true;
}
else
{
// ═══════════════════════════════════════════════════════════════
// ZÁLOŽKY
// ═══════════════════════════════════════════════════════════════
string[] tabs = { "Připojit se", "Vytvořit hru", "Statistiky" };
mainMenuTab = GUILayout.Toolbar(mainMenuTab, tabs);
GUILayout.Space(10);
switch (mainMenuTab)
{
case 0:
DrawJoinLobbyTab();
break;
case 1:
DrawCreateLobbyTab();
break;
case 2:
DrawStatsTab();
break;
}
GUILayout.Space(20);
// Tlačítko odpojení
GUI.color = new Color(1f, 0.5f, 0.5f);
if (GUILayout.Button("Odpojit", buttonStyle))
{
DisconnectFromServer();
}
GUI.color = Color.white;
}
} // end try
catch (System.ArgumentException)
{
// Ignorujeme chyby při změně stavu GUI mezi Layout a Repaint
// Příští frame se vykreslí správně
}
GUILayout.EndArea();
}
///
/// Záložka pro připojení do existujícího lobby
///
private void DrawJoinLobbyTab()
{
GUILayout.Label("Kód lobby:", labelStyle);
// Vstup pro kód - automaticky uppercase
inputJoinCode = GUILayout.TextField(inputJoinCode.ToUpper(), 6, textFieldStyle);
GUILayout.Space(5);
GUILayout.Label("Heslo (volitelné):", labelStyle);
inputPassword = GUILayout.PasswordField(inputPassword, '*', textFieldStyle);
GUILayout.Space(20);
GUI.enabled = inputJoinCode.Length == 6;
if (GUILayout.Button("Připojit do lobby", bigButtonStyle))
{
JoinLobby(inputJoinCode, string.IsNullOrWhiteSpace(inputPassword) ? null : inputPassword);
}
GUI.enabled = true;
}
///
/// Záložka pro vytvoření nového lobby
///
private void DrawCreateLobbyTab()
{
// ─────────────────────────────────────────────────────────────────
// GPS souřadnice
// ─────────────────────────────────────────────────────────────────
GUILayout.Label("Střed herní oblasti (GPS):", labelStyle);
GUILayout.BeginHorizontal();
GUILayout.Label("Lat:", GUILayout.Width(40));
inputLatitude = GUILayout.TextField(inputLatitude, textFieldStyle);
GUILayout.Label("Lon:", GUILayout.Width(40));
inputLongitude = GUILayout.TextField(inputLongitude, textFieldStyle);
GUILayout.EndHorizontal();
GUILayout.Space(5);
// ─────────────────────────────────────────────────────────────────
// Poloměr
// ─────────────────────────────────────────────────────────────────
GUILayout.Label($"Poloměr herní oblasti: {inputRadius}m", labelStyle);
// Slider pro poloměr
float radius = 300f;
float.TryParse(inputRadius, out radius);
radius = GUILayout.HorizontalSlider(radius, 100f, 1000f);
inputRadius = Mathf.RoundToInt(radius).ToString();
GUILayout.Space(10);
// ─────────────────────────────────────────────────────────────────
// Počet impostorů
// ─────────────────────────────────────────────────────────────────
GUILayout.Label($"Počet impostorů: {inputImpostorCount}", labelStyle);
GUILayout.BeginHorizontal();
if (GUILayout.Button("-", GUILayout.Width(40))) inputImpostorCount = Mathf.Max(1, inputImpostorCount - 1);
GUILayout.HorizontalSlider(inputImpostorCount, 1, 3);
if (GUILayout.Button("+", GUILayout.Width(40))) inputImpostorCount = Mathf.Min(3, inputImpostorCount + 1);
GUILayout.EndHorizontal();
GUILayout.Space(5);
// ─────────────────────────────────────────────────────────────────
// Počet úkolů
// ─────────────────────────────────────────────────────────────────
GUILayout.Label($"Počet úkolů: {inputTaskCount}", labelStyle);
GUILayout.BeginHorizontal();
if (GUILayout.Button("-", GUILayout.Width(40))) inputTaskCount = Mathf.Max(3, inputTaskCount - 1);
GUILayout.HorizontalSlider(inputTaskCount, 3, 10);
if (GUILayout.Button("+", GUILayout.Width(40))) inputTaskCount = Mathf.Min(10, inputTaskCount + 1);
GUILayout.EndHorizontal();
GUILayout.Space(5);
// ─────────────────────────────────────────────────────────────────
// Heslo
// ─────────────────────────────────────────────────────────────────
GUILayout.Label("Heslo (volitelné):", labelStyle);
inputPassword = GUILayout.PasswordField(inputPassword, '*', textFieldStyle);
GUILayout.Space(20);
// ─────────────────────────────────────────────────────────────────
// Tlačítko vytvoření
// ─────────────────────────────────────────────────────────────────
if (GUILayout.Button("Vytvořit lobby", bigButtonStyle))
{
double lat, lon, rad;
if (double.TryParse(inputLatitude, out lat) &&
double.TryParse(inputLongitude, out lon) &&
double.TryParse(inputRadius, out rad))
{
Position center = new Position(lat, lon);
CreateLobby(center, rad, inputImpostorCount, inputTaskCount,
string.IsNullOrWhiteSpace(inputPassword) ? null : inputPassword);
}
else
{
ShowError("Neplatné souřadnice nebo poloměr!");
}
}
}
///
/// Záložka pro statistiky hráče
///
private void DrawStatsTab()
{
statsScroll = GUILayout.BeginScrollView(statsScroll, GUILayout.Height(350));
// ═══════════════════════════════════════════════════════════════
// SERVER HEALTH
// ═══════════════════════════════════════════════════════════════
GUILayout.Label("═══ SERVER STATUS ═══", labelStyle);
if (healthStatus == null)
{
GUILayout.Label("Načítám stav serveru...", labelStyle);
}
else
{
Color statusColor = healthStatus.status == "ok" ? Color.green : Color.red;
GUI.color = statusColor;
DrawStatRow("Status", healthStatus.status.ToUpper());
GUI.color = Color.white;
if (healthStatus.uptimeSeconds > 0)
{
var uptime = TimeSpan.FromSeconds(healthStatus.uptimeSeconds);
DrawStatRow("Uptime", $"{uptime.Hours:D2}:{uptime.Minutes:D2}:{uptime.Seconds:D2}");
}
DrawStatRow("Aktivní lobby", healthStatus.activeLobbies.ToString());
DrawStatRow("Hráčů online", healthStatus.connectedPlayers.ToString());
if (!string.IsNullOrEmpty(healthStatus.version))
DrawStatRow("Verze", healthStatus.version);
}
GUILayout.Space(10);
// ═══════════════════════════════════════════════════════════════
// MOJE STATISTIKY
// ═══════════════════════════════════════════════════════════════
GUILayout.Label("═══ MOJE STATISTIKY ═══", labelStyle);
if (playerStats == null)
{
GUILayout.Label("Načítám statistiky...", labelStyle);
}
else
{
DrawStatRow("Odehráno her", playerStats.totalGames.ToString());
DrawStatRow("Výher", playerStats.GamesWon.ToString());
DrawStatRow("Win rate", $"{(playerStats.WinRate * 100):F1}%");
DrawStatRow("Zabití", playerStats.totalKills.ToString());
DrawStatRow("Smrti", playerStats.totalDeaths.ToString());
DrawStatRow("K/D ratio", $"{playerStats.killDeathRatio:F2}");
DrawStatRow("Dokončené úkoly", playerStats.tasksCompleted.ToString());
DrawStatRow("Her jako impostor", playerStats.gamesAsImpostor.ToString());
DrawStatRow("Her jako crew", playerStats.gamesAsCrew.ToString());
}
GUILayout.Space(10);
// ═══════════════════════════════════════════════════════════════
// LEADERBOARD
// ═══════════════════════════════════════════════════════════════
GUILayout.Label("═══ LEADERBOARD (TOP 5) ═══", labelStyle);
if (leaderboard == null || leaderboard.Count == 0)
{
GUILayout.Label(isLoadingLeaderboard ? "Načítám leaderboard..." : "Žádní hráči v leaderboardu", labelStyle);
}
else
{
// Header
GUILayout.BeginHorizontal();
GUILayout.Label("#", labelStyle, GUILayout.Width(25));
GUILayout.Label("Hráč", labelStyle, GUILayout.Width(120));
GUILayout.Label("Výhry", labelStyle, GUILayout.Width(50));
GUILayout.Label("Win%", labelStyle, GUILayout.Width(50));
GUILayout.EndHorizontal();
// Entries
int rank = 1;
foreach (var entry in leaderboard)
{
GUILayout.BeginHorizontal();
// Barvy pro top 3
if (rank == 1) GUI.color = Color.yellow;
else if (rank == 2) GUI.color = new Color(0.8f, 0.8f, 0.8f);
else if (rank == 3) GUI.color = new Color(0.8f, 0.5f, 0.2f);
else GUI.color = Color.white;
GUILayout.Label($"{rank}.", labelStyle, GUILayout.Width(25));
GUILayout.Label(entry.displayName ?? "???", labelStyle, GUILayout.Width(120));
GUILayout.Label(entry.TotalWins.ToString(), labelStyle, GUILayout.Width(50));
GUILayout.Label($"{(entry.WinRate * 100):F0}%", labelStyle, GUILayout.Width(50));
GUI.color = Color.white;
GUILayout.EndHorizontal();
rank++;
}
}
GUILayout.EndScrollView();
// Refresh button
GUILayout.Space(5);
if (GUILayout.Button("Obnovit vše", buttonStyle))
{
RefreshAllStats();
}
}
///
/// Obnoví všechny statistiky (player stats, leaderboard, health)
///
private void RefreshAllStats()
{
FetchPlayerStats();
FetchLeaderboard(5);
CheckServerHealth();
}
///
/// Pomocná metoda pro vykreslení řádku statistiky
///
private void DrawStatRow(string label, string value)
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, labelStyle, GUILayout.Width(200));
GUILayout.Label(value, labelStyle);
GUILayout.EndHorizontal();
}
#region ═══════════════════════════════════════════════════════════════════
// LOBBY OBRAZOVKA
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Vykreslení lobby obrazovky.
/// Zobrazuje seznam hráčů, nastavení a tlačítko start.
///
protected void DrawLobbyScreen()
{
if (titleStyle == null) InitializeUIStyles();
float panelWidth = 600;
float panelHeight = 700;
float panelX = (Screen.width / uiScale - panelWidth) / 2;
float panelY = (Screen.height / uiScale - panelHeight) / 2;
GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight));
GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle);
// ═══════════════════════════════════════════════════════════════════
// NADPIS A KÓD
// ═══════════════════════════════════════════════════════════════════
GUILayout.Space(10);
GUILayout.Label("LOBBY", titleStyle);
if (client?.JoinCode != null)
{
GUILayout.Label($"Kód: {client.JoinCode}", subtitleStyle);
}
GUILayout.Space(10);
// ═══════════════════════════════════════════════════════════════════
// SEZNAM HRÁČŮ
// ═══════════════════════════════════════════════════════════════════
GUILayout.Label("Hráči:", labelStyle);
playerListScroll = GUILayout.BeginScrollView(playerListScroll, GUILayout.Height(200));
if (client?.CurrentLobbyState?.Players != null)
{
foreach (var player in client.CurrentLobbyState.Players)
{
GUILayout.BeginHorizontal(boxStyle);
// Ikona vlastníka
if (player.IsOwner)
{
GUILayout.Label("👑", GUILayout.Width(25));
}
else
{
GUILayout.Space(25);
}
// Jméno hráče
string nameColor = player.ClientUuid == clientUuid ? "yellow" : "white";
GUILayout.Label($"{player.DisplayName}", richTextStyle);
// Stav ready
if (player.IsReady)
{
GUI.color = Color.green;
GUILayout.Label("✓ Ready", GUILayout.Width(70));
GUI.color = Color.white;
}
GUILayout.EndHorizontal();
}
}
GUILayout.EndScrollView();
// ═══════════════════════════════════════════════════════════════════
// NASTAVENÍ LOBBY (pouze pro vlastníka)
// ═══════════════════════════════════════════════════════════════════
bool isOwner = client?.IsOwner ?? false;
GUILayout.Space(10);
GUILayout.Label("Nastavení hry:", labelStyle);
GUILayout.BeginVertical(boxStyle);
if (client?.CurrentLobbyState != null)
{
var state = client.CurrentLobbyState;
GUILayout.Label($"Střed: {state.PlayAreaCenter.Lat:F4}, {state.PlayAreaCenter.Lon:F4}");
GUILayout.Label($"Poloměr: {state.PlayAreaRadius}m");
GUILayout.Label($"Impostorů: {state.ImpostorCount}");
GUILayout.Label($"Heslo: {(state.HasPassword ? "Ano" : "Ne")}");
}
GUILayout.EndVertical();
// ═══════════════════════════════════════════════════════════════════
// TLAČÍTKA
// ═══════════════════════════════════════════════════════════════════
GUILayout.Space(20);
if (isOwner)
{
// Tlačítko START (pouze owner)
int playerCount = client?.CurrentLobbyState?.Players?.Count ?? 0;
GUI.enabled = playerCount >= 2; // Minimum 2 hráči
GUI.color = Color.green;
if (GUILayout.Button($"SPUSTIT HRU ({playerCount} hráčů)", bigButtonStyle))
{
StartGame();
}
GUI.color = Color.white;
GUI.enabled = true;
if (playerCount < 2)
{
GUILayout.Label("Potřeba minimálně 2 hráči", richTextStyle);
}
}
else
{
GUILayout.Label("Čekám na vlastníka lobby...", labelStyle);
}
GUILayout.Space(10);
// Tlačítko opuštění
GUI.color = new Color(1f, 0.5f, 0.5f);
if (GUILayout.Button("Opustit lobby", buttonStyle))
{
LeaveLobby();
}
GUI.color = Color.white;
GUILayout.EndArea();
}
#region ═══════════════════════════════════════════════════════════════════
// LOADING OBRAZOVKA
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Vykreslení loading obrazovky.
/// Zobrazuje progress bar během načítání mapových dat.
///
protected void DrawLoadingScreen()
{
if (titleStyle == null) InitializeUIStyles();
float panelWidth = 400;
float panelHeight = 200;
float panelX = (Screen.width / uiScale - panelWidth) / 2;
float panelY = (Screen.height / uiScale - panelHeight) / 2;
GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight));
GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle);
GUILayout.Space(30);
GUILayout.Label("Načítání hry", titleStyle);
GUILayout.Space(20);
// Progress bar
Rect progressRect = GUILayoutUtility.GetRect(panelWidth - 40, 30);
progressRect.x = 20;
GUI.Box(progressRect, "");
Rect fillRect = new Rect(progressRect.x + 2, progressRect.y + 2,
(progressRect.width - 4) * loadingProgress, progressRect.height - 4);
GUI.color = Color.green;
GUI.DrawTexture(fillRect, Texture2D.whiteTexture);
GUI.color = Color.white;
GUILayout.Space(10);
GUILayout.Label(loadingMessage, labelStyle);
GUILayout.EndArea();
}
#region ═══════════════════════════════════════════════════════════════════
// HERNÍ HUD
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Vykreslení herního HUD.
/// Zobrazuje roli, úkoly, ovládání a stav hry.
///
protected void DrawGameHUD()
{
if (titleStyle == null) InitializeUIStyles();
// ═══════════════════════════════════════════════════════════════════
// HORNÍ PANEL - Role a stav
// ═══════════════════════════════════════════════════════════════════
GUILayout.BeginArea(new Rect(10, 10, 300, 150));
GUI.Box(new Rect(0, 0, 300, 150), "", boxStyle);
// Role
if (client?.MyRole != null)
{
string roleText = client.MyRole == PlayerRole.Impostor ? "IMPOSTOR" : "CREWMATE";
Color roleColor = client.MyRole == PlayerRole.Impostor ? Color.red : Color.cyan;
GUI.color = roleColor;
GUILayout.Label(roleText, subtitleStyle);
GUI.color = Color.white;
}
// Stav (naživu/mrtvý)
if (client?.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var myState))
{
if (myState.State == PlayerState.Dead)
{
GUI.color = Color.gray;
GUILayout.Label("💀 MRTVÝ (duch)", labelStyle);
GUI.color = Color.white;
}
}
// Sabotáž varování
if (currentSabotage != null)
{
GUI.color = Color.red;
string sabText = currentSabotage.Type == SabotageType.CriticalMeltdown
? "⚠ MELTDOWN!" : "⚠ COMMS DOWN!";
GUILayout.Label(sabText, labelStyle);
if (currentSabotage.Deadline.HasValue)
{
float remaining = (float)(currentSabotage.Deadline.Value - DateTime.UtcNow).TotalSeconds;
GUILayout.Label($"Zbývá: {remaining:F0}s", labelStyle);
}
GUI.color = Color.white;
}
// Ping
GUILayout.Label($"Ping: {client?.Ping ?? 0}ms", labelStyle);
GUILayout.EndArea();
// ═══════════════════════════════════════════════════════════════════
// PRAVÝ PANEL - Úkoly
// ═══════════════════════════════════════════════════════════════════
float rightPanelX = Screen.width / uiScale - 260;
GUILayout.BeginArea(new Rect(rightPanelX, 10, 250, 400));
GUI.Box(new Rect(0, 0, 250, 400), "", boxStyle);
GUILayout.Label("ÚKOLY", subtitleStyle);
// Progress bar úkolů
GUILayout.Label($"Dokončeno: {totalTasksCompleted}/{totalTasksRequired}");
Rect taskProgressRect = GUILayoutUtility.GetRect(230, 20);
GUI.Box(taskProgressRect, "");
if (totalTasksRequired > 0)
{
float taskProgress = (float)totalTasksCompleted / totalTasksRequired;
Rect taskFillRect = new Rect(taskProgressRect.x + 2, taskProgressRect.y + 2,
(taskProgressRect.width - 4) * taskProgress, taskProgressRect.height - 4);
GUI.color = Color.green;
GUI.DrawTexture(taskFillRect, Texture2D.whiteTexture);
GUI.color = Color.white;
}
GUILayout.Space(10);
// Seznam úkolů
if (client?.MyTasks != null)
{
foreach (var task in client.MyTasks)
{
bool completed = myCompletedTaskIds.Contains(task.TaskId);
string taskColor = completed ? "green" : "white";
string checkmark = completed ? "✓ " : "○ ";
GUILayout.Label($"{checkmark}{task.Name}", richTextStyle);
}
}
GUILayout.EndArea();
// ═══════════════════════════════════════════════════════════════════
// SPODNÍ PANEL - Akce
// ═══════════════════════════════════════════════════════════════════
float bottomPanelY = Screen.height / uiScale - 100;
GUILayout.BeginArea(new Rect(10, bottomPanelY, Screen.width / uiScale - 20, 90));
GUILayout.BeginHorizontal();
// Tlačítko USE (úkoly, reporty)
GUI.enabled = CanPerformAction();
if (GUILayout.Button(GetActionButtonText(), bigButtonStyle, GUILayout.Width(150)))
{
PerformPrimaryAction();
}
GUI.enabled = true;
GUILayout.Space(20);
// Tlačítko KILL (pouze impostor)
if (client?.MyRole == PlayerRole.Impostor)
{
string nearbyTarget = client.FindNearbyPlayer(5.0, true);
GUI.enabled = nearbyTarget != null && !IsOnCooldown();
GUI.color = Color.red;
if (GUILayout.Button("🔪 KILL", bigButtonStyle, GUILayout.Width(120)))
{
if (nearbyTarget != null)
{
AttemptKill(nearbyTarget);
}
}
GUI.color = Color.white;
GUI.enabled = true;
GUILayout.Space(10);
// Tlačítko SABOTAGE
GUI.enabled = currentSabotage == null;
if (GUILayout.Button("⚠ SABOTAGE", bigButtonStyle, GUILayout.Width(150)))
{
showSabotagePanel = !showSabotagePanel;
}
GUI.enabled = true;
}
GUILayout.FlexibleSpace();
// Tlačítko MEETING
GUI.enabled = CanCallMeeting();
if (GUILayout.Button("🔔 MEETING", bigButtonStyle, GUILayout.Width(150)))
{
CallEmergencyMeeting();
}
GUI.enabled = true;
GUILayout.EndHorizontal();
GUILayout.EndArea();
// ═══════════════════════════════════════════════════════════════════
// SABOTAGE PANEL (popup)
// ═══════════════════════════════════════════════════════════════════
if (showSabotagePanel && client?.MyRole == PlayerRole.Impostor)
{
DrawSabotagePanel();
}
// ═══════════════════════════════════════════════════════════════════
// PROGRESS BAR OPRAVY
// ═══════════════════════════════════════════════════════════════════
if (isRepairing)
{
DrawProgressBar();
}
}
///
/// Vykreslení panelu sabotáží
///
private void DrawSabotagePanel()
{
float panelWidth = 200;
float panelHeight = 120;
float panelX = (Screen.width / uiScale - panelWidth) / 2;
float panelY = Screen.height / uiScale - 220;
GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight));
GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle);
GUILayout.Label("Sabotáž", subtitleStyle);
if (GUILayout.Button("📡 Comms Blackout", buttonStyle))
{
StartSabotage(SabotageType.CommsBlackout);
showSabotagePanel = false;
}
if (GUILayout.Button("☢ Critical Meltdown", buttonStyle))
{
StartSabotage(SabotageType.CriticalMeltdown);
showSabotagePanel = false;
}
if (GUILayout.Button("Zavřít", buttonStyle))
{
showSabotagePanel = false;
}
GUILayout.EndArea();
}
///
/// Vykreslení progress baru pro úkoly/opravy
///
private void DrawProgressBar()
{
float barWidth = 300;
float barHeight = 30;
float barX = (Screen.width / uiScale - barWidth) / 2;
float barY = Screen.height / uiScale / 2;
Rect bgRect = new Rect(barX - 10, barY - 10, barWidth + 20, barHeight + 40);
GUI.Box(bgRect, "", boxStyle);
GUILayout.BeginArea(new Rect(barX, barY, barWidth, barHeight + 20));
string label = "Opravuji...";
GUILayout.Label(label, labelStyle);
Rect progressRect = GUILayoutUtility.GetRect(barWidth, barHeight);
GUI.Box(progressRect, "");
Rect fillRect = new Rect(progressRect.x + 2, progressRect.y + 2,
(progressRect.width - 4) * repairProgress, progressRect.height - 4);
GUI.color = Color.yellow;
GUI.DrawTexture(fillRect, Texture2D.whiteTexture);
GUI.color = Color.white;
GUILayout.EndArea();
}
#region ═══════════════════════════════════════════════════════════════════
// MEETING PANEL
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Vykreslení hlasovacího panelu.
/// Zobrazuje seznam hráčů a tlačítka pro hlasování.
///
protected void DrawMeetingPanel()
{
// Kontrola, zda je meeting aktivní nebo zobrazujeme výsledky
if (currentMeeting == null && !showVotingResults) return;
// Kontrola herní fáze (povolíme i Playing když zobrazujeme výsledky)
var phase = client?.CurrentLobbyState?.Phase;
bool isInMeeting = phase == GamePhase.Meeting || phase == GamePhase.Voting;
if (!isInMeeting && !showVotingResults) return;
float panelWidth = 500;
float panelHeight = 600;
float panelX = (Screen.width / uiScale - panelWidth) / 2;
float panelY = (Screen.height / uiScale - panelHeight) / 2;
GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight));
GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle);
// ═══════════════════════════════════════════════════════════════════
// NADPIS
// ═══════════════════════════════════════════════════════════════════
GUILayout.Space(10);
// Pokud zobrazujeme výsledky, jiný nadpis
if (showVotingResults)
{
GUI.color = Color.yellow;
GUILayout.Label("VÝSLEDKY HLASOVÁNÍ", titleStyle);
GUI.color = Color.white;
}
else if (currentMeeting != null)
{
string meetingType = currentMeeting.Type == MeetingType.BodyReport
? "TĚLO NALEZENO!" : "EMERGENCY MEETING!";
GUI.color = Color.red;
GUILayout.Label(meetingType, titleStyle);
GUI.color = Color.white;
}
// ═══════════════════════════════════════════════════════════════════
// ČASOVAČ A FÁZE MEETINGU
// ═══════════════════════════════════════════════════════════════════
if (currentMeeting != null && !showVotingResults)
{
DateTime now = DateTime.UtcNow;
bool isArrival = now < currentMeeting.ArrivalDeadline;
bool isDiscussion = !isArrival && currentMeeting.DiscussionEndTime.HasValue && now < currentMeeting.DiscussionEndTime.Value;
bool isVoting = !isArrival && !isDiscussion && now < currentMeeting.VotingEndTime;
// Fáze 1: Příchod na meeting
if (isArrival)
{
float remaining = (float)(currentMeeting.ArrivalDeadline - now).TotalSeconds;
if (!iArrivedAtMeeting)
{
GUI.color = Color.yellow;
GUILayout.Label($"⚠ BĚŽ NA MEETING! ({remaining:F0}s)", subtitleStyle);
GUI.color = Color.white;
GUILayout.Label("Pokud nedorazíš včas, nebudeš moci hlasovat!", labelStyle);
}
else
{
GUI.color = Color.green;
GUILayout.Label($"✓ Dorazil/a jsi! Čekání: {remaining:F0}s", subtitleStyle);
GUI.color = Color.white;
}
// Zobraz počet dorazivších
int arrivedCount = arrivedAtMeeting.Count;
int totalAlive = 0;
if (client?.CurrentLobbyState?.Players != null)
{
foreach (var p in client.CurrentLobbyState.Players)
{
if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(p.ClientUuid, out var info))
{
if (info.State == PlayerState.Alive) totalAlive++;
}
else
{
totalAlive++;
}
}
}
GUILayout.Label($"Dorazilo: {arrivedCount}/{totalAlive}", labelStyle);
canVote = false;
}
// Fáze 2: Diskuze
else if (isDiscussion && currentMeeting.DiscussionEndTime.HasValue)
{
float remaining = (float)(currentMeeting.DiscussionEndTime.Value - now).TotalSeconds;
GUILayout.Label($"Diskuze: {remaining:F0}s", subtitleStyle);
canVote = false;
}
// Fáze 3: Hlasování
else if (isVoting)
{
float remaining = (float)(currentMeeting.VotingEndTime - now).TotalSeconds;
GUILayout.Label($"Hlasování: {remaining:F0}s", subtitleStyle);
// Můžeme hlasovat pouze pokud jsme dorazili a jsme naživu
if (!iArrivedAtMeeting)
{
GUI.color = Color.red;
GUILayout.Label("❌ Nedorazil/a jsi včas - nemůžeš hlasovat!", labelStyle);
GUI.color = Color.white;
canVote = false;
}
else
{
canVote = canVoteInMeeting && myVote == null;
}
}
else
{
GUILayout.Label("Čekám na výsledky...", subtitleStyle);
canVote = false;
}
}
GUILayout.Space(10);
// ═══════════════════════════════════════════════════════════════════
// VÝSLEDKY HLASOVÁNÍ
// ═══════════════════════════════════════════════════════════════════
if (showVotingResults && votingResults != null)
{
GUILayout.BeginVertical(boxStyle);
GUI.color = Color.yellow;
GUILayout.Label("═══ VÝSLEDKY HLASOVÁNÍ ═══", subtitleStyle);
GUI.color = Color.white;
GUILayout.Space(5);
if (votingResults.VoteCounts != null)
{
foreach (var kvp in votingResults.VoteCounts)
{
// Server používá "__SKIP__" pro skip vote
string name = (kvp.Key == "__SKIP__" || kvp.Key == "skip")
? "⏭ Přeskočit"
: $"👤 {GetPlayerName(kvp.Key)}";
// Zvýrazníme vyhozeného hráče
if (kvp.Key == votingResults.EjectedPlayerId)
{
GUI.color = Color.red;
GUILayout.Label($"🚪 {name}: {kvp.Value} hlasů", labelStyle);
GUI.color = Color.white;
}
else
{
GUILayout.Label($" {name}: {kvp.Value} hlasů", labelStyle);
}
}
}
GUILayout.Space(10);
// Výsledek hlasování
if (votingResults.EjectedPlayerId != null)
{
string ejectedName = GetPlayerName(votingResults.EjectedPlayerId);
GUI.color = Color.red;
GUILayout.Label($"🚪 {ejectedName} byl/a vyhozen/a!", subtitleStyle);
GUI.color = Color.white;
}
else if (votingResults.WasTie)
{
GUI.color = Color.yellow;
GUILayout.Label("⚖ Remíza - nikdo nebyl vyhozen", subtitleStyle);
GUI.color = Color.white;
}
else
{
GUI.color = Color.gray;
GUILayout.Label("✗ Nikdo nebyl vyhozen", subtitleStyle);
GUI.color = Color.white;
}
GUILayout.EndVertical();
GUILayout.Space(10);
if (Time.time > votingResultsEndTime)
{
showVotingResults = false;
currentMeeting = null;
}
}
// ═══════════════════════════════════════════════════════════════════
// SEZNAM HRÁČŮ PRO HLASOVÁNÍ
// ═══════════════════════════════════════════════════════════════════
if (!showVotingResults)
{
playerListScroll = GUILayout.BeginScrollView(playerListScroll, GUILayout.Height(350));
if (client?.CurrentLobbyState?.Players != null)
{
foreach (var player in client.CurrentLobbyState.Players)
{
// Získáme stav hráče - bezpečný přístup
bool isAlive = true;
if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(player.ClientUuid, out var pInfo))
{
isAlive = pInfo.State == PlayerState.Alive;
}
GUILayout.BeginHorizontal(boxStyle);
// Ikona stavu
if (!isAlive)
{
GUI.color = Color.gray;
GUILayout.Label("💀", GUILayout.Width(25));
}
else
{
GUILayout.Label("👤", GUILayout.Width(25));
}
// Jméno
string nameColor = player.ClientUuid == clientUuid ? "yellow" : (isAlive ? "white" : "gray");
GUILayout.Label($"{player.DisplayName}", richTextStyle, GUILayout.Width(150));
// Indikátor hlasování
if (meetingVotes.ContainsKey(player.ClientUuid))
{
GUILayout.Label("✓ Hlasoval", GUILayout.Width(80));
}
// Tlačítko hlasování
GUI.enabled = canVote && isAlive && player.ClientUuid != clientUuid;
if (GUILayout.Button("Hlasovat", GUILayout.Width(80)))
{
CastVote(player.ClientUuid);
}
GUI.enabled = true;
GUI.color = Color.white;
GUILayout.EndHorizontal();
}
}
GUILayout.EndScrollView();
// ═══════════════════════════════════════════════════════════════
// TLAČÍTKO SKIP
// ═══════════════════════════════════════════════════════════════
GUILayout.Space(10);
GUI.enabled = canVote;
GUI.color = Color.gray;
if (GUILayout.Button("⏭ Přeskočit hlasování", bigButtonStyle))
{
CastVote(null); // null = skip
}
GUI.color = Color.white;
GUI.enabled = true;
}
GUILayout.EndArea();
}
#region ═══════════════════════════════════════════════════════════════════
// KONEC HRY
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Vykreslení obrazovky konce hry.
/// Zobrazuje vítěze a statistiky.
///
protected void DrawGameEndScreen()
{
if (titleStyle == null) InitializeUIStyles();
float panelWidth = 500;
float panelHeight = 400;
float panelX = (Screen.width / uiScale - panelWidth) / 2;
float panelY = (Screen.height / uiScale - panelHeight) / 2;
GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight));
GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle);
GUILayout.Space(20);
// ═══════════════════════════════════════════════════════════════════
// VÍTĚZ
// ═══════════════════════════════════════════════════════════════════
if (gameEndData != null)
{
// Určíme, zda jsme vyhráli
bool weWon = gameEndData.Winners.Contains(clientUuid);
if (weWon)
{
GUI.color = Color.green;
GUILayout.Label("🏆 VÍTĚZSTVÍ!", titleStyle);
}
else
{
GUI.color = Color.red;
GUILayout.Label("💔 PROHRA", titleStyle);
}
GUI.color = Color.white;
GUILayout.Space(20);
// Vítězná frakce
string faction = gameEndData.WinningFaction == "Impostor" ? "Impostoři" : "Posádka";
GUILayout.Label($"Vyhráli: {faction}", subtitleStyle);
GUILayout.Space(10);
// Důvod
GUILayout.Label($"Důvod: {gameEndData.Reason}", labelStyle);
GUILayout.Space(20);
// Seznam vítězů
GUILayout.Label("Vítězové:", labelStyle);
foreach (var winnerId in gameEndData.Winners)
{
string name = GetPlayerName(winnerId);
GUILayout.Label($" • {name}", labelStyle);
}
}
GUILayout.Space(30);
// ═══════════════════════════════════════════════════════════════════
// TLAČÍTKA
// ═══════════════════════════════════════════════════════════════════
// Pouze owner může restartovat hru
bool isOwner = client?.IsOwner ?? false;
if (isOwner)
{
if (GUILayout.Button("🔄 Nová hra (zpět do lobby)", bigButtonStyle))
{
// Pošleme serveru požadavek na návrat do lobby
client?.ReturnToLobby();
}
}
else
{
GUI.enabled = false;
GUILayout.Button("Čekám na hostitele...", bigButtonStyle);
GUI.enabled = true;
}
GUILayout.Space(10);
if (GUILayout.Button("Hlavní menu", buttonStyle))
{
LeaveLobby();
gameEndData = null;
}
GUILayout.EndArea();
}
#region ═══════════════════════════════════════════════════════════════════
// NOTIFIKACE A CHYBY
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Vykreslení notifikací
///
protected void DrawNotifications()
{
if (currentNotification.message == null) return;
float notifWidth = 400;
float notifHeight = 60;
float notifX = (Screen.width / uiScale - notifWidth) / 2;
float notifY = 50;
// Fade efekt
float remaining = notificationEndTime - Time.time;
float alpha = Mathf.Clamp01(remaining);
Color bgColor = currentNotification.color;
bgColor.a = alpha * 0.9f;
GUI.color = bgColor;
GUI.Box(new Rect(notifX, notifY, notifWidth, notifHeight), "", notificationStyle);
GUI.color = new Color(1, 1, 1, alpha);
GUI.Label(new Rect(notifX, notifY, notifWidth, notifHeight),
$"{currentNotification.icon} {currentNotification.message}", notificationStyle);
GUI.color = Color.white;
}
///
/// Vykreslení chybové zprávy
///
protected void DrawErrorMessage()
{
if (string.IsNullOrEmpty(errorMessage) || Time.time > errorMessageEndTime)
{
errorMessage = null;
return;
}
float errorWidth = 500;
float errorHeight = 40;
float errorX = (Screen.width / uiScale - errorWidth) / 2;
float errorY = Screen.height / uiScale - 60;
GUI.color = new Color(0.8f, 0.2f, 0.2f, 0.9f);
GUI.Box(new Rect(errorX, errorY, errorWidth, errorHeight), "", notificationStyle);
GUI.color = Color.white;
GUI.Label(new Rect(errorX, errorY, errorWidth, errorHeight),
$"⚠ {errorMessage}", notificationStyle);
}
#region ═══════════════════════════════════════════════════════════════════
// POMOCNÉ UI METODY
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Získá text pro hlavní akční tlačítko
///
private string GetActionButtonText()
{
// Kontrola těla poblíž
var body = client?.FindNearbyBody(5.0);
if (body != null) return "🚨 REPORT";
// Kontrola úkolu poblíž
var task = client?.FindNearbyTask(5.0);
if (task != null) return "✋ USE";
// Kontrola opravné stanice poblíž
if (currentSabotage != null)
{
var station = FindNearbyRepairStation(5.0);
if (station != null) return "🔧 REPAIR";
}
return "USE";
}
///
/// Zjistí, zda můžeme provést akci
///
private bool CanPerformAction()
{
if (client == null) return false;
// Zjistíme, jestli jsme duch
bool isGhost = false;
if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var playerInfo))
{
isGhost = playerInfo.State != PlayerState.Alive;
}
// Duchové NEMOHOU reportovat těla ani volat meeting
// Ale MOHOU plnit úkoly!
if (!isGhost && client.FindNearbyBody(5.0) != null) return true;
// Kontrola úkolu - duchové i živí crew mohou dělat úkoly
if (client.FindNearbyTask(5.0) != null && client.MyRole == PlayerRole.Crew) return true;
// Kontrola opravné stanice - pouze živí mohou opravovat
if (!isGhost && currentSabotage != null && FindNearbyRepairStation(5.0) != null) return true;
return false;
}
///
/// Zjistí, zda můžeme svolat meeting
///
private bool CanCallMeeting()
{
if (client == null) return false;
if (currentSabotage?.Type == SabotageType.CommsBlackout) return false;
// Kontrola stavu hráče - bezpečný přístup
if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var playerInfo))
{
if (playerInfo.State != PlayerState.Alive)
return false;
}
return true;
}
///
/// Zjistí, zda je kill na cooldownu
///
private bool IsOnCooldown()
{
// TODO: Implementovat cooldown tracking
return false;
}
}