/* ╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 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); } } }