1645 lines
72 KiB
C#
1645 lines
72 KiB
C#
/*
|
|
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
|
|
║ ║
|
|
║ 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()
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Styl pro velké nadpisy</summary>
|
|
protected GUIStyle titleStyle;
|
|
|
|
/// <summary>Styl pro podnadpisy</summary>
|
|
protected GUIStyle subtitleStyle;
|
|
|
|
/// <summary>Styl pro běžný text</summary>
|
|
protected GUIStyle labelStyle;
|
|
|
|
/// <summary>Styl pro tlačítka</summary>
|
|
protected GUIStyle buttonStyle;
|
|
|
|
/// <summary>Styl pro velká tlačítka</summary>
|
|
protected GUIStyle bigButtonStyle;
|
|
|
|
/// <summary>Styl pro textová pole</summary>
|
|
protected GUIStyle textFieldStyle;
|
|
|
|
/// <summary>Styl pro boxy/panely</summary>
|
|
protected GUIStyle boxStyle;
|
|
|
|
/// <summary>Styl pro rich text</summary>
|
|
protected GUIStyle richTextStyle;
|
|
|
|
/// <summary>Styl pro notifikace</summary>
|
|
protected GUIStyle notificationStyle;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Vstupy pro formuláře
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Vstupní pole pro jméno hráče</summary>
|
|
protected string inputPlayerName = "";
|
|
|
|
/// <summary>Vstupní pole pro kód lobby</summary>
|
|
protected string inputJoinCode = "";
|
|
|
|
/// <summary>Vstupní pole pro heslo</summary>
|
|
protected string inputPassword = "";
|
|
|
|
/// <summary>Vstupní pole pro zeměpisnou šířku</summary>
|
|
protected string inputLatitude = "50.7735892";
|
|
|
|
/// <summary>Vstupní pole pro zeměpisnou délku</summary>
|
|
protected string inputLongitude = "15.0721653";
|
|
|
|
/// <summary>Vstupní pole pro poloměr</summary>
|
|
protected string inputRadius = "300";
|
|
|
|
/// <summary>Počet impostorů</summary>
|
|
protected int inputImpostorCount = 1;
|
|
|
|
/// <summary>Počet úkolů</summary>
|
|
protected int inputTaskCount = 5;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// UI stav
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Aktuální záložka v hlavním menu</summary>
|
|
protected int mainMenuTab = 0;
|
|
|
|
/// <summary>Scroll pozice pro seznam hráčů</summary>
|
|
protected Vector2 playerListScroll;
|
|
|
|
/// <summary>Scroll pozice pro statistiky</summary>
|
|
protected Vector2 statsScroll;
|
|
|
|
/// <summary>Škálovací faktor UI</summary>
|
|
protected float uiScale = 1f;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Loading stav
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Zpráva během načítání</summary>
|
|
protected string loadingMessage = "Načítání...";
|
|
|
|
/// <summary>Progress načítání (0-1)</summary>
|
|
protected float loadingProgress = 0f;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Meeting stav
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Aktuální meeting data</summary>
|
|
protected MeetingStartedPayload currentMeeting;
|
|
|
|
/// <summary>Kdo už hlasoval</summary>
|
|
protected Dictionary<string, bool> meetingVotes = new Dictionary<string, bool>();
|
|
|
|
/// <summary>Kdo dorazil na meeting</summary>
|
|
protected HashSet<string> arrivedAtMeeting = new HashSet<string>();
|
|
|
|
/// <summary>Dorazili jsme na meeting?</summary>
|
|
protected bool iArrivedAtMeeting = false;
|
|
|
|
/// <summary>Náš hlas (UUID nebo null pro skip)</summary>
|
|
protected string myVote;
|
|
|
|
/// <summary>Můžeme hlasovat?</summary>
|
|
protected bool canVote = false;
|
|
|
|
/// <summary>Můžeme hlasovat v tomto meetingu (jsme naživu)?</summary>
|
|
protected bool canVoteInMeeting = false;
|
|
|
|
/// <summary>Výsledky hlasování</summary>
|
|
protected VotingClosedPayload votingResults;
|
|
|
|
/// <summary>Zobrazit výsledky hlasování?</summary>
|
|
protected bool showVotingResults = false;
|
|
|
|
/// <summary>Čas konce zobrazení výsledků</summary>
|
|
protected float votingResultsEndTime;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Herní HUD stav
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Celkový počet dokončených úkolů (globálně pro všechny hráče)</summary>
|
|
protected int totalTasksCompleted = 0;
|
|
|
|
/// <summary>Celkový počet požadovaných úkolů (globálně)</summary>
|
|
protected int totalTasksRequired = 0;
|
|
|
|
/// <summary>ID mých splněných úkolů</summary>
|
|
protected HashSet<string> myCompletedTaskIds = new HashSet<string>();
|
|
|
|
/// <summary>Zobrazit sabotage panel?</summary>
|
|
protected bool showSabotagePanel = false;
|
|
|
|
#region ═══════════════════════════════════════════════════════════════════
|
|
// INICIALIZACE STYLŮ
|
|
// ════════════════════════════════════════════════════════════════════════
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Inicializace GUI stylů.
|
|
/// DŮLEŽITÉ: Musí se volat v Start() nebo později, ne v Awake()!
|
|
/// GUISkin není dostupný v Awake.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aplikace škálování UI pro různá rozlišení.
|
|
/// Základní rozlišení je 1920x1080.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Vykreslení hlavního menu.
|
|
/// Obsahuje záložky pro připojení, vytvoření lobby a statistiky.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Záložka pro připojení do existujícího lobby
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Záložka pro vytvoření nového lobby
|
|
/// </summary>
|
|
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!");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Záložka pro statistiky hráče
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obnoví všechny statistiky (player stats, leaderboard, health)
|
|
/// </summary>
|
|
private void RefreshAllStats()
|
|
{
|
|
FetchPlayerStats();
|
|
FetchLeaderboard(5);
|
|
CheckServerHealth();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pomocná metoda pro vykreslení řádku statistiky
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Vykreslení lobby obrazovky.
|
|
/// Zobrazuje seznam hráčů, nastavení a tlačítko start.
|
|
/// </summary>
|
|
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($"<color={nameColor}>{player.DisplayName}</color>", 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("<color=yellow>Potřeba minimálně 2 hráči</color>", 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
|
|
|
|
/// <summary>
|
|
/// Vykreslení loading obrazovky.
|
|
/// Zobrazuje progress bar během načítání mapových dat.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Vykreslení herního HUD.
|
|
/// Zobrazuje roli, úkoly, ovládání a stav hry.
|
|
/// </summary>
|
|
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($"<color={taskColor}>{checkmark}{task.Name}</color>", 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vykreslení panelu sabotáží
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vykreslení progress baru pro úkoly/opravy
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Vykreslení hlasovacího panelu.
|
|
/// Zobrazuje seznam hráčů a tlačítka pro hlasování.
|
|
/// </summary>
|
|
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($"<color={nameColor}>{player.DisplayName}</color>", 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
|
|
|
|
/// <summary>
|
|
/// Vykreslení obrazovky konce hry.
|
|
/// Zobrazuje vítěze a statistiky.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Vykreslení notifikací
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vykreslení chybové zprávy
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Získá text pro hlavní akční tlačítko
|
|
/// </summary>
|
|
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";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Zjistí, zda můžeme provést akci
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Zjistí, zda můžeme svolat meeting
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Zjistí, zda je kill na cooldownu
|
|
/// </summary>
|
|
private bool IsOnCooldown()
|
|
{
|
|
// TODO: Implementovat cooldown tracking
|
|
return false;
|
|
}
|
|
}
|