From fc22d4f544894b785dae3dc723af515c1f045b7b Mon Sep 17 00:00:00 2001 From: trubkokrtek Date: Sat, 31 Jan 2026 15:44:30 +0100 Subject: [PATCH 1/3] Added ITask and basic connection --- Assets/ClientSDK/Encryption.cs | 285 +++ Assets/ClientSDK/EventDispatcher.cs | 73 + Assets/ClientSDK/GameClient.cs | 607 +++++++ Assets/ClientSDK/Protocol.cs | 1054 +++++++++++ Assets/ClientSDK/SimulatorClient.cs | 1992 +++++++++++++++++++++ Assets/GameManager/GameManager.cs | 42 + Assets/GameManager/GameManager_Network.cs | 38 + Assets/GameManager/ITask.cs | 53 + Assets/Scenes/Client.unity | 362 ++++ 9 files changed, 4506 insertions(+) create mode 100644 Assets/ClientSDK/Encryption.cs create mode 100644 Assets/ClientSDK/EventDispatcher.cs create mode 100644 Assets/ClientSDK/GameClient.cs create mode 100644 Assets/ClientSDK/Protocol.cs create mode 100644 Assets/ClientSDK/SimulatorClient.cs create mode 100644 Assets/GameManager/GameManager.cs create mode 100644 Assets/GameManager/GameManager_Network.cs create mode 100644 Assets/GameManager/ITask.cs create mode 100644 Assets/Scenes/Client.unity diff --git a/Assets/ClientSDK/Encryption.cs b/Assets/ClientSDK/Encryption.cs new file mode 100644 index 0000000..3def88a --- /dev/null +++ b/Assets/ClientSDK/Encryption.cs @@ -0,0 +1,285 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace GeoSus.Client +{ + // Klientská strana šifrování - generuje session key, šifruje RSA, AES-CBC session + // Používá AES-CBC místo AES-GCM pro kompatibilitu s Unity + public class ClientEncryption : IDisposable + { + private byte[] _sessionKey; + private byte[] _sessionIv; + private long _nonceCounter; + private readonly object _lock = new object(); + + // Kontrola, zda je session key nastaven + public bool HasSessionKey => _sessionKey != null && _sessionIv != null; + + // Generuje nový session key a IV + public void GenerateSessionKey() + { + _sessionKey = new byte[32]; // AES-256 + _sessionIv = new byte[16]; // CBC IV (16 bytes) + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(_sessionKey); + rng.GetBytes(_sessionIv); + } + } + + public byte[] SessionKey => _sessionKey ?? throw new InvalidOperationException("Session key not generated"); + public byte[] SessionIV => _sessionIv ?? throw new InvalidOperationException("Session IV not generated"); + + // Zašifruje session key pomocí RSA public key serveru + public (string EncryptedKey, string EncryptedIV) EncryptSessionKeyForServer(string rsaPublicKeyPem) + { + if (_sessionKey == null || _sessionIv == null) + throw new InvalidOperationException("Session key not generated"); + + using (var rsa = RSA.Create()) + { + // Parse PEM - extrahuj Base64 obsah + var pemLines = rsaPublicKeyPem.Split('\n'); + var base64 = new StringBuilder(); + foreach (var line in pemLines) + { + var trimmed = line.Trim(); + if (!trimmed.StartsWith("-----") && !string.IsNullOrEmpty(trimmed)) + { + base64.Append(trimmed); + } + } + + var keyBytes = Convert.FromBase64String(base64.ToString()); + + // Unity kompatibilní import - parsujeme SubjectPublicKeyInfo ručně + ImportSubjectPublicKeyInfoManual(rsa, keyBytes); + + // Používáme OaepSHA1 pro Unity kompatibilitu (OaepSHA256 není podporován) + var encryptedKey = rsa.Encrypt(_sessionKey, RSAEncryptionPadding.OaepSHA1); + var encryptedIv = rsa.Encrypt(_sessionIv, RSAEncryptionPadding.OaepSHA1); + + return (Convert.ToBase64String(encryptedKey), Convert.ToBase64String(encryptedIv)); + } + } + + // Ručně parsuje SubjectPublicKeyInfo (DER) a importuje RSA klíč - Unity kompatibilní + private static void ImportSubjectPublicKeyInfoManual(RSA rsa, byte[] subjectPublicKeyInfo) + { + // SubjectPublicKeyInfo ::= SEQUENCE { + // algorithm AlgorithmIdentifier, + // subjectPublicKey BIT STRING } + // RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER } + + int index = 0; + + // Outer SEQUENCE + if (subjectPublicKeyInfo[index++] != 0x30) + throw new InvalidOperationException("Invalid SubjectPublicKeyInfo"); + ReadLength(subjectPublicKeyInfo, ref index); + + // AlgorithmIdentifier SEQUENCE - skip it + if (subjectPublicKeyInfo[index++] != 0x30) + throw new InvalidOperationException("Invalid AlgorithmIdentifier"); + int algLen = ReadLength(subjectPublicKeyInfo, ref index); + index += algLen; + + // BIT STRING containing RSAPublicKey + if (subjectPublicKeyInfo[index++] != 0x03) + throw new InvalidOperationException("Invalid BIT STRING"); + ReadLength(subjectPublicKeyInfo, ref index); + index++; // Skip unused bits byte (should be 0) + + // RSAPublicKey SEQUENCE + if (subjectPublicKeyInfo[index++] != 0x30) + throw new InvalidOperationException("Invalid RSAPublicKey"); + ReadLength(subjectPublicKeyInfo, ref index); + + // Modulus INTEGER + byte[] modulus = ReadInteger(subjectPublicKeyInfo, ref index); + + // Exponent INTEGER + byte[] exponent = ReadInteger(subjectPublicKeyInfo, ref index); + + var parameters = new RSAParameters + { + Modulus = modulus, + Exponent = exponent + }; + rsa.ImportParameters(parameters); + } + + private static int ReadLength(byte[] data, ref int index) + { + int length = data[index++]; + if ((length & 0x80) != 0) + { + int numBytes = length & 0x7F; + length = 0; + for (int i = 0; i < numBytes; i++) + { + length = (length << 8) | data[index++]; + } + } + return length; + } + + private static byte[] ReadInteger(byte[] data, ref int index) + { + if (data[index++] != 0x02) + throw new InvalidOperationException("Expected INTEGER"); + int length = ReadLength(data, ref index); + + // Skip leading zero if present (used for positive sign in DER) + int originalLength = length; + int start = index; + if (length > 1 && data[start] == 0x00) + { + start++; + length--; + } + + byte[] result = new byte[length]; + Buffer.BlockCopy(data, start, result, 0, length); + index += originalLength; + + return result; + } + + // Šifruje zprávu pomocí AES-256-CBC s HMAC + public byte[] Encrypt(byte[] plaintext) + { + if (_sessionKey == null || _sessionIv == null) + throw new InvalidOperationException("Session key not set"); + + lock (_lock) + { + // Generuj unikátní IV pro tuto zprávu + var iv = GetNextIV(); + + using (var aes = Aes.Create()) + { + aes.Key = _sessionKey; + aes.IV = iv; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + + byte[] ciphertext; + using (var encryptor = aes.CreateEncryptor()) + using (var ms = new MemoryStream()) + { + using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + cs.Write(plaintext, 0, plaintext.Length); + } + ciphertext = ms.ToArray(); + } + + // Compute HMAC pro integritu + byte[] hmac; + using (var hmacSha = new HMACSHA256(_sessionKey)) + { + var toSign = new byte[iv.Length + ciphertext.Length]; + Buffer.BlockCopy(iv, 0, toSign, 0, iv.Length); + Buffer.BlockCopy(ciphertext, 0, toSign, iv.Length, ciphertext.Length); + hmac = hmacSha.ComputeHash(toSign); + } + + // Výstup: [16 bytes IV][32 bytes HMAC][ciphertext] + var result = new byte[16 + 32 + ciphertext.Length]; + Buffer.BlockCopy(iv, 0, result, 0, 16); + Buffer.BlockCopy(hmac, 0, result, 16, 32); + Buffer.BlockCopy(ciphertext, 0, result, 48, ciphertext.Length); + + return result; + } + } + } + + // Dešifruje zprávu pomocí AES-256-CBC s HMAC ověřením + public byte[] Decrypt(byte[] encrypted) + { + if (_sessionKey == null) + throw new InvalidOperationException("Session key not set"); + + if (encrypted.Length < 48) return null; + + try + { + var iv = new byte[16]; + var hmac = new byte[32]; + var ciphertext = new byte[encrypted.Length - 48]; + + Buffer.BlockCopy(encrypted, 0, iv, 0, 16); + Buffer.BlockCopy(encrypted, 16, hmac, 0, 32); + Buffer.BlockCopy(encrypted, 48, ciphertext, 0, ciphertext.Length); + + // Ověř HMAC + byte[] expectedHmac; + using (var hmacSha = new HMACSHA256(_sessionKey)) + { + var toVerify = new byte[iv.Length + ciphertext.Length]; + Buffer.BlockCopy(iv, 0, toVerify, 0, iv.Length); + Buffer.BlockCopy(ciphertext, 0, toVerify, iv.Length, ciphertext.Length); + expectedHmac = hmacSha.ComputeHash(toVerify); + } + + // Constant-time compare + var diff = 0; + for (int i = 0; i < 32; i++) + { + diff |= hmac[i] ^ expectedHmac[i]; + } + if (diff != 0) return null; // HMAC mismatch + + using (var aes = Aes.Create()) + { + aes.Key = _sessionKey; + aes.IV = iv; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + + using (var decryptor = aes.CreateDecryptor()) + using (var ms = new MemoryStream(ciphertext)) + using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) + using (var output = new MemoryStream()) + { + cs.CopyTo(output); + return output.ToArray(); + } + } + } + catch (CryptographicException) + { + return null; + } + } + + private byte[] GetNextIV() + { + if (_sessionIv == null) + throw new InvalidOperationException("Session IV not set"); + + var iv = new byte[16]; + Buffer.BlockCopy(_sessionIv, 0, iv, 0, 8); + + var counter = System.Threading.Interlocked.Increment(ref _nonceCounter); + var counterBytes = BitConverter.GetBytes(counter); + Buffer.BlockCopy(counterBytes, 0, iv, 8, 8); + + return iv; + } + + public void Dispose() + { + if (_sessionKey != null) + { + Array.Clear(_sessionKey, 0, _sessionKey.Length); + _sessionKey = null; + } + } + } +} diff --git a/Assets/ClientSDK/EventDispatcher.cs b/Assets/ClientSDK/EventDispatcher.cs new file mode 100644 index 0000000..839def1 --- /dev/null +++ b/Assets/ClientSDK/EventDispatcher.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace GeoSus.Client +{ + // Event dispatcher pro Unity main thread +// Unity může přidat SynchronizationContext, nebo polling z Update() +public class EventDispatcher +{ + private readonly Queue _pendingActions = new Queue(); + private readonly object _lock = new object(); + private SynchronizationContext? _syncContext; + + public EventDispatcher() + { + // Pokusíme se zachytit aktuální synchronization context (Unity main thread) + _syncContext = SynchronizationContext.Current; + } + + // Volat z networking vlákna - naplánuje callback na main thread + public void Post(Action action) + { + if (_syncContext != null) + { + _syncContext.Post(_ => action(), null); + } + else + { + // Fallback - přidáme do fronty pro polling + lock (_lock) + { + _pendingActions.Enqueue(action); + } + } + } + + // Volat z Unity Update() pokud není SynchronizationContext + public void ProcessPendingActions() + { + Action[] actions; + lock (_lock) + { + if (_pendingActions.Count == 0) return; + actions = _pendingActions.ToArray(); + _pendingActions.Clear(); + } + + foreach (var action in actions) + { + try + { + action(); + } + catch (Exception ex) + { + Console.WriteLine($"EventDispatcher error: {ex}"); + } + } + } + + public int PendingCount + { + get + { + lock (_lock) + { + return _pendingActions.Count; + } + } + } +} +} diff --git a/Assets/ClientSDK/GameClient.cs b/Assets/ClientSDK/GameClient.cs new file mode 100644 index 0000000..09fa7f1 --- /dev/null +++ b/Assets/ClientSDK/GameClient.cs @@ -0,0 +1,607 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace GeoSus.Client +{ + // Hlavní klientská třída pro připojení k serveru +public class GameClient : IDisposable +{ + private TcpClient? _tcpClient; + private NetworkStream? _stream; + private ClientEncryption? _encryption; + private CancellationTokenSource? _cts; + private Task? _receiveTask; + private int _clientSeq; + private readonly object _sendLock = new object(); + private bool _handshakeComplete; + + public string ClientUuid { get; } + public string DisplayName { get; set; } + public bool IsConnected => _tcpClient?.Connected ?? false; + public bool IsReady => IsConnected && _handshakeComplete && (_encryption?.HasSessionKey ?? false); + public EventDispatcher Dispatcher { get; } + + // Events - voláno na main thread přes dispatcher + public event Action? OnConnected; + public event Action? OnDisconnected; + public event Action? OnError; + public event Action? OnMessage; + public event Action? OnGameEvent; + + // Lobby state + public string? LobbyId { get; private set; } + public string? JoinCode { get; private set; } + public LobbyState? CurrentLobbyState { get; private set; } + public PlayerRole? MyRole { get; private set; } + public List MyTasks { get; } = new List(); + public Position MyPosition { get; set; } + public Dictionary PlayerPositions { get; } = new Dictionary(); + public List Bodies { get; } = new List(); + public int Ping { get; private set; } + public long LastEventId { get; private set; } + + /// Returns true if this client is the current lobby owner + public bool IsOwner => CurrentLobbyState?.OwnerId == ClientUuid; + + public GameClient(string clientUuid, string displayName) + { + ClientUuid = clientUuid; + DisplayName = displayName; + Dispatcher = new EventDispatcher(); + } + + #region Connection + + public async Task ConnectAsync(string host, int port) + { + try + { + _tcpClient = new TcpClient(); + await _tcpClient.ConnectAsync(host, port); + _stream = _tcpClient.GetStream(); + _encryption = new ClientEncryption(); + _cts = new CancellationTokenSource(); + + // Handshake + if (!await PerformHandshakeAsync()) + { + Disconnect("Handshake failed"); + return false; + } + + // Spustíme příjem zpráv + _receiveTask = Task.Run(() => ReceiveLoopAsync(_cts.Token)); + + Dispatcher.Post(() => OnConnected?.Invoke()); + return true; + } + catch (Exception ex) + { + Dispatcher.Post(() => OnError?.Invoke(ex.Message)); + return false; + } + } + + private async Task PerformHandshakeAsync() + { + if (_stream == null || _encryption == null) return false; + + // 1. ClientHello + var hello = new ClientHello + { + ClientUuid = ClientUuid, + DisplayName = DisplayName + }; + await SendPlainAsync(hello); + + // 2. ServerHello + var serverHelloData = await ReadMessageAsync(); + if (serverHelloData == null) return false; + + var serverHello = MessageSerializer.Deserialize(serverHelloData) as ServerHello; + if (serverHello == null) return false; + + // 3. Generujeme session key a šifrujeme RSA + _encryption.GenerateSessionKey(); + var (encKey, encIv) = _encryption.EncryptSessionKeyForServer(serverHello.RsaPublicKeyPem); + + var keyExchange = new KeyExchange + { + EncryptedSessionKey = encKey, + EncryptedIV = encIv + }; + await SendPlainAsync(keyExchange); + + // 4. KeyExchangeAck (šifrovaně) + var ackData = await ReadMessageAsync(); + if (ackData == null) return false; + + var decrypted = _encryption.Decrypt(ackData); + if (decrypted == null) return false; + + var ack = MessageSerializer.Deserialize(decrypted) as KeyExchangeAck; + if (ack?.Status == "success") + { + _handshakeComplete = true; + return true; + } + return false; + } + + public void Disconnect(string reason = "User disconnected") + { + _cts?.Cancel(); + _tcpClient?.Close(); + _tcpClient = null; + _stream = null; + _encryption?.Dispose(); + _encryption = null; + + LobbyId = null; + JoinCode = null; + CurrentLobbyState = null; + MyRole = null; + MyTasks.Clear(); + PlayerPositions.Clear(); + Bodies.Clear(); + + Dispatcher.Post(() => OnDisconnected?.Invoke(reason)); + } + + #endregion + + #region Sending + + public void Send(Message message) + { + if (_stream == null || _encryption == null || !IsConnected) return; + + message.ClientSeq = Interlocked.Increment(ref _clientSeq); + if (string.IsNullOrEmpty(message.ActionId)) + { + message.ActionId = Guid.NewGuid().ToString("N").Substring(0, 8); + } + + var plain = MessageSerializer.Serialize(message); + var encrypted = _encryption.Encrypt(plain); + + lock (_sendLock) + { + try + { + SendData(encrypted); + } + catch (Exception ex) + { + Dispatcher.Post(() => OnError?.Invoke($"Send error: {ex.Message}")); + } + } + } + + private async Task SendPlainAsync(Message message) + { + if (_stream == null) return; + var data = MessageSerializer.Serialize(message); + await SendDataAsync(data); + } + + private void SendData(byte[] data) + { + if (_stream == null) return; + + var lengthBuffer = BitConverter.GetBytes(data.Length); + if (BitConverter.IsLittleEndian) + Array.Reverse(lengthBuffer); + + _stream.Write(lengthBuffer, 0, 4); + _stream.Write(data, 0, data.Length); + _stream.Flush(); + } + + private async Task SendDataAsync(byte[] data) + { + if (_stream == null) return; + + var lengthBuffer = BitConverter.GetBytes(data.Length); + if (BitConverter.IsLittleEndian) + Array.Reverse(lengthBuffer); + + await _stream.WriteAsync(lengthBuffer, 0, 4); + await _stream.WriteAsync(data, 0, data.Length); + await _stream.FlushAsync(); + } + + #endregion + + #region Receiving + + private async Task ReceiveLoopAsync(CancellationToken ct) + { + int decryptFailures = 0; + + try + { + while (!ct.IsCancellationRequested && IsConnected) + { + var data = await ReadMessageAsync(); + if (data == null) break; + + var decrypted = _encryption?.Decrypt(data); + if (decrypted == null) + { + decryptFailures++; + if (decryptFailures >= 3) + { + Disconnect("Too many decryption failures"); + return; + } + continue; + } + + decryptFailures = 0; + + var message = MessageSerializer.Deserialize(decrypted); + if (message != null) + { + ProcessMessage(message); + } + } + } + catch (Exception ex) when (!ct.IsCancellationRequested) + { + Disconnect($"Connection error: {ex.Message}"); + } + } + + private async Task ReadMessageAsync() + { + if (_stream == null) return null; + + var lengthBuffer = new byte[4]; + var read = await _stream.ReadAsync(lengthBuffer, 0, 4); + if (read < 4) return null; + + if (BitConverter.IsLittleEndian) + Array.Reverse(lengthBuffer); + var length = BitConverter.ToInt32(lengthBuffer, 0); + + if (length <= 0 || length > 1048576) return null; + + var buffer = new byte[length]; + var totalRead = 0; + while (totalRead < length) + { + read = await _stream.ReadAsync(buffer, totalRead, length - totalRead); + if (read == 0) return null; + totalRead += read; + } + + return buffer; + } + + private void ProcessMessage(Message message) + { + // Zpracujeme speciální typy + switch (message) + { + case CreateLobbyResponse r: + if (r.Success) + { + LobbyId = r.LobbyId; + JoinCode = r.JoinCode; + CurrentLobbyState = r.LobbyState; + } + break; + + case JoinLobbyResponse r: + if (r.Success) + { + LobbyId = r.LobbyId; + CurrentLobbyState = r.LobbyState; + JoinCode = r.LobbyState?.JoinCode; + } + break; + + case PositionBroadcast b: + ProcessPositionBroadcast(b); + break; + + case Pong p: + var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + Ping = (int)(now - p.ClientTime); + break; + + case GameEvent evt: + ProcessGameEvent(evt); + Dispatcher.Post(() => OnGameEvent?.Invoke(evt)); + break; + } + + Dispatcher.Post(() => OnMessage?.Invoke(message)); + } + + private void ProcessPositionBroadcast(PositionBroadcast broadcast) + { + PlayerPositions.Clear(); + foreach (var player in broadcast.Players) + { + PlayerPositions[player.ClientUuid] = player; + } + } + + private void ProcessGameEvent(GameEvent evt) + { + LastEventId = evt.EventId; + + switch (evt.EventType) + { + case "PlayerJoined": + // Add player to lobby state + var joinedPayload = evt.GetPayload(); + if (joinedPayload != null && CurrentLobbyState?.Players != null) + { + // Check if player already exists + bool exists = CurrentLobbyState.Players.Any(p => p.ClientUuid == joinedPayload.ClientUuid); + if (!exists) + { + CurrentLobbyState.Players.Add(new PlayerInfo + { + ClientUuid = joinedPayload.ClientUuid, + DisplayName = joinedPayload.DisplayName, + IsOwner = false, + IsReady = false, + State = PlayerState.Alive + }); + } + } + break; + + case "PlayerLeft": + // Remove player from lobby state + var leftPayload = evt.GetPayload(); + if (leftPayload != null && CurrentLobbyState?.Players != null) + { + CurrentLobbyState.Players.RemoveAll(p => p.ClientUuid == leftPayload.ClientUuid); + } + break; + + case "HostChanged": + // Update lobby owner + var hostPayload = evt.GetPayload(); + if (hostPayload != null && CurrentLobbyState != null) + { + CurrentLobbyState.OwnerId = hostPayload.NewHostId; + // Update IsOwner flag on all players + foreach (var player in CurrentLobbyState.Players) + { + player.IsOwner = player.ClientUuid == hostPayload.NewHostId; + } + } + break; + + case "GameStarting": + // Game is entering loading phase - update lobby state if available + if (CurrentLobbyState != null) + { + CurrentLobbyState.Phase = GamePhase.Loading; + } + break; + + case "MapDataReady": + // Map data received - store it and send confirmation + var mapDataPayload = evt.GetPayload(); + if (mapDataPayload != null && CurrentLobbyState != null) + { + CurrentLobbyState.MapData = mapDataPayload.MapData; + CurrentLobbyState.MapDataReady = true; + } + // Send confirmation to server + Send(new MapDataReceived()); + break; + + case "GameStarted": + // Game officially started - update phase + if (CurrentLobbyState != null) + { + CurrentLobbyState.Phase = GamePhase.Playing; + } + break; + + case "RoleAssigned": + var rolePayload = evt.GetPayload(); + if (rolePayload != null && rolePayload.ClientUuid == ClientUuid) + { + MyRole = rolePayload.Role; + MyTasks.Clear(); + if (rolePayload.Tasks != null) + { + MyTasks.AddRange(rolePayload.Tasks); + } + } + break; + + case "PlayerKilled": + var killPayload = evt.GetPayload(); + if (killPayload != null) + { + Bodies.Add(new Body + { + BodyId = killPayload.BodyId, + VictimId = killPayload.VictimId, + Location = killPayload.Location + }); + } + break; + + case "MeetingStarted": + if (CurrentLobbyState != null) + { + CurrentLobbyState.Phase = GamePhase.Meeting; + } + break; + + case "VotingClosed": + Bodies.Clear(); // Bodies zmizí po meetingu + if (CurrentLobbyState != null) + { + CurrentLobbyState.Phase = GamePhase.Playing; + } + break; + + case "GameEnded": + if (CurrentLobbyState != null) + { + CurrentLobbyState.Phase = GamePhase.Ended; + } + break; + } + } + + #endregion + + #region Game Actions + + public void CreateLobby(Position? center = null, int impostorCount = 1, int taskCount = 5, string? password = null, double playAreaRadius = 500) + { + Send(new CreateLobby + { + PlayAreaCenter = center, + PlayAreaRadius = playAreaRadius, + ImpostorCount = impostorCount, + TaskCount = taskCount, + Password = password + }); + } + + public void JoinLobby(string joinCode, string? password = null) + { + Send(new JoinLobby + { + JoinCode = joinCode.ToUpperInvariant(), + Password = password + }); + } + + public void LeaveLobby() + { + Send(new LeaveLobby()); + LobbyId = null; + JoinCode = null; + } + + public void StartGame() + { + Send(new StartGame()); + } + + public void ReturnToLobby() + { + Send(new ReturnToLobby()); + } + + public void UpdatePosition(Position position) + { + MyPosition = position; + Send(new UpdatePosition { Position = position }); + } + + public void Kill(string targetUuid) + { + Send(new KillAttempt { TargetClientUuid = targetUuid }); + } + + public void ReportBody(string bodyId) + { + Send(new ReportBody { BodyId = bodyId }); + } + + public void CallEmergencyMeeting() + { + Send(new CallEmergencyMeeting()); + } + + public void Vote(string? targetUuid) + { + Send(new CastVote { TargetClientUuid = targetUuid }); + } + + /// + /// Pokus o dokončení tasku. Server ověří že hráč je na správné pozici. + /// + public void CompleteTask(string taskId) + { + Send(new TaskComplete { TaskId = taskId }); + } + + public void SendPing() + { + Send(new Ping { ClientTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }); + } + + public void Reconnect(string lobbyId) + { + Send(new Reconnect { LobbyId = lobbyId, LastEventId = LastEventId }); + } + + #endregion + + #region Helpers + + public Body? FindNearbyBody(double maxDistance) + { + foreach (var body in Bodies) + { + if (MyPosition.DistanceTo(body.Location) <= maxDistance) + { + return body; + } + } + return null; + } + + public string? FindNearbyPlayer(double maxDistance, bool aliveOnly = true) + { + foreach (var (uuid, info) in PlayerPositions) + { + if (uuid == ClientUuid) continue; + if (aliveOnly && info.State != PlayerState.Alive) continue; + + if (MyPosition.DistanceTo(info.Position) <= maxDistance) + { + return uuid; + } + } + return null; + } + + public GameTask? FindNearbyTask(double maxDistance) + { + foreach (var task in MyTasks) + { + if (MyPosition.DistanceTo(task.Location) <= maxDistance) + { + return task; + } + } + return null; + } + + // Volat z Unity Update() pro zpracování callbacků + public void Update() + { + Dispatcher.ProcessPendingActions(); + } + + #endregion + + public void Dispose() + { + Disconnect("Disposed"); + _encryption?.Dispose(); + } +} +} diff --git a/Assets/ClientSDK/Protocol.cs b/Assets/ClientSDK/Protocol.cs new file mode 100644 index 0000000..f6d4d04 --- /dev/null +++ b/Assets/ClientSDK/Protocol.cs @@ -0,0 +1,1054 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Converters; +using System.Collections.Generic; +using System.Text; + +namespace GeoSus.Client +{ + #region Základní typy + +public struct Position +{ + [JsonProperty("lat")] + public double Lat { get; set; } + + [JsonProperty("lon")] + public double Lon { get; set; } + + public Position(double lat, double lon) + { + Lat = lat; + Lon = lon; + } + + // Haversine vzdálenost v metrech + public double DistanceTo(Position other) + { + const double R = 6371000; + var lat1 = Lat * Math.PI / 180; + var lat2 = other.Lat * Math.PI / 180; + var dLat = (other.Lat - Lat) * Math.PI / 180; + var dLon = (other.Lon - Lon) * Math.PI / 180; + + var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + + Math.Cos(lat1) * Math.Cos(lat2) * + Math.Sin(dLon / 2) * Math.Sin(dLon / 2); + var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); + + return R * c; + } +} + +[JsonConverter(typeof(StringEnumConverter))] +public enum PlayerRole { Crew, Impostor } + +[JsonConverter(typeof(StringEnumConverter))] +public enum PlayerState { Alive, Dead } + +[JsonConverter(typeof(StringEnumConverter))] +public enum GamePhase { Lobby, Loading, Playing, Meeting, Voting, Ended } + +[JsonConverter(typeof(StringEnumConverter))] +public enum TaskType { Instant } + +[JsonConverter(typeof(StringEnumConverter))] +public enum MeetingType { BodyReport, Emergency } + +[JsonConverter(typeof(StringEnumConverter))] +public enum SabotageType { CommsBlackout, CriticalMeltdown } + +[JsonConverter(typeof(StringEnumConverter))] +public enum SabotageState { Inactive, Active, Repaired } + +// Map data types for Overpass integration +[JsonConverter(typeof(StringEnumConverter))] +public enum PathType +{ + Footway, + Path, + Steps, + Cycleway, + Pedestrian, + Road, + Service, + Residential, + Track, + Other +} + +[JsonConverter(typeof(StringEnumConverter))] +public enum MapAreaType +{ + Park, + Garden, + Playground, + Forest, + Grass, + Water, + Other +} + +[JsonConverter(typeof(StringEnumConverter))] +public enum MapPOIType +{ + FoodDrink, // Restaurants, cafes, bars + Shop, // Shops + Health, // Pharmacies, hospitals + Transport, // Bus stops, parking + Culture, // Museums, theaters + Landmark, // Churches, monuments + Recreation, // Parks, playgrounds + Other +} + +#endregion + +#region Zprávy + +public abstract class Message +{ + [JsonProperty("type")] + public abstract string Type { get; } + + [JsonProperty("clientSeq")] + public int ClientSeq { get; set; } + + [JsonProperty("actionId")] + public string? ActionId { get; set; } +} + +// Handshake +public class ClientHello : Message +{ + public override string Type => "ClientHello"; + + [JsonProperty("protocolVersion")] + public string ProtocolVersion { get; set; } = "1.0"; + + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("displayName")] + public string? DisplayName { get; set; } +} + +public class ServerHello : Message +{ + public override string Type => "ServerHello"; + + [JsonProperty("rsaPublicKeyPem")] + public string RsaPublicKeyPem { get; set; } = ""; + + [JsonProperty("serverId")] + public string ServerId { get; set; } = ""; +} + +public class KeyExchange : Message +{ + public override string Type => "KeyExchange"; + + [JsonProperty("encryptedSessionKey")] + public string EncryptedSessionKey { get; set; } = ""; + + [JsonProperty("encryptedIV")] + public string EncryptedIV { get; set; } = ""; +} + +public class KeyExchangeAck : Message +{ + public override string Type => "KeyExchangeAck"; + + [JsonProperty("status")] + public string Status { get; set; } = ""; +} + +// Lobby +public class CreateLobby : Message +{ + public override string Type => "CreateLobby"; + + [JsonProperty("password")] + public string? Password { get; set; } + + [JsonProperty("playAreaCenter")] + public Position? PlayAreaCenter { get; set; } + + [JsonProperty("playAreaRadius")] + public double PlayAreaRadius { get; set; } = 500; + + [JsonProperty("impostorCount")] + public int ImpostorCount { get; set; } = 1; + + [JsonProperty("taskCount")] + public int TaskCount { get; set; } = 5; +} + +public class CreateLobbyResponse : Message +{ + public override string Type => "CreateLobbyResponse"; + + [JsonProperty("success")] + public bool Success { get; set; } + + [JsonProperty("joinCode")] + public string? JoinCode { get; set; } + + [JsonProperty("lobbyId")] + public string? LobbyId { get; set; } + + [JsonProperty("error")] + public string? Error { get; set; } + + [JsonProperty("lobbyState")] + public LobbyState? LobbyState { get; set; } +} + +public class JoinLobby : Message +{ + public override string Type => "JoinLobby"; + + [JsonProperty("joinCode")] + public string JoinCode { get; set; } = ""; + + [JsonProperty("password")] + public string? Password { get; set; } +} + +public class JoinLobbyResponse : Message +{ + public override string Type => "JoinLobbyResponse"; + + [JsonProperty("success")] + public bool Success { get; set; } + + [JsonProperty("lobbyId")] + public string? LobbyId { get; set; } + + [JsonProperty("error")] + public string? Error { get; set; } + + [JsonProperty("lobbyState")] + public LobbyState? LobbyState { get; set; } +} + +public class LeaveLobby : Message +{ + public override string Type => "LeaveLobby"; +} + +public class ReturnToLobby : Message +{ + public override string Type => "ReturnToLobby"; +} + +public class StartGame : Message +{ + public override string Type => "StartGame"; +} + +// Client confirmation that map data was received +public class MapDataReceived : Message +{ + public override string Type => "MapDataReceived"; +} + +// Hra +public class UpdatePosition : Message +{ + public override string Type => "UpdatePosition"; + + [JsonProperty("position")] + public Position Position { get; set; } +} + +public class PositionBroadcast : Message +{ + public override string Type => "PositionBroadcast"; + + [JsonProperty("players")] + public List Players { get; set; } = new List(); +} + +public class PlayerPositionInfo +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("position")] + public Position Position { get; set; } + + [JsonProperty("state")] + public PlayerState State { get; set; } +} + +public class KillAttempt : Message +{ + public override string Type => "KillAttempt"; + + [JsonProperty("targetClientUuid")] + public string TargetClientUuid { get; set; } = ""; +} + +public class ReportBody : Message +{ + public override string Type => "ReportBody"; + + [JsonProperty("bodyId")] + public string BodyId { get; set; } = ""; +} + +public class CallEmergencyMeeting : Message +{ + public override string Type => "CallEmergencyMeeting"; +} + +public class CastVote : Message +{ + public override string Type => "CastVote"; + + [JsonProperty("targetClientUuid")] + public string? TargetClientUuid { get; set; } +} + +public class TaskStart : Message +{ + public override string Type => "TaskStart"; + + [JsonProperty("taskId")] + public string TaskId { get; set; } = ""; +} + +public class TaskProgress : Message +{ + public override string Type => "TaskProgress"; + + [JsonProperty("taskId")] + public string TaskId { get; set; } = ""; + + [JsonProperty("step")] + public int Step { get; set; } = 1; +} + +public class TaskComplete : Message +{ + public override string Type => "TaskComplete"; + + [JsonProperty("taskId")] + public string TaskId { get; set; } = ""; +} + +public class Ping : Message +{ + public override string Type => "Ping"; + + [JsonProperty("clientTime")] + public long ClientTime { get; set; } +} + +public class Pong : Message +{ + public override string Type => "Pong"; + + [JsonProperty("clientTime")] + public long ClientTime { get; set; } + + [JsonProperty("serverTime")] + public long ServerTime { get; set; } +} + +public class Reconnect : Message +{ + public override string Type => "Reconnect"; + + [JsonProperty("lobbyId")] + public string LobbyId { get; set; } = ""; + + [JsonProperty("lastEventId")] + public long LastEventId { get; set; } +} + +public class Ack : Message +{ + public override string Type => "Ack"; + + [JsonProperty("ackedSeq")] + public int AckedSeq { get; set; } + + [JsonProperty("success")] + public bool Success { get; set; } + + [JsonProperty("error")] + public string? Error { get; set; } +} + +public class ErrorMessage : Message +{ + public override string Type => "Error"; + + [JsonProperty("errorCode")] + public string ErrorCode { get; set; } = ""; + + [JsonProperty("errorText")] + public string ErrorText { get; set; } = ""; +} + +// Sabotage messages +public class StartSabotage : Message +{ + public override string Type => "StartSabotage"; + + [JsonProperty("sabotageType")] + public SabotageType SabotageType { get; set; } +} + +public class ActivateRepairStation : Message +{ + public override string Type => "ActivateRepairStation"; + + [JsonProperty("stationId")] + public string StationId { get; set; } = ""; +} + +public class DeactivateRepairStation : Message +{ + public override string Type => "DeactivateRepairStation"; + + [JsonProperty("stationId")] + public string StationId { get; set; } = ""; +} + +#endregion + +#region Eventy + +public class GameEvent : Message +{ + public override string Type => "GameEvent"; + + [JsonProperty("eventId")] + public long EventId { get; set; } + + [JsonProperty("serverSeq")] + public long ServerSeq { get; set; } + + [JsonProperty("timestamp")] + public DateTime Timestamp { get; set; } + + [JsonProperty("actor")] + public string? Actor { get; set; } + + [JsonProperty("eventType")] + public string EventType { get; set; } = ""; + + [JsonProperty("payload")] + public JObject? Payload { get; set; } + + public T? GetPayload() where T : class + { + if (Payload == null) return null; + return Payload.ToObject(JsonSerializer.Create(JsonOptions.Default)); + } +} + +// Payload typy +public class HostChangedPayload +{ + [JsonProperty("newHostId")] + public string NewHostId { get; set; } = ""; + + [JsonProperty("previousHostId")] + public string PreviousHostId { get; set; } = ""; +} + +public class PlayerJoinedPayload +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("displayName")] + public string DisplayName { get; set; } = ""; +} + +public class PlayerLeftPayload +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("reason")] + public string? Reason { get; set; } +} + +// Loading phase payloads +public class GameStartingPayload +{ + [JsonProperty("message")] + public string Message { get; set; } = ""; +} + +public class MapDataReadyPayload +{ + [JsonProperty("mapData")] + public MapDataPayload? MapData { get; set; } +} + +public class PlayerMapDataReceivedPayload +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("displayName")] + public string DisplayName { get; set; } = ""; + + [JsonProperty("playersReady")] + public int PlayersReady { get; set; } + + [JsonProperty("totalPlayers")] + public int TotalPlayers { get; set; } +} + +public class GameStartedPayload +{ + [JsonProperty("impostorCount")] + public int ImpostorCount { get; set; } + + [JsonProperty("taskCount")] + public int TaskCount { get; set; } +} + +public class RoleAssignedPayload +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("role")] + public PlayerRole Role { get; set; } + + [JsonProperty("tasks")] + public List? Tasks { get; set; } +} + +public class PlayerKilledPayload +{ + [JsonProperty("victimId")] + public string VictimId { get; set; } = ""; + + [JsonProperty("killerId")] + public string KillerId { get; set; } = ""; + + [JsonProperty("bodyId")] + public string BodyId { get; set; } = ""; + + [JsonProperty("location")] + public Position Location { get; set; } +} + +public class BodyReportedPayload +{ + [JsonProperty("reporterId")] + public string ReporterId { get; set; } = ""; + + [JsonProperty("bodyId")] + public string BodyId { get; set; } = ""; + + [JsonProperty("victimId")] + public string VictimId { get; set; } = ""; +} + +public class EmergencyMeetingCalledPayload +{ + [JsonProperty("callerId")] + public string CallerId { get; set; } = ""; +} + +public class MeetingStartedPayload +{ + [JsonProperty("meetingId")] + public string MeetingId { get; set; } = ""; + + [JsonProperty("type")] + public MeetingType Type { get; set; } + + [JsonProperty("meetingLocation")] + public Position MeetingLocation { get; set; } + + [JsonProperty("arrivalDeadline")] + public DateTime ArrivalDeadline { get; set; } + + [JsonProperty("discussionEndTime")] + public DateTime? DiscussionEndTime { get; set; } + + [JsonProperty("votingEndTime")] + public DateTime VotingEndTime { get; set; } +} + +public class PlayerArrivedAtMeetingPayload +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("meetingId")] + public string MeetingId { get; set; } = ""; +} + +public class PlayerVotedPayload +{ + [JsonProperty("voterId")] + public string VoterId { get; set; } = ""; + + [JsonProperty("targetId")] + public string? TargetId { get; set; } +} + +public class VotingClosedPayload +{ + [JsonProperty("voteCounts")] + public Dictionary VoteCounts { get; set; } = new Dictionary(); + + [JsonProperty("ejectedPlayerId")] + public string? EjectedPlayerId { get; set; } + + [JsonProperty("wasTie")] + public bool WasTie { get; set; } +} + +public class PlayerEjectedPayload +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("role")] + public PlayerRole Role { get; set; } +} + +public class TaskCompletedPayload +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("taskId")] + public string TaskId { get; set; } = ""; + + [JsonProperty("totalCompleted")] + public int TotalCompleted { get; set; } + + [JsonProperty("totalTasks")] + public int TotalTasks { get; set; } +} + +public class GameEndedPayload +{ + [JsonProperty("winningFaction")] + public string WinningFaction { get; set; } = ""; + + [JsonProperty("reason")] + public string Reason { get; set; } = ""; + + [JsonProperty("winners")] + public List Winners { get; set; } = new List(); +} + +public class ReturnedToLobbyPayload +{ + [JsonProperty("message")] + public string Message { get; set; } = ""; +} + +// System message payload (admin broadcast) +public class SystemMessagePayload +{ + [JsonProperty("message")] + public string Message { get; set; } = ""; + + [JsonProperty("timestamp")] + public DateTime Timestamp { get; set; } +} + +// Sabotage event payloads +public class SabotageStartedPayload +{ + [JsonProperty("sabotageId")] + public string SabotageId { get; set; } = ""; + + [JsonProperty("type")] + public SabotageType Type { get; set; } + + [JsonProperty("initiatorId")] + public string InitiatorId { get; set; } = ""; + + [JsonProperty("deadline")] + public DateTime? Deadline { get; set; } + + [JsonProperty("repairStations")] + public List RepairStations { get; set; } = new List(); + + [JsonProperty("requiredSimultaneousRepairs")] + public int RequiredSimultaneousRepairs { get; set; } +} + +public class RepairStationInfo +{ + [JsonProperty("stationId")] + public string StationId { get; set; } = ""; + + [JsonProperty("name")] + public string Name { get; set; } = ""; + + [JsonProperty("location")] + public Position Location { get; set; } + + [JsonProperty("repairDurationMs")] + public int RepairDurationMs { get; set; } + + /// + /// Track locally if this station has been repaired + /// + [JsonIgnore] + public bool IsRepaired { get; set; } +} + +public class RepairStartedPayload +{ + [JsonProperty("sabotageId")] + public string SabotageId { get; set; } = ""; + + [JsonProperty("stationId")] + public string StationId { get; set; } = ""; + + [JsonProperty("playerId")] + public string PlayerId { get; set; } = ""; +} + +public class RepairStoppedPayload +{ + [JsonProperty("sabotageId")] + public string SabotageId { get; set; } = ""; + + [JsonProperty("stationId")] + public string StationId { get; set; } = ""; + + [JsonProperty("playerId")] + public string PlayerId { get; set; } = ""; +} + +public class SabotageRepairedPayload +{ + [JsonProperty("sabotageId")] + public string SabotageId { get; set; } = ""; + + [JsonProperty("type")] + public SabotageType Type { get; set; } + + [JsonProperty("repairerIds")] + public List RepairerIds { get; set; } = new List(); +} + +public class SabotageMeltdownPayload +{ + [JsonProperty("sabotageId")] + public string SabotageId { get; set; } = ""; +} + +#endregion + +#region State + +public class LobbyState +{ + [JsonProperty("lobbyId")] + public string LobbyId { get; set; } = ""; + + [JsonProperty("joinCode")] + public string JoinCode { get; set; } = ""; + + [JsonProperty("ownerId")] + public string? OwnerId { get; set; } + + [JsonProperty("phase")] + public GamePhase Phase { get; set; } + + [JsonProperty("players")] + public List Players { get; set; } = new List(); + + [JsonProperty("playAreaCenter")] + public Position PlayAreaCenter { get; set; } + + [JsonProperty("playAreaRadius")] + public double PlayAreaRadius { get; set; } + + [JsonProperty("impostorCount")] + public int ImpostorCount { get; set; } + + [JsonProperty("hasPassword")] + public bool HasPassword { get; set; } + + [JsonProperty("mapData")] + public MapDataPayload? MapData { get; set; } + + /// True if map data has been loaded (or Overpass is disabled) + [JsonProperty("mapDataReady")] + public bool MapDataReady { get; set; } = true; +} + +// Map data classes for rendering - compact format from server +public class MapDataPayload +{ + [JsonProperty("center")] + public Position Center { get; set; } + + [JsonProperty("radiusMeters")] + public double RadiusMeters { get; set; } + + /// Buildings: [[lat,lon,lat,lon,...], ...] + [JsonProperty("buildings")] + public List Buildings { get; set; } = new List(); + + /// Building types: ["residential", "commercial", ...] + [JsonProperty("buildingTypes")] + public List BuildingTypes { get; set; } = new List(); + + /// Pathways: [[lat,lon,lat,lon,...], ...] + [JsonProperty("pathways")] + public List Pathways { get; set; } = new List(); + + /// Pathway types: [0=footway, 1=steps, ...] + [JsonProperty("pathwayTypes")] + public List PathwayTypes { get; set; } = new List(); + + /// Areas (parks): [[lat,lon,lat,lon,...], ...] + [JsonProperty("areas")] + public List Areas { get; set; } = new List(); + + /// Area types + [JsonProperty("areaTypes")] + public List AreaTypes { get; set; } = new List(); + + /// POIs: [lat, lon, type, lat, lon, type, ...] + [JsonProperty("pOIs")] + public List POIs { get; set; } = new List(); + + // Helper methods for extracting structured data + public List GetBuildings() + { + var result = new List(); + for (int i = 0; i < Buildings.Count; i++) + { + var coords = Buildings[i]; + var outline = new List(); + for (int j = 0; j < coords.Length - 1; j += 2) + { + outline.Add(new Position(coords[j], coords[j + 1])); + } + result.Add(new MapBuilding + { + Id = i, + Outline = outline, + BuildingType = i < BuildingTypes.Count ? BuildingTypes[i] : "yes" + }); + } + return result; + } + + public List GetPathways() + { + var result = new List(); + for (int i = 0; i < Pathways.Count; i++) + { + var coords = Pathways[i]; + var points = new List(); + for (int j = 0; j < coords.Length - 1; j += 2) + { + points.Add(new Position(coords[j], coords[j + 1])); + } + result.Add(new MapPathway + { + Id = i, + Points = points, + PathType = i < PathwayTypes.Count ? (PathType)PathwayTypes[i] : PathType.Other + }); + } + return result; + } + + public List GetAreas() + { + var result = new List(); + for (int i = 0; i < Areas.Count; i++) + { + var coords = Areas[i]; + var outline = new List(); + for (int j = 0; j < coords.Length - 1; j += 2) + { + outline.Add(new Position(coords[j], coords[j + 1])); + } + result.Add(new MapArea + { + Id = i, + Outline = outline, + AreaType = i < AreaTypes.Count ? (MapAreaType)AreaTypes[i] : MapAreaType.Other + }); + } + return result; + } + + public List GetPOIs() + { + var result = new List(); + for (int i = 0; i < POIs.Count - 2; i += 3) + { + result.Add(new MapPOI + { + Id = i / 3, + Location = new Position(POIs[i], POIs[i + 1]), + POIType = (MapPOIType)(int)POIs[i + 2] + }); + } + return result; + } +} + +public class MapBuilding +{ + public long Id { get; set; } + public List Outline { get; set; } = new List(); + public string? Name { get; set; } + public string? BuildingType { get; set; } +} + +public class MapPathway +{ + public long Id { get; set; } + public List Points { get; set; } = new List(); + public PathType PathType { get; set; } + public string? Name { get; set; } +} + +public class MapArea +{ + public long Id { get; set; } + public List Outline { get; set; } = new List(); + public MapAreaType AreaType { get; set; } + public string? Name { get; set; } +} + +public class MapPOI +{ + public long Id { get; set; } + public Position Location { get; set; } + public string? Name { get; set; } + public MapPOIType POIType { get; set; } +} + +public class PlayerInfo +{ + [JsonProperty("clientUuid")] + public string ClientUuid { get; set; } = ""; + + [JsonProperty("displayName")] + public string DisplayName { get; set; } = ""; + + [JsonProperty("isOwner")] + public bool IsOwner { get; set; } + + [JsonProperty("isReady")] + public bool IsReady { get; set; } + + [JsonProperty("state")] + public PlayerState State { get; set; } +} + +public class GameTask +{ + [JsonProperty("taskId")] + public string TaskId { get; set; } = ""; + + [JsonProperty("name")] + public string Name { get; set; } = ""; + + [JsonProperty("location")] + public Position Location { get; set; } + + [JsonProperty("type")] + public TaskType Type { get; set; } = TaskType.Instant; +} + +public class Body +{ + [JsonProperty("bodyId")] + public string BodyId { get; set; } = ""; + + [JsonProperty("victimId")] + public string VictimId { get; set; } = ""; + + [JsonProperty("location")] + public Position Location { get; set; } +} + +#endregion + +#region Serializace + +public static class JsonOptions +{ + public static readonly JsonSerializerSettings Default = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + Converters = { new StringEnumConverter() } + }; +} + +public static class MessageSerializer +{ + private static readonly Dictionary MessageTypes = new Dictionary + { + ["ClientHello"] = typeof(ClientHello), + ["ServerHello"] = typeof(ServerHello), + ["KeyExchange"] = typeof(KeyExchange), + ["KeyExchangeAck"] = typeof(KeyExchangeAck), + ["CreateLobby"] = typeof(CreateLobby), + ["CreateLobbyResponse"] = typeof(CreateLobbyResponse), + ["JoinLobby"] = typeof(JoinLobby), + ["JoinLobbyResponse"] = typeof(JoinLobbyResponse), + ["LeaveLobby"] = typeof(LeaveLobby), + ["ReturnToLobby"] = typeof(ReturnToLobby), + ["StartGame"] = typeof(StartGame), + ["MapDataReceived"] = typeof(MapDataReceived), + ["UpdatePosition"] = typeof(UpdatePosition), + ["PositionBroadcast"] = typeof(PositionBroadcast), + ["KillAttempt"] = typeof(KillAttempt), + ["ReportBody"] = typeof(ReportBody), + ["CallEmergencyMeeting"] = typeof(CallEmergencyMeeting), + ["CastVote"] = typeof(CastVote), + ["TaskStart"] = typeof(TaskStart), + ["TaskProgress"] = typeof(TaskProgress), + ["TaskComplete"] = typeof(TaskComplete), + ["Ping"] = typeof(Ping), + ["Pong"] = typeof(Pong), + ["Reconnect"] = typeof(Reconnect), + ["Ack"] = typeof(Ack), + ["Error"] = typeof(ErrorMessage), + ["GameEvent"] = typeof(GameEvent) + }; + + public static byte[] Serialize(Message msg) + { + var json = JsonConvert.SerializeObject(msg, JsonOptions.Default); + return Encoding.UTF8.GetBytes(json); + } + + public static Message? Deserialize(byte[] data) + { + var json = Encoding.UTF8.GetString(data); + var jObj = JObject.Parse(json); + + var typeName = jObj["type"]?.Value(); + if (typeName == null || !MessageTypes.TryGetValue(typeName, out var type)) + return null; + + return (Message?)JsonConvert.DeserializeObject(json, type, JsonOptions.Default); + } +} + + #endregion +} diff --git a/Assets/ClientSDK/SimulatorClient.cs b/Assets/ClientSDK/SimulatorClient.cs new file mode 100644 index 0000000..733418a --- /dev/null +++ b/Assets/ClientSDK/SimulatorClient.cs @@ -0,0 +1,1992 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace GeoSus.Client +{ + /// +/// Comprehensive headless simulator client for testing all game aspects. +/// Supports both autonomous simulation and step-by-step controlled testing. +/// +public class SimulatorClient : IDisposable +{ + private readonly GameClient _client; + private readonly Random _random = new Random(); + private CancellationTokenSource? _cts; + private Task? _simulationTask; + private PlayerState _myState = PlayerState.Alive; + + #region Public Properties + + public string ClientUuid => _client.ClientUuid; + public string DisplayName => _client.DisplayName; + public bool IsConnected => _client.IsConnected; + public LobbyState? LobbyState => _client.CurrentLobbyState; + public string? LobbyId => _client.LobbyId; + public string? JoinCode => _client.JoinCode; + public PlayerRole? Role => _client.MyRole; + public PlayerState State => _myState; + public Position Position => _client.MyPosition; + public bool IsAlive => _myState == PlayerState.Alive; + public bool IsDead => _myState == PlayerState.Dead; + public bool IsImpostor => _client.MyRole == PlayerRole.Impostor; + public bool IsCrew => _client.MyRole == PlayerRole.Crew; + + // Tasks + public List MyTasks => _client.MyTasks; + public HashSet CompletedTaskIds { get; } = new HashSet(); + public string? CurrentTaskId { get; private set; } + + // Game state tracking + public int TotalKills { get; private set; } + public int TasksCompleted { get; private set; } + public int MeetingsAttended { get; private set; } + public int VotesCast { get; private set; } + public int BodiesReported { get; private set; } + public bool WasEjected { get; private set; } + public bool WasKilled { get; private set; } + public string? LastError { get; private set; } + public string? GameResult { get; private set; } + public string? WinningFaction { get; private set; } + public bool GameEnded => GameResult != null; + + // Nearby entities + public Dictionary NearbyPlayers => _client.PlayerPositions; + public List NearbyBodies => _client.Bodies; + + // Meeting state + public bool InMeeting { get; private set; } + public bool HasVotedThisMeeting { get; private set; } + public string? CurrentMeetingId { get; private set; } + public Position? MeetingLocation { get; private set; } + public DateTime? MeetingVotingEndTime { get; private set; } + public DateTime? MeetingDiscussionEndTime { get; private set; } + + /// + /// Returns true if the discussion phase has ended and voting can begin. + /// + public bool CanVote => InMeeting && + (!MeetingDiscussionEndTime.HasValue || DateTime.UtcNow >= MeetingDiscussionEndTime.Value); + + // Current game phase from lobby state + public string GamePhase => _client.CurrentLobbyState?.Phase.ToString() ?? "Unknown"; + + // Sabotage state + public bool SabotageActive { get; private set; } + public string? CurrentSabotageId { get; private set; } + public SabotageType? CurrentSabotageType { get; private set; } + public DateTime? SabotageDeadline { get; private set; } + public List RepairStations { get; private set; } = new List(); + public int SabotagesStarted { get; private set; } + public int SabotagesRepaired { get; private set; } + public bool IsRepairing { get; private set; } + public string? RepairingStationId { get; private set; } + + /// + /// Returns true if comms are blocked (can't report/meeting) + /// + public bool IsCommsBlocked => SabotageActive && CurrentSabotageType == SabotageType.CommsBlackout; + + /// + /// Returns true if there's a critical meltdown countdown + /// + public bool IsMeltdownActive => SabotageActive && CurrentSabotageType == SabotageType.CriticalMeltdown; + + #endregion + + #region Events + + public event Action? OnLog; + public event Action? OnError; + public event Action? OnGameEvent; + public event Action? OnKilled; + public event Action? OnEjected; + public event Action? OnGameEnded; + public event Action? OnMeetingStarted; + public event Action? OnMeetingEnded; + public event Action? OnSabotageStarted; + public event Action? OnSabotageRepaired; + public event Action? OnMeltdown; + + #endregion + + #region Constructor + + public SimulatorClient(string clientUuid, string displayName) + { + _client = new GameClient(clientUuid, displayName); + + _client.OnConnected += () => Log("Připojen k serveru"); + _client.OnDisconnected += (reason) => Log($"Odpojen: {reason}"); + _client.OnError += (error) => + { + LastError = error; + Log($"Chyba: {error}"); + OnError?.Invoke(error); + }; + + _client.OnMessage += HandleMessage; + _client.OnGameEvent += HandleGameEvent; + } + + #endregion + + #region Message Handlers + + private void HandleMessage(Message msg) + { + switch (msg) + { + case CreateLobbyResponse r: + if (r.Success) + Log($"Lobby vytvořeno: {r.JoinCode}"); + else + LogError($"Lobby creation failed"); + break; + + case JoinLobbyResponse r: + if (r.Success) + Log($"Připojen do lobby: {r.LobbyId}"); + else + LogError("Join lobby failed"); + break; + + case Ack a when !a.Success: + LastError = a.Error; + Log($"Akce zamítnuta: {a.Error}"); + break; + } + } + + private void HandleGameEvent(GameEvent evt) + { + OnGameEvent?.Invoke(evt.EventType, evt.Payload); + + switch (evt.EventType) + { + case "GameStarting": + Log("Game loading - fetching map data..."); + break; + + case "MapDataReady": + var mapPayload = evt.GetPayload(); + var buildingCount = mapPayload?.MapData?.Buildings?.Count ?? 0; + var pathwayCount = mapPayload?.MapData?.Pathways?.Count ?? 0; + Log($"Map data received: {buildingCount} buildings, {pathwayCount} pathways - sending confirmation"); + break; + + case "PlayerMapDataReceived": + var progressPayload = evt.GetPayload(); + if (progressPayload != null) + { + Log($"Player {progressPayload.DisplayName} ready ({progressPayload.PlayersReady}/{progressPayload.TotalPlayers})"); + } + break; + + case "GameStarted": + Log("Hra začala!"); + break; + + case "RoleAssigned": + var rolePayload = evt.GetPayload(); + if (rolePayload?.ClientUuid == ClientUuid) + { + Log($"Moje role: {rolePayload.Role}"); + } + break; + + case "PlayerKilled": + var killPayload = evt.GetPayload(); + Log($"Hráč {killPayload?.VictimId} byl zabit"); + if (killPayload?.VictimId == ClientUuid) + { + WasKilled = true; + _myState = PlayerState.Dead; + Log("BYL JSEM ZABIT!"); + OnKilled?.Invoke(); + } + break; + + case "MeetingStarted": + var meetingPayload = evt.GetPayload(); + InMeeting = true; + HasVotedThisMeeting = false; + CurrentMeetingId = meetingPayload?.MeetingId; + MeetingLocation = meetingPayload?.MeetingLocation; + MeetingVotingEndTime = meetingPayload?.VotingEndTime; + MeetingDiscussionEndTime = meetingPayload?.DiscussionEndTime; + Log($"MEETING ZAČAL! Typ: {meetingPayload?.Type}, Lokace: {MeetingLocation?.Lat:F4},{MeetingLocation?.Lon:F4}"); + if (MeetingDiscussionEndTime.HasValue) + { + var discussionMs = (MeetingDiscussionEndTime.Value - DateTime.UtcNow).TotalMilliseconds; + Log($" Diskuze do: {MeetingDiscussionEndTime.Value:HH:mm:ss} ({discussionMs:F0}ms)"); + } + OnMeetingStarted?.Invoke(); + break; + + case "VotingStarted": + Log("Hlasování začalo!"); + break; + + case "PlayerVoted": + var voteInfoPayload = evt.GetPayload(); + Log($"Hráč {voteInfoPayload?.VoterId} hlasoval"); + break; + + case "VotingClosed": + var votePayload = evt.GetPayload(); + InMeeting = false; + CurrentMeetingId = null; + MeetingLocation = null; + MeetingVotingEndTime = null; + MeetingDiscussionEndTime = null; + if (votePayload?.EjectedPlayerId != null) + { + Log($"Hráč {votePayload.EjectedPlayerId} byl VYHOZEN! (remíza: {votePayload.WasTie})"); + if (votePayload.EjectedPlayerId == ClientUuid) + { + WasEjected = true; + _myState = PlayerState.Dead; + Log("BYL JSEM VYHOZEN!"); + OnEjected?.Invoke(); + } + } + else + { + Log("Nikdo nebyl vyhozen (skip nebo remíza)"); + } + OnMeetingEnded?.Invoke(); + break; + + case "TaskCompleted": + var taskPayload = evt.GetPayload(); + if (taskPayload?.ClientUuid == ClientUuid) + { + TasksCompleted++; + CompletedTaskIds.Add(taskPayload.TaskId); + CurrentTaskId = null; + Log($"TASK DOKONČEN: {taskPayload.TaskId} (celkem: {TasksCompleted})"); + } + break; + + case "GameEnded": + var endPayload = evt.GetPayload(); + GameResult = endPayload?.Reason; + WinningFaction = endPayload?.WinningFaction; + Log($"=== HRA SKONČILA! Vítěz: {endPayload?.WinningFaction} - {endPayload?.Reason} ==="); + OnGameEnded?.Invoke(endPayload?.WinningFaction ?? "Unknown"); + break; + + // Sabotage events + case "SabotageStarted": + var sabStartPayload = evt.GetPayload(); + if (sabStartPayload != null) + { + SabotageActive = true; + CurrentSabotageId = sabStartPayload.SabotageId; + CurrentSabotageType = sabStartPayload.Type; + SabotageDeadline = sabStartPayload.Deadline; + RepairStations = sabStartPayload.RepairStations; + + Log($"⚠ SABOTÁŽ SPUŠTĚNA: {sabStartPayload.Type}!"); + if (sabStartPayload.Deadline.HasValue) + { + var remaining = (sabStartPayload.Deadline.Value - DateTime.UtcNow).TotalSeconds; + Log($" ⏱ DEADLINE: {remaining:F0}s - musíte opravit nebo prohrajete!"); + } + foreach (var station in sabStartPayload.RepairStations) + { + Log($" 📍 Stanice {station.Name}: {station.Location.Lat:F4},{station.Location.Lon:F4}"); + } + OnSabotageStarted?.Invoke(sabStartPayload.Type); + } + break; + + case "RepairStarted": + var repStartPayload = evt.GetPayload(); + if (repStartPayload?.PlayerId == ClientUuid) + { + IsRepairing = true; + RepairingStationId = repStartPayload.StationId; + Log($"🔧 Zahájil jsem opravu stanice {repStartPayload.StationId}"); + } + else + { + Log($"🔧 Hráč {repStartPayload?.PlayerId} opravuje {repStartPayload?.StationId}"); + } + break; + + case "RepairStopped": + var repStopPayload = evt.GetPayload(); + if (repStopPayload?.PlayerId == ClientUuid) + { + IsRepairing = false; + RepairingStationId = null; + Log($"❌ Oprava přerušena: {repStopPayload.StationId}"); + } + break; + + case "SabotageRepaired": + case "SabotageExpired": + var sabRepPayload = evt.GetPayload(); + if (sabRepPayload != null) + { + SabotageActive = false; + CurrentSabotageId = null; + CurrentSabotageType = null; + SabotageDeadline = null; + RepairStations.Clear(); + IsRepairing = false; + RepairingStationId = null; + SabotagesRepaired++; + + var repairers = sabRepPayload.RepairerIds.Count > 0 + ? string.Join(", ", sabRepPayload.RepairerIds) + : "auto-expire"; + Log($"✅ SABOTÁŽ OPRAVENA: {sabRepPayload.Type} (opravili: {repairers})"); + OnSabotageRepaired?.Invoke(sabRepPayload.Type); + } + break; + + case "SabotageMeltdown": + var meltdownPayload = evt.GetPayload(); + Log($"💥 MELTDOWN! Sabotáž nebyla opravena včas - Impostoři vyhráli!"); + OnMeltdown?.Invoke(); + break; + } + } + + #endregion + + #region Connection & Lobby + + public async Task ConnectAsync(string host, int port) + { + return await _client.ConnectAsync(host, port); + } + + public void Disconnect() + { + StopSimulation(); + _client.Disconnect(); + } + + public void Update() + { + _client.Update(); + } + + public void CreateLobby(Position center, int impostorCount = 1, int taskCount = 5, string? password = null) + { + _client.CreateLobby(center, impostorCount, taskCount, password); + } + + /// + /// Async wrapper for CreateLobby - waits for lobby to be created + /// + public async Task CreateLobbyAsync(string? password, Position center, double radius = 500, int impostorCount = 1, int taskCount = 5) + { + _client.CreateLobby(center, impostorCount, taskCount, password, radius); + + // Wait for lobby creation response + for (int i = 0; i < 50; i++) // 5 seconds timeout + { + Update(); + if (!string.IsNullOrEmpty(JoinCode)) + return true; + if (LastError != null && LastError.Contains("lobby")) + return false; + await Task.Delay(100); + } + return false; + } + + public void JoinLobby(string joinCode, string? password = null) + { + _client.JoinLobby(joinCode, password); + } + + /// + /// Async wrapper for JoinLobby - waits for join confirmation + /// + public async Task JoinLobbyAsync(string joinCode, string? password = null) + { + _client.JoinLobby(joinCode, password); + + // Wait for join response + for (int i = 0; i < 50; i++) + { + Update(); + if (!string.IsNullOrEmpty(LobbyId)) + return true; + if (LastError != null && LastError.Contains("join")) + return false; + await Task.Delay(100); + } + return false; + } + + public void LeaveLobby() + { + _client.LeaveLobby(); + } + + public void StartGame() + { + _client.StartGame(); + } + + /// + /// Async wrapper for StartGame - waits for game to start + /// + public async Task StartGameAsync() + { + _client.StartGame(); + + // Wait for game start + for (int i = 0; i < 50; i++) + { + Update(); + if (Role.HasValue) + return true; + await Task.Delay(100); + } + return false; + } + + #endregion + + #region Movement + + public void MoveTo(Position position) + { + _client.UpdatePosition(position); + } + + public void MoveTowards(Position target, double maxDistanceMeters) + { + var current = Position; + var distance = current.DistanceTo(target); + + if (distance <= maxDistanceMeters) + { + MoveTo(target); + } + else + { + var ratio = maxDistanceMeters / distance; + var newPos = new Position( + current.Lat + (target.Lat - current.Lat) * ratio, + current.Lon + (target.Lon - current.Lon) * ratio + ); + MoveTo(newPos); + } + } + + public Position GetRandomPositionNear(Position center, double radiusMeters) + { + var angle = _random.NextDouble() * 2 * Math.PI; + var distance = _random.NextDouble() * radiusMeters; + var lat = center.Lat + (distance / 111000) * Math.Cos(angle); + var lon = center.Lon + (distance / (111000 * Math.Cos(center.Lat * Math.PI / 180))) * Math.Sin(angle); + return new Position(lat, lon); + } + + #endregion + + #region Kill Actions (Impostor) + + public bool TryKill(string targetUuid) + { + if (!IsImpostor || !IsAlive) + { + Log("Nemohu zabíjet - nejsem živý impostor"); + return false; + } + + Log($">>> POKUS O ZABITÍ: {targetUuid}"); + _client.Kill(targetUuid); + TotalKills++; + return true; + } + + public string? FindKillTarget(double maxDistance = 5.0) + { + return _client.FindNearbyPlayer(maxDistance, aliveOnly: true); + } + + public bool TryKillNearby(double maxDistance = 5.0) + { + var target = FindKillTarget(maxDistance); + if (target != null) + { + return TryKill(target); + } + return false; + } + + #endregion + + #region Report & Meeting Actions + + public bool TryReportBody(string bodyId) + { + if (!IsAlive) + { + Log("Nemohu reportovat - jsem mrtvý"); + return false; + } + + Log($">>> REPORTUJI TĚLO: {bodyId}"); + _client.ReportBody(bodyId); + BodiesReported++; + return true; + } + + public Body? FindNearbyBody(double maxDistance = 5.0) + { + return _client.FindNearbyBody(maxDistance); + } + + public bool TryReportNearbyBody(double maxDistance = 5.0) + { + var body = FindNearbyBody(maxDistance); + if (body != null) + { + return TryReportBody(body.BodyId); + } + return false; + } + + public bool TryCallEmergencyMeeting() + { + if (!IsAlive) + { + Log("Nemohu svolat meeting - jsem mrtvý"); + return false; + } + + Log(">>> SVOLÁVÁM EMERGENCY MEETING!"); + _client.CallEmergencyMeeting(); + return true; + } + + #endregion + + #region Voting Actions + + public bool TryVote(string? targetUuid) + { + if (!IsAlive) + { + Log("Nemohu hlasovat - jsem mrtvý"); + return false; + } + + if (!InMeeting) + { + Log("Nemohu hlasovat - není meeting"); + return false; + } + + var voteTarget = targetUuid ?? "SKIP"; + Log($">>> HLASUJI PRO: {voteTarget}"); + _client.Vote(targetUuid); + VotesCast++; + HasVotedThisMeeting = true; + MeetingsAttended++; + return true; + } + + public bool TryVoteSkip() + { + return TryVote(null); + } + + public bool TryVoteRandom() + { + if (!InMeeting || !IsAlive) return false; + + // Pick a random alive player (or skip) + var alivePlayers = NearbyPlayers.Values + .Where(p => p.State == PlayerState.Alive && p.ClientUuid != ClientUuid) + .ToList(); + + if (alivePlayers.Count == 0 || _random.NextDouble() < 0.3) + { + return TryVoteSkip(); + } + + var target = alivePlayers[_random.Next(alivePlayers.Count)]; + return TryVote(target.ClientUuid); + } + + /// + /// Vote for the player with the most suspicion (for crew) or a random crew (for impostor) + /// + public bool TryVoteSmart() + { + if (!InMeeting || !IsAlive) return false; + + var alivePlayers = NearbyPlayers.Values + .Where(p => p.State == PlayerState.Alive && p.ClientUuid != ClientUuid) + .ToList(); + + if (alivePlayers.Count == 0) + { + return TryVoteSkip(); + } + + // Impostors vote randomly among non-impostors or skip + if (IsImpostor) + { + if (_random.NextDouble() < 0.5) + { + return TryVoteSkip(); + } + var target = alivePlayers[_random.Next(alivePlayers.Count)]; + return TryVote(target.ClientUuid); + } + + // Crew votes randomly for now (could be smarter with suspicion tracking) + if (_random.NextDouble() < 0.2) + { + return TryVoteSkip(); + } + var crewTarget = alivePlayers[_random.Next(alivePlayers.Count)]; + return TryVote(crewTarget.ClientUuid); + } + + #endregion + + #region Task Actions + + public bool TryCompleteTask(string taskId) + { + if (IsImpostor) + { + Log($"Nemohu dělat tasky - jsem impostor"); + return false; + } + + if (CompletedTaskIds.Contains(taskId)) + { + Log($"Task {taskId} již dokončen"); + return false; + } + + Log($">>> DOKONČUJI TASK: {taskId}"); + _client.CompleteTask(taskId); + return true; + } + + public GameTask? FindNearbyTask(double maxDistance = 5.0) + { + foreach (var task in MyTasks) + { + if (CompletedTaskIds.Contains(task.TaskId)) continue; + + if (Position.DistanceTo(task.Location) <= maxDistance) + { + return task; + } + } + return null; + } + + public GameTask? GetNextIncompleteTask() + { + return MyTasks.FirstOrDefault(t => !CompletedTaskIds.Contains(t.TaskId)); + } + + public int GetRemainingTaskCount() + { + return MyTasks.Count - CompletedTaskIds.Count; + } + + #endregion + + #region Sabotage Actions (Impostor) + + /// + /// Start a sabotage (impostor only) + /// + public bool TrySabotage(SabotageType sabotageType) + { + if (!IsImpostor) + { + Log("Nemohu sabotovat - nejsem impostor"); + return false; + } + + if (!IsAlive) + { + Log("Nemohu sabotovat - jsem mrtvý"); + return false; + } + + if (SabotageActive) + { + Log($"Nemohu sabotovat - již probíhá sabotáž: {CurrentSabotageType}"); + return false; + } + + if (InMeeting) + { + Log("Nemohu sabotovat - probíhá meeting"); + return false; + } + + Log($">>> SPOUŠTÍM SABOTÁŽ: {sabotageType}"); + _client.Send(new StartSabotage { SabotageType = sabotageType }); + SabotagesStarted++; + return true; + } + + /// + /// Start repairing at a repair station (crew or impostor can repair) + /// + public bool TryStartRepair(string stationId) + { + if (!IsAlive) + { + Log("Nemohu opravovat - jsem mrtvý"); + return false; + } + + if (!SabotageActive) + { + Log("Nemohu opravovat - není aktivní sabotáž"); + return false; + } + + if (IsRepairing) + { + Log($"Již opravuji stanici: {RepairingStationId}"); + return false; + } + + var station = RepairStations.FirstOrDefault(s => s.StationId == stationId); + if (station == null) + { + Log($"Opravná stanice {stationId} neexistuje"); + return false; + } + + var distance = Position.DistanceTo(station.Location); + if (distance > 5.0) + { + Log($"Opravná stanice {stationId} je příliš daleko: {distance:F1}m"); + return false; + } + + Log($">>> ZAČÍNÁM OPRAVU stanice: {stationId}"); + _client.Send(new ActivateRepairStation { StationId = stationId }); + return true; + } + + /// + /// Stop repairing current station + /// + public bool TryStopRepair() + { + if (!IsRepairing || RepairingStationId == null) + { + Log("Nejsem u opravné stanice"); + return false; + } + + Log($">>> UKONČUJI OPRAVU stanice: {RepairingStationId}"); + _client.Send(new DeactivateRepairStation { StationId = RepairingStationId }); + return true; + } + + /// + /// Find nearest repair station for current sabotage + /// + public RepairStationInfo? FindNearestRepairStation(double maxDistance = double.MaxValue) + { + if (!SabotageActive) return null; + + RepairStationInfo? nearest = null; + double nearestDist = maxDistance; + + foreach (var station in RepairStations) + { + if (station.IsRepaired) continue; + + var dist = Position.DistanceTo(station.Location); + if (dist < nearestDist) + { + nearestDist = dist; + nearest = station; + } + } + + return nearest; + } + + /// + /// Move to nearest repair station + /// + public bool MoveTowardsNearestRepairStation(double speed = 1.0) + { + var station = FindNearestRepairStation(); + if (station == null) return false; + + MoveTowards(station.Location, speed); + return true; + } + + /// + /// Check if at repair station and can start repair + /// + public bool IsAtRepairStation(string stationId, double maxDistance = 5.0) + { + var station = RepairStations.FirstOrDefault(s => s.StationId == stationId); + if (station == null) return false; + + return Position.DistanceTo(station.Location) <= maxDistance; + } + + /// + /// Automatic repair: find nearest station, move to it, and start repair + /// + public bool TryAutoRepair() + { + if (!SabotageActive) return false; + if (IsRepairing) return false; + + var station = FindNearestRepairStation(5.0); + if (station != null && !station.IsRepaired) + { + return TryStartRepair(station.StationId); + } + + return false; + } + + #endregion + + #region Autonomous Simulation + + public void StartSimulation() + { + if (_simulationTask != null) return; + + _cts = new CancellationTokenSource(); + _simulationTask = Task.Run(() => SimulationLoopAsync(_cts.Token)); + Log("Simulace spuštěna"); + } + + public void StopSimulation() + { + if (_cts == null) return; + + _cts.Cancel(); + try { _simulationTask?.Wait(1000); } catch { } + _simulationTask = null; + _cts = null; + Log("Simulace zastavena"); + } + + private async Task SimulationLoopAsync(CancellationToken ct) + { + var center = LobbyState?.PlayAreaCenter ?? new Position(50.0, 14.0); + var radius = LobbyState?.PlayAreaRadius ?? 500; + + MoveTo(center); + + while (!ct.IsCancellationRequested && !GameEnded) + { + try + { + Update(); + + // In meeting - handle voting + if (InMeeting) + { + // First move to meeting location + if (MeetingLocation.HasValue && IsAlive) + { + var meetLoc = MeetingLocation.Value; + var distToMeeting = Position.DistanceTo(meetLoc); + if (distToMeeting > 5) + { + MoveTowards(meetLoc, 20); + await Task.Delay(200, ct); + continue; + } + } + + if (IsAlive && !HasVotedThisMeeting) + { + // Wait a bit before voting (simulate discussion) + await Task.Delay(500 + _random.Next(1500), ct); + Update(); + + if (InMeeting && !HasVotedThisMeeting) + { + TryVoteSmart(); + } + } + + // Wait for meeting to end + await Task.Delay(300, ct); + continue; + } + + // Impostor logic + if (IsImpostor && IsAlive) + { + await ImpostorActionAsync(center, radius, ct); + } + // Alive Crew logic + else if (IsCrew && IsAlive) + { + await CrewActionAsync(center, radius, ct); + } + // Dead player (ghost) - can still do tasks + else if (IsCrew && IsDead) + { + await GhostTaskActionAsync(center, radius, ct); + } + // Dead impostor - just watch + else if (IsImpostor && IsDead) + { + await Task.Delay(1000, ct); + } + + await Task.Delay(300, ct); + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + Log($"Simulation error: {ex.Message}"); + await Task.Delay(1000, ct); + } + } + + Log("Simulace dokončena"); + } + + private async Task ImpostorActionAsync(Position center, double radius, CancellationToken ct) + { + // Try to kill nearby player + var target = FindKillTarget(8.0); + if (target != null) + { + // Higher chance to kill if alone with victim + var nearbyCount = NearbyPlayers.Values.Count(p => + p.State == PlayerState.Alive && + p.ClientUuid != ClientUuid && + Position.DistanceTo(p.Position) < 20); + + var killChance = nearbyCount <= 1 ? 0.9 : 0.3; + + if (_random.NextDouble() < killChance) + { + TryKill(target); + await Task.Delay(300, ct); + + // Move away from body + var escapePos = GetRandomPositionNear(Position, 30); + MoveTowards(escapePos, 15); + await Task.Delay(500, ct); + return; + } + } + + // Wander around looking for isolated targets + var wanderPos = GetRandomPositionNear(center, radius * 0.6); + MoveTowards(wanderPos, 5); + } + + private async Task CrewActionAsync(Position center, double radius, CancellationToken ct) + { + // Priority 1: Report nearby bodies + var body = FindNearbyBody(8.0); + if (body != null) + { + Log($"NAŠEL JSEM TĚLO: {body.BodyId}"); + // Move closer if needed + if (Position.DistanceTo(body.Location) > 3) + { + MoveTowards(body.Location, 3); + await Task.Delay(200, ct); + } + TryReportBody(body.BodyId); + await Task.Delay(500, ct); + return; + } + + // Priority 2: Do tasks (instant completion) + var nearbyTask = FindNearbyTask(5.0); + if (nearbyTask != null) + { + TryCompleteTask(nearbyTask.TaskId); + await Task.Delay(500, ct); + return; + } + + // Priority 3: Move to next task + var nextTask = GetNextIncompleteTask(); + if (nextTask != null) + { + MoveTowards(nextTask.Location, 5); + } + else + { + // All tasks done, wander + var wanderPos = GetRandomPositionNear(center, radius * 0.5); + MoveTowards(wanderPos, 3); + } + } + + private async Task GhostTaskActionAsync(Position center, double radius, CancellationToken ct) + { + // Ghosts can complete tasks faster (no danger) + var nearbyTask = FindNearbyTask(10.0); + if (nearbyTask != null) + { + TryCompleteTask(nearbyTask.TaskId); + await Task.Delay(100, ct); + return; + } + + // Move to next task + var nextTask = GetNextIncompleteTask(); + if (nextTask != null) + { + // Ghosts move much faster + MoveTowards(nextTask.Location, 20); + } + } + + #endregion + + #region Logging + + private void Log(string message) + { + OnLog?.Invoke(message); + } + + private void LogError(string message) + { + LastError = message; + OnError?.Invoke(message); + Log($"ERROR: {message}"); + } + + #endregion + + #region Stats + + public string GetStats() + { + return $"[{DisplayName}] Role={Role}, State={State}, Kills={TotalKills}, Tasks={TasksCompleted}/{MyTasks.Count}, " + + $"Reports={BodiesReported}, Votes={VotesCast}, Meetings={MeetingsAttended}, " + + $"Killed={WasKilled}, Ejected={WasEjected}"; + } + + public void PrintDetailedStats() + { + Log("========== DETAILED STATS =========="); + Log($" Display Name: {DisplayName}"); + Log($" Client UUID: {ClientUuid}"); + Log($" Role: {Role}"); + Log($" Final State: {State}"); + Log($" --- Actions ---"); + Log($" Kills Attempted: {TotalKills}"); + Log($" Tasks Completed: {TasksCompleted}/{MyTasks.Count}"); + Log($" Bodies Reported: {BodiesReported}"); + Log($" Votes Cast: {VotesCast}"); + Log($" Meetings Attended: {MeetingsAttended}"); + Log($" --- Fate ---"); + Log($" Was Killed: {WasKilled}"); + Log($" Was Ejected: {WasEjected}"); + Log($" --- Game Result ---"); + Log($" Winning Faction: {WinningFaction}"); + Log($" Result: {GameResult}"); + Log("====================================="); + } + + #endregion + + public void Dispose() + { + StopSimulation(); + _client.Dispose(); + } + + #region Map Data Access + + /// + /// Get the map data payload from the current lobby state (if available) + /// + public MapDataPayload? MapData => _client.CurrentLobbyState?.MapData; + + /// + /// Check if map data is available for the current lobby + /// + public bool HasMapData => MapData != null; + + /// + /// Check if map data loading is complete (true if loaded or Overpass disabled) + /// + public bool IsMapDataReady => _client.CurrentLobbyState?.MapDataReady ?? false; + + /// + /// Get the play area center from lobby state + /// + public Position? PlayAreaCenter => _client.CurrentLobbyState?.PlayAreaCenter; + + /// + /// Get the play area radius from lobby state + /// + public double PlayAreaRadius => _client.CurrentLobbyState?.PlayAreaRadius ?? 500; + + #endregion +} + +/// +/// Comprehensive test suite for Overpass API and reachability testing +/// +public class OverpassApiTests +{ + private readonly string _serverHost; + private readonly int _serverPort; + private readonly Action? _logger; + + public OverpassApiTests(string serverHost = "localhost", int serverPort = 7777, Action? logger = null) + { + _serverHost = serverHost; + _serverPort = serverPort; + _logger = logger; + } + + private void Log(string message) + { + _logger?.Invoke(message); + Console.WriteLine(message); + } + + /// + /// Run all Overpass API tests + /// + public async Task RunAllTestsAsync(Position testCenter, double testRadius = 500) + { + var results = new OverpassTestResults(); + + Log("═══════════════════════════════════════════════════════════════"); + Log(" OVERPASS API & REACHABILITY TEST SUITE"); + Log("═══════════════════════════════════════════════════════════════"); + Log($"Test Center: {testCenter.Lat:F6}, {testCenter.Lon:F6}"); + Log($"Test Radius: {testRadius}m"); + Log(""); + + // Test 1: Create lobby and verify map data is fetched + Log("TEST 1: Map Data Fetch on Lobby Creation"); + Log("─────────────────────────────────────────"); + results.MapDataFetchTest = await TestMapDataFetchAsync(testCenter, testRadius); + LogTestResult("Map Data Fetch", results.MapDataFetchTest); + await Task.Delay(1000); // Delay between tests + + // Test 2: Verify map data structure + Log(""); + Log("TEST 2: Map Data Structure Validation"); + Log("─────────────────────────────────────────"); + results.MapDataStructureTest = await TestMapDataStructureAsync(testCenter, testRadius); + LogTestResult("Map Data Structure", results.MapDataStructureTest); + await Task.Delay(1000); // Delay between tests + + // Test 3: Verify task positions are on reachable paths + Log(""); + Log("TEST 3: Task Position Reachability"); + Log("─────────────────────────────────────────"); + results.TaskReachabilityTest = await TestTaskReachabilityAsync(testCenter, testRadius); + LogTestResult("Task Reachability", results.TaskReachabilityTest); + await Task.Delay(1000); // Delay between tests + + // Test 4: Verify repair station positions are reachable + Log(""); + Log("TEST 4: Repair Station Reachability (Sabotage)"); + Log("─────────────────────────────────────────"); + results.RepairStationReachabilityTest = await TestRepairStationReachabilityAsync(testCenter, testRadius); + LogTestResult("Repair Station Reachability", results.RepairStationReachabilityTest); + await Task.Delay(1000); // Delay between tests + + // Test 5: Verify positions are within play area + Log(""); + Log("TEST 5: Play Area Boundary Validation"); + Log("─────────────────────────────────────────"); + results.PlayAreaBoundaryTest = await TestPlayAreaBoundaryAsync(testCenter, testRadius); + LogTestResult("Play Area Boundary", results.PlayAreaBoundaryTest); + await Task.Delay(1000); // Delay between tests + + // Test 6: Test multiple lobby creations for consistency + Log(""); + Log("TEST 6: Map Data Consistency Across Lobbies"); + Log("─────────────────────────────────────────"); + results.ConsistencyTest = await TestMapDataConsistencyAsync(testCenter, testRadius); + LogTestResult("Map Data Consistency", results.ConsistencyTest); + + // Summary + Log(""); + Log("═══════════════════════════════════════════════════════════════"); + Log(" TEST SUMMARY"); + Log("═══════════════════════════════════════════════════════════════"); + results.PrintSummary(Log); + + return results; + } + + private void LogTestResult(string testName, TestResult result) + { + var status = result.Passed ? "✓ PASS" : "✗ FAIL"; + Log($" [{status}] {testName}"); + if (!string.IsNullOrEmpty(result.Details)) + { + foreach (var line in result.Details.Split('\n')) + { + Log($" {line}"); + } + } + if (!result.Passed && !string.IsNullOrEmpty(result.Error)) + { + Log($" ERROR: {result.Error}"); + } + } + + /// + /// Wait for map data to be loaded with polling and timeout + /// Note: Map data is now fetched when game starts, not on lobby creation + /// + private async Task WaitForMapDataAsync(SimulatorClient client, SimulatorClient? otherClient, int timeoutMs = 10000, int pollIntervalMs = 500) + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + + while (sw.ElapsedMilliseconds < timeoutMs) + { + // Update client to receive any pending messages + client.Update(); + otherClient?.Update(); + + // Check if client has map data loaded + if (client.HasMapData) + { + Log($" Map data loaded after {sw.ElapsedMilliseconds}ms"); + return true; + } + + // Also check if MapDataReady indicates no data expected (Overpass disabled) + if (client.IsMapDataReady && !client.HasMapData) + { + Log($" Map data not available (Overpass may be disabled)"); + return false; + } + + await Task.Delay(pollIntervalMs); + } + + Log($" Timeout waiting for map data ({timeoutMs}ms)"); + return false; + } + + /// + /// Wait for game to start after map data is received (Loading phase complete) + /// + private async Task WaitForGameStartAfterMapDataAsync(SimulatorClient owner, SimulatorClient player, int timeoutMs = 20000, int pollIntervalMs = 500) + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + + while (sw.ElapsedMilliseconds < timeoutMs) + { + owner.Update(); + player.Update(); + + // Game is fully started when phase is Playing and roles are assigned + if (owner.GamePhase == "Playing" && owner.Role.HasValue) + { + Log($" Game started after {sw.ElapsedMilliseconds}ms"); + return true; + } + + await Task.Delay(pollIntervalMs); + } + + Log($" Timeout waiting for game start ({timeoutMs}ms) - Phase: {owner.GamePhase}"); + return false; + } + + /// + /// Start a game and wait for map data to be loaded + /// The new flow is: StartGame -> Loading phase -> MapData fetched -> All confirm -> Playing + /// + private async Task StartGameAndWaitForMapDataAsync(SimulatorClient owner, SimulatorClient player, int timeoutMs = 25000) + { + Log(" Starting game (this will trigger map data fetch)..."); + owner.StartGame(); + + // Wait for map data first (update both clients so both send confirmations) + if (!await WaitForMapDataAsync(owner, player, timeoutMs)) + { + return false; + } + + // Ensure both clients update to send MapDataReceived confirmation + for (int i = 0; i < 10; i++) + { + owner.Update(); + player.Update(); + await Task.Delay(100); + } + + // Wait for game to actually start (after all confirmations) + return await WaitForGameStartAfterMapDataAsync(owner, player, 10000); + } + + private async Task TestMapDataFetchAsync(Position center, double radius) + { + var result = new TestResult { TestName = "MapDataFetch" }; + SimulatorClient? owner = null; + SimulatorClient? player = null; + + try + { + // Create owner and player (need 2 players to start game) + owner = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "MapOwner"); + player = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "MapPlayer"); + + // Connect owner + Log(" Connecting owner to server..."); + if (!await owner.ConnectAsync(_serverHost, _serverPort)) + { + result.Error = $"Failed to connect owner to server at {_serverHost}:{_serverPort}"; + return result; + } + + // Create lobby with specific center + Log($" Creating lobby at {center.Lat:F6}, {center.Lon:F6} with radius {radius}m..."); + if (!await owner.CreateLobbyAsync(null, center, radius)) + { + result.Error = $"Failed to create lobby: {owner.LastError ?? "Unknown error"}"; + return result; + } + Log($" Lobby created: {owner.JoinCode}"); + + // Connect player + Log(" Connecting player to server..."); + if (!await player.ConnectAsync(_serverHost, _serverPort)) + { + result.Error = "Failed to connect player"; + return result; + } + + // Join player to lobby + if (!await player.JoinLobbyAsync(owner.JoinCode!)) + { + result.Error = "Failed to join player to lobby"; + return result; + } + + // Start game - this triggers map data fetch + Log(" Starting game (triggers Overpass API fetch)..."); + if (!await StartGameAndWaitForMapDataAsync(owner, player, timeoutMs: 25000)) + { + result.Error = "Timeout waiting for map data (Overpass API may be slow or unavailable)"; + return result; + } + + if (owner.HasMapData) + { + var mapData = owner.MapData!; + var buildings = mapData.GetBuildings(); + var pathways = mapData.GetPathways(); + var areas = mapData.GetAreas(); + var pois = mapData.GetPOIs(); + result.Passed = true; + result.Details = $"Buildings: {buildings.Count}\n" + + $"Pathways: {pathways.Count}\n" + + $"Areas: {areas.Count}\n" + + $"POIs: {pois.Count}"; + } + else + { + result.Error = "Map data was not received after game start"; + } + } + catch (Exception ex) + { + result.Error = ex.Message; + } + finally + { + owner?.Dispose(); + player?.Dispose(); + } + + return result; + } + + private async Task TestMapDataStructureAsync(Position center, double radius) + { + var result = new TestResult { TestName = "MapDataStructure" }; + SimulatorClient? owner = null; + SimulatorClient? player = null; + + try + { + owner = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "StructOwner"); + player = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "StructPlayer"); + + if (!await owner.ConnectAsync(_serverHost, _serverPort)) + { + result.Error = "Failed to connect owner"; + return result; + } + + if (!await owner.CreateLobbyAsync(null, center, radius)) + { + result.Error = "Failed to create lobby"; + return result; + } + + if (!await player.ConnectAsync(_serverHost, _serverPort)) + { + result.Error = "Failed to connect player"; + return result; + } + + if (!await player.JoinLobbyAsync(owner.JoinCode!)) + { + result.Error = "Failed to join player to lobby"; + return result; + } + + // Start game to trigger map data fetch + Log(" Starting game (triggers Overpass API fetch)..."); + if (!await StartGameAndWaitForMapDataAsync(owner, player, timeoutMs: 25000)) + { + result.Error = "Timeout waiting for map data"; + return result; + } + + if (!owner.HasMapData) + { + result.Error = "No map data available"; + return result; + } + + var mapData = owner.MapData!; + var buildings = mapData.GetBuildings(); + var pathways = mapData.GetPathways(); + var areas = mapData.GetAreas(); + var pois = mapData.GetPOIs(); + var issues = new List(); + var stats = new List(); + + // Check buildings have valid outlines + int validBuildings = 0; + foreach (var building in buildings) + { + if (building.Outline == null || building.Outline.Count < 3) + { + issues.Add($"Building {building.Id}: Invalid outline (< 3 points)"); + } + else + { + validBuildings++; + } + } + stats.Add($"Valid buildings: {validBuildings}/{buildings.Count}"); + + // Check pathways have valid points + int validPathways = 0; + foreach (var pathway in pathways) + { + if (pathway.Points == null || pathway.Points.Count < 2) + { + issues.Add($"Pathway {pathway.Id}: Invalid points (< 2)"); + } + else + { + validPathways++; + } + } + stats.Add($"Valid pathways: {validPathways}/{pathways.Count}"); + + // Check areas + int validAreas = 0; + foreach (var area in areas) + { + if (area.Outline == null || area.Outline.Count < 3) + { + issues.Add($"Area {area.Id}: Invalid outline"); + } + else + { + validAreas++; + } + } + stats.Add($"Valid areas: {validAreas}/{areas.Count}"); + + // Check POIs have valid positions + int validPOIs = 0; + foreach (var poi in pois) + { + if (poi.Location.Lat != 0 || poi.Location.Lon != 0) + { + validPOIs++; + } + } + stats.Add($"Valid POIs: {validPOIs}/{pois.Count}"); + + result.Passed = issues.Count == 0; + result.Details = string.Join("\n", stats); + if (issues.Count > 0) + { + result.Error = string.Join("; ", issues.Take(5)); + } + } + catch (Exception ex) + { + result.Error = ex.Message; + } + finally + { + owner?.Dispose(); + player?.Dispose(); + } + + return result; + } + + private async Task TestTaskReachabilityAsync(Position center, double radius) + { + var result = new TestResult { TestName = "TaskReachability" }; + SimulatorClient? owner = null; + SimulatorClient? player = null; + + try + { + // Create lobby owner + owner = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "TaskOwner"); + player = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "TaskPlayer"); + + if (!await owner.ConnectAsync(_serverHost, _serverPort)) + { + result.Error = "Owner failed to connect"; + return result; + } + + if (!await owner.CreateLobbyAsync(null, center, radius, 1, 5)) + { + result.Error = "Failed to create lobby"; + return result; + } + + var joinCode = owner.JoinCode; + + if (!await player.ConnectAsync(_serverHost, _serverPort)) + { + result.Error = "Player failed to connect"; + return result; + } + + if (!await player.JoinLobbyAsync(joinCode!)) + { + result.Error = "Player failed to join"; + return result; + } + + // Start game - this triggers map data fetch + Log(" Starting game (triggers Overpass API fetch)..."); + if (!await StartGameAndWaitForMapDataAsync(owner, player, timeoutMs: 25000)) + { + result.Error = "Timeout waiting for game to start"; + return result; + } + + // Check task positions + var mapData = owner.MapData; + var tasks = owner.MyTasks.Count > 0 ? owner.MyTasks : player.MyTasks; + + if (tasks.Count == 0) + { + result.Error = "No tasks were assigned"; + return result; + } + + int tasksInPlayArea = 0; + int tasksOnPathways = 0; + var details = new List(); + + foreach (var task in tasks) + { + var distFromCenter = task.Location.DistanceTo(center); + bool inPlayArea = distFromCenter <= radius; + if (inPlayArea) tasksInPlayArea++; + + // Check if task is near any pathway + bool nearPathway = false; + if (mapData != null) + { + var pathways = mapData.GetPathways(); + foreach (var pathway in pathways) + { + foreach (var point in pathway.Points) + { + if (task.Location.DistanceTo(point) < 15) // Within 15m of pathway + { + nearPathway = true; + break; + } + } + if (nearPathway) break; + } + } + if (nearPathway) tasksOnPathways++; + + details.Add($"{task.Name}: {distFromCenter:F0}m from center, {(inPlayArea ? "in" : "OUT OF")} play area, {(nearPathway ? "near" : "NOT NEAR")} pathway"); + } + + result.Passed = tasksInPlayArea == tasks.Count; + result.Details = $"Tasks in play area: {tasksInPlayArea}/{tasks.Count}\n" + + $"Tasks near pathways: {tasksOnPathways}/{tasks.Count}\n" + + string.Join("\n", details.Take(3)); + + if (tasksInPlayArea < tasks.Count) + { + result.Error = $"{tasks.Count - tasksInPlayArea} tasks are outside play area!"; + } + } + catch (Exception ex) + { + result.Error = ex.Message; + } + finally + { + owner?.Dispose(); + player?.Dispose(); + } + + return result; + } + + private async Task TestRepairStationReachabilityAsync(Position center, double radius) + { + var result = new TestResult { TestName = "RepairStationReachability" }; + SimulatorClient? owner = null; + SimulatorClient? player = null; + + try + { + // Create lobby with 2 players + owner = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "SabOwner"); + player = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "SabPlayer"); + + if (!await owner.ConnectAsync(_serverHost, _serverPort)) { result.Error = "Owner connect failed"; return result; } + if (!await owner.CreateLobbyAsync(null, center, radius, 1, 3)) { result.Error = "Create lobby failed"; return result; } + + var joinCode = owner.JoinCode; + + if (!await player.ConnectAsync(_serverHost, _serverPort)) { result.Error = "Player connect failed"; return result; } + if (!await player.JoinLobbyAsync(joinCode!)) { result.Error = "Join lobby failed"; return result; } + + // Start game - this triggers map data fetch + Log(" Starting game (triggers Overpass API fetch)..."); + if (!await StartGameAndWaitForMapDataAsync(owner, player, timeoutMs: 25000)) + { + result.Error = "Timeout waiting for game to start"; + return result; + } + + // Find impostor and trigger sabotage + var impostor = owner.IsImpostor ? owner : (player.IsImpostor ? player : null); + if (impostor == null) + { + result.Error = "No impostor found"; + return result; + } + + // Wait for sabotage cooldown + await Task.Delay(1000); + + // Try to start a sabotage + impostor.TrySabotage(SabotageType.CriticalMeltdown); + await Task.Delay(1500); + + // Check repair station positions + var crew = owner.IsCrew ? owner : player; + var stations = crew.RepairStations; + + if (stations.Count == 0) + { + // Sabotage might have been blocked, try CommsBlackout + impostor.TrySabotage(SabotageType.CommsBlackout); + await Task.Delay(1000); + stations = crew.RepairStations; + } + + if (stations.Count == 0) + { + result.Passed = true; // No sabotage was possible, but that's OK + result.Details = "Sabotage cooldown active, skipping repair station test"; + return result; + } + + int stationsInPlayArea = 0; + var details = new List(); + + foreach (var station in stations) + { + var distFromCenter = station.Location.DistanceTo(center); + bool inPlayArea = distFromCenter <= radius; + if (inPlayArea) stationsInPlayArea++; + + details.Add($"{station.Name}: {distFromCenter:F0}m from center ({(inPlayArea ? "OK" : "OUT!")})"); + } + + result.Passed = stationsInPlayArea == stations.Count; + result.Details = $"Stations in play area: {stationsInPlayArea}/{stations.Count}\n" + + string.Join("\n", details); + + if (stationsInPlayArea < stations.Count) + { + result.Error = $"{stations.Count - stationsInPlayArea} repair stations are outside play area!"; + } + } + catch (Exception ex) + { + result.Error = ex.Message; + } + finally + { + owner?.Dispose(); + player?.Dispose(); + } + + return result; + } + + private async Task TestPlayAreaBoundaryAsync(Position center, double radius) + { + var result = new TestResult { TestName = "PlayAreaBoundary" }; + SimulatorClient? owner = null; + SimulatorClient? player = null; + + try + { + owner = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "BoundOwner"); + player = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), "BoundPlayer"); + + if (!await owner.ConnectAsync(_serverHost, _serverPort)) { result.Error = "Connect failed"; return result; } + if (!await owner.CreateLobbyAsync(null, center, radius)) { result.Error = "Create lobby failed"; return result; } + + if (!await player.ConnectAsync(_serverHost, _serverPort)) { result.Error = "Player connect failed"; return result; } + if (!await player.JoinLobbyAsync(owner.JoinCode!)) { result.Error = "Join lobby failed"; return result; } + + // Start game - this triggers map data fetch + Log(" Starting game (triggers Overpass API fetch)..."); + if (!await StartGameAndWaitForMapDataAsync(owner, player, timeoutMs: 25000)) + { + result.Passed = true; + result.Details = "No map data to validate (Overpass might be disabled or timed out)"; + return result; + } + + if (!owner.HasMapData) + { + result.Passed = true; + result.Details = "No map data to validate (Overpass might be disabled)"; + return result; + } + + var mapData = owner.MapData!; + var pathways = mapData.GetPathways(); + var pois = mapData.GetPOIs(); + int outsidePathwayPoints = 0; + int totalPathwayPoints = 0; + int outsidePOIs = 0; + int totalPOIs = pois.Count; + + // Check all pathway points + foreach (var pathway in pathways) + { + foreach (var point in pathway.Points) + { + totalPathwayPoints++; + if (point.DistanceTo(center) > radius * 1.1) // Allow 10% margin + { + outsidePathwayPoints++; + } + } + } + + // Check all POIs + foreach (var poi in pois) + { + if (poi.Location.DistanceTo(center) > radius * 1.1) + { + outsidePOIs++; + } + } + + // Allow some points outside (Overpass query might include slightly outside data) + double pathwayOutsidePercent = totalPathwayPoints > 0 ? (outsidePathwayPoints * 100.0 / totalPathwayPoints) : 0; + double poiOutsidePercent = totalPOIs > 0 ? (outsidePOIs * 100.0 / totalPOIs) : 0; + + result.Passed = pathwayOutsidePercent < 20 && poiOutsidePercent < 20; + result.Details = $"Pathway points outside boundary: {outsidePathwayPoints}/{totalPathwayPoints} ({pathwayOutsidePercent:F1}%)\n" + + $"POIs outside boundary: {outsidePOIs}/{totalPOIs} ({poiOutsidePercent:F1}%)"; + + if (!result.Passed) + { + result.Error = "Too many map elements outside play area boundary"; + } + } + catch (Exception ex) + { + result.Error = ex.Message; + } + finally + { + owner?.Dispose(); + player?.Dispose(); + } + + return result; + } + + private async Task TestMapDataConsistencyAsync(Position center, double radius) + { + var result = new TestResult { TestName = "MapDataConsistency" }; + var owners = new List(); + var players = new List(); + + try + { + // Create 3 lobbies at the same location and compare map data + var mapDataResults = new List<(int buildings, int pathways, int pois)>(); + + for (int i = 0; i < 3; i++) + { + Log($" Creating lobby {i + 1}/3..."); + var owner = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), $"ConsOwner{i}"); + var player = new SimulatorClient(Guid.NewGuid().ToString("N").Substring(0, 8), $"ConsPlayer{i}"); + owners.Add(owner); + players.Add(player); + + if (!await owner.ConnectAsync(_serverHost, _serverPort)) continue; + if (!await owner.CreateLobbyAsync(null, center, radius)) continue; + + if (!await player.ConnectAsync(_serverHost, _serverPort)) continue; + if (!await player.JoinLobbyAsync(owner.JoinCode!)) continue; + + // Start game to trigger map data fetch + if (await StartGameAndWaitForMapDataAsync(owner, player, timeoutMs: 25000)) + { + if (owner.HasMapData) + { + var md = owner.MapData!; + mapDataResults.Add((md.GetBuildings().Count, md.GetPathways().Count, md.GetPOIs().Count)); + } + } + + await Task.Delay(300); + } + + if (mapDataResults.Count < 2) + { + result.Passed = true; + result.Details = "Not enough successful map data fetches to compare consistency"; + return result; + } + + // Check if all results are the same (should be cached) + var first = mapDataResults[0]; + bool allSame = mapDataResults.All(r => r == first); + + result.Passed = allSame; + result.Details = string.Join("\n", mapDataResults.Select((r, i) => + $"Lobby {i + 1}: {r.buildings} buildings, {r.pathways} pathways, {r.pois} POIs")); + + if (!allSame) + { + result.Error = "Map data varies between lobbies at same location (caching issue?)"; + } + } + catch (Exception ex) + { + result.Error = ex.Message; + } + finally + { + foreach (var o in owners) o?.Dispose(); + foreach (var p in players) p?.Dispose(); + } + + return result; + } +} + +/// +/// Results from a single test +/// +public class TestResult +{ + public string TestName { get; set; } = ""; + public bool Passed { get; set; } + public string? Details { get; set; } + public string? Error { get; set; } +} + +/// +/// Aggregate results from all Overpass tests +/// +public class OverpassTestResults +{ + public TestResult MapDataFetchTest { get; set; } = new(); + public TestResult MapDataStructureTest { get; set; } = new(); + public TestResult TaskReachabilityTest { get; set; } = new(); + public TestResult RepairStationReachabilityTest { get; set; } = new(); + public TestResult PlayAreaBoundaryTest { get; set; } = new(); + public TestResult ConsistencyTest { get; set; } = new(); + + public int TotalTests => 6; + public int PassedTests => new[] { MapDataFetchTest, MapDataStructureTest, TaskReachabilityTest, + RepairStationReachabilityTest, PlayAreaBoundaryTest, ConsistencyTest }.Count(t => t.Passed); + public int FailedTests => TotalTests - PassedTests; + public bool AllPassed => PassedTests == TotalTests; + + public void PrintSummary(Action log) + { + log($" Total Tests: {TotalTests}"); + log($" Passed: {PassedTests}"); + log($" Failed: {FailedTests}"); + log(""); + + if (AllPassed) + { + log(" ✓ ALL OVERPASS TESTS PASSED!"); + } + else + { + log(" ✗ SOME TESTS FAILED:"); + if (!MapDataFetchTest.Passed) log($" - Map Data Fetch: {MapDataFetchTest.Error}"); + if (!MapDataStructureTest.Passed) log($" - Map Data Structure: {MapDataStructureTest.Error}"); + if (!TaskReachabilityTest.Passed) log($" - Task Reachability: {TaskReachabilityTest.Error}"); + if (!RepairStationReachabilityTest.Passed) log($" - Repair Station Reachability: {RepairStationReachabilityTest.Error}"); + if (!PlayAreaBoundaryTest.Passed) log($" - Play Area Boundary: {PlayAreaBoundaryTest.Error}"); + if (!ConsistencyTest.Passed) log($" - Consistency: {ConsistencyTest.Error}"); + } + } +} +} diff --git a/Assets/GameManager/GameManager.cs b/Assets/GameManager/GameManager.cs new file mode 100644 index 0000000..d34a7c3 --- /dev/null +++ b/Assets/GameManager/GameManager.cs @@ -0,0 +1,42 @@ +using UnityEngine; +using GeoSus.Client; +using Subsystems; +using System.Threading; +using System.Threading.Tasks; +using System.Collections; +using System.Collections.Generic; +/* + GameManager - hlavn tida pro sprvu hry + GameManager_Network - subsystm pro sprvu komunikace se serverem + GameManager_Game - subsystm pro sprvu logiky hry (sabote, tasky, atd.) + GameManager_Map - subsystm pro sprvu mapy a prosted + GameManager_Input - subsystm pro sprvu vstupu od hre + GameManager_UI - subsystm pro sprvu uivatelskho rozhran + GamaManager_Stats - subsystm pro sprvu statistik pro server + */ +public class GameManager : MonoBehaviour +{ + protected GameClient gameClient; + protected GameManager_Network networkSubsystem; + public string displayName; + + void Start() + { + DontDestroyOnLoad(this); + if (displayName == null || displayName == "") + { + displayName = "Player_" + Random.Range(1000, 9999).ToString(); + } + gameClient = new GameClient(GenerateUUID(), displayName); + networkSubsystem = new GameManager_Network(gameClient); + networkSubsystem.checkState(); + } + + + protected string GenerateUUID() + { + string UUID = System.Guid.NewGuid().ToString(); + Debug.Log(UUID); + return UUID; + } +} diff --git a/Assets/GameManager/GameManager_Network.cs b/Assets/GameManager/GameManager_Network.cs new file mode 100644 index 0000000..e86f9c9 --- /dev/null +++ b/Assets/GameManager/GameManager_Network.cs @@ -0,0 +1,38 @@ +using GeoSus.Client; +using System.Threading.Tasks; +using UnityEngine; + +namespace Subsystems +{ + public class GameManager_Network + { + private const string _serverAddress = "geosus.honzuvkod.dev"; + private const int _serverPort = 7777; + private GameClient _gameClient; + public async void checkState() + { + while (true) + { + Task state = _gameClient.ConnectAsync(_serverAddress, _serverPort); + await state; + if (state.Result) + { + Debug.Log("Connected to server."); + _gameClient.Disconnect(); + } + else + { + Debug.Log("Failed to connect to server"); + } + await Task.Delay(5000); + } + } + public GameManager_Network(GameClient gameClient) + { + _gameClient = gameClient; + } + + } + +} + diff --git a/Assets/GameManager/ITask.cs b/Assets/GameManager/ITask.cs new file mode 100644 index 0000000..9d4ffd8 --- /dev/null +++ b/Assets/GameManager/ITask.cs @@ -0,0 +1,53 @@ +using GeoSus.Client; +using System; +using UnityEngine; + +public enum TaskType +{ + Task //TODO: Typy kol +} + + + +public interface ITask +{ + public string TaskID { get; } // Uniktn ID kolu pro server + public TaskType TaskType { get; } // Typ kolu + public string TaskName { get; } // Viditeln nzev kolu + public (float, float) TaskLocation { get; } // Polohy na map + public bool IsCompleted { get; } // Stav dokonen kolu + + void Initialize(Action onCompleted); // Vytvoen tasku + naten postupu + void ExitTask(Action onExit); // Pi oputn kolu poslat hotovo / uloit postup / reset + void Complete(); // Oznait kol jako dokonen, poslat na server a zavt + +} +/* Ukzokov implementace ITask +public class Wires : ITask{ + public string TaskID { get; set; } // Uniktn ID kolu pro server + public TaskType TaskType { get; set; } // Typ kolu + public string TaskName { get; set; } // Viditeln nzev kolu + public (float, float) TaskLocation { get; set; } // Poloha na map + public bool IsCompleted { get; private set; } // Stav dokonen kolu + private Action _onCompleted; + + public void Initialize(Action onCompleted) // Vytvoen tasku + { + IsCompleted = false; + _onCompleted = onCompleted; + } + public void ExitTask(Action onExit) //Zaven tasku + { + onExit?.Invoke(this); + } + public void Complete() // Dokonen tasku a zaven + { + IsCompleted = true; + _onCompleted?.Invoke(this); + ExitTask(null); + } + + + +} +*/ \ No newline at end of file diff --git a/Assets/Scenes/Client.unity b/Assets/Scenes/Client.unity new file mode 100644 index 0000000..27b3ab7 --- /dev/null +++ b/Assets/Scenes/Client.unity @@ -0,0 +1,362 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &442151206 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 442151208} + - component: {fileID: 442151207} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &442151207 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 442151206} + m_Enabled: 1 + serializedVersion: 11 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!4 &442151208 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 442151206} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &1010702369 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1010702372} + - component: {fileID: 1010702371} + - component: {fileID: 1010702370} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1010702370 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1010702369} + m_Enabled: 1 +--- !u!20 &1010702371 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1010702369} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1010702372 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1010702369} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1353866370 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1353866371} + - component: {fileID: 1353866372} + m_Layer: 0 + m_Name: Game + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1353866371 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1353866370} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 56.6539, y: 0, z: 0.06448} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1353866372 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1353866370} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9e2c3e4ba4e36ea40a686e58feca4d2b, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::GameManager + displayName: Player +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1010702372} + - {fileID: 442151208} + - {fileID: 1353866371} -- 2.49.1 From eeaf092780c0dc1e4114e9b4f0e1d095628179e5 Mon Sep 17 00:00:00 2001 From: Dominik Stepanek Date: Sun, 1 Feb 2026 20:10:52 +0100 Subject: [PATCH 2/3] double patch --- Assets/GameManager/ITask.cs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Assets/GameManager/ITask.cs b/Assets/GameManager/ITask.cs index 9d4ffd8..11d1362 100644 --- a/Assets/GameManager/ITask.cs +++ b/Assets/GameManager/ITask.cs @@ -4,43 +4,43 @@ using UnityEngine; public enum TaskType { - Task //TODO: Typy kol + Task //TODO: Typy úkolù } public interface ITask { - public string TaskID { get; } // Uniktn ID kolu pro server - public TaskType TaskType { get; } // Typ kolu - public string TaskName { get; } // Viditeln nzev kolu - public (float, float) TaskLocation { get; } // Polohy na map - public bool IsCompleted { get; } // Stav dokonen kolu + public string TaskID { get; } // Unikátní ID úkolu pro server + public TaskType TaskType { get; } // Typ úkolu + public string TaskName { get; } // Viditelný název úkolu + public (double, double) TaskLocation { get; } // Polohy na mapì + public bool IsCompleted { get; } // Stav dokonèení úkolu - void Initialize(Action onCompleted); // Vytvoen tasku + naten postupu - void ExitTask(Action onExit); // Pi oputn kolu poslat hotovo / uloit postup / reset - void Complete(); // Oznait kol jako dokonen, poslat na server a zavt + void Initialize(Action onCompleted); // Vytvoøení tasku + naètení postupu + void ExitTask(Action onExit); // Pøi opuštìní úkolu poslat hotovo / uložit postup / reset + void Complete(); // Oznaèit úkol jako dokonèený, poslat na server a zavøít } -/* Ukzokov implementace ITask +/* Ukázoková implementace ITask public class Wires : ITask{ - public string TaskID { get; set; } // Uniktn ID kolu pro server - public TaskType TaskType { get; set; } // Typ kolu - public string TaskName { get; set; } // Viditeln nzev kolu - public (float, float) TaskLocation { get; set; } // Poloha na map - public bool IsCompleted { get; private set; } // Stav dokonen kolu + public string TaskID { get; set; } // Unikátní ID úkolu pro server + public TaskType TaskType { get; set; } // Typ úkolu + public string TaskName { get; set; } // Viditelný název úkolu + public (double, double) TaskLocation { get; set; } // Poloha na mapì + public bool IsCompleted { get; private set; } // Stav dokonèení úkolu private Action _onCompleted; - public void Initialize(Action onCompleted) // Vytvoen tasku + public void Initialize(Action onCompleted) // Vytvoøení tasku { IsCompleted = false; _onCompleted = onCompleted; } - public void ExitTask(Action onExit) //Zaven tasku + public void ExitTask(Action onExit) //Zavøení tasku { onExit?.Invoke(this); } - public void Complete() // Dokonen tasku a zaven + public void Complete() // Dokonèení tasku a zavøení { IsCompleted = true; _onCompleted?.Invoke(this); -- 2.49.1 From 94a40e3d141d0a25c709679b2f1355e5be7bb64e Mon Sep 17 00:00:00 2001 From: trubkokrtek Date: Thu, 19 Feb 2026 16:14:30 +0100 Subject: [PATCH 3/3] Added Lobbies --- Assets/GameManager/GameManager.cs | 59 +- Assets/GameManager/GameManager_Network.cs | 122 +- Assets/GameManager/GameManager_UI.cs | 35 + Assets/Scenes/Client.unity | 1885 +++++++++++++++++++- ProjectSettings/EditorBuildSettings.asset | 7 +- ProjectSettings/UnityConnectSettings.asset | 2 +- 6 files changed, 2097 insertions(+), 13 deletions(-) create mode 100644 Assets/GameManager/GameManager_UI.cs diff --git a/Assets/GameManager/GameManager.cs b/Assets/GameManager/GameManager.cs index d34a7c3..0d480df 100644 --- a/Assets/GameManager/GameManager.cs +++ b/Assets/GameManager/GameManager.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using System.Collections; using System.Collections.Generic; +using TMPro; /* GameManager - hlavn tida pro sprvu hry GameManager_Network - subsystm pro sprvu komunikace se serverem @@ -16,10 +17,19 @@ using System.Collections.Generic; */ public class GameManager : MonoBehaviour { - protected GameClient gameClient; + [Header("Subsystems")] protected GameManager_Network networkSubsystem; + protected GameManager_UI uiSubsystem; + + + protected GameClient gameClient; + [Header("Player Info")] public string displayName; - + + [Header("UI Elements")] + public Canvas JoinCreateLobby; + public Canvas InLobby; + void Start() { DontDestroyOnLoad(this); @@ -27,11 +37,20 @@ public class GameManager : MonoBehaviour { displayName = "Player_" + Random.Range(1000, 9999).ToString(); } - gameClient = new GameClient(GenerateUUID(), displayName); + gameClient = new GameClient(GenerateUUID(), /*displayName*/ GenerateUsername()); + uiSubsystem = new GameManager_UI(gameClient, JoinCreateLobby, InLobby); networkSubsystem = new GameManager_Network(gameClient); - networkSubsystem.checkState(); + networkSubsystem.OpenConection(); } - + private void Update() + { + if (gameClient.CurrentLobbyState != null) + { + uiSubsystem.UpdateLobbyUI(); + + } + } + protected string GenerateUUID() { @@ -39,4 +58,34 @@ public class GameManager : MonoBehaviour Debug.Log(UUID); return UUID; } + protected string GenerateUsername() + { + string Username = Random.Range(0,10).ToString() + Random.Range(0, 10).ToString() + Random.Range(0, 10).ToString() + Random.Range(0, 10).ToString(); + Debug.Log(Username); + return Username; + } + public void CreateLobbyButton() + { + networkSubsystem.CrateLobby(50.0755, 14.4378); + } + public void JoinLobbyButton() + { + TMP_InputField joinCode = JoinCreateLobby.transform.Find("InputCode").GetComponent(); + if (joinCode.text != null && joinCode.text != "") + { + networkSubsystem.JoinLobby(joinCode.text); + } + else + { + Debug.Log("Join code is empty!"); + } + } + public void LeaveLobbyButton() + { + networkSubsystem.LeaveLobby(); + } + void OnApplicationQuit() + { + gameClient.Disconnect(); + } } diff --git a/Assets/GameManager/GameManager_Network.cs b/Assets/GameManager/GameManager_Network.cs index e86f9c9..bdfca9d 100644 --- a/Assets/GameManager/GameManager_Network.cs +++ b/Assets/GameManager/GameManager_Network.cs @@ -1,4 +1,5 @@ using GeoSus.Client; +using System.Collections; using System.Threading.Tasks; using UnityEngine; @@ -9,7 +10,7 @@ namespace Subsystems private const string _serverAddress = "geosus.honzuvkod.dev"; private const int _serverPort = 7777; private GameClient _gameClient; - public async void checkState() + public async void OpenConection() { while (true) { @@ -18,7 +19,7 @@ namespace Subsystems if (state.Result) { Debug.Log("Connected to server."); - _gameClient.Disconnect(); + break; } else { @@ -29,10 +30,123 @@ namespace Subsystems } public GameManager_Network(GameClient gameClient) { - _gameClient = gameClient; + _gameClient = gameClient; + RegisterEventHandlers(); + } + public void RegisterEventHandlers() + { + _gameClient.OnConnected += OnConnected; + _gameClient.OnDisconnected += OnDisconnected; + _gameClient.OnError += OnError; + _gameClient.OnMessage += OnMessage; + _gameClient.OnGameEvent += OnGameEvent; + } + private void OnConnected() + { + Debug.Log("Successfully connected to the server."); + } + private void OnDisconnected(string reason) + { + Debug.Log($"Host disconnected due to {reason}"); + } + private void OnError(string error) + { + Debug.LogError($"Network error: {error}"); + } + private void OnMessage(Message message) + { + switch (message.Type) + { + case "GameEvent": + OnGameEvent(message as GameEvent); + break; + case "CreateLobbyResponse": + Debug.Log("Received CreateLobbyResponse message"); + HandleCreateLobbyResponse(message as CreateLobbyResponse); + break; + case "JoinLobbyResponse": + Debug.Log("Received JoinLobbyResponse message"); + HandleJoinLobbyResponse(message as JoinLobbyResponse); + break; + case "Ack": + Debug.Log("Received Ack message"); + break; + default: + Debug.Log("Received message of type: " + message.Type); + break; + } + } + private void OnGameEvent(GameEvent gameEvent) + { + switch (gameEvent.Type) + { + case "PlayerJoined": + Debug.Log($"Player {gameEvent.GetPayload().DisplayName} joined"); + HandlePlayerJoined(gameEvent); + break; + default: + Debug.Log("Received GameEvent of type: " + gameEvent.Type); + break; + } + } + private void HandleCreateLobbyResponse(CreateLobbyResponse message) + { + if (message.Success) + { + Debug.Log("Lobby created successfully. Join Code: " + message.JoinCode + ", Lobby ID: " + message.LobbyId); + } + else + { + Debug.LogError("Failed to create lobby: " + message.Error); + } + } + private void HandleJoinLobbyResponse(JoinLobbyResponse message) + { + if (message.Success) + { + Debug.Log("Lobby created successfully." + ", Lobby ID: " + message.LobbyId); + } + else + { + Debug.LogError("Failed to create lobby: " + message.Error); + } + } + private void HandlePlayerJoined(GameEvent gameEvent) + { + var payload = gameEvent.GetPayload(); + _gameClient.CurrentLobbyState.Players.Add(new PlayerInfo + { + ClientUuid = payload.ClientUuid, + DisplayName = payload.DisplayName, + IsOwner = false, + IsReady = false, + State = PlayerState.Alive + }); } - } + public void CrateLobby(double lat, double lon) + { + _gameClient.CreateLobby(new Position(lat, lon)); + } + public void JoinLobby(string joinCode) + { + try + { + _gameClient.JoinLobby(joinCode); + } + catch (System.Exception ex) + { + Debug.LogError("Error joining lobby: " + ex.Message); + } + } + public void LeaveLobby() + { + _gameClient.Disconnect(); + Application.Quit(); + } + + + } } diff --git a/Assets/GameManager/GameManager_UI.cs b/Assets/GameManager/GameManager_UI.cs new file mode 100644 index 0000000..7822d42 --- /dev/null +++ b/Assets/GameManager/GameManager_UI.cs @@ -0,0 +1,35 @@ +using UnityEngine; +using Subsystems; +using GeoSus.Client; +using System.ComponentModel; + + +namespace Subsystems +{ + public class GameManager_UI + { + private GameClient _gameClient; + private Canvas _CreateJoinLobby; + private Canvas _InLobby; + public GameManager_UI(GameClient gameClient, Canvas CreateJoinLobby, Canvas InLobby) + { + _gameClient = gameClient; + _CreateJoinLobby = CreateJoinLobby; + _InLobby = InLobby; + _CreateJoinLobby.enabled = true; + _InLobby.enabled = false; + } + public void UpdateLobbyUI() + { + _InLobby.enabled = true; + _CreateJoinLobby.enabled = false; + var playerList = _InLobby.transform.Find("PlayerList").GetComponent(); + playerList.text = ""; + foreach (var player in _gameClient.CurrentLobbyState.Players) + { + playerList.text += player.DisplayName + "\n"; + } + _InLobby.transform.Find("JoinCode").GetComponent().text = _gameClient.CurrentLobbyState.JoinCode; + } + } +} diff --git a/Assets/Scenes/Client.unity b/Assets/Scenes/Client.unity index 27b3ab7..d3cdcc3 100644 --- a/Assets/Scenes/Client.unity +++ b/Assets/Scenes/Client.unity @@ -119,6 +119,450 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &12226900 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 12226904} + - component: {fileID: 12226903} + - component: {fileID: 12226902} + - component: {fileID: 12226901} + m_Layer: 5 + m_Name: InLobby + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &12226901 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 12226900} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GraphicRaycaster + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &12226902 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 12226900} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &12226903 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 12226900} + m_Enabled: 0 + serializedVersion: 3 + m_RenderMode: 1 + m_Camera: {fileID: 1010702371} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 25 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &12226904 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 12226900} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 422937440} + - {fileID: 1304493836} + - {fileID: 1765933876} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &12846507 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 12846508} + - component: {fileID: 12846510} + - component: {fileID: 12846509} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &12846508 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 12846507} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1304493836} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &12846509 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 12846507} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Leave + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 24 + m_fontSizeBase: 24 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &12846510 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 12846507} + m_CullTransparentMesh: 1 +--- !u!1 &157221433 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 157221436} + - component: {fileID: 157221435} + - component: {fileID: 157221434} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &157221434 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157221433} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.EventSystems.StandaloneInputModule + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &157221435 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157221433} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.EventSystems.EventSystem + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &157221436 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157221433} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &422937439 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 422937440} + - component: {fileID: 422937442} + - component: {fileID: 422937441} + m_Layer: 5 + m_Name: PlayerList + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &422937440 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 422937439} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 12226904} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -220, y: 101.90001} + m_SizeDelta: {x: 200, y: 276.2} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &422937441 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 422937439} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Player + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &422937442 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 422937439} + m_CullTransparentMesh: 1 --- !u!1 &442151206 GameObject: m_ObjectHideFlags: 0 @@ -216,6 +660,411 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &665665133 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 665665134} + - component: {fileID: 665665136} + - component: {fileID: 665665135} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &665665134 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 665665133} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1222944787} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &665665135 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 665665133} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Join + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 24 + m_fontSizeBase: 24 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &665665136 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 665665133} + m_CullTransparentMesh: 1 +--- !u!1 &808348697 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 808348698} + - component: {fileID: 808348701} + - component: {fileID: 808348700} + - component: {fileID: 808348699} + m_Layer: 5 + m_Name: Create + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &808348698 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 808348697} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 970667518} + m_Father: {fileID: 1403738865} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 80, y: -15} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &808348699 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 808348697} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 808348700} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1353866372} + m_TargetAssemblyTypeName: GameManager, Assembly-CSharp + m_MethodName: CreateLobbyButton + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!114 &808348700 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 808348697} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &808348701 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 808348697} + m_CullTransparentMesh: 1 +--- !u!1 &970667517 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 970667518} + - component: {fileID: 970667520} + - component: {fileID: 970667519} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &970667518 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 970667517} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 808348698} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &970667519 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 970667517} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Create + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 24 + m_fontSizeBase: 24 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &970667520 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 970667517} + m_CullTransparentMesh: 1 --- !u!1 &1010702369 GameObject: m_ObjectHideFlags: 0 @@ -302,12 +1151,414 @@ Transform: m_GameObject: {fileID: 1010702369} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalPosition: {x: 0, y: 1, z: -1631} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1222944786 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1222944787} + - component: {fileID: 1222944790} + - component: {fileID: 1222944789} + - component: {fileID: 1222944788} + m_Layer: 5 + m_Name: Join + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1222944787 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1222944786} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 665665134} + m_Father: {fileID: 1403738865} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -80, y: -15} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1222944788 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1222944786} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1222944789} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1353866372} + m_TargetAssemblyTypeName: GameManager, Assembly-CSharp + m_MethodName: JoinLobbyButton + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!114 &1222944789 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1222944786} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1222944790 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1222944786} + m_CullTransparentMesh: 1 +--- !u!1 &1299024342 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1299024343} + - component: {fileID: 1299024345} + - component: {fileID: 1299024344} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1299024343 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1299024342} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1797826874} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1299024344 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1299024342} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: "\u200B" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 14 + m_fontSizeBase: 14 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 3 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 1 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1299024345 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1299024342} + m_CullTransparentMesh: 1 +--- !u!1 &1304493835 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1304493836} + - component: {fileID: 1304493839} + - component: {fileID: 1304493838} + - component: {fileID: 1304493837} + m_Layer: 5 + m_Name: Leave + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1304493836 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1304493835} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 12846508} + m_Father: {fileID: 12226904} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: -51.2} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1304493837 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1304493835} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1304493838} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1353866372} + m_TargetAssemblyTypeName: GameManager, Assembly-CSharp + m_MethodName: LeaveLobbyButton + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!114 &1304493838 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1304493835} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1304493839 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1304493835} + m_CullTransparentMesh: 1 --- !u!1 &1353866370 GameObject: m_ObjectHideFlags: 0 @@ -353,6 +1604,635 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Assembly-CSharp::GameManager displayName: Player + JoinCreateLobby: {fileID: 1403738864} + InLobby: {fileID: 12226903} +--- !u!1 &1403738861 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1403738865} + - component: {fileID: 1403738864} + - component: {fileID: 1403738863} + - component: {fileID: 1403738862} + m_Layer: 5 + m_Name: LobbySelector + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1403738862 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1403738861} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GraphicRaycaster + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1403738863 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1403738861} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1403738864 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1403738861} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 1 + m_Camera: {fileID: 1010702371} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 25 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1403738865 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1403738861} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 808348698} + - {fileID: 1222944787} + - {fileID: 2029041224} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1494413235 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1494413236} + - component: {fileID: 1494413239} + - component: {fileID: 1494413238} + - component: {fileID: 1494413237} + m_Layer: 5 + m_Name: Placeholder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1494413236 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1494413235} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1797826874} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1494413237 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1494413235} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement + m_IgnoreLayout: 1 + m_MinWidth: -1 + m_MinHeight: -1 + m_PreferredWidth: -1 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 +--- !u!114 &1494413238 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1494413235} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Enter text... + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 2150773298 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.5} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 14 + m_fontSizeBase: 14 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 2 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 1 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1494413239 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1494413235} + m_CullTransparentMesh: 1 +--- !u!1 &1765933875 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1765933876} + - component: {fileID: 1765933878} + - component: {fileID: 1765933877} + m_Layer: 5 + m_Name: JoinCode + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1765933876 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1765933875} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 12226904} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 220, y: 215.00003} + m_SizeDelta: {x: 200, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1765933877 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1765933875} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: New Text + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1765933878 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1765933875} + m_CullTransparentMesh: 1 +--- !u!1 &1797826873 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1797826874} + - component: {fileID: 1797826875} + m_Layer: 5 + m_Name: Text Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1797826874 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1797826873} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1494413236} + - {fileID: 1299024343} + m_Father: {fileID: 2029041224} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.5} + m_SizeDelta: {x: -20, y: -13} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1797826875 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1797826873} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3312d7739989d2b4e91e6319e9a96d76, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.RectMask2D + m_Padding: {x: -8, y: -5, z: -8, w: -5} + m_Softness: {x: 0, y: 0} +--- !u!1 &2029041223 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2029041224} + - component: {fileID: 2029041227} + - component: {fileID: 2029041226} + - component: {fileID: 2029041225} + m_Layer: 5 + m_Name: InputCode + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2029041224 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2029041223} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1797826874} + m_Father: {fileID: 1403738865} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -80, y: 15} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2029041225 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2029041223} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2da0c512f12947e489f739169773d7ca, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TMP_InputField + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 2029041226} + m_TextViewport: {fileID: 1797826874} + m_TextComponent: {fileID: 1299024344} + m_Placeholder: {fileID: 1494413238} + m_VerticalScrollbar: {fileID: 0} + m_VerticalScrollbarEventHandler: {fileID: 0} + m_LayoutGroup: {fileID: 0} + m_ScrollSensitivity: 1 + m_ContentType: 0 + m_InputType: 0 + m_AsteriskChar: 42 + m_KeyboardType: 0 + m_LineType: 0 + m_HideMobileInput: 0 + m_HideSoftKeyboard: 0 + m_CharacterValidation: 0 + m_RegexValue: + m_GlobalPointSize: 14 + m_CharacterLimit: 0 + m_OnEndEdit: + m_PersistentCalls: + m_Calls: [] + m_OnSubmit: + m_PersistentCalls: + m_Calls: [] + m_OnSelect: + m_PersistentCalls: + m_Calls: [] + m_OnDeselect: + m_PersistentCalls: + m_Calls: [] + m_OnTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnEndTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] + m_OnTouchScreenKeyboardStatusChanged: + m_PersistentCalls: + m_Calls: [] + m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_CustomCaretColor: 0 + m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} + m_Text: + m_CaretBlinkRate: 0.85 + m_CaretWidth: 1 + m_ReadOnly: 0 + m_RichText: 1 + m_GlobalFontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_OnFocusSelectAll: 1 + m_ResetOnDeActivation: 1 + m_KeepTextSelectionVisible: 0 + m_RestoreOriginalTextOnEscape: 1 + m_isRichTextEditingAllowed: 0 + m_LineLimit: 0 + isAlert: 0 + m_InputValidator: {fileID: 0} + m_ShouldActivateOnSelect: 1 +--- !u!114 &2029041226 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2029041223} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &2029041227 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2029041223} + m_CullTransparentMesh: 1 --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -360,3 +2240,6 @@ SceneRoots: - {fileID: 1010702372} - {fileID: 442151208} - {fileID: 1353866371} + - {fileID: 1403738865} + - {fileID: 157221436} + - {fileID: 12226904} diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index d057ba3..3572d17 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -5,9 +5,12 @@ EditorBuildSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_Scenes: - - enabled: 1 + - enabled: 0 path: Assets/Scenes/SampleScene.unity - guid: 99c9720ab356a0642a771bea13969a05 + guid: 3e95f16d8e50b3341925e51e50768027 + - enabled: 1 + path: Assets/Scenes/Client.unity + guid: 8f736798e2d13f14f903b26a2df0eed8 m_configObjects: com.unity.input.settings.actions: {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3} m_UseUCBPForAssetBundles: 0 diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset index 029ad8b..7a17e8f 100644 --- a/ProjectSettings/UnityConnectSettings.asset +++ b/ProjectSettings/UnityConnectSettings.asset @@ -4,7 +4,7 @@ UnityConnectSettings: m_ObjectHideFlags: 0 serializedVersion: 1 - m_Enabled: 0 + m_Enabled: 1 m_TestMode: 0 m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events -- 2.49.1