1197 lines
52 KiB
C#
1197 lines
52 KiB
C#
/*
|
|
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
|
|
║ ║
|
|
║ 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
|
|
|
|
/// <summary>
|
|
/// Příznak, zda probíhá připojování k serveru
|
|
/// </summary>
|
|
protected bool isConnecting = false;
|
|
|
|
/// <summary>
|
|
/// Časovač pro pravidelné odesílání pozice (každých 100ms)
|
|
/// </summary>
|
|
protected float lastPositionUpdateTime = 0f;
|
|
|
|
/// <summary>
|
|
/// Interval odesílání pozice v sekundách
|
|
/// </summary>
|
|
protected const float POSITION_UPDATE_INTERVAL = 0.1f;
|
|
|
|
/// <summary>
|
|
/// Časovač pro ping
|
|
/// </summary>
|
|
protected float lastPingTime = 0f;
|
|
|
|
/// <summary>
|
|
/// Interval pingu v sekundách
|
|
/// </summary>
|
|
protected const float PING_INTERVAL = 5f;
|
|
|
|
/// <summary>
|
|
/// Aktuální pozice hráče (GPS)
|
|
/// </summary>
|
|
protected Position playerPosition;
|
|
|
|
/// <summary>
|
|
/// ID opravné stanice, kterou právě opravujeme
|
|
/// </summary>
|
|
protected string currentRepairStationId;
|
|
|
|
/// <summary>
|
|
/// Poslední chybová zpráva z ACK - pro prevenci spamu
|
|
/// </summary>
|
|
private string lastAckError = null;
|
|
|
|
/// <summary>
|
|
/// Čas posledního zobrazení chyby z ACK
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Odpojení od serveru.
|
|
/// Volá se při opuštění hry nebo při chybě.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Registrace všech event handlerů.
|
|
/// Volá se při vytvoření klienta, PŘED připojením.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler pro úspěšné připojení
|
|
/// </summary>
|
|
private void OnClientConnected()
|
|
{
|
|
Debug.Log("[GeoSus] Event: OnConnected");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler pro odpojení od serveru
|
|
/// </summary>
|
|
/// <param name="reason">Důvod odpojení</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler pro chyby
|
|
/// </summary>
|
|
/// <param name="error">Popis chyby</param>
|
|
private void OnClientError(string error)
|
|
{
|
|
Debug.LogError($"[GeoSus] Event: OnError - {error}");
|
|
ShowError(error);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler pro příchozí zprávy (všechny typy).
|
|
/// Používá se hlavně pro debug a speciální zprávy.
|
|
/// </summary>
|
|
/// <param name="message">Příchozí zpráva</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Hlavní handler pro herní události.
|
|
/// Zde se zpracovávají všechny události ze serveru.
|
|
///
|
|
/// DŮLEŽITÉ: Payload se deserializuje pomocí GetPayload<T>()
|
|
/// </summary>
|
|
/// <param name="evt">Herní událost</param>
|
|
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
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Zpracování odpovědi na vytvoření lobby
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Zpracování odpovědi na připojení do lobby
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Zpracování ACK zprávy (potvrzení akce)
|
|
/// </summary>
|
|
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, "⚠");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hráč se připojil do lobby
|
|
/// </summary>
|
|
private void HandlePlayerJoined(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<PlayerJoinedPayload>();
|
|
if (payload != null)
|
|
{
|
|
// SDK automaticky aktualizuje CurrentLobbyState.Players
|
|
ShowNotification($"{payload.DisplayName} se připojil/a", Color.cyan, 2f, "→");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hráč opustil lobby
|
|
/// </summary>
|
|
private void HandlePlayerLeft(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<PlayerLeftPayload>();
|
|
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, "←");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vlastník lobby se změnil (host migration)
|
|
/// </summary>
|
|
private void HandleHostChanged(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<HostChangedPayload>();
|
|
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, "👑");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Nastavení lobby se změnilo
|
|
/// </summary>
|
|
private void HandleLobbySettingsChanged(GameEvent evt)
|
|
{
|
|
ShowNotification("Nastavení lobby změněno", Color.yellow, 2f, "⚙");
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// LOADING FÁZE HANDLERY
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Hra začíná - vstupujeme do loading fáze
|
|
/// </summary>
|
|
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, "🎮");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mapová data jsou připravena
|
|
/// </summary>
|
|
private void HandleMapDataReady(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<MapDataReadyPayload>();
|
|
|
|
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í
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hráč potvrdil příjem mapových dat
|
|
/// </summary>
|
|
private void HandlePlayerMapDataReceived(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<PlayerMapDataReceivedPayload>();
|
|
if (payload != null)
|
|
{
|
|
loadingMessage = $"Hráči připraveni: {payload.PlayersReady}/{payload.TotalPlayers}";
|
|
loadingProgress = 0.5f + (0.5f * payload.PlayersReady / payload.TotalPlayers);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// HRA HANDLERY
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Hra oficiálně začala
|
|
/// </summary>
|
|
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, "🎮");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Přiřazení role hráči
|
|
/// </summary>
|
|
private void HandleRoleAssigned(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<RoleAssignedPayload>();
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hráč byl zabit
|
|
/// </summary>
|
|
private void HandlePlayerKilled(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<PlayerKilledPayload>();
|
|
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, "💀");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tělo bylo nahlášeno
|
|
/// </summary>
|
|
private void HandleBodyReported(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<BodyReportedPayload>();
|
|
if (payload != null)
|
|
{
|
|
// Najdeme jméno reportéra
|
|
string reporterName = GetPlayerName(payload.ReporterId);
|
|
ShowNotification($"{reporterName} nahlásil/a tělo!", Color.red, 3f, "🚨");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emergency meeting bylo svoláno
|
|
/// </summary>
|
|
private void HandleEmergencyMeetingCalled(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<EmergencyMeetingCalledPayload>();
|
|
if (payload != null)
|
|
{
|
|
string callerName = GetPlayerName(payload.CallerId);
|
|
ShowNotification($"{callerName} svolal/a emergency meeting!", Color.yellow, 3f, "🔔");
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// MEETING HANDLERY
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Meeting začal
|
|
/// </summary>
|
|
private void HandleMeetingStarted(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<MeetingStartedPayload>();
|
|
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, "📢");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hráč dorazil na meeting lokaci
|
|
/// </summary>
|
|
private void HandlePlayerArrivedAtMeeting(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<PlayerArrivedAtMeetingPayload>();
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hráč hlasoval (vidíme pouze, že hlasoval, ne koho)
|
|
/// </summary>
|
|
private void HandlePlayerVoted(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<PlayerVotedPayload>();
|
|
if (payload != null)
|
|
{
|
|
meetingVotes[payload.VoterId] = true; // Označíme, že hlasoval
|
|
|
|
string voterName = GetPlayerName(payload.VoterId);
|
|
ShowNotification($"{voterName} hlasoval/a", Color.gray, 1f, "✓");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hlasování skončilo
|
|
/// </summary>
|
|
private void HandleVotingClosed(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<VotingClosedPayload>();
|
|
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, "✗");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hráč byl vyhozen
|
|
/// </summary>
|
|
private void HandlePlayerEjected(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<PlayerEjectedPayload>();
|
|
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
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Úkol byl dokončen (kýmkoli)
|
|
/// </summary>
|
|
private void HandleTaskCompleted(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<TaskCompletedPayload>();
|
|
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
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Sabotáž byla spuštěna
|
|
/// </summary>
|
|
private void HandleSabotageStarted(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<SabotageStartedPayload>();
|
|
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, "⚠");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Někdo začal opravovat stanici
|
|
/// </summary>
|
|
private void HandleRepairStarted(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<RepairStartedPayload>();
|
|
if (payload != null)
|
|
{
|
|
// Aktualizujeme marker stanice
|
|
UpdateRepairStationMarker(payload.StationId, true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Někdo přestal opravovat stanici
|
|
/// </summary>
|
|
private void HandleRepairStopped(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<RepairStoppedPayload>();
|
|
if (payload != null)
|
|
{
|
|
UpdateRepairStationMarker(payload.StationId, false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sabotáž byla opravena
|
|
/// </summary>
|
|
private void HandleSabotageRepaired(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<SabotageRepairedPayload>();
|
|
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, "✓");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Meltdown - impostoři vyhráli
|
|
/// </summary>
|
|
private void HandleSabotageMeltdown(GameEvent evt)
|
|
{
|
|
ShowNotification("MELTDOWN! Impostoři vyhráli!", Color.red, 5f, "💥");
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// KONEC HRY
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Hra skončila
|
|
/// </summary>
|
|
private void HandleGameEnded(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<GameEndedPayload>();
|
|
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, "💔");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vráceno do lobby po skončení hry
|
|
/// </summary>
|
|
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)
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Zpracování systémové zprávy od administrátora
|
|
/// </summary>
|
|
private void HandleSystemMessage(GameEvent evt)
|
|
{
|
|
var payload = evt.GetPayload<SystemMessagePayload>();
|
|
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
|
|
|
|
/// <summary>
|
|
/// Vytvoření nového lobby
|
|
/// </summary>
|
|
/// <param name="center">Střed herní oblasti (GPS)</param>
|
|
/// <param name="radius">Poloměr herní oblasti v metrech</param>
|
|
/// <param name="impostorCount">Počet impostorů</param>
|
|
/// <param name="taskCount">Počet úkolů</param>
|
|
/// <param name="password">Heslo (volitelné)</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Připojení do existujícího lobby
|
|
/// </summary>
|
|
/// <param name="joinCode">6-místný kód pro připojení</param>
|
|
/// <param name="password">Heslo (pokud je vyžadováno)</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opuštění lobby
|
|
/// </summary>
|
|
protected void LeaveLobby()
|
|
{
|
|
if (client != null)
|
|
{
|
|
client.LeaveLobby();
|
|
}
|
|
|
|
currentState = AppState.MainMenu;
|
|
CleanupMapObjects();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spuštění hry (pouze owner)
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Odeslání aktualizace pozice
|
|
/// </summary>
|
|
protected void SendPositionUpdate()
|
|
{
|
|
if (client != null && client.IsConnected && currentState == AppState.InGame)
|
|
{
|
|
client.UpdatePosition(playerPosition);
|
|
}
|
|
}
|
|
|
|
}
|