/*
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║
║ SÍŤOVÁ KOMUNIKACE - UnityTestClient_Network.cs ║
║ ║
║ Tento soubor obsahuje veškerou síťovou logiku: ║
║ • Připojení k serveru (TCP s AES-256 šifrováním) ║
║ • Zpracování příchozích zpráv a událostí ║
║ • Odesílání herních akcí ║
║ • Lobby management ║
║ ║
║ KOMUNIKAČNÍ PROTOKOL: ║
║ 1. Handshake: ClientHello → ServerHello (RSA klíč) → KeyExchange (AES) → KeyExchangeAck ║
║ 2. Po handshake: Veškerá komunikace je šifrovaná AES-256 ║
║ 3. Formát zpráv: [4 bajty délka] + [JSON payload] ║
║ ║
║ DŮLEŽITÉ ZPRÁVY (klient → server): ║
║ • CreateLobby/JoinLobby - vytvoření/vstup do lobby ║
║ • StartGame - spuštění hry (pouze owner) ║
║ • UpdatePosition - GPS pozice hráče (každých 100ms) ║
║ • CompleteTask - dokončení úkolu (server validuje pozici!) ║
║ • KillAttempt - pokus o zabití (pouze impostor) ║
║ • ReportBody - report těla (svolá meeting) ║
║ • CallEmergencyMeeting - emergency button ║
║ • CastVote - hlasování ║
║ • StartSabotage - spuštění sabotáže (pouze impostor) ║
║ • ActivateRepairStation - oprava sabotáže ║
║ ║
║ DŮLEŽITÉ UDÁLOSTI (server → klient): ║
║ • LobbyCreated/LobbyJoined - úspěšný vstup do lobby ║
║ • GameStarted - hra začala ║
║ • RoleAssigned - přiřazení role (Crew/Impostor) a seznamu úkolů ║
║ • TaskCompleted - úkol dokončen (s globálním progress) ║
║ • PlayerKilled - hráč byl zabit ║
║ • MeetingStarted - začátek meetingu ║
║ • VotingClosed - výsledky hlasování ║
║ • SabotageStarted - sabotáž spuštěna (s lokacemi repair stations) ║
║ • SabotageRepaired - sabotáž opravena ║
║ • GameEnded - konec hry (s výsledky) ║
║ ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
*/
using UnityEngine;
using System;
using System.Threading.Tasks;
using GeoSus.Client;
public partial class UnityTestClient
{
#region ═══════════════════════════════════════════════════════════════════
// SÍŤOVÉ PROMĚNNÉ
// ════════════════════════════════════════════════════════════════════════
#endregion
///
/// Příznak, zda probíhá připojování k serveru
///
protected bool isConnecting = false;
///
/// Časovač pro pravidelné odesílání pozice (každých 100ms)
///
protected float lastPositionUpdateTime = 0f;
///
/// Interval odesílání pozice v sekundách
///
protected const float POSITION_UPDATE_INTERVAL = 0.1f;
///
/// Časovač pro ping
///
protected float lastPingTime = 0f;
///
/// Interval pingu v sekundách
///
protected const float PING_INTERVAL = 5f;
///
/// Aktuální pozice hráče (GPS)
///
protected Position playerPosition;
///
/// ID opravné stanice, kterou právě opravujeme
///
protected string currentRepairStationId;
///
/// Poslední chybová zpráva z ACK - pro prevenci spamu
///
private string lastAckError = null;
///
/// Čas posledního zobrazení chyby z ACK
///
private float lastAckErrorTime = 0f;
#region ═══════════════════════════════════════════════════════════════════
// PŘIPOJENÍ K SERVERU
// ════════════════════════════════════════════════════════════════════════
// Připojení je asynchronní operace. Používáme async/await pattern.
// Unity vyžaduje volat síťové operace mimo hlavní vlákno,
// ale GameClient to řeší interně pomocí EventDispatcher.
#endregion
///
/// Připojení k serveru.
/// Tato metoda vytvoří novou instanci GameClient a pokusí se připojit.
///
/// DŮLEŽITÉ: Po úspěšném připojení se automaticky registrují event handlery
/// pro příjem zpráv ze serveru.
///
protected async void ConnectToServer()
{
// Ochrana proti vícenásobnému připojení
if (isConnecting || (client != null && client.IsConnected))
{
ShowError("Již se připojujete nebo jste připojeni!");
return;
}
isConnecting = true;
Debug.Log($"[GeoSus] Připojuji k serveru {serverHost}:{serverPort}...");
try
{
// ═══════════════════════════════════════════════════════════════
// KROK 1: Vytvoření instance GameClient
// ═══════════════════════════════════════════════════════════════
// GameClient je hlavní třída z ClientSDK pro komunikaci se serverem.
// Vyžaduje unikátní UUID klienta a zobrazované jméno.
client = new GameClient(clientUuid, displayName);
// ═══════════════════════════════════════════════════════════════
// KROK 2: Registrace event handlerů
// ═══════════════════════════════════════════════════════════════
// Události jsou volány na hlavním vlákně díky EventDispatcher.
// Registrujeme je PŘED připojením, abychom neminuli žádné události.
RegisterEventHandlers();
// ═══════════════════════════════════════════════════════════════
// KROK 3: Připojení k serveru
// ═══════════════════════════════════════════════════════════════
// ConnectAsync provede celý handshake proces:
// ClientHello -> ServerHello -> KeyExchange -> KeyExchangeAck
bool success = await client.ConnectAsync(serverHost, serverPort);
if (success)
{
Debug.Log("[GeoSus] Úspěšně připojeno k serveru!");
ShowNotification("Připojeno k serveru", Color.green, 2f, "✓");
// Načteme statistiky z HTTP API
FetchPlayerStats();
FetchLeaderboard(5);
CheckServerHealth();
}
else
{
ShowError("Nepodařilo se připojit k serveru. Zkontrolujte, zda server běží.");
client.Dispose();
client = null;
}
}
catch (Exception ex)
{
ShowError($"Chyba připojení: {ex.Message}");
Debug.LogError($"[GeoSus] Chyba připojení: {ex}");
if (client != null)
{
client.Dispose();
client = null;
}
}
finally
{
isConnecting = false;
}
}
///
/// Odpojení od serveru.
/// Volá se při opuštění hry nebo při chybě.
///
protected void DisconnectFromServer()
{
if (client != null)
{
client.Disconnect("Hráč se odpojil");
client.Dispose();
client = null;
}
// Reset stavu
currentState = AppState.MainMenu;
CleanupMapObjects();
Debug.Log("[GeoSus] Odpojeno od serveru");
}
#region ═══════════════════════════════════════════════════════════════════
// EVENT HANDLERY
// ════════════════════════════════════════════════════════════════════════
// Event handlery jsou volány když přijde zpráva ze serveru.
// GameClient má několik typů událostí:
// • OnConnected - úspěšné připojení
// • OnDisconnected - odpojení (s důvodem)
// • OnError - chyba
// • OnMessage - jakákoli zpráva (pro debug)
// • OnGameEvent - herní události (GameEvent)
#endregion
///
/// Registrace všech event handlerů.
/// Volá se při vytvoření klienta, PŘED připojením.
///
private void RegisterEventHandlers()
{
// ═══════════════════════════════════════════════════════════════════
// Základní síťové události
// ═══════════════════════════════════════════════════════════════════
client.OnConnected += OnClientConnected;
client.OnDisconnected += OnClientDisconnected;
client.OnError += OnClientError;
// ═══════════════════════════════════════════════════════════════════
// Herní události - toto je hlavní zdroj informací ze serveru
// ═══════════════════════════════════════════════════════════════════
client.OnGameEvent += OnGameEvent;
// ═══════════════════════════════════════════════════════════════════
// Zprávy - pro debug a speciální případy
// ═══════════════════════════════════════════════════════════════════
client.OnMessage += OnMessage;
}
///
/// Handler pro úspěšné připojení
///
private void OnClientConnected()
{
Debug.Log("[GeoSus] Event: OnConnected");
}
///
/// Handler pro odpojení od serveru
///
/// Důvod odpojení
private void OnClientDisconnected(string reason)
{
Debug.Log($"[GeoSus] Event: OnDisconnected - {reason}");
ShowNotification($"Odpojeno: {reason}", Color.red, 5f, "⚠");
// Reset do hlavního menu
currentState = AppState.MainMenu;
CleanupMapObjects();
client = null;
}
///
/// Handler pro chyby
///
/// Popis chyby
private void OnClientError(string error)
{
Debug.LogError($"[GeoSus] Event: OnError - {error}");
ShowError(error);
}
///
/// Handler pro příchozí zprávy (všechny typy).
/// Používá se hlavně pro debug a speciální zprávy.
///
/// Příchozí zpráva
private void OnMessage(Message message)
{
// ═══════════════════════════════════════════════════════════════════
// Zpracování odpovědí na lobby operace
// ═══════════════════════════════════════════════════════════════════
switch (message)
{
case CreateLobbyResponse response:
HandleCreateLobbyResponse(response);
break;
case JoinLobbyResponse response:
HandleJoinLobbyResponse(response);
break;
case Ack ack:
HandleAck(ack);
break;
}
}
#region ═══════════════════════════════════════════════════════════════════
// HERNÍ UDÁLOSTI (GameEvent)
// ════════════════════════════════════════════════════════════════════════
// GameEvent je hlavní způsob, jak server informuje klienty o změnách.
// Každý event má:
// • EventId - sekvenční číslo pro řazení
// • EventType - typ události (string)
// • Payload - data události (JObject, deserializuje se na konkrétní typ)
// • Actor - UUID hráče, který akci provedl
#endregion
///
/// Hlavní handler pro herní události.
/// Zde se zpracovávají všechny události ze serveru.
///
/// DŮLEŽITÉ: Payload se deserializuje pomocí GetPayload()
///
/// Herní událost
private void OnGameEvent(GameEvent evt)
{
Debug.Log($"[GeoSus] GameEvent: {evt.EventType}");
// ═══════════════════════════════════════════════════════════════════
// SWITCH podle typu události
// Každý typ má svůj vlastní payload s daty
// ═══════════════════════════════════════════════════════════════════
switch (evt.EventType)
{
// ───────────────────────────────────────────────────────────────
// LOBBY UDÁLOSTI
// ───────────────────────────────────────────────────────────────
case "PlayerJoined":
HandlePlayerJoined(evt);
break;
case "PlayerLeft":
HandlePlayerLeft(evt);
break;
case "HostChanged":
HandleHostChanged(evt);
break;
case "LobbySettingsChanged":
HandleLobbySettingsChanged(evt);
break;
// ───────────────────────────────────────────────────────────────
// LOADING FÁZE
// ───────────────────────────────────────────────────────────────
case "GameStarting":
HandleGameStarting(evt);
break;
case "MapDataReady":
HandleMapDataReady(evt);
break;
case "PlayerMapDataReceived":
HandlePlayerMapDataReceived(evt);
break;
// ───────────────────────────────────────────────────────────────
// HRA ZAČÍNÁ
// ───────────────────────────────────────────────────────────────
case "GameStarted":
HandleGameStarted(evt);
break;
case "RoleAssigned":
HandleRoleAssigned(evt);
break;
// ───────────────────────────────────────────────────────────────
// HERNÍ AKCE
// ───────────────────────────────────────────────────────────────
case "PlayerKilled":
HandlePlayerKilled(evt);
break;
case "BodyReported":
HandleBodyReported(evt);
break;
case "EmergencyMeetingCalled":
HandleEmergencyMeetingCalled(evt);
break;
// ───────────────────────────────────────────────────────────────
// MEETING A HLASOVÁNÍ
// ───────────────────────────────────────────────────────────────
case "MeetingStarted":
HandleMeetingStarted(evt);
break;
case "PlayerArrivedAtMeeting":
HandlePlayerArrivedAtMeeting(evt);
break;
case "PlayerVoted":
HandlePlayerVoted(evt);
break;
case "VotingClosed":
HandleVotingClosed(evt);
break;
case "PlayerEjected":
HandlePlayerEjected(evt);
break;
// ───────────────────────────────────────────────────────────────
// ÚKOLY
// ───────────────────────────────────────────────────────────────
case "TaskCompleted":
HandleTaskCompleted(evt);
break;
// ───────────────────────────────────────────────────────────────
// SABOTÁŽE
// ───────────────────────────────────────────────────────────────
case "SabotageStarted":
HandleSabotageStarted(evt);
break;
case "RepairStarted":
HandleRepairStarted(evt);
break;
case "RepairStopped":
HandleRepairStopped(evt);
break;
case "SabotageRepaired":
HandleSabotageRepaired(evt);
break;
case "SabotageMeltdown":
HandleSabotageMeltdown(evt);
break;
// ───────────────────────────────────────────────────────────────
// KONEC HRY
// ───────────────────────────────────────────────────────────────
case "GameEnded":
HandleGameEnded(evt);
break;
case "ReturnedToLobby":
HandleReturnedToLobby(evt);
break;
// ───────────────────────────────────────────────────────────────
// SYSTÉMOVÉ ZPRÁVY
// ───────────────────────────────────────────────────────────────
case "SystemMessage":
HandleSystemMessage(evt);
break;
}
}
#region ═══════════════════════════════════════════════════════════════════
// HANDLERY JEDNOTLIVÝCH UDÁLOSTÍ
// ════════════════════════════════════════════════════════════════════════
#endregion
// ─────────────────────────────────────────────────────────────────────────
// LOBBY HANDLERY
// ─────────────────────────────────────────────────────────────────────────
///
/// Zpracování odpovědi na vytvoření lobby
///
private void HandleCreateLobbyResponse(CreateLobbyResponse response)
{
if (response.Success)
{
Debug.Log($"[GeoSus] Lobby vytvořeno: {response.JoinCode}");
currentState = AppState.Lobby;
ShowNotification($"Lobby vytvořeno! Kód: {response.JoinCode}", Color.green, 3f, "🏠");
}
else
{
ShowError($"Nelze vytvořit lobby: {response.Error}");
}
}
///
/// Zpracování odpovědi na připojení do lobby
///
private void HandleJoinLobbyResponse(JoinLobbyResponse response)
{
if (response.Success)
{
Debug.Log($"[GeoSus] Připojeno do lobby: {response.LobbyId}");
currentState = AppState.Lobby;
ShowNotification("Připojeno do lobby!", Color.green, 2f, "✓");
}
else
{
ShowError($"Nelze se připojit: {response.Error}");
}
}
///
/// Zpracování ACK zprávy (potvrzení akce)
///
private void HandleAck(Ack ack)
{
if (!ack.Success && !string.IsNullOrEmpty(ack.Error))
{
// Prevence spamu - nezobrazujeme stejnou chybu opakovaně v krátkém čase
float currentTime = Time.time;
if (ack.Error == lastAckError && currentTime - lastAckErrorTime < 3f)
{
// Stejná chyba před méně než 3 sekundami - nezobrazovat
return;
}
lastAckError = ack.Error;
lastAckErrorTime = currentTime;
// Zobrazíme chybu uživateli
ShowNotification(ack.Error, new Color(1f, 0.5f, 0f), 3f, "⚠");
}
}
///
/// Hráč se připojil do lobby
///
private void HandlePlayerJoined(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// SDK automaticky aktualizuje CurrentLobbyState.Players
ShowNotification($"{payload.DisplayName} se připojil/a", Color.cyan, 2f, "→");
}
}
///
/// Hráč opustil lobby
///
private void HandlePlayerLeft(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// Zkontrolujeme jestli jsme byli kicknuti my
if (payload.ClientUuid == clientUuid)
{
// Jsme kicknuti!
string reason = payload.Reason ?? "Vyhozen administrátorem";
Debug.Log($"[GeoSus] Byl jsem kicknut: {reason}");
ShowNotification($"🚫 Byli jste vyhozeni: {reason}", Color.red, 10f, "🚫");
// Odpojíme se a vrátíme do menu
currentState = AppState.MainMenu;
client?.Disconnect();
return;
}
// SDK automaticky aktualizuje CurrentLobbyState.Players
// Najdeme jméno hráče pro zobrazení
string name = payload.ClientUuid;
if (client?.CurrentLobbyState?.Players != null)
{
foreach (var p in client.CurrentLobbyState.Players)
{
if (p.ClientUuid == payload.ClientUuid)
{
name = p.DisplayName;
break;
}
}
}
// Zobrazíme jestli byl hráč kicknut nebo odešel sám
bool wasKicked = !string.IsNullOrEmpty(payload.Reason) && payload.Reason.Contains("Kicked");
if (wasKicked)
{
ShowNotification($"{name} byl/a vyhozen/a", Color.red, 3f, "🚫");
}
else
{
ShowNotification($"{name} odešel/a", Color.gray, 2f, "←");
}
}
}
///
/// Vlastník lobby se změnil (host migration)
///
private void HandleHostChanged(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// SDK automaticky aktualizuje CurrentLobbyState.OwnerId a IsOwner flags
bool iAmNewOwner = payload.NewHostId == clientUuid;
if (iAmNewOwner)
{
ShowNotification("Jsi nyní vlastníkem lobby!", Color.yellow, 4f, "👑");
Debug.Log("[GeoSus] Stal jsem se vlastníkem lobby");
}
else
{
// Najdeme jméno nového vlastníka
string newOwnerName = payload.NewHostId;
if (client?.CurrentLobbyState?.Players != null)
{
foreach (var p in client.CurrentLobbyState.Players)
{
if (p.ClientUuid == payload.NewHostId)
{
newOwnerName = p.DisplayName;
break;
}
}
}
ShowNotification($"{newOwnerName} je nyní vlastníkem", Color.cyan, 3f, "👑");
}
}
}
///
/// Nastavení lobby se změnilo
///
private void HandleLobbySettingsChanged(GameEvent evt)
{
ShowNotification("Nastavení lobby změněno", Color.yellow, 2f, "⚙");
}
// ─────────────────────────────────────────────────────────────────────────
// LOADING FÁZE HANDLERY
// ─────────────────────────────────────────────────────────────────────────
///
/// Hra začíná - vstupujeme do loading fáze
///
private void HandleGameStarting(GameEvent evt)
{
Debug.Log("[GeoSus] Hra začíná - načítání mapových dat...");
currentState = AppState.Loading;
loadingMessage = "Načítání mapových dat...";
loadingProgress = 0f;
ShowNotification("Hra začíná!", Color.yellow, 2f, "🎮");
}
///
/// Mapová data jsou připravena
///
private void HandleMapDataReady(GameEvent evt)
{
var payload = evt.GetPayload();
Debug.Log("[GeoSus] Mapová data přijata");
loadingMessage = "Mapová data přijata, čekám na ostatní hráče...";
loadingProgress = 0.5f;
// Vykreslíme mapu - ProcessMapData inicializuje mapu
if (payload?.MapData != null && client?.CurrentLobbyState != null)
{
mapCenter = client.CurrentLobbyState.PlayAreaCenter;
mapRadius = client.CurrentLobbyState.PlayAreaRadius;
ProcessMapData(payload.MapData);
}
// POZNÁMKA: GameClient automaticky pošle MapDataReceived potvrzení
}
///
/// Hráč potvrdil příjem mapových dat
///
private void HandlePlayerMapDataReceived(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
loadingMessage = $"Hráči připraveni: {payload.PlayersReady}/{payload.TotalPlayers}";
loadingProgress = 0.5f + (0.5f * payload.PlayersReady / payload.TotalPlayers);
}
}
// ─────────────────────────────────────────────────────────────────────────
// HRA HANDLERY
// ─────────────────────────────────────────────────────────────────────────
///
/// Hra oficiálně začala
///
private void HandleGameStarted(GameEvent evt)
{
Debug.Log("[GeoSus] Hra začala!");
currentState = AppState.InGame;
// Reset herních proměnných
isRepairing = false;
repairProgress = 0f;
// Inicializace pozice hráče
InitializePlayerPosition();
ShowNotification("HRA ZAČALA!", Color.green, 3f, "🎮");
}
///
/// Přiřazení role hráči
///
private void HandleRoleAssigned(GameEvent evt)
{
var payload = evt.GetPayload();
// Kontrola, zda je to naše role
if (payload != null && payload.ClientUuid == clientUuid)
{
Debug.Log($"[GeoSus] Moje role: {payload.Role}");
// Zobrazíme velkou notifikaci s rolí
if (payload.Role == PlayerRole.Impostor)
{
ShowNotification("JSI IMPOSTOR!", Color.red, 5f, "🔪");
}
else
{
ShowNotification("Jsi Crewmate", Color.cyan, 3f, "👤");
}
// Aktualizujeme markery úkolů na mapě
CreateTaskMarkers();
}
}
///
/// Hráč byl zabit
///
private void HandlePlayerKilled(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// Vytvoříme marker těla na mapě
CreateBodyObject(payload.BodyId, payload.Location, GetPlayerName(payload.VictimId));
// Pokud jsme to my, kdo byl zabit
if (payload.VictimId == clientUuid)
{
ShowNotification("BYL JSI ZABIT!", Color.red, 5f, "💀");
}
}
}
///
/// Tělo bylo nahlášeno
///
private void HandleBodyReported(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// Najdeme jméno reportéra
string reporterName = GetPlayerName(payload.ReporterId);
ShowNotification($"{reporterName} nahlásil/a tělo!", Color.red, 3f, "🚨");
}
}
///
/// Emergency meeting bylo svoláno
///
private void HandleEmergencyMeetingCalled(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
string callerName = GetPlayerName(payload.CallerId);
ShowNotification($"{callerName} svolal/a emergency meeting!", Color.yellow, 3f, "🔔");
}
}
// ─────────────────────────────────────────────────────────────────────────
// MEETING HANDLERY
// ─────────────────────────────────────────────────────────────────────────
///
/// Meeting začal
///
private void HandleMeetingStarted(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
Debug.Log($"[GeoSus] Meeting začal: {payload.Type}");
// Uložíme data meetingu
currentMeeting = payload;
meetingVotes.Clear();
arrivedAtMeeting.Clear();
iArrivedAtMeeting = false;
myVote = null;
canVote = false;
// Určíme, zda můžeme hlasovat (jsme naživu)
bool amAlive = true;
if (client?.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var myInfo))
{
amAlive = myInfo.State == PlayerState.Alive;
}
canVoteInMeeting = amAlive;
// Notifikace s informací o meeting lokaci
string meetingInfo = payload.Type == MeetingType.BodyReport
? "TĚLO NALEZENO! Běž na meeting lokaci!"
: "EMERGENCY MEETING! Běž na meeting lokaci!";
ShowNotification(meetingInfo, Color.yellow, 5f, "📢");
}
}
///
/// Hráč dorazil na meeting lokaci
///
private void HandlePlayerArrivedAtMeeting(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
arrivedAtMeeting.Add(payload.ClientUuid);
if (payload.ClientUuid == clientUuid)
{
iArrivedAtMeeting = true;
ShowNotification("Dorazil/a jsi na meeting!", Color.green, 2f, "✓");
}
else
{
string playerName = GetPlayerName(payload.ClientUuid);
Debug.Log($"[GeoSus] {playerName} dorazil na meeting");
}
}
}
///
/// Hráč hlasoval (vidíme pouze, že hlasoval, ne koho)
///
private void HandlePlayerVoted(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
meetingVotes[payload.VoterId] = true; // Označíme, že hlasoval
string voterName = GetPlayerName(payload.VoterId);
ShowNotification($"{voterName} hlasoval/a", Color.gray, 1f, "✓");
}
}
///
/// Hlasování skončilo
///
private void HandleVotingClosed(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// Uložíme výsledky pro zobrazení
votingResults = payload;
showVotingResults = true;
votingResultsEndTime = Time.time + 5f; // Zobrazíme na 5 sekund
if (payload.EjectedPlayerId != null)
{
string ejectedName = GetPlayerName(payload.EjectedPlayerId);
ShowNotification($"{ejectedName} byl/a vyhozen/a!", Color.red, 5f, "🚪");
}
else if (payload.WasTie)
{
ShowNotification("Remíza - nikdo nebyl vyhozen", Color.gray, 3f, "⚖");
}
else
{
ShowNotification("Nikdo nebyl vyhozen", Color.gray, 3f, "✗");
}
}
}
///
/// Hráč byl vyhozen
///
private void HandlePlayerEjected(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// Pokud jsme to my
if (payload.ClientUuid == clientUuid)
{
ShowNotification("BYL JSI VYHOZEN!", Color.red, 5f, "🚪");
}
// Zavřeme meeting panel
currentMeeting = null;
}
}
// ─────────────────────────────────────────────────────────────────────────
// TASK HANDLERY
// ─────────────────────────────────────────────────────────────────────────
///
/// Úkol byl dokončen (kýmkoli)
///
private void HandleTaskCompleted(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// Pokud jsme to my, označíme task jako splněný
if (payload.ClientUuid == clientUuid)
{
myCompletedTaskIds.Add(payload.TaskId);
ShowNotification("Úkol dokončen!", Color.green, 2f, "✓");
// Aktualizuj marker
UpdateTaskMarker(payload.TaskId, true);
}
// Aktualizujeme globální progress bar úkolů
totalTasksCompleted = payload.TotalCompleted;
totalTasksRequired = payload.TotalTasks;
Debug.Log($"[GeoSus] Task completed: {payload.TaskId}, progress {payload.TotalCompleted}/{payload.TotalTasks}");
}
}
// ─────────────────────────────────────────────────────────────────────────
// SABOTAGE HANDLERY
// ─────────────────────────────────────────────────────────────────────────
///
/// Sabotáž byla spuštěna
///
private void HandleSabotageStarted(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
Debug.Log($"[GeoSus] Sabotáž spuštěna: {payload.Type}, stanic: {payload.RepairStations?.Count ?? 0}");
if (payload.RepairStations != null)
{
foreach (var station in payload.RepairStations)
{
Debug.Log($"[GeoSus] Repair station: {station.StationId} '{station.Name}' @ {station.Location.Lat:F6}, {station.Location.Lon:F6}");
}
}
currentSabotage = payload;
// Vytvoříme markery opravných stanic
CreateRepairStationMarkers();
// Zobrazíme notifikaci
string message = payload.Type == SabotageType.CommsBlackout
? "⚠ COMMS BLACKOUT - Opravte komunikaci!"
: "⚠ CRITICAL MELTDOWN - Opravte reaktor!";
Color color = payload.Type == SabotageType.CriticalMeltdown ? Color.red : Color.yellow;
ShowNotification(message, color, 5f, "⚠");
}
}
///
/// Někdo začal opravovat stanici
///
private void HandleRepairStarted(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
// Aktualizujeme marker stanice
UpdateRepairStationMarker(payload.StationId, true);
}
}
///
/// Někdo přestal opravovat stanici
///
private void HandleRepairStopped(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
UpdateRepairStationMarker(payload.StationId, false);
}
}
///
/// Sabotáž byla opravena
///
private void HandleSabotageRepaired(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
Debug.Log($"[GeoSus] Sabotáž opravena: {payload.Type}");
currentSabotage = null;
isRepairing = false;
// Odstraníme markery opravných stanic
ClearRepairStationMarkers();
ShowNotification("Sabotáž opravena!", Color.green, 3f, "✓");
}
}
///
/// Meltdown - impostoři vyhráli
///
private void HandleSabotageMeltdown(GameEvent evt)
{
ShowNotification("MELTDOWN! Impostoři vyhráli!", Color.red, 5f, "💥");
}
// ─────────────────────────────────────────────────────────────────────────
// KONEC HRY
// ─────────────────────────────────────────────────────────────────────────
///
/// Hra skončila
///
private void HandleGameEnded(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload != null)
{
Debug.Log($"[GeoSus] Hra skončila! Vítěz: {payload.WinningFaction}");
gameEndData = payload;
currentState = AppState.GameEnded;
// Notifikace
bool weWon = payload.Winners.Contains(clientUuid);
if (weWon)
{
ShowNotification("VÍTĚZSTVÍ!", Color.green, 5f, "🏆");
}
else
{
ShowNotification("Prohra...", Color.red, 5f, "💔");
}
}
}
///
/// Vráceno do lobby po skončení hry
///
private void HandleReturnedToLobby(GameEvent evt)
{
Debug.Log("[GeoSus] Vráceno do lobby");
gameEndData = null;
currentSabotage = null; // Reset sabotage stavu!
currentState = AppState.Lobby;
CleanupMapObjects();
ShowNotification("Zpět v lobby!", Color.cyan, 3f, "🔄");
}
// ─────────────────────────────────────────────────────────────────────────
// SYSTÉMOVÉ ZPRÁVY (Admin broadcast)
// ─────────────────────────────────────────────────────────────────────────
///
/// Zpracování systémové zprávy od administrátora
///
private void HandleSystemMessage(GameEvent evt)
{
var payload = evt.GetPayload();
if (payload == null) return;
Debug.Log($"[GeoSus] Systémová zpráva: {payload.Message}");
// Zobrazíme výraznou notifikaci s delším trváním (oranžová barva pro admin zprávy)
ShowNotification($"📢 ADMIN: {payload.Message}", new Color(1f, 0.8f, 0f), 8f, "📢");
}
#region ═══════════════════════════════════════════════════════════════════
// ODESÍLÁNÍ ZPRÁV
// ════════════════════════════════════════════════════════════════════════
// Tyto metody obalují volání GameClient pro pohodlnější použití.
// GameClient.Send() automaticky šifruje a odesílá zprávy.
#endregion
///
/// Vytvoření nového lobby
///
/// Střed herní oblasti (GPS)
/// Poloměr herní oblasti v metrech
/// Počet impostorů
/// Počet úkolů
/// Heslo (volitelné)
protected void CreateLobby(Position center, double radius, int impostorCount, int taskCount, string password = null)
{
if (client == null || !client.IsConnected)
{
ShowError("Nejste připojeni k serveru!");
return;
}
Debug.Log($"[GeoSus] Vytvářím lobby: center={center.Lat},{center.Lon}, radius={radius}m");
client.CreateLobby(center, impostorCount, taskCount, password, radius);
}
///
/// Připojení do existujícího lobby
///
/// 6-místný kód pro připojení
/// Heslo (pokud je vyžadováno)
protected void JoinLobby(string joinCode, string password = null)
{
if (client == null || !client.IsConnected)
{
ShowError("Nejste připojeni k serveru!");
return;
}
Debug.Log($"[GeoSus] Připojuji do lobby: {joinCode}");
client.JoinLobby(joinCode, password);
}
///
/// Opuštění lobby
///
protected void LeaveLobby()
{
if (client != null)
{
client.LeaveLobby();
}
currentState = AppState.MainMenu;
CleanupMapObjects();
}
///
/// Spuštění hry (pouze owner)
///
protected void StartGame()
{
if (client == null || !client.IsConnected)
{
ShowError("Nejste připojeni k serveru!");
return;
}
// Kontrola, zda jsme owner (používáme novou vlastnost IsOwner)
if (!client.IsOwner)
{
ShowError("Pouze vlastník lobby může spustit hru!");
return;
}
Debug.Log("[GeoSus] Spouštím hru...");
client.StartGame();
}
///
/// Odeslání aktualizace pozice
///
protected void SendPositionUpdate()
{
if (client != null && client.IsConnected && currentState == AppState.InGame)
{
client.UpdatePosition(playerPosition);
}
}
}