From c402c5513bcb8f19da42ffe45f25f762f45065f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20R=C3=A1=C4=8Dek?= Date: Tue, 27 Jan 2026 21:36:29 +0100 Subject: [PATCH] Init --- .gitignore | 99 + .vsconfig | 6 + Assets/ClientSDK.meta | 8 + Assets/ClientSDK/ClientSDK.csproj.meta | 7 + Assets/ClientSDK/Encryption.cs | 285 +++ Assets/ClientSDK/Encryption.cs.meta | 2 + Assets/ClientSDK/EventDispatcher.cs | 73 + Assets/ClientSDK/EventDispatcher.cs.meta | 2 + Assets/ClientSDK/GameClient.cs | 607 +++++ Assets/ClientSDK/GameClient.cs.meta | 2 + Assets/ClientSDK/Protocol.cs | 1054 +++++++++ Assets/ClientSDK/Protocol.cs.meta | 2 + Assets/ClientSDK/SimulatorClient.cs | 1992 +++++++++++++++++ Assets/ClientSDK/SimulatorClient.cs.meta | 2 + Assets/ClientSDK/bin.meta | 8 + Assets/ClientSDK/bin/Debug.meta | 8 + .../ClientSDK/bin/Debug/netstandard2.1.meta | 8 + .../Debug/netstandard2.1/ClientSDK.deps.json | 42 + .../netstandard2.1/ClientSDK.deps.json.meta | 7 + .../bin/Debug/netstandard2.1/ClientSDK.dll | Bin 0 -> 143360 bytes .../Debug/netstandard2.1/ClientSDK.dll.meta | 2 + Assets/ClientSDK/obj.meta | 8 + .../obj/ClientSDK.csproj.nuget.dgspec.json | 80 + .../ClientSDK.csproj.nuget.dgspec.json.meta | 7 + .../obj/ClientSDK.csproj.nuget.g.props | 16 + .../obj/ClientSDK.csproj.nuget.g.props.meta | 7 + .../obj/ClientSDK.csproj.nuget.g.targets | 2 + .../obj/ClientSDK.csproj.nuget.g.targets.meta | 7 + Assets/ClientSDK/obj/Debug.meta | 8 + .../ClientSDK/obj/Debug/netstandard2.1.meta | 8 + ...tandard,Version=v2.1.AssemblyAttributes.cs | 4 + .../netstandard2.1/ClientSDK.AssemblyInfo.cs | 22 + .../ClientSDK.AssemblyInfo.cs.meta | 2 + .../ClientSDK.AssemblyInfoInputs.cache | 1 + .../ClientSDK.AssemblyInfoInputs.cache.meta | 7 + ....GeneratedMSBuildEditorConfig.editorconfig | 8 + ...ratedMSBuildEditorConfig.editorconfig.meta | 7 + .../netstandard2.1/ClientSDK.assets.cache | Bin 0 -> 989 bytes .../ClientSDK.assets.cache.meta | 7 + .../ClientSDK.csproj.AssemblyReference.cache | Bin 0 -> 453 bytes ...entSDK.csproj.AssemblyReference.cache.meta | 7 + .../ClientSDK.csproj.CoreCompileInputs.cache | 1 + ...entSDK.csproj.CoreCompileInputs.cache.meta | 7 + .../ClientSDK.csproj.FileListAbsolute.txt | 10 + ...ClientSDK.csproj.FileListAbsolute.txt.meta | 7 + .../obj/Debug/netstandard2.1/ClientSDK.dll | Bin 0 -> 143360 bytes .../Debug/netstandard2.1/ClientSDK.dll.meta | 2 + Assets/ClientSDK/obj/project.assets.json | 133 ++ Assets/ClientSDK/obj/project.assets.json.meta | 7 + Assets/ClientSDK/obj/project.nuget.cache | 10 + Assets/ClientSDK/obj/project.nuget.cache.meta | 7 + Assets/InputSystem_Actions.inputactions | 1057 +++++++++ Assets/InputSystem_Actions.inputactions.meta | 14 + Assets/Readme.asset | 34 + Assets/Readme.asset.meta | 8 + Assets/Scenes.meta | 8 + Assets/Scenes/SampleScene.unity | 491 ++++ Assets/Scenes/SampleScene.unity.meta | 7 + Assets/Settings.meta | 8 + Assets/Settings/DefaultVolumeProfile.asset | 983 ++++++++ .../Settings/DefaultVolumeProfile.asset.meta | 8 + Assets/Settings/Mobile_RPAsset.asset | 135 ++ Assets/Settings/Mobile_RPAsset.asset.meta | 8 + Assets/Settings/Mobile_Renderer.asset | 52 + Assets/Settings/Mobile_Renderer.asset.meta | 8 + Assets/Settings/PC_RPAsset.asset | 141 ++ Assets/Settings/PC_RPAsset.asset.meta | 8 + Assets/Settings/PC_Renderer.asset | 95 + Assets/Settings/PC_Renderer.asset.meta | 8 + Assets/Settings/SampleSceneProfile.asset | 159 ++ Assets/Settings/SampleSceneProfile.asset.meta | 8 + ...niversalRenderPipelineGlobalSettings.asset | 407 ++++ ...salRenderPipelineGlobalSettings.asset.meta | 8 + Assets/TutorialInfo.meta | 8 + Assets/TutorialInfo/Icons.meta | 9 + Assets/TutorialInfo/Icons/URP.png | Bin 0 -> 24069 bytes Assets/TutorialInfo/Icons/URP.png.meta | 134 ++ Assets/TutorialInfo/Layout.wlt | 654 ++++++ Assets/TutorialInfo/Layout.wlt.meta | 8 + Assets/TutorialInfo/Scripts.meta | 9 + Assets/TutorialInfo/Scripts/Editor.meta | 9 + .../Scripts/Editor/ReadmeEditor.cs | 242 ++ .../Scripts/Editor/ReadmeEditor.cs.meta | 12 + Assets/TutorialInfo/Scripts/Readme.cs | 16 + Assets/TutorialInfo/Scripts/Readme.cs.meta | 12 + Assets/UnityTestClient.meta | 8 + Assets/UnityTestClient/UnityTestClient.cs | 675 ++++++ .../UnityTestClient/UnityTestClient.cs.meta | 2 + .../UnityTestClient/UnityTestClient_Game.cs | 673 ++++++ .../UnityTestClient_Game.cs.meta | 2 + Assets/UnityTestClient/UnityTestClient_Map.cs | 1532 +++++++++++++ .../UnityTestClient_Map.cs.meta | 2 + .../UnityTestClient_Network.cs | 1196 ++++++++++ .../UnityTestClient_Network.cs.meta | 2 + .../UnityTestClient/UnityTestClient_Stats.cs | 589 +++++ .../UnityTestClient_Stats.cs.meta | 2 + Assets/UnityTestClient/UnityTestClient_UI.cs | 1642 ++++++++++++++ .../UnityTestClient_UI.cs.meta | 2 + Packages/manifest.json | 48 + Packages/packages-lock.json | 472 ++++ ProjectSettings/AudioManager.asset | 19 + ProjectSettings/ClusterInputManager.asset | 6 + ProjectSettings/DynamicsManager.asset | 36 + ProjectSettings/EditorBuildSettings.asset | 13 + ProjectSettings/EditorSettings.asset | 50 + ProjectSettings/GraphicsSettings.asset | 68 + ProjectSettings/InputManager.asset | 487 ++++ ProjectSettings/MemorySettings.asset | 35 + ProjectSettings/MultiplayerManager.asset | 7 + ProjectSettings/NavMeshAreas.asset | 91 + ProjectSettings/PackageManagerSettings.asset | 43 + ProjectSettings/Physics2DSettings.asset | 56 + ProjectSettings/PresetManager.asset | 7 + ProjectSettings/ProjectSettings.asset | 943 ++++++++ ProjectSettings/ProjectVersion.txt | 2 + ProjectSettings/QualitySettings.asset | 134 ++ ProjectSettings/SceneTemplateSettings.json | 121 + ProjectSettings/ShaderGraphSettings.asset | 19 + ProjectSettings/TagManager.asset | 76 + ProjectSettings/TimeManager.asset | 9 + ProjectSettings/URPProjectSettings.asset | 15 + ProjectSettings/UnityConnectSettings.asset | 40 + ProjectSettings/VFXManager.asset | 12 + ProjectSettings/VersionControlSettings.asset | 8 + ProjectSettings/XRSettings.asset | 10 + 125 files changed, 18530 insertions(+) create mode 100644 .gitignore create mode 100644 .vsconfig create mode 100644 Assets/ClientSDK.meta create mode 100644 Assets/ClientSDK/ClientSDK.csproj.meta create mode 100644 Assets/ClientSDK/Encryption.cs create mode 100644 Assets/ClientSDK/Encryption.cs.meta create mode 100644 Assets/ClientSDK/EventDispatcher.cs create mode 100644 Assets/ClientSDK/EventDispatcher.cs.meta create mode 100644 Assets/ClientSDK/GameClient.cs create mode 100644 Assets/ClientSDK/GameClient.cs.meta create mode 100644 Assets/ClientSDK/Protocol.cs create mode 100644 Assets/ClientSDK/Protocol.cs.meta create mode 100644 Assets/ClientSDK/SimulatorClient.cs create mode 100644 Assets/ClientSDK/SimulatorClient.cs.meta create mode 100644 Assets/ClientSDK/bin.meta create mode 100644 Assets/ClientSDK/bin/Debug.meta create mode 100644 Assets/ClientSDK/bin/Debug/netstandard2.1.meta create mode 100644 Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.deps.json create mode 100644 Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.deps.json.meta create mode 100644 Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.dll create mode 100644 Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.dll.meta create mode 100644 Assets/ClientSDK/obj.meta create mode 100644 Assets/ClientSDK/obj/ClientSDK.csproj.nuget.dgspec.json create mode 100644 Assets/ClientSDK/obj/ClientSDK.csproj.nuget.dgspec.json.meta create mode 100644 Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.props create mode 100644 Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.props.meta create mode 100644 Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.targets create mode 100644 Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.targets.meta create mode 100644 Assets/ClientSDK/obj/Debug.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/.NETStandard,Version=v2.1.AssemblyAttributes.cs create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfo.cs create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfo.cs.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfoInputs.cache create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfoInputs.cache.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.GeneratedMSBuildEditorConfig.editorconfig.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.assets.cache create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.assets.cache.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.AssemblyReference.cache create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.AssemblyReference.cache.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.CoreCompileInputs.cache create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.CoreCompileInputs.cache.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.FileListAbsolute.txt create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.FileListAbsolute.txt.meta create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.dll create mode 100644 Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.dll.meta create mode 100644 Assets/ClientSDK/obj/project.assets.json create mode 100644 Assets/ClientSDK/obj/project.assets.json.meta create mode 100644 Assets/ClientSDK/obj/project.nuget.cache create mode 100644 Assets/ClientSDK/obj/project.nuget.cache.meta create mode 100644 Assets/InputSystem_Actions.inputactions create mode 100644 Assets/InputSystem_Actions.inputactions.meta create mode 100644 Assets/Readme.asset create mode 100644 Assets/Readme.asset.meta create mode 100644 Assets/Scenes.meta create mode 100644 Assets/Scenes/SampleScene.unity create mode 100644 Assets/Scenes/SampleScene.unity.meta create mode 100644 Assets/Settings.meta create mode 100644 Assets/Settings/DefaultVolumeProfile.asset create mode 100644 Assets/Settings/DefaultVolumeProfile.asset.meta create mode 100644 Assets/Settings/Mobile_RPAsset.asset create mode 100644 Assets/Settings/Mobile_RPAsset.asset.meta create mode 100644 Assets/Settings/Mobile_Renderer.asset create mode 100644 Assets/Settings/Mobile_Renderer.asset.meta create mode 100644 Assets/Settings/PC_RPAsset.asset create mode 100644 Assets/Settings/PC_RPAsset.asset.meta create mode 100644 Assets/Settings/PC_Renderer.asset create mode 100644 Assets/Settings/PC_Renderer.asset.meta create mode 100644 Assets/Settings/SampleSceneProfile.asset create mode 100644 Assets/Settings/SampleSceneProfile.asset.meta create mode 100644 Assets/Settings/UniversalRenderPipelineGlobalSettings.asset create mode 100644 Assets/Settings/UniversalRenderPipelineGlobalSettings.asset.meta create mode 100644 Assets/TutorialInfo.meta create mode 100644 Assets/TutorialInfo/Icons.meta create mode 100644 Assets/TutorialInfo/Icons/URP.png create mode 100644 Assets/TutorialInfo/Icons/URP.png.meta create mode 100644 Assets/TutorialInfo/Layout.wlt create mode 100644 Assets/TutorialInfo/Layout.wlt.meta create mode 100644 Assets/TutorialInfo/Scripts.meta create mode 100644 Assets/TutorialInfo/Scripts/Editor.meta create mode 100644 Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs create mode 100644 Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta create mode 100644 Assets/TutorialInfo/Scripts/Readme.cs create mode 100644 Assets/TutorialInfo/Scripts/Readme.cs.meta create mode 100644 Assets/UnityTestClient.meta create mode 100644 Assets/UnityTestClient/UnityTestClient.cs create mode 100644 Assets/UnityTestClient/UnityTestClient.cs.meta create mode 100644 Assets/UnityTestClient/UnityTestClient_Game.cs create mode 100644 Assets/UnityTestClient/UnityTestClient_Game.cs.meta create mode 100644 Assets/UnityTestClient/UnityTestClient_Map.cs create mode 100644 Assets/UnityTestClient/UnityTestClient_Map.cs.meta create mode 100644 Assets/UnityTestClient/UnityTestClient_Network.cs create mode 100644 Assets/UnityTestClient/UnityTestClient_Network.cs.meta create mode 100644 Assets/UnityTestClient/UnityTestClient_Stats.cs create mode 100644 Assets/UnityTestClient/UnityTestClient_Stats.cs.meta create mode 100644 Assets/UnityTestClient/UnityTestClient_UI.cs create mode 100644 Assets/UnityTestClient/UnityTestClient_UI.cs.meta create mode 100644 Packages/manifest.json create mode 100644 Packages/packages-lock.json create mode 100644 ProjectSettings/AudioManager.asset create mode 100644 ProjectSettings/ClusterInputManager.asset create mode 100644 ProjectSettings/DynamicsManager.asset create mode 100644 ProjectSettings/EditorBuildSettings.asset create mode 100644 ProjectSettings/EditorSettings.asset create mode 100644 ProjectSettings/GraphicsSettings.asset create mode 100644 ProjectSettings/InputManager.asset create mode 100644 ProjectSettings/MemorySettings.asset create mode 100644 ProjectSettings/MultiplayerManager.asset create mode 100644 ProjectSettings/NavMeshAreas.asset create mode 100644 ProjectSettings/PackageManagerSettings.asset create mode 100644 ProjectSettings/Physics2DSettings.asset create mode 100644 ProjectSettings/PresetManager.asset create mode 100644 ProjectSettings/ProjectSettings.asset create mode 100644 ProjectSettings/ProjectVersion.txt create mode 100644 ProjectSettings/QualitySettings.asset create mode 100644 ProjectSettings/SceneTemplateSettings.json create mode 100644 ProjectSettings/ShaderGraphSettings.asset create mode 100644 ProjectSettings/TagManager.asset create mode 100644 ProjectSettings/TimeManager.asset create mode 100644 ProjectSettings/URPProjectSettings.asset create mode 100644 ProjectSettings/UnityConnectSettings.asset create mode 100644 ProjectSettings/VFXManager.asset create mode 100644 ProjectSettings/VersionControlSettings.asset create mode 100644 ProjectSettings/XRSettings.asset diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9eb70ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,99 @@ +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore +# +.utmp/ +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ +*.log + +# By default unity supports Blender asset imports, *.blend1 blender files do not need to be commited to version control. +*.blend1 +*.blend1.meta + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Recordings can get excessive in size +/[Rr]ecordings/ + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* +# Jetbrains Rider personal-layer settings +*.DotSettings.user + +# Visual Studio cache directory +.vs/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Mono auto generated files +mono_crash.* + +# Builds +*.apk +*.aab +*.unitypackage +*.unitypackage.meta +*.app + +# Crashlytics generated file +crashlytics-build.properties + +# TestRunner generated files +InitTestScene*.unity* + +# Addressables default ignores, before user customizations +/ServerData +/[Aa]ssets/StreamingAssets/aa* +/[Aa]ssets/AddressableAssetsData/link.xml* +/[Aa]ssets/Addressables_Temp* +# By default, Addressables content builds will generate addressables_content_state.bin +# files in platform-specific subfolders, for example: +# /Assets/AddressableAssetsData/OSX/addressables_content_state.bin +/[Aa]ssets/AddressableAssetsData/*/*.bin* + +# Visual Scripting auto-generated files +/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db +/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db.meta +/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers +/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers.meta + +# Auto-generated scenes by play mode tests +/[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity* diff --git a/.vsconfig b/.vsconfig new file mode 100644 index 0000000..f019fd0 --- /dev/null +++ b/.vsconfig @@ -0,0 +1,6 @@ +{ + "version": "1.0", + "components": [ + "Microsoft.VisualStudio.Workload.ManagedGame" + ] +} diff --git a/Assets/ClientSDK.meta b/Assets/ClientSDK.meta new file mode 100644 index 0000000..28d2c00 --- /dev/null +++ b/Assets/ClientSDK.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d1d7531f2b0dd1c44a811f5704eeed90 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/ClientSDK.csproj.meta b/Assets/ClientSDK/ClientSDK.csproj.meta new file mode 100644 index 0000000..94c70c7 --- /dev/null +++ b/Assets/ClientSDK/ClientSDK.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7009ccbbf5326044c8f2df72a309d093 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: 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/Encryption.cs.meta b/Assets/ClientSDK/Encryption.cs.meta new file mode 100644 index 0000000..f850920 --- /dev/null +++ b/Assets/ClientSDK/Encryption.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bc06bb57786c7e142b06ec231e5cf709 \ No newline at end of file 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/EventDispatcher.cs.meta b/Assets/ClientSDK/EventDispatcher.cs.meta new file mode 100644 index 0000000..2eaf448 --- /dev/null +++ b/Assets/ClientSDK/EventDispatcher.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1d2251b279edb0147bd274a884ac878b \ No newline at end of file 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/GameClient.cs.meta b/Assets/ClientSDK/GameClient.cs.meta new file mode 100644 index 0000000..b454c2d --- /dev/null +++ b/Assets/ClientSDK/GameClient.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 91e0f647c37b0b94b83f53bb854db28c \ No newline at end of file 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/Protocol.cs.meta b/Assets/ClientSDK/Protocol.cs.meta new file mode 100644 index 0000000..719b429 --- /dev/null +++ b/Assets/ClientSDK/Protocol.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 14463228dfea2264ebfc36c3a7dc4b99 \ No newline at end of file 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/ClientSDK/SimulatorClient.cs.meta b/Assets/ClientSDK/SimulatorClient.cs.meta new file mode 100644 index 0000000..33e1383 --- /dev/null +++ b/Assets/ClientSDK/SimulatorClient.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 80ef0979df5d1fe489225f3e5edadc5c \ No newline at end of file diff --git a/Assets/ClientSDK/bin.meta b/Assets/ClientSDK/bin.meta new file mode 100644 index 0000000..595f334 --- /dev/null +++ b/Assets/ClientSDK/bin.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3a4035bdb812fee4f96cb1aa1b24c999 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/bin/Debug.meta b/Assets/ClientSDK/bin/Debug.meta new file mode 100644 index 0000000..7f949f6 --- /dev/null +++ b/Assets/ClientSDK/bin/Debug.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7d9de4c3bce3edb458cde89bd004889b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/bin/Debug/netstandard2.1.meta b/Assets/ClientSDK/bin/Debug/netstandard2.1.meta new file mode 100644 index 0000000..6e0a0f3 --- /dev/null +++ b/Assets/ClientSDK/bin/Debug/netstandard2.1.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c7a6dde302cf01245974d243ceeba40f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.deps.json b/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.deps.json new file mode 100644 index 0000000..0d7c376 --- /dev/null +++ b/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.deps.json @@ -0,0 +1,42 @@ +{ + "runtimeTarget": { + "name": ".NETStandard,Version=v2.1/", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETStandard,Version=v2.1": {}, + ".NETStandard,Version=v2.1/": { + "ClientSDK/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.3" + }, + "runtime": { + "ClientSDK.dll": {} + } + }, + "Newtonsoft.Json/13.0.3": { + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.3.27908" + } + } + } + } + }, + "libraries": { + "ClientSDK/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Newtonsoft.Json/13.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "path": "newtonsoft.json/13.0.3", + "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512" + } + } +} \ No newline at end of file diff --git a/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.deps.json.meta b/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.deps.json.meta new file mode 100644 index 0000000..df4489c --- /dev/null +++ b/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.deps.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 110fcc3f27ca804418e17744b419333f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.dll b/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.dll new file mode 100644 index 0000000000000000000000000000000000000000..884654c2481aea74cc7abfbb3f7bdf0d687b16d0 GIT binary patch literal 143360 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

ZJ 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 z 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 literal 0 HcmV?d00001 diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.dll.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.dll.meta new file mode 100644 index 0000000..c455a8e --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.dll.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8e41cfc0681c5ee488261b7967cb388a \ No newline at end of file diff --git a/Assets/ClientSDK/obj/project.assets.json b/Assets/ClientSDK/obj/project.assets.json new file mode 100644 index 0000000..5b8edec --- /dev/null +++ b/Assets/ClientSDK/obj/project.assets.json @@ -0,0 +1,133 @@ +{ + "version": 3, + "targets": { + ".NETStandard,Version=v2.1": { + "Newtonsoft.Json/13.0.3": { + "type": "package", + "compile": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + } + } + } + }, + "libraries": { + "Newtonsoft.Json/13.0.3": { + "sha512": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "type": "package", + "path": "newtonsoft.json/13.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net20/Newtonsoft.Json.dll", + "lib/net20/Newtonsoft.Json.xml", + "lib/net35/Newtonsoft.Json.dll", + "lib/net35/Newtonsoft.Json.xml", + "lib/net40/Newtonsoft.Json.dll", + "lib/net40/Newtonsoft.Json.xml", + "lib/net45/Newtonsoft.Json.dll", + "lib/net45/Newtonsoft.Json.xml", + "lib/net6.0/Newtonsoft.Json.dll", + "lib/net6.0/Newtonsoft.Json.xml", + "lib/netstandard1.0/Newtonsoft.Json.dll", + "lib/netstandard1.0/Newtonsoft.Json.xml", + "lib/netstandard1.3/Newtonsoft.Json.dll", + "lib/netstandard1.3/Newtonsoft.Json.xml", + "lib/netstandard2.0/Newtonsoft.Json.dll", + "lib/netstandard2.0/Newtonsoft.Json.xml", + "newtonsoft.json.13.0.3.nupkg.sha512", + "newtonsoft.json.nuspec", + "packageIcon.png" + ] + } + }, + "projectFileDependencyGroups": { + ".NETStandard,Version=v2.1": [ + "Newtonsoft.Json >= 13.0.3" + ] + }, + "packageFolders": { + "C:\\Users\\racek\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\ClientSDK.csproj", + "projectName": "ClientSDK", + "projectPath": "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\ClientSDK.csproj", + "packagesPath": "C:\\Users\\racek\\.nuget\\packages\\", + "outputPath": "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\racek\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "netstandard2.1" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "dependencies": { + "Newtonsoft.Json": { + "target": "Package", + "version": "[13.0.3, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "NETStandard.Library": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Assets/ClientSDK/obj/project.assets.json.meta b/Assets/ClientSDK/obj/project.assets.json.meta new file mode 100644 index 0000000..f4897d4 --- /dev/null +++ b/Assets/ClientSDK/obj/project.assets.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 02541c1b9976feb498ad976779a14cab +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/project.nuget.cache b/Assets/ClientSDK/obj/project.nuget.cache new file mode 100644 index 0000000..fe7906f --- /dev/null +++ b/Assets/ClientSDK/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "2Ec22eQ8t2M=", + "success": true, + "projectFilePath": "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\ClientSDK.csproj", + "expectedPackageFiles": [ + "C:\\Users\\racek\\.nuget\\packages\\newtonsoft.json\\13.0.3\\newtonsoft.json.13.0.3.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Assets/ClientSDK/obj/project.nuget.cache.meta b/Assets/ClientSDK/obj/project.nuget.cache.meta new file mode 100644 index 0000000..856bb2f --- /dev/null +++ b/Assets/ClientSDK/obj/project.nuget.cache.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 55e25d9881240614a8f797a612adcfb0 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/InputSystem_Actions.inputactions b/Assets/InputSystem_Actions.inputactions new file mode 100644 index 0000000..1a12cb9 --- /dev/null +++ b/Assets/InputSystem_Actions.inputactions @@ -0,0 +1,1057 @@ +{ + "name": "InputSystem_Actions", + "maps": [ + { + "name": "Player", + "id": "df70fa95-8a34-4494-b137-73ab6b9c7d37", + "actions": [ + { + "name": "Move", + "type": "Value", + "id": "351f2ccd-1f9f-44bf-9bec-d62ac5c5f408", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Look", + "type": "Value", + "id": "6b444451-8a00-4d00-a97e-f47457f736a8", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Attack", + "type": "Button", + "id": "6c2ab1b8-8984-453a-af3d-a3c78ae1679a", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Interact", + "type": "Button", + "id": "852140f2-7766-474d-8707-702459ba45f3", + "expectedControlType": "Button", + "processors": "", + "interactions": "Hold", + "initialStateCheck": false + }, + { + "name": "Crouch", + "type": "Button", + "id": "27c5f898-bc57-4ee1-8800-db469aca5fe3", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Jump", + "type": "Button", + "id": "f1ba0d36-48eb-4cd5-b651-1c94a6531f70", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Previous", + "type": "Button", + "id": "2776c80d-3c14-4091-8c56-d04ced07a2b0", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Next", + "type": "Button", + "id": "b7230bb6-fc9b-4f52-8b25-f5e19cb2c2ba", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Sprint", + "type": "Button", + "id": "641cd816-40e6-41b4-8c3d-04687c349290", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "", + "id": "978bfe49-cc26-4a3d-ab7b-7d7a29327403", + "path": "/leftStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "WASD", + "id": "00ca640b-d935-4593-8157-c05846ea39b3", + "path": "Dpad", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "e2062cb9-1b15-46a2-838c-2f8d72a0bdd9", + "path": "/w", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "8180e8bd-4097-4f4e-ab88-4523101a6ce9", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "320bffee-a40b-4347-ac70-c210eb8bc73a", + "path": "/s", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "1c5327b5-f71c-4f60-99c7-4e737386f1d1", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "d2581a9b-1d11-4566-b27d-b92aff5fabbc", + "path": "/a", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "2e46982e-44cc-431b-9f0b-c11910bf467a", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcfe95b8-67b9-4526-84b5-5d0bc98d6400", + "path": "/d", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "77bff152-3580-4b21-b6de-dcd0c7e41164", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "1635d3fe-58b6-4ba9-a4e2-f4b964f6b5c8", + "path": "/{Primary2DAxis}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3ea4d645-4504-4529-b061-ab81934c3752", + "path": "/stick", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c1f7a91b-d0fd-4a62-997e-7fb9b69bf235", + "path": "/rightStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8c8e490b-c610-4785-884f-f04217b23ca4", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse;Touch", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3e5f5442-8668-4b27-a940-df99bad7e831", + "path": "/{Hatswitch}", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "143bb1cd-cc10-4eca-a2f0-a3664166fe91", + "path": "/buttonWest", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "05f6913d-c316-48b2-a6bb-e225f14c7960", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "886e731e-7071-4ae4-95c0-e61739dad6fd", + "path": "/primaryTouch/tap", + "interactions": "", + "processors": "", + "groups": ";Touch", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "ee3d0cd2-254e-47a7-a8cb-bc94d9658c54", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8255d333-5683-4943-a58a-ccb207ff1dce", + "path": "/{PrimaryAction}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3c1c7f0-bd20-4ee7-a0f1-899b24bca6d7", + "path": "/enter", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "cbac6039-9c09-46a1-b5f2-4e5124ccb5ed", + "path": "/2", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e15ca19d-e649-4852-97d5-7fe8ccc44e94", + "path": "/dpad/right", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "f2e9ba44-c423-42a7-ad56-f20975884794", + "path": "/leftShift", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8cbb2f4b-a784-49cc-8d5e-c010b8c7f4e6", + "path": "/leftStickPress", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "d8bf24bf-3f2f-4160-a97c-38ec1eb520ba", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "eb40bb66-4559-4dfa-9a2f-820438abb426", + "path": "/space", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "daba33a1-ad0c-4742-a909-43ad1cdfbeb6", + "path": "/buttonSouth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "603f3daf-40bd-4854-8724-93e8017f59e3", + "path": "/secondaryButton", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1534dc16-a6aa-499d-9c3a-22b47347b52a", + "path": "/1", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "25060bbd-a3a6-476e-8fba-45ae484aad05", + "path": "/dpad/left", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1c04ea5f-b012-41d1-a6f7-02e963b52893", + "path": "/e", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3f66d0b-7751-423f-908b-a11c5bd95930", + "path": "/buttonNorth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4f4649ac-64a8-4a73-af11-b3faef356a4d", + "path": "/buttonEast", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "36e52cba-0905-478e-a818-f4bfcb9f3b9a", + "path": "/c", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + } + ] + }, + { + "name": "UI", + "id": "272f6d14-89ba-496f-b7ff-215263d3219f", + "actions": [ + { + "name": "Navigate", + "type": "PassThrough", + "id": "c95b2375-e6d9-4b88-9c4c-c5e76515df4b", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Submit", + "type": "Button", + "id": "7607c7b6-cd76-4816-beef-bd0341cfe950", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Cancel", + "type": "Button", + "id": "15cef263-9014-4fd5-94d9-4e4a6234a6ef", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Point", + "type": "PassThrough", + "id": "32b35790-4ed0-4e9a-aa41-69ac6d629449", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Click", + "type": "PassThrough", + "id": "3c7022bf-7922-4f7c-a998-c437916075ad", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "RightClick", + "type": "PassThrough", + "id": "44b200b1-1557-4083-816c-b22cbdf77ddf", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "MiddleClick", + "type": "PassThrough", + "id": "dad70c86-b58c-4b17-88ad-f5e53adf419e", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "ScrollWheel", + "type": "PassThrough", + "id": "0489e84a-4833-4c40-bfae-cea84b696689", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDevicePosition", + "type": "PassThrough", + "id": "24908448-c609-4bc3-a128-ea258674378a", + "expectedControlType": "Vector3", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDeviceOrientation", + "type": "PassThrough", + "id": "9caa3d8a-6b2f-4e8e-8bad-6ede561bd9be", + "expectedControlType": "Quaternion", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "Gamepad", + "id": "809f371f-c5e2-4e7a-83a1-d867598f40dd", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "14a5d6e8-4aaf-4119-a9ef-34b8c2c548bf", + "path": "/leftStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "9144cbe6-05e1-4687-a6d7-24f99d23dd81", + "path": "/rightStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2db08d65-c5fb-421b-983f-c71163608d67", + "path": "/leftStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "58748904-2ea9-4a80-8579-b500e6a76df8", + "path": "/rightStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "8ba04515-75aa-45de-966d-393d9bbd1c14", + "path": "/leftStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "712e721c-bdfb-4b23-a86c-a0d9fcfea921", + "path": "/rightStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcd248ae-a788-4676-a12e-f4d81205600b", + "path": "/leftStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "1f04d9bc-c50b-41a1-bfcc-afb75475ec20", + "path": "/rightStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "fb8277d4-c5cd-4663-9dc7-ee3f0b506d90", + "path": "/dpad", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "Joystick", + "id": "e25d9774-381c-4a61-b47c-7b6b299ad9f9", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "3db53b26-6601-41be-9887-63ac74e79d19", + "path": "/stick/up", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "0cb3e13e-3d90-4178-8ae6-d9c5501d653f", + "path": "/stick/down", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "0392d399-f6dd-4c82-8062-c1e9c0d34835", + "path": "/stick/left", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "942a66d9-d42f-43d6-8d70-ecb4ba5363bc", + "path": "/stick/right", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "Keyboard", + "id": "ff527021-f211-4c02-933e-5976594c46ed", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "563fbfdd-0f09-408d-aa75-8642c4f08ef0", + "path": "/w", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "eb480147-c587-4a33-85ed-eb0ab9942c43", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2bf42165-60bc-42ca-8072-8c13ab40239b", + "path": "/s", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "85d264ad-e0a0-4565-b7ff-1a37edde51ac", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "74214943-c580-44e4-98eb-ad7eebe17902", + "path": "/a", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "cea9b045-a000-445b-95b8-0c171af70a3b", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "8607c725-d935-4808-84b1-8354e29bab63", + "path": "/d", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "4cda81dc-9edd-4e03-9d7c-a71a14345d0b", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "9e92bb26-7e3b-4ec4-b06b-3c8f8e498ddc", + "path": "*/{Submit}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Submit", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "82627dcc-3b13-4ba9-841d-e4b746d6553e", + "path": "*/{Cancel}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Cancel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c52c8e0b-8179-41d3-b8a1-d149033bbe86", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e1394cbc-336e-44ce-9ea8-6007ed6193f7", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "5693e57a-238a-46ed-b5ae-e64e6e574302", + "path": "/touch*/position", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4faf7dc9-b979-4210-aa8c-e808e1ef89f5", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8d66d5ba-88d7-48e6-b1cd-198bbfef7ace", + "path": "/tip", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "47c2a644-3ebc-4dae-a106-589b7ca75b59", + "path": "/touch*/press", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "bb9e6b34-44bf-4381-ac63-5aa15d19f677", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "38c99815-14ea-4617-8627-164d27641299", + "path": "/scroll", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "ScrollWheel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4c191405-5738-4d4b-a523-c6a301dbf754", + "path": "/rightButton", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "RightClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "24066f69-da47-44f3-a07e-0015fb02eb2e", + "path": "/middleButton", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "MiddleClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "7236c0d9-6ca3-47cf-a6ee-a97f5b59ea77", + "path": "/devicePosition", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDevicePosition", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "23e01e3a-f935-4948-8d8b-9bcac77714fb", + "path": "/deviceRotation", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDeviceOrientation", + "isComposite": false, + "isPartOfComposite": false + } + ] + } + ], + "controlSchemes": [ + { + "name": "Keyboard&Mouse", + "bindingGroup": "Keyboard&Mouse", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + }, + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Gamepad", + "bindingGroup": "Gamepad", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Touch", + "bindingGroup": "Touch", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Joystick", + "bindingGroup": "Joystick", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "XR", + "bindingGroup": "XR", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + } + ] +} \ No newline at end of file diff --git a/Assets/InputSystem_Actions.inputactions.meta b/Assets/InputSystem_Actions.inputactions.meta new file mode 100644 index 0000000..6b38b04 --- /dev/null +++ b/Assets/InputSystem_Actions.inputactions.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 052faaac586de48259a63d0c4782560b +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 0 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: diff --git a/Assets/Readme.asset b/Assets/Readme.asset new file mode 100644 index 0000000..77c2f83 --- /dev/null +++ b/Assets/Readme.asset @@ -0,0 +1,34 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fcf7219bab7fe46a1ad266029b2fee19, type: 3} + m_Name: Readme + m_EditorClassIdentifier: + icon: {fileID: 2800000, guid: 727a75301c3d24613a3ebcec4a24c2c8, type: 3} + title: URP Empty Template + sections: + - heading: Welcome to the Universal Render Pipeline + text: This template includes the settings and assets you need to start creating with the Universal Render Pipeline. + linkText: + url: + - heading: URP Documentation + text: + linkText: Read more about URP + url: https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest + - heading: Forums + text: + linkText: Get answers and support + url: https://forum.unity.com/forums/universal-render-pipeline.383/ + - heading: Report bugs + text: + linkText: Submit a report + url: https://unity3d.com/unity/qa/bug-reporting + loadedLayout: 1 diff --git a/Assets/Readme.asset.meta b/Assets/Readme.asset.meta new file mode 100644 index 0000000..ab3ad45 --- /dev/null +++ b/Assets/Readme.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8105016687592461f977c054a80ce2f2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes.meta b/Assets/Scenes.meta new file mode 100644 index 0000000..e59fb45 --- /dev/null +++ b/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9c53962885c2c4f449125a979d6ad240 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity new file mode 100644 index 0000000..f9ef62d --- /dev/null +++ b/Assets/Scenes/SampleScene.unity @@ -0,0 +1,491 @@ +%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: 5 + m_PVRFilteringGaussRadiusAO: 2 + 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 &330585543 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 330585546} + - component: {fileID: 330585545} + - component: {fileID: 330585544} + - component: {fileID: 330585547} + 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 &330585544 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 +--- !u!20 &330585545 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + 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 &330585546 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + 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!114 &330585547 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 1 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 + m_Version: 2 +--- !u!1 &410087039 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 410087041} + - component: {fileID: 410087040} + - component: {fileID: 410087042} + 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 &410087040 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + m_Enabled: 1 + serializedVersion: 11 + m_Type: 1 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 2 + 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: 5000 + m_UseColorTemperature: 1 + 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 &410087041 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + 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!114 &410087042 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_CustomShadowLayers: 0 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} + m_SoftShadowQuality: 1 + m_RenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_ShadowRenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_Version: 4 + m_LightLayerMask: 1 + m_ShadowLayerMask: 1 + m_RenderingLayers: 1 + m_ShadowRenderingLayers: 1 +--- !u!1 &525096926 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 525096928} + - component: {fileID: 525096929} + m_Layer: 0 + m_Name: GameManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &525096928 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 525096926} + 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!114 &525096929 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 525096926} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 719b1c3bbdc301646b2dbfa653d9a455, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::UnityTestClient + serverHost: geosus.honzuvkod.dev + serverPort: 7777 + httpPort: 8080 + useHttps: 1 + currentState: 0 +--- !u!1 &832575517 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 832575519} + - component: {fileID: 832575518} + m_Layer: 0 + m_Name: Global Volume + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &832575518 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 832575517} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 172515602e62fb746b5d573b38a5fe58, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IsGlobal: 1 + priority: 0 + blendDistance: 0 + weight: 1 + sharedProfile: {fileID: 11400000, guid: 10fc4df2da32a41aaa32d77bc913491c, type: 2} +--- !u!4 &832575519 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 832575517} + 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!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 330585546} + - {fileID: 410087041} + - {fileID: 832575519} + - {fileID: 525096928} diff --git a/Assets/Scenes/SampleScene.unity.meta b/Assets/Scenes/SampleScene.unity.meta new file mode 100644 index 0000000..9531828 --- /dev/null +++ b/Assets/Scenes/SampleScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 99c9720ab356a0642a771bea13969a05 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings.meta b/Assets/Settings.meta new file mode 100644 index 0000000..39b94dd --- /dev/null +++ b/Assets/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 709f11a7f3c4041caa4ef136ea32d874 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/DefaultVolumeProfile.asset b/Assets/Settings/DefaultVolumeProfile.asset new file mode 100644 index 0000000..9e4bbfd --- /dev/null +++ b/Assets/Settings/DefaultVolumeProfile.asset @@ -0,0 +1,983 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-9167874883656233139 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5485954d14dfb9a4c8ead8edb0ded5b1, type: 3} + m_Name: LiftGammaGain + m_EditorClassIdentifier: + active: 1 + lift: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + gamma: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + gain: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} +--- !u!114 &-8270506406425502121 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 70afe9e12c7a7ed47911bb608a23a8ff, type: 3} + m_Name: SplitToning + m_EditorClassIdentifier: + active: 1 + shadows: + m_OverrideState: 1 + m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} + highlights: + m_OverrideState: 1 + m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} + balance: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-8104416584915340131 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: CopyPasteTestComponent2 + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEditor.Rendering.Tests:VolumeComponentCopyPasteTests/CopyPasteTestComponent2 + active: 1 + p1: + m_OverrideState: 1 + m_Value: 0 + p2: + m_OverrideState: 1 + m_Value: 0 + p21: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-7750755424749557576 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 60f3b30c03e6ba64d9a27dc9dba8f28d, type: 3} + m_Name: OutlineVolumeComponent + m_EditorClassIdentifier: + active: 1 + Enabled: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-7743500325797982168 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ccf1aba9553839d41ae37dd52e9ebcce, type: 3} + m_Name: MotionBlur + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + quality: + m_OverrideState: 1 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0 + clamp: + m_OverrideState: 1 + m_Value: 0.05 +--- !u!114 &-7274224791359825572 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0fd9ee276a1023e439cf7a9c393195fa, type: 3} + m_Name: TestAnimationCurveVolumeComponent + m_EditorClassIdentifier: + active: 1 + testParameter: + m_OverrideState: 1 + m_Value: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0.5 + value: 10 + inSlope: 0 + outSlope: 10 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 15 + inSlope: 10 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!114 &-6335409530604852063 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3} + m_Name: ColorAdjustments + m_EditorClassIdentifier: + active: 1 + postExposure: + m_OverrideState: 1 + m_Value: 0 + contrast: + m_OverrideState: 1 + m_Value: 0 + colorFilter: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + hueShift: + m_OverrideState: 1 + m_Value: 0 + saturation: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-6288072647309666549 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 29fa0085f50d5e54f8144f766051a691, type: 3} + m_Name: FilmGrain + m_EditorClassIdentifier: + active: 1 + type: + m_OverrideState: 1 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0 + response: + m_OverrideState: 1 + m_Value: 0.8 + texture: + m_OverrideState: 1 + m_Value: {fileID: 0} +--- !u!114 &-5520245016509672950 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 97c23e3b12dc18c42a140437e53d3951, type: 3} + m_Name: Tonemapping + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + neutralHDRRangeReductionMode: + m_OverrideState: 1 + m_Value: 2 + acesPreset: + m_OverrideState: 1 + m_Value: 3 + hueShiftAmount: + m_OverrideState: 1 + m_Value: 0 + detectPaperWhite: + m_OverrideState: 1 + m_Value: 0 + paperWhite: + m_OverrideState: 1 + m_Value: 300 + detectBrightnessLimits: + m_OverrideState: 1 + m_Value: 1 + minNits: + m_OverrideState: 1 + m_Value: 0.005 + maxNits: + m_OverrideState: 1 + m_Value: 1000 +--- !u!114 &-5360449096862653589 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: VolumeComponentSupportedEverywhere + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEngine.Rendering.Tests:VolumeComponentEditorSupportedOnTests/VolumeComponentSupportedEverywhere + active: 1 +--- !u!114 &-5139089513906902183 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5a00a63fdd6bd2a45ab1f2d869305ffd, type: 3} + m_Name: OasisFogVolumeComponent + m_EditorClassIdentifier: + active: 1 + Density: + m_OverrideState: 1 + m_Value: 0 + StartDistance: + m_OverrideState: 1 + m_Value: 0 + HeightRange: + m_OverrideState: 1 + m_Value: {x: 0, y: 50} + Tint: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + SunScatteringIntensity: + m_OverrideState: 1 + m_Value: 2 +--- !u!114 &-4463884970436517307 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fb60a22f311433c4c962b888d1393f88, type: 3} + m_Name: PaniniProjection + m_EditorClassIdentifier: + active: 1 + distance: + m_OverrideState: 1 + m_Value: 0 + cropToFit: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &-1410297666881709256 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6bd486065ce11414fa40e631affc4900, type: 3} + m_Name: ProbeVolumesOptions + m_EditorClassIdentifier: + active: 1 + normalBias: + m_OverrideState: 1 + m_Value: 0.33 + viewBias: + m_OverrideState: 1 + m_Value: 0 + scaleBiasWithMinProbeDistance: + m_OverrideState: 1 + m_Value: 0 + samplingNoise: + m_OverrideState: 1 + m_Value: 0.1 + animateSamplingNoise: + m_OverrideState: 1 + m_Value: 1 + leakReductionMode: + m_OverrideState: 1 + m_Value: 1 + minValidDotProductValue: + m_OverrideState: 1 + m_Value: 0.1 + occlusionOnlyReflectionNormalization: + m_OverrideState: 1 + m_Value: 1 + intensityMultiplier: + m_OverrideState: 1 + m_Value: 1 + skyOcclusionIntensityMultiplier: + m_OverrideState: 1 + m_Value: 1 + worldOffset: + m_OverrideState: 1 + m_Value: {x: 0, y: 0, z: 0} +--- !u!114 &-1216621516061285780 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0b2db86121404754db890f4c8dfe81b2, type: 3} + m_Name: Bloom + m_EditorClassIdentifier: + active: 1 + skipIterations: + m_OverrideState: 1 + m_Value: 1 + threshold: + m_OverrideState: 1 + m_Value: 0.9 + intensity: + m_OverrideState: 1 + m_Value: 0 + scatter: + m_OverrideState: 1 + m_Value: 0.7 + clamp: + m_OverrideState: 1 + m_Value: 65472 + tint: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + highQualityFiltering: + m_OverrideState: 1 + m_Value: 0 + downscale: + m_OverrideState: 1 + m_Value: 0 + maxIterations: + m_OverrideState: 1 + m_Value: 6 + dirtTexture: + m_OverrideState: 1 + m_Value: {fileID: 0} + dimension: 1 + dirtIntensity: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-1170528603972255243 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 221518ef91623a7438a71fef23660601, type: 3} + m_Name: WhiteBalance + m_EditorClassIdentifier: + active: 1 + temperature: + m_OverrideState: 1 + m_Value: 0 + tint: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-581120513425526550 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: CopyPasteTestComponent3 + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEditor.Rendering.Tests:VolumeComponentCopyPasteTests/CopyPasteTestComponent3 + active: 1 + p1: + m_OverrideState: 1 + m_Value: 0 + p2: + m_OverrideState: 1 + m_Value: 0 + p31: + m_OverrideState: 1 + m_Value: {r: 0, g: 0, b: 0, a: 1} +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} + m_Name: DefaultVolumeProfile + m_EditorClassIdentifier: + components: + - {fileID: -9167874883656233139} + - {fileID: 1918650496244738858} + - {fileID: 853819529557874667} + - {fileID: 1052315754049611418} + - {fileID: -1170528603972255243} + - {fileID: -8270506406425502121} + - {fileID: -5520245016509672950} + - {fileID: 7173750748008157695} + - {fileID: 1666464333004379222} + - {fileID: 9001657382290151224} + - {fileID: -6335409530604852063} + - {fileID: -1216621516061285780} + - {fileID: 3959858460715838825} + - {fileID: -7743500325797982168} + - {fileID: 4644742534064026673} + - {fileID: -4463884970436517307} + - {fileID: -6288072647309666549} + - {fileID: 7518938298396184218} + - {fileID: -1410297666881709256} +--- !u!114 &853819529557874667 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 06437c1ff663d574d9447842ba0a72e4, type: 3} + m_Name: ScreenSpaceLensFlare + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 + tintColor: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + bloomMip: + m_OverrideState: 1 + m_Value: 1 + firstFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + secondaryFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + warpedFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + warpedFlareScale: + m_OverrideState: 1 + m_Value: {x: 1, y: 1} + samples: + m_OverrideState: 1 + m_Value: 1 + sampleDimmer: + m_OverrideState: 1 + m_Value: 0.5 + vignetteEffect: + m_OverrideState: 1 + m_Value: 1 + startingPosition: + m_OverrideState: 1 + m_Value: 1.25 + scale: + m_OverrideState: 1 + m_Value: 1.5 + streaksIntensity: + m_OverrideState: 1 + m_Value: 0 + streaksLength: + m_OverrideState: 1 + m_Value: 0.5 + streaksOrientation: + m_OverrideState: 1 + m_Value: 0 + streaksThreshold: + m_OverrideState: 1 + m_Value: 0.25 + resolution: + m_OverrideState: 1 + m_Value: 4 + chromaticAbberationIntensity: + m_OverrideState: 1 + m_Value: 0.5 +--- !u!114 &1052315754049611418 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 558a8e2b6826cf840aae193990ba9f2e, type: 3} + m_Name: ShadowsMidtonesHighlights + m_EditorClassIdentifier: + active: 1 + shadows: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + midtones: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + highlights: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + shadowsStart: + m_OverrideState: 1 + m_Value: 0 + shadowsEnd: + m_OverrideState: 1 + m_Value: 0.3 + highlightsStart: + m_OverrideState: 1 + m_Value: 0.55 + highlightsEnd: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &1666464333004379222 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3eb4b772797da9440885e8bd939e9560, type: 3} + m_Name: ColorCurves + m_EditorClassIdentifier: + active: 1 + master: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + red: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + green: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + blue: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + hueVsHue: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 1 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + hueVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 1 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + satVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 0 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + lumVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 0 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!114 &1918650496244738858 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e021b4c809a781e468c2988c016ebbea, type: 3} + m_Name: ColorLookup + m_EditorClassIdentifier: + active: 1 + texture: + m_OverrideState: 1 + m_Value: {fileID: 0} + dimension: 1 + contribution: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &3959858460715838825 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c01700fd266d6914ababb731e09af2eb, type: 3} + m_Name: DepthOfField + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + gaussianStart: + m_OverrideState: 1 + m_Value: 10 + gaussianEnd: + m_OverrideState: 1 + m_Value: 30 + gaussianMaxRadius: + m_OverrideState: 1 + m_Value: 1 + highQualitySampling: + m_OverrideState: 1 + m_Value: 0 + focusDistance: + m_OverrideState: 1 + m_Value: 10 + aperture: + m_OverrideState: 1 + m_Value: 5.6 + focalLength: + m_OverrideState: 1 + m_Value: 50 + bladeCount: + m_OverrideState: 1 + m_Value: 5 + bladeCurvature: + m_OverrideState: 1 + m_Value: 1 + bladeRotation: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &4251301726029935498 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 74955a4b0b4243bc87231e8b59ed9140, type: 3} + m_Name: TestVolume + m_EditorClassIdentifier: + active: 1 + param: + m_OverrideState: 1 + m_Value: 123 +--- !u!114 &4644742534064026673 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 81180773991d8724ab7f2d216912b564, type: 3} + m_Name: ChromaticAberration + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &6940869943325143175 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: VolumeComponentSupportedOnAnySRP + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEngine.Rendering.Tests:VolumeComponentEditorSupportedOnTests/VolumeComponentSupportedOnAnySRP + active: 1 +--- !u!114 &7173750748008157695 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 899c54efeace73346a0a16faa3afe726, type: 3} + m_Name: Vignette + m_EditorClassIdentifier: + active: 1 + color: + m_OverrideState: 1 + m_Value: {r: 0, g: 0, b: 0, a: 1} + center: + m_OverrideState: 1 + m_Value: {x: 0.5, y: 0.5} + intensity: + m_OverrideState: 1 + m_Value: 0 + smoothness: + m_OverrideState: 1 + m_Value: 0.2 + rounded: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &7518938298396184218 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c5e1dc532bcb41949b58bc4f2abfbb7e, type: 3} + m_Name: LensDistortion + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 + xMultiplier: + m_OverrideState: 1 + m_Value: 1 + yMultiplier: + m_OverrideState: 1 + m_Value: 1 + center: + m_OverrideState: 1 + m_Value: {x: 0.5, y: 0.5} + scale: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &9001657382290151224 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cdfbdbb87d3286943a057f7791b43141, type: 3} + m_Name: ChannelMixer + m_EditorClassIdentifier: + active: 1 + redOutRedIn: + m_OverrideState: 1 + m_Value: 100 + redOutGreenIn: + m_OverrideState: 1 + m_Value: 0 + redOutBlueIn: + m_OverrideState: 1 + m_Value: 0 + greenOutRedIn: + m_OverrideState: 1 + m_Value: 0 + greenOutGreenIn: + m_OverrideState: 1 + m_Value: 100 + greenOutBlueIn: + m_OverrideState: 1 + m_Value: 0 + blueOutRedIn: + m_OverrideState: 1 + m_Value: 0 + blueOutGreenIn: + m_OverrideState: 1 + m_Value: 0 + blueOutBlueIn: + m_OverrideState: 1 + m_Value: 100 +--- !u!114 &9122958982931076880 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: CopyPasteTestComponent1 + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEditor.Rendering.Tests:VolumeComponentCopyPasteTests/CopyPasteTestComponent1 + active: 1 + p1: + m_OverrideState: 1 + m_Value: 0 + p2: + m_OverrideState: 1 + m_Value: 0 diff --git a/Assets/Settings/DefaultVolumeProfile.asset.meta b/Assets/Settings/DefaultVolumeProfile.asset.meta new file mode 100644 index 0000000..53b314a --- /dev/null +++ b/Assets/Settings/DefaultVolumeProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ab09877e2e707104187f6f83e2f62510 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Mobile_RPAsset.asset b/Assets/Settings/Mobile_RPAsset.asset new file mode 100644 index 0000000..7ceffe7 --- /dev/null +++ b/Assets/Settings/Mobile_RPAsset.asset @@ -0,0 +1,135 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3} + m_Name: Mobile_RPAsset + m_EditorClassIdentifier: + k_AssetVersion: 12 + k_AssetPreviousVersion: 12 + m_RendererType: 1 + m_RendererData: {fileID: 0} + m_RendererDataList: + - {fileID: 11400000, guid: 65bc7dbf4170f435aa868c779acfb082, type: 2} + m_DefaultRendererIndex: 0 + m_RequireDepthTexture: 0 + m_RequireOpaqueTexture: 0 + m_OpaqueDownsampling: 0 + m_SupportsTerrainHoles: 1 + m_SupportsHDR: 1 + m_HDRColorBufferPrecision: 0 + m_MSAA: 1 + m_RenderScale: 0.8 + m_UpscalingFilter: 0 + m_FsrOverrideSharpness: 0 + m_FsrSharpness: 0.92 + m_EnableLODCrossFade: 1 + m_LODCrossFadeDitheringType: 1 + m_ShEvalMode: 0 + m_LightProbeSystem: 0 + m_ProbeVolumeMemoryBudget: 1024 + m_ProbeVolumeBlendingMemoryBudget: 256 + m_SupportProbeVolumeGPUStreaming: 0 + m_SupportProbeVolumeDiskStreaming: 0 + m_SupportProbeVolumeScenarios: 0 + m_SupportProbeVolumeScenarioBlending: 0 + m_ProbeVolumeSHBands: 1 + m_MainLightRenderingMode: 1 + m_MainLightShadowsSupported: 1 + m_MainLightShadowmapResolution: 1024 + m_AdditionalLightsRenderingMode: 1 + m_AdditionalLightsPerObjectLimit: 4 + m_AdditionalLightShadowsSupported: 0 + m_AdditionalLightsShadowmapResolution: 2048 + m_AdditionalLightsShadowResolutionTierLow: 256 + m_AdditionalLightsShadowResolutionTierMedium: 512 + m_AdditionalLightsShadowResolutionTierHigh: 1024 + m_ReflectionProbeBlending: 1 + m_ReflectionProbeBoxProjection: 1 + m_ShadowDistance: 50 + m_ShadowCascadeCount: 1 + m_Cascade2Split: 0.25 + m_Cascade3Split: {x: 0.1, y: 0.3} + m_Cascade4Split: {x: 0.067, y: 0.2, z: 0.467} + m_CascadeBorder: 0.2 + m_ShadowDepthBias: 1 + m_ShadowNormalBias: 1 + m_AnyShadowsSupported: 1 + m_SoftShadowsSupported: 0 + m_ConservativeEnclosingSphere: 1 + m_NumIterationsEnclosingSphere: 64 + m_SoftShadowQuality: 2 + m_AdditionalLightsCookieResolution: 1024 + m_AdditionalLightsCookieFormat: 1 + m_UseSRPBatcher: 1 + m_SupportsDynamicBatching: 0 + m_MixedLightingSupported: 1 + m_SupportsLightCookies: 1 + m_SupportsLightLayers: 1 + m_DebugLevel: 0 + m_StoreActionsOptimization: 0 + m_UseAdaptivePerformance: 1 + m_ColorGradingMode: 0 + m_ColorGradingLutSize: 32 + m_UseFastSRGBLinearConversion: 1 + m_SupportDataDrivenLensFlare: 1 + m_SupportScreenSpaceLensFlare: 1 + m_GPUResidentDrawerMode: 0 + m_UseLegacyLightmaps: 0 + m_SmallMeshScreenPercentage: 0 + m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0 + m_ShadowType: 1 + m_LocalShadowsSupported: 0 + m_LocalShadowsAtlasResolution: 256 + m_MaxPixelLights: 0 + m_ShadowAtlasResolution: 256 + m_VolumeFrameworkUpdateMode: 0 + m_VolumeProfile: {fileID: 11400000, guid: 10fc4df2da32a41aaa32d77bc913491c, type: 2} + apvScenesData: + obsoleteSceneBounds: + m_Keys: [] + m_Values: [] + obsoleteHasProbeVolumes: + m_Keys: [] + m_Values: + m_PrefilteringModeMainLightShadows: 3 + m_PrefilteringModeAdditionalLight: 4 + m_PrefilteringModeAdditionalLightShadows: 0 + m_PrefilterXRKeywords: 1 + m_PrefilteringModeForwardPlus: 1 + m_PrefilteringModeDeferredRendering: 0 + m_PrefilteringModeScreenSpaceOcclusion: 0 + m_PrefilterDebugKeywords: 1 + m_PrefilterWriteRenderingLayers: 1 + m_PrefilterHDROutput: 1 + m_PrefilterSSAODepthNormals: 1 + m_PrefilterSSAOSourceDepthLow: 1 + m_PrefilterSSAOSourceDepthMedium: 0 + m_PrefilterSSAOSourceDepthHigh: 1 + m_PrefilterSSAOInterleaved: 0 + m_PrefilterSSAOBlueNoise: 1 + m_PrefilterSSAOSampleCountLow: 1 + m_PrefilterSSAOSampleCountMedium: 0 + m_PrefilterSSAOSampleCountHigh: 1 + m_PrefilterDBufferMRT1: 1 + m_PrefilterDBufferMRT2: 1 + m_PrefilterDBufferMRT3: 1 + m_PrefilterSoftShadowsQualityLow: 1 + m_PrefilterSoftShadowsQualityMedium: 1 + m_PrefilterSoftShadowsQualityHigh: 1 + m_PrefilterSoftShadows: 0 + m_PrefilterScreenCoord: 1 + m_PrefilterNativeRenderPass: 1 + m_PrefilterUseLegacyLightmaps: 0 + m_ShaderVariantLogLevel: 0 + m_ShadowCascades: 0 + m_Textures: + blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3} + bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3} diff --git a/Assets/Settings/Mobile_RPAsset.asset.meta b/Assets/Settings/Mobile_RPAsset.asset.meta new file mode 100644 index 0000000..3660d15 --- /dev/null +++ b/Assets/Settings/Mobile_RPAsset.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5e6cbd92db86f4b18aec3ed561671858 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Mobile_Renderer.asset b/Assets/Settings/Mobile_Renderer.asset new file mode 100644 index 0000000..ea246b2 --- /dev/null +++ b/Assets/Settings/Mobile_Renderer.asset @@ -0,0 +1,52 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3} + m_Name: Mobile_Renderer + m_EditorClassIdentifier: + debugShaders: + debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, + type: 3} + hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3} + probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, + type: 3} + probeVolumeResources: + probeVolumeDebugShader: {fileID: 0} + probeVolumeFragmentationDebugShader: {fileID: 0} + probeVolumeOffsetDebugShader: {fileID: 0} + probeVolumeSamplingDebugShader: {fileID: 0} + probeSamplingDebugMesh: {fileID: 0} + probeSamplingDebugTexture: {fileID: 0} + probeVolumeBlendStatesCS: {fileID: 0} + m_RendererFeatures: [] + m_RendererFeatureMap: + m_UseNativeRenderPass: 1 + postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2} + m_AssetVersion: 2 + m_OpaqueLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_TransparentLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_DefaultStencilState: + overrideStencilState: 0 + stencilReference: 0 + stencilCompareFunction: 8 + passOperation: 2 + failOperation: 0 + zFailOperation: 0 + m_ShadowTransparentReceive: 0 + m_RenderingMode: 0 + m_DepthPrimingMode: 0 + m_CopyDepthMode: 0 + m_AccurateGbufferNormals: 0 + m_IntermediateTextureMode: 0 diff --git a/Assets/Settings/Mobile_Renderer.asset.meta b/Assets/Settings/Mobile_Renderer.asset.meta new file mode 100644 index 0000000..a3588b1 --- /dev/null +++ b/Assets/Settings/Mobile_Renderer.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 65bc7dbf4170f435aa868c779acfb082 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/PC_RPAsset.asset b/Assets/Settings/PC_RPAsset.asset new file mode 100644 index 0000000..a48b885 --- /dev/null +++ b/Assets/Settings/PC_RPAsset.asset @@ -0,0 +1,141 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3} + m_Name: PC_RPAsset + m_EditorClassIdentifier: + k_AssetVersion: 12 + k_AssetPreviousVersion: 12 + m_RendererType: 1 + m_RendererData: {fileID: 0} + m_RendererDataList: + - {fileID: 11400000, guid: f288ae1f4751b564a96ac7587541f7a2, type: 2} + m_DefaultRendererIndex: 0 + m_RequireDepthTexture: 1 + m_RequireOpaqueTexture: 1 + m_OpaqueDownsampling: 1 + m_SupportsTerrainHoles: 1 + m_SupportsHDR: 1 + m_HDRColorBufferPrecision: 0 + m_MSAA: 1 + m_RenderScale: 1 + m_UpscalingFilter: 0 + m_FsrOverrideSharpness: 0 + m_FsrSharpness: 0.92 + m_EnableLODCrossFade: 1 + m_LODCrossFadeDitheringType: 1 + m_ShEvalMode: 0 + m_LightProbeSystem: 0 + m_ProbeVolumeMemoryBudget: 1024 + m_ProbeVolumeBlendingMemoryBudget: 256 + m_SupportProbeVolumeGPUStreaming: 0 + m_SupportProbeVolumeDiskStreaming: 0 + m_SupportProbeVolumeScenarios: 0 + m_SupportProbeVolumeScenarioBlending: 0 + m_ProbeVolumeSHBands: 1 + m_MainLightRenderingMode: 1 + m_MainLightShadowsSupported: 1 + m_MainLightShadowmapResolution: 2048 + m_AdditionalLightsRenderingMode: 1 + m_AdditionalLightsPerObjectLimit: 4 + m_AdditionalLightShadowsSupported: 1 + m_AdditionalLightsShadowmapResolution: 2048 + m_AdditionalLightsShadowResolutionTierLow: 256 + m_AdditionalLightsShadowResolutionTierMedium: 512 + m_AdditionalLightsShadowResolutionTierHigh: 1024 + m_ReflectionProbeBlending: 1 + m_ReflectionProbeBoxProjection: 1 + m_ReflectionProbeAtlas: 1 + m_ShadowDistance: 50 + m_ShadowCascadeCount: 4 + m_Cascade2Split: 0.25 + m_Cascade3Split: {x: 0.1, y: 0.3} + m_Cascade4Split: {x: 0.12299999, y: 0.2926, z: 0.53599995} + m_CascadeBorder: 0.107758604 + m_ShadowDepthBias: 0.1 + m_ShadowNormalBias: 0.5 + m_AnyShadowsSupported: 1 + m_SoftShadowsSupported: 1 + m_ConservativeEnclosingSphere: 1 + m_NumIterationsEnclosingSphere: 64 + m_SoftShadowQuality: 3 + m_AdditionalLightsCookieResolution: 2048 + m_AdditionalLightsCookieFormat: 3 + m_UseSRPBatcher: 1 + m_SupportsDynamicBatching: 0 + m_MixedLightingSupported: 1 + m_SupportsLightCookies: 1 + m_SupportsLightLayers: 1 + m_DebugLevel: 0 + m_StoreActionsOptimization: 0 + m_UseAdaptivePerformance: 1 + m_ColorGradingMode: 0 + m_ColorGradingLutSize: 32 + m_AllowPostProcessAlphaOutput: 0 + m_UseFastSRGBLinearConversion: 0 + m_SupportDataDrivenLensFlare: 1 + m_SupportScreenSpaceLensFlare: 1 + m_GPUResidentDrawerMode: 0 + m_SmallMeshScreenPercentage: 0 + m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0 + m_ShadowType: 1 + m_LocalShadowsSupported: 0 + m_LocalShadowsAtlasResolution: 256 + m_MaxPixelLights: 0 + m_ShadowAtlasResolution: 256 + m_VolumeFrameworkUpdateMode: 0 + m_VolumeProfile: {fileID: 11400000, guid: 10fc4df2da32a41aaa32d77bc913491c, type: 2} + apvScenesData: + obsoleteSceneBounds: + m_Keys: [] + m_Values: [] + obsoleteHasProbeVolumes: + m_Keys: [] + m_Values: + m_PrefilteringModeMainLightShadows: 3 + m_PrefilteringModeAdditionalLight: 0 + m_PrefilteringModeAdditionalLightShadows: 2 + m_PrefilterXRKeywords: 1 + m_PrefilteringModeForwardPlus: 2 + m_PrefilteringModeDeferredRendering: 0 + m_PrefilteringModeScreenSpaceOcclusion: 2 + m_PrefilterDebugKeywords: 1 + m_PrefilterWriteRenderingLayers: 1 + m_PrefilterHDROutput: 1 + m_PrefilterAlphaOutput: 1 + m_PrefilterSSAODepthNormals: 0 + m_PrefilterSSAOSourceDepthLow: 1 + m_PrefilterSSAOSourceDepthMedium: 1 + m_PrefilterSSAOSourceDepthHigh: 1 + m_PrefilterSSAOInterleaved: 1 + m_PrefilterSSAOBlueNoise: 0 + m_PrefilterSSAOSampleCountLow: 1 + m_PrefilterSSAOSampleCountMedium: 0 + m_PrefilterSSAOSampleCountHigh: 1 + m_PrefilterDBufferMRT1: 1 + m_PrefilterDBufferMRT2: 1 + m_PrefilterDBufferMRT3: 1 + m_PrefilterSoftShadowsQualityLow: 1 + m_PrefilterSoftShadowsQualityMedium: 1 + m_PrefilterSoftShadowsQualityHigh: 1 + m_PrefilterSoftShadows: 0 + m_PrefilterScreenCoord: 1 + m_PrefilterNativeRenderPass: 1 + m_PrefilterUseLegacyLightmaps: 0 + m_PrefilterBicubicLightmapSampling: 1 + m_PrefilterReflectionProbeBlending: 0 + m_PrefilterReflectionProbeBoxProjection: 0 + m_PrefilterReflectionProbeAtlas: 0 + m_ShaderVariantLogLevel: 0 + m_ShadowCascades: 0 + m_Textures: + blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3} + bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3} diff --git a/Assets/Settings/PC_RPAsset.asset.meta b/Assets/Settings/PC_RPAsset.asset.meta new file mode 100644 index 0000000..e286b2f --- /dev/null +++ b/Assets/Settings/PC_RPAsset.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4b83569d67af61e458304325a23e5dfd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/PC_Renderer.asset b/Assets/Settings/PC_Renderer.asset new file mode 100644 index 0000000..475b02e --- /dev/null +++ b/Assets/Settings/PC_Renderer.asset @@ -0,0 +1,95 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3} + m_Name: PC_Renderer + m_EditorClassIdentifier: + debugShaders: + debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, + type: 3} + hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3} + probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, + type: 3} + probeVolumeResources: + probeVolumeDebugShader: {fileID: 4800000, guid: e5c6678ed2aaa91408dd3df699057aae, + type: 3} + probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 03cfc4915c15d504a9ed85ecc404e607, + type: 3} + probeVolumeOffsetDebugShader: {fileID: 4800000, guid: 53a11f4ebaebf4049b3638ef78dc9664, + type: 3} + probeVolumeSamplingDebugShader: {fileID: 4800000, guid: 8f96cd657dc40064aa21efcc7e50a2e7, + type: 3} + probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 57d7c4c16e2765b47a4d2069b311bffe, + type: 3} + probeSamplingDebugTexture: {fileID: 2800000, guid: 24ec0e140fb444a44ab96ee80844e18e, + type: 3} + probeVolumeBlendStatesCS: {fileID: 7200000, guid: b9a23f869c4fd45f19c5ada54dd82176, + type: 3} + m_RendererFeatures: + - {fileID: 7833122117494664109} + m_RendererFeatureMap: ad6b866f10d7b46c + m_UseNativeRenderPass: 1 + postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2} + m_AssetVersion: 2 + m_OpaqueLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_TransparentLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_DefaultStencilState: + overrideStencilState: 0 + stencilReference: 1 + stencilCompareFunction: 3 + passOperation: 2 + failOperation: 0 + zFailOperation: 0 + m_ShadowTransparentReceive: 1 + m_RenderingMode: 2 + m_DepthPrimingMode: 0 + m_CopyDepthMode: 0 + m_AccurateGbufferNormals: 0 + m_IntermediateTextureMode: 0 +--- !u!114 &7833122117494664109 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f62c9c65cf3354c93be831c8bc075510, type: 3} + m_Name: ScreenSpaceAmbientOcclusion + m_EditorClassIdentifier: + m_Active: 1 + m_Settings: + AOMethod: 0 + Downsample: 0 + AfterOpaque: 0 + Source: 1 + NormalSamples: 1 + Intensity: 0.4 + DirectLightingStrength: 0.25 + Radius: 0.3 + Samples: 1 + BlurQuality: 0 + Falloff: 100 + SampleCount: -1 + m_BlueNoise256Textures: + - {fileID: 2800000, guid: 36f118343fc974119bee3d09e2111500, type: 3} + - {fileID: 2800000, guid: 4b7b083e6b6734e8bb2838b0b50a0bc8, type: 3} + - {fileID: 2800000, guid: c06cc21c692f94f5fb5206247191eeee, type: 3} + - {fileID: 2800000, guid: cb76dd40fa7654f9587f6a344f125c9a, type: 3} + - {fileID: 2800000, guid: e32226222ff144b24bf3a5a451de54bc, type: 3} + - {fileID: 2800000, guid: 3302065f671a8450b82c9ddf07426f3a, type: 3} + - {fileID: 2800000, guid: 56a77a3e8d64f47b6afe9e3c95cb57d5, type: 3} + m_Shader: {fileID: 4800000, guid: 0849e84e3d62649e8882e9d6f056a017, type: 3} diff --git a/Assets/Settings/PC_Renderer.asset.meta b/Assets/Settings/PC_Renderer.asset.meta new file mode 100644 index 0000000..ddae6a5 --- /dev/null +++ b/Assets/Settings/PC_Renderer.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f288ae1f4751b564a96ac7587541f7a2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/SampleSceneProfile.asset b/Assets/Settings/SampleSceneProfile.asset new file mode 100644 index 0000000..c1b0f63 --- /dev/null +++ b/Assets/Settings/SampleSceneProfile.asset @@ -0,0 +1,159 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-7893295128165547882 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0b2db86121404754db890f4c8dfe81b2, type: 3} + m_Name: Bloom + m_EditorClassIdentifier: + active: 1 + skipIterations: + m_OverrideState: 1 + m_Value: 0 + threshold: + m_OverrideState: 1 + m_Value: 1 + intensity: + m_OverrideState: 1 + m_Value: 0.25 + scatter: + m_OverrideState: 1 + m_Value: 0.5 + clamp: + m_OverrideState: 0 + m_Value: 65472 + tint: + m_OverrideState: 0 + m_Value: {r: 1, g: 1, b: 1, a: 1} + highQualityFiltering: + m_OverrideState: 1 + m_Value: 1 + downscale: + m_OverrideState: 0 + m_Value: 0 + maxIterations: + m_OverrideState: 0 + m_Value: 6 + dirtTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + dimension: 1 + dirtIntensity: + m_OverrideState: 0 + m_Value: 0 +--- !u!114 &-3357603926938260329 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 899c54efeace73346a0a16faa3afe726, type: 3} + m_Name: Vignette + m_EditorClassIdentifier: + active: 1 + color: + m_OverrideState: 0 + m_Value: {r: 0, g: 0, b: 0, a: 1} + center: + m_OverrideState: 0 + m_Value: {x: 0.5, y: 0.5} + intensity: + m_OverrideState: 1 + m_Value: 0.2 + smoothness: + m_OverrideState: 0 + m_Value: 0.2 + rounded: + m_OverrideState: 0 + m_Value: 0 +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} + m_Name: SampleSceneProfile + m_EditorClassIdentifier: + components: + - {fileID: 849379129802519247} + - {fileID: -7893295128165547882} + - {fileID: 7391319092446245454} + - {fileID: -3357603926938260329} +--- !u!114 &849379129802519247 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 97c23e3b12dc18c42a140437e53d3951, type: 3} + m_Name: Tonemapping + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 1 + neutralHDRRangeReductionMode: + m_OverrideState: 0 + m_Value: 2 + acesPreset: + m_OverrideState: 0 + m_Value: 3 + hueShiftAmount: + m_OverrideState: 0 + m_Value: 0 + detectPaperWhite: + m_OverrideState: 1 + m_Value: 0 + paperWhite: + m_OverrideState: 1 + m_Value: 234 + detectBrightnessLimits: + m_OverrideState: 1 + m_Value: 1 + minNits: + m_OverrideState: 1 + m_Value: 0.005 + maxNits: + m_OverrideState: 1 + m_Value: 647 +--- !u!114 &7391319092446245454 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ccf1aba9553839d41ae37dd52e9ebcce, type: 3} + m_Name: MotionBlur + m_EditorClassIdentifier: + active: 0 + mode: + m_OverrideState: 0 + m_Value: 0 + quality: + m_OverrideState: 1 + m_Value: 2 + intensity: + m_OverrideState: 1 + m_Value: 0.6 + clamp: + m_OverrideState: 0 + m_Value: 0.05 diff --git a/Assets/Settings/SampleSceneProfile.asset.meta b/Assets/Settings/SampleSceneProfile.asset.meta new file mode 100644 index 0000000..b82270c --- /dev/null +++ b/Assets/Settings/SampleSceneProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 10fc4df2da32a41aaa32d77bc913491c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset b/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset new file mode 100644 index 0000000..bc3daf7 --- /dev/null +++ b/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset @@ -0,0 +1,407 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2ec995e51a6e251468d2a3fd8a686257, type: 3} + m_Name: UniversalRenderPipelineGlobalSettings + m_EditorClassIdentifier: + m_ShaderStrippingSetting: + m_Version: 0 + m_ExportShaderVariants: 1 + m_ShaderVariantLogLevel: 0 + m_StripRuntimeDebugShaders: 1 + m_URPShaderStrippingSetting: + m_Version: 0 + m_StripUnusedPostProcessingVariants: 1 + m_StripUnusedVariants: 1 + m_StripScreenCoordOverrideVariants: 1 + m_ShaderVariantLogLevel: 0 + m_ExportShaderVariants: 1 + m_StripDebugVariants: 1 + m_StripUnusedPostProcessingVariants: 1 + m_StripUnusedVariants: 1 + m_StripScreenCoordOverrideVariants: 1 + supportRuntimeDebugDisplay: 0 + m_EnableRenderGraph: 0 + m_Settings: + m_SettingsList: + m_List: + - rid: 6852985685364965376 + - rid: 6852985685364965377 + - rid: 6852985685364965378 + - rid: 6852985685364965379 + - rid: 6852985685364965380 + - rid: 6852985685364965381 + - rid: 6852985685364965382 + - rid: 6852985685364965383 + - rid: 6852985685364965384 + - rid: 6852985685364965385 + - rid: 6852985685364965386 + - rid: 6852985685364965387 + - rid: 6852985685364965388 + - rid: 6852985685364965389 + - rid: 6852985685364965390 + - rid: 6852985685364965391 + - rid: 6852985685364965392 + - rid: 6852985685364965393 + - rid: 6852985685364965394 + - rid: 8712630790384254976 + - rid: 6474872856636817408 + - rid: 6474872856636817409 + - rid: 6474872856636817410 + - rid: 6474872856636817411 + - rid: 6474872856636817412 + - rid: 6474872856636817413 + - rid: 6474872856636817414 + - rid: 6474872856636817415 + - rid: 6474872856636817416 + m_RuntimeSettings: + m_List: + - rid: 6852985685364965378 + - rid: 6852985685364965379 + - rid: 6852985685364965380 + - rid: 6852985685364965381 + - rid: 6852985685364965384 + - rid: 6852985685364965385 + - rid: 6852985685364965392 + - rid: 6852985685364965394 + - rid: 8712630790384254976 + - rid: 6474872856636817409 + - rid: 6474872856636817411 + - rid: 6474872856636817412 + - rid: 6474872856636817415 + - rid: 6474872856636817416 + m_AssetVersion: 8 + m_ObsoleteDefaultVolumeProfile: {fileID: 0} + m_RenderingLayerNames: + - Light Layer default + - Light Layer 1 + - Light Layer 2 + - Light Layer 3 + - Light Layer 4 + - Light Layer 5 + - Light Layer 6 + - Light Layer 7 + m_ValidRenderingLayers: 0 + lightLayerName0: Light Layer default + lightLayerName1: Light Layer 1 + lightLayerName2: Light Layer 2 + lightLayerName3: Light Layer 3 + lightLayerName4: Light Layer 4 + lightLayerName5: Light Layer 5 + lightLayerName6: Light Layer 6 + lightLayerName7: Light Layer 7 + apvScenesData: + obsoleteSceneBounds: + m_Keys: [] + m_Values: [] + obsoleteHasProbeVolumes: + m_Keys: [] + m_Values: + references: + version: 2 + RefIds: + - rid: 6474872856636817408 + type: {class: PostProcessData/ShaderResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + stopNanPS: {fileID: 4800000, guid: 1121bb4e615ca3c48b214e79e841e823, type: 3} + subpixelMorphologicalAntialiasingPS: {fileID: 4800000, guid: 63eaba0ebfb82cc43bde059b4a8c65f6, type: 3} + gaussianDepthOfFieldPS: {fileID: 4800000, guid: 5e7134d6e63e0bc47a1dd2669cedb379, type: 3} + bokehDepthOfFieldPS: {fileID: 4800000, guid: 2aed67ad60045d54ba3a00c91e2d2631, type: 3} + cameraMotionBlurPS: {fileID: 4800000, guid: 1edcd131364091c46a17cbff0b1de97a, type: 3} + paniniProjectionPS: {fileID: 4800000, guid: a15b78cf8ca26ca4fb2090293153c62c, type: 3} + lutBuilderLdrPS: {fileID: 4800000, guid: 65df88701913c224d95fc554db28381a, type: 3} + lutBuilderHdrPS: {fileID: 4800000, guid: ec9fec698a3456d4fb18cf8bacb7a2bc, type: 3} + bloomPS: {fileID: 4800000, guid: 5f1864addb451f54bae8c86d230f736e, type: 3} + temporalAntialiasingPS: {fileID: 4800000, guid: 9c70c1a35ff15f340b38ea84842358bf, type: 3} + LensFlareDataDrivenPS: {fileID: 4800000, guid: 6cda457ac28612740adb23da5d39ea92, type: 3} + LensFlareScreenSpacePS: {fileID: 4800000, guid: 701880fecb344ea4c9cd0db3407ab287, type: 3} + scalingSetupPS: {fileID: 4800000, guid: e8ee25143a34b8c4388709ea947055d1, type: 3} + easuPS: {fileID: 4800000, guid: 562b7ae4f629f144aa97780546fce7c6, type: 3} + uberPostPS: {fileID: 4800000, guid: e7857e9d0c934dc4f83f270f8447b006, type: 3} + finalPostPassPS: {fileID: 4800000, guid: c49e63ed1bbcb334780a3bd19dfed403, type: 3} + m_ShaderResourcesVersion: 0 + - rid: 6474872856636817409 + type: {class: ScreenSpaceAmbientOcclusionPersistentResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Shader: {fileID: 4800000, guid: 0849e84e3d62649e8882e9d6f056a017, type: 3} + m_Version: 0 + - rid: 6474872856636817410 + type: {class: UniversalRenderPipelineEditorAssets, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_DefaultSettingsVolumeProfile: {fileID: 11400000, guid: eda47df5b85f4f249abf7abd73db2cb2, type: 2} + - rid: 6474872856636817411 + type: {class: ScreenSpaceAmbientOcclusionDynamicResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_BlueNoise256Textures: + - {fileID: 2800000, guid: 36f118343fc974119bee3d09e2111500, type: 3} + - {fileID: 2800000, guid: 4b7b083e6b6734e8bb2838b0b50a0bc8, type: 3} + - {fileID: 2800000, guid: c06cc21c692f94f5fb5206247191eeee, type: 3} + - {fileID: 2800000, guid: cb76dd40fa7654f9587f6a344f125c9a, type: 3} + - {fileID: 2800000, guid: e32226222ff144b24bf3a5a451de54bc, type: 3} + - {fileID: 2800000, guid: 3302065f671a8450b82c9ddf07426f3a, type: 3} + - {fileID: 2800000, guid: 56a77a3e8d64f47b6afe9e3c95cb57d5, type: 3} + m_Version: 0 + - rid: 6474872856636817412 + type: {class: UniversalRenderPipelineRuntimeXRResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_xrOcclusionMeshPS: {fileID: 4800000, guid: 4431b1f1f743fbf4eb310a967890cbea, type: 3} + m_xrMirrorViewPS: {fileID: 4800000, guid: d5a307c014552314b9f560906d708772, type: 3} + m_xrMotionVector: {fileID: 4800000, guid: f89aac1e4f84468418fe30e611dff395, type: 3} + - rid: 6474872856636817413 + type: {class: PostProcessData/TextureResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + blueNoise16LTex: + - {fileID: 2800000, guid: 81200413a40918d4d8702e94db29911c, type: 3} + - {fileID: 2800000, guid: d50c5e07c9911a74982bddf7f3075e7b, type: 3} + - {fileID: 2800000, guid: 1134690bf9216164dbc75050e35b7900, type: 3} + - {fileID: 2800000, guid: 7ce2118f74614a94aa8a0cdf2e6062c3, type: 3} + - {fileID: 2800000, guid: 2ca97df9d1801e84a8a8f2c53cb744f0, type: 3} + - {fileID: 2800000, guid: e63eef8f54aa9dc4da9a5ac094b503b5, type: 3} + - {fileID: 2800000, guid: 39451254daebd6d40b52899c1f1c0c1b, type: 3} + - {fileID: 2800000, guid: c94ad916058dff743b0f1c969ddbe660, type: 3} + - {fileID: 2800000, guid: ed5ea7ce59ca8ec4f9f14bf470a30f35, type: 3} + - {fileID: 2800000, guid: 071e954febf155243a6c81e48f452644, type: 3} + - {fileID: 2800000, guid: 96aaab9cc247d0b4c98132159688c1af, type: 3} + - {fileID: 2800000, guid: fc3fa8f108657e14486697c9a84ccfc5, type: 3} + - {fileID: 2800000, guid: bfed3e498947fcb4890b7f40f54d85b9, type: 3} + - {fileID: 2800000, guid: d512512f4af60a442ab3458489412954, type: 3} + - {fileID: 2800000, guid: 47a45908f6db0cb44a0d5e961143afec, type: 3} + - {fileID: 2800000, guid: 4dcc0502f8586f941b5c4a66717205e8, type: 3} + - {fileID: 2800000, guid: 9d92991794bb5864c8085468b97aa067, type: 3} + - {fileID: 2800000, guid: 14381521ff11cb74abe3fe65401c23be, type: 3} + - {fileID: 2800000, guid: d36f0fe53425e08499a2333cf423634c, type: 3} + - {fileID: 2800000, guid: d4044ea2490d63b43aa1765f8efbf8a9, type: 3} + - {fileID: 2800000, guid: c9bd74624d8070f429e3f46d161f9204, type: 3} + - {fileID: 2800000, guid: d5c9b274310e5524ebe32a4e4da3df1f, type: 3} + - {fileID: 2800000, guid: f69770e54f2823f43badf77916acad83, type: 3} + - {fileID: 2800000, guid: 10b6c6d22e73dea46a8ab36b6eebd629, type: 3} + - {fileID: 2800000, guid: a2ec5cbf5a9b64345ad3fab0912ddf7b, type: 3} + - {fileID: 2800000, guid: 1c3c6d69a645b804fa232004b96b7ad3, type: 3} + - {fileID: 2800000, guid: d18a24d7b4ed50f4387993566d9d3ae2, type: 3} + - {fileID: 2800000, guid: c989e1ed85cf7154caa922fec53e6af6, type: 3} + - {fileID: 2800000, guid: ff47e5a0f105eb34883b973e51f4db62, type: 3} + - {fileID: 2800000, guid: fa042edbfc40fbd4bad0ab9d505b1223, type: 3} + - {fileID: 2800000, guid: 896d9004736809c4fb5973b7c12eb8b9, type: 3} + - {fileID: 2800000, guid: 179f794063d2a66478e6e726f84a65bc, type: 3} + filmGrainTex: + - {fileID: 2800000, guid: 654c582f7f8a5a14dbd7d119cbde215d, type: 3} + - {fileID: 2800000, guid: dd77ffd079630404e879388999033049, type: 3} + - {fileID: 2800000, guid: 1097e90e1306e26439701489f391a6c0, type: 3} + - {fileID: 2800000, guid: f0b67500f7fad3b4c9f2b13e8f41ba6e, type: 3} + - {fileID: 2800000, guid: 9930fb4528622b34687b00bbe6883de7, type: 3} + - {fileID: 2800000, guid: bd9e8c758250ef449a4b4bfaad7a2133, type: 3} + - {fileID: 2800000, guid: 510a2f57334933e4a8dbabe4c30204e4, type: 3} + - {fileID: 2800000, guid: b4db8180660810945bf8d55ab44352ad, type: 3} + - {fileID: 2800000, guid: fd2fd78b392986e42a12df2177d3b89c, type: 3} + - {fileID: 2800000, guid: 5cdee82a77d13994f83b8fdabed7c301, type: 3} + smaaAreaTex: {fileID: 2800000, guid: d1f1048909d55cd4fa1126ab998f617e, type: 3} + smaaSearchTex: {fileID: 2800000, guid: 51eee22c2a633ef4aada830eed57c3fd, type: 3} + m_TexturesResourcesVersion: 0 + - rid: 6474872856636817414 + type: {class: RenderingDebuggerRuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_version: 0 + - rid: 6474872856636817415 + type: {class: VrsRenderPipelineRuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_TextureComputeShader: {fileID: 7200000, guid: cacb30de6c40c7444bbc78cb0a81fd2a, type: 3} + m_VisualizationShader: {fileID: 4800000, guid: 620b55b8040a88d468e94abe55bed5ba, type: 3} + m_VisualizationLookupTable: + m_Data: + - {r: 1, g: 0, b: 0, a: 1} + - {r: 1, g: 0.92156863, b: 0.015686275, a: 1} + - {r: 1, g: 1, b: 1, a: 1} + - {r: 0, g: 1, b: 0, a: 1} + - {r: 0.75, g: 0.75, b: 0, a: 1} + - {r: 0, g: 0.75, b: 0.55, a: 1} + - {r: 0.5, g: 0, b: 0.5, a: 1} + - {r: 0.5, g: 0.5, b: 0.5, a: 1} + - {r: 0, g: 0, b: 1, a: 1} + m_ConversionLookupTable: + m_Data: + - {r: 1, g: 0, b: 0, a: 1} + - {r: 1, g: 0.92156863, b: 0.015686275, a: 1} + - {r: 1, g: 1, b: 1, a: 1} + - {r: 0, g: 1, b: 0, a: 1} + - {r: 0.75, g: 0.75, b: 0, a: 1} + - {r: 0, g: 0.75, b: 0.55, a: 1} + - {r: 0.5, g: 0, b: 0.5, a: 1} + - {r: 0.5, g: 0.5, b: 0.5, a: 1} + - {r: 0, g: 0, b: 1, a: 1} + - rid: 6474872856636817416 + type: {class: LightmapSamplingSettings, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + m_UseBicubicLightmapSampling: 0 + - rid: 6852985685364965376 + type: {class: URPShaderStrippingSetting, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_StripUnusedPostProcessingVariants: 1 + m_StripUnusedVariants: 1 + m_StripScreenCoordOverrideVariants: 1 + - rid: 6852985685364965377 + type: {class: UniversalRenderPipelineEditorShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_AutodeskInteractive: {fileID: 4800000, guid: 0e9d5a909a1f7e84882a534d0d11e49f, type: 3} + m_AutodeskInteractiveTransparent: {fileID: 4800000, guid: 5c81372d981403744adbdda4433c9c11, type: 3} + m_AutodeskInteractiveMasked: {fileID: 4800000, guid: 80aa867ac363ac043847b06ad71604cd, type: 3} + m_DefaultSpeedTree7Shader: {fileID: 4800000, guid: 0f4122b9a743b744abe2fb6a0a88868b, type: 3} + m_DefaultSpeedTree8Shader: {fileID: -6465566751694194690, guid: 9920c1f1781549a46ba081a2a15a16ec, type: 3} + m_DefaultSpeedTree9Shader: {fileID: -6465566751694194690, guid: cbd3e1cc4ae141c42a30e33b4d666a61, type: 3} + - rid: 6852985685364965378 + type: {class: UniversalRendererResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_CopyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3} + m_CameraMotionVector: {fileID: 4800000, guid: c56b7e0d4c7cb484e959caeeedae9bbf, type: 3} + m_StencilDeferredPS: {fileID: 4800000, guid: e9155b26e1bc55942a41e518703fe304, type: 3} + m_ClusterDeferred: {fileID: 4800000, guid: 222cce62363a44a380c36bf03b392608, type: 3} + m_StencilDitherMaskSeedPS: {fileID: 4800000, guid: 8c3ee818f2efa514c889881ccb2e95a2, type: 3} + m_DBufferClear: {fileID: 4800000, guid: f056d8bd2a1c7e44e9729144b4c70395, type: 3} + - rid: 6852985685364965379 + type: {class: UniversalRenderPipelineDebugShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_DebugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3} + m_HdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3} + m_ProbeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, type: 3} + - rid: 6852985685364965380 + type: {class: UniversalRenderPipelineRuntimeShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_FallbackErrorShader: {fileID: 4800000, guid: e6e9a19c3678ded42a3bc431ebef7dbd, type: 3} + m_BlitHDROverlay: {fileID: 4800000, guid: a89bee29cffa951418fc1e2da94d1959, type: 3} + m_CoreBlitPS: {fileID: 4800000, guid: 93446b5c5339d4f00b85c159e1159b7c, type: 3} + m_CoreBlitColorAndDepthPS: {fileID: 4800000, guid: d104b2fc1ca6445babb8e90b0758136b, type: 3} + m_SamplingPS: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3} + m_TerrainDetailLit: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3} + m_TerrainDetailGrassBillboard: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3} + m_TerrainDetailGrass: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3} + - rid: 6852985685364965381 + type: {class: UniversalRenderPipelineRuntimeTextures, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 1 + m_BlueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3} + m_BayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3} + m_DebugFontTex: {fileID: 2800000, guid: 26a413214480ef144b2915d6ff4d0beb, type: 3} + - rid: 6852985685364965382 + type: {class: Renderer2DResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_LightShader: {fileID: 4800000, guid: 3f6c848ca3d7bca4bbe846546ac701a1, type: 3} + m_ProjectedShadowShader: {fileID: 4800000, guid: ce09d4a80b88c5a4eb9768fab4f1ee00, type: 3} + m_SpriteShadowShader: {fileID: 4800000, guid: 44fc62292b65ab04eabcf310e799ccf6, type: 3} + m_SpriteUnshadowShader: {fileID: 4800000, guid: de02b375720b5c445afe83cd483bedf3, type: 3} + m_GeometryShadowShader: {fileID: 4800000, guid: 19349a0f9a7ed4c48a27445bcf92e5e1, type: 3} + m_GeometryUnshadowShader: {fileID: 4800000, guid: 77774d9009bb81447b048c907d4c6273, type: 3} + m_FallOffLookup: {fileID: 2800000, guid: 5688ab254e4c0634f8d6c8e0792331ca, type: 3} + m_CopyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3} + m_DefaultLitMaterial: {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_DefaultUnlitMaterial: {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2} + m_DefaultMaskMaterial: {fileID: 2100000, guid: 15d0c3709176029428a0da2f8cecf0b5, type: 2} + - rid: 6852985685364965383 + type: {class: UniversalRenderPipelineEditorMaterials, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_DefaultMaterial: {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2} + m_DefaultParticleMaterial: {fileID: 2100000, guid: e823cd5b5d27c0f4b8256e7c12ee3e6d, type: 2} + m_DefaultLineMaterial: {fileID: 2100000, guid: e823cd5b5d27c0f4b8256e7c12ee3e6d, type: 2} + m_DefaultTerrainMaterial: {fileID: 2100000, guid: 594ea882c5a793440b60ff72d896021e, type: 2} + m_DefaultDecalMaterial: {fileID: 2100000, guid: 31d0dcc6f2dd4e4408d18036a2c93862, type: 2} + m_DefaultSpriteMaterial: {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2} + - rid: 6852985685364965384 + type: {class: URPDefaultVolumeProfileSettings, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_VolumeProfile: {fileID: 11400000, guid: ab09877e2e707104187f6f83e2f62510, type: 2} + - rid: 6852985685364965385 + type: {class: RenderGraphSettings, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_EnableRenderCompatibilityMode: 0 + - rid: 6852985685364965386 + type: {class: GPUResidentDrawerResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.GPUDriven.Runtime} + data: + m_Version: 0 + m_InstanceDataBufferCopyKernels: {fileID: 7200000, guid: f984aeb540ded8b4fbb8a2047ab5b2e2, type: 3} + m_InstanceDataBufferUploadKernels: {fileID: 7200000, guid: 53864816eb00f2343b60e1a2c5a262ef, type: 3} + m_TransformUpdaterKernels: {fileID: 7200000, guid: 2a567b9b2733f8d47a700c3c85bed75b, type: 3} + m_WindDataUpdaterKernels: {fileID: 7200000, guid: fde76746e4fd0ed418c224f6b4084114, type: 3} + m_OccluderDepthPyramidKernels: {fileID: 7200000, guid: 08b2b5fb307b0d249860612774a987da, type: 3} + m_InstanceOcclusionCullingKernels: {fileID: 7200000, guid: f6d223acabc2f974795a5a7864b50e6c, type: 3} + m_OcclusionCullingDebugKernels: {fileID: 7200000, guid: b23e766bcf50ca4438ef186b174557df, type: 3} + m_DebugOcclusionTestPS: {fileID: 4800000, guid: d3f0849180c2d0944bc71060693df100, type: 3} + m_DebugOccluderPS: {fileID: 4800000, guid: b3c92426a88625841ab15ca6a7917248, type: 3} + - rid: 6852985685364965387 + type: {class: STP/RuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_setupCS: {fileID: 7200000, guid: 33be2e9a5506b2843bdb2bdff9cad5e1, type: 3} + m_preTaaCS: {fileID: 7200000, guid: a679dba8ec4d9ce45884a270b0e22dda, type: 3} + m_taaCS: {fileID: 7200000, guid: 3923900e2b41b5e47bc25bfdcbcdc9e6, type: 3} + - rid: 6852985685364965388 + type: {class: ProbeVolumeBakingResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + dilationShader: {fileID: 7200000, guid: 6bb382f7de370af41b775f54182e491d, type: 3} + subdivideSceneCS: {fileID: 7200000, guid: bb86f1f0af829fd45b2ebddda1245c22, type: 3} + voxelizeSceneShader: {fileID: 4800000, guid: c8b6a681c7b4e2e4785ffab093907f9e, type: 3} + traceVirtualOffsetCS: {fileID: -6772857160820960102, guid: ff2cbab5da58bf04d82c5f34037ed123, type: 3} + traceVirtualOffsetRT: {fileID: -5126288278712620388, guid: ff2cbab5da58bf04d82c5f34037ed123, type: 3} + skyOcclusionCS: {fileID: -6772857160820960102, guid: 5a2a534753fbdb44e96c3c78b5a6999d, type: 3} + skyOcclusionRT: {fileID: -5126288278712620388, guid: 5a2a534753fbdb44e96c3c78b5a6999d, type: 3} + renderingLayerCS: {fileID: -6772857160820960102, guid: 94a070d33e408384bafc1dea4a565df9, type: 3} + renderingLayerRT: {fileID: -5126288278712620388, guid: 94a070d33e408384bafc1dea4a565df9, type: 3} + - rid: 6852985685364965389 + type: {class: ProbeVolumeGlobalSettings, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + m_ProbeVolumeDisableStreamingAssets: 0 + - rid: 6852985685364965390 + type: {class: ProbeVolumeDebugResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + probeVolumeDebugShader: {fileID: 4800000, guid: 3b21275fd12d65f49babb5286f040f2d, type: 3} + probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 3a80877c579b9144ebdcc6d923bca303, type: 3} + probeVolumeSamplingDebugShader: {fileID: 4800000, guid: bf54e6528c79a224e96346799064c393, type: 3} + probeVolumeOffsetDebugShader: {fileID: 4800000, guid: db8bd7436dc2c5f4c92655307d198381, type: 3} + probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 20be25aac4e22ee49a7db76fb3df6de2, type: 3} + numbersDisplayTex: {fileID: 2800000, guid: 73fe53b428c5b3440b7e87ee830b608a, type: 3} + - rid: 6852985685364965391 + type: {class: IncludeAdditionalRPAssets, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_version: 0 + m_IncludeReferencedInScenes: 0 + m_IncludeAssetsByLabel: 0 + m_LabelToInclude: + - rid: 6852985685364965392 + type: {class: ShaderStrippingSetting, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 0 + m_ExportShaderVariants: 1 + m_ShaderVariantLogLevel: 0 + m_StripRuntimeDebugShaders: 1 + - rid: 6852985685364965393 + type: {class: ProbeVolumeRuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + probeVolumeBlendStatesCS: {fileID: 7200000, guid: a3f7b8c99de28a94684cb1daebeccf5d, type: 3} + probeVolumeUploadDataCS: {fileID: 7200000, guid: 0951de5992461754fa73650732c4954c, type: 3} + probeVolumeUploadDataL2CS: {fileID: 7200000, guid: 6196f34ed825db14b81fb3eb0ea8d931, type: 3} + - rid: 6852985685364965394 + type: {class: RenderGraphGlobalSettings, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_version: 0 + m_EnableCompilationCaching: 1 + m_EnableValidityChecks: 1 + - rid: 8712630790384254976 + type: {class: RenderGraphUtilsResources, ns: UnityEngine.Rendering.RenderGraphModule.Util, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 0 + m_CoreCopyPS: {fileID: 4800000, guid: 12dc59547ea167a4ab435097dd0f9add, type: 3} diff --git a/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset.meta b/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset.meta new file mode 100644 index 0000000..81b84f2 --- /dev/null +++ b/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18dc0cd2c080841dea60987a38ce93fa +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TutorialInfo.meta b/Assets/TutorialInfo.meta new file mode 100644 index 0000000..a700bca --- /dev/null +++ b/Assets/TutorialInfo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ba062aa6c92b140379dbc06b43dd3b9b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TutorialInfo/Icons.meta b/Assets/TutorialInfo/Icons.meta new file mode 100644 index 0000000..1d19fb9 --- /dev/null +++ b/Assets/TutorialInfo/Icons.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 8a0c9218a650547d98138cd835033977 +folderAsset: yes +timeCreated: 1484670163 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TutorialInfo/Icons/URP.png b/Assets/TutorialInfo/Icons/URP.png new file mode 100644 index 0000000000000000000000000000000000000000..6194a807e27158f864a7c7677f4cbf62d8b94503 GIT binary patch literal 24069 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 HejoV +/// Hlavní třída Unity testovacího klienta pro GeoSus. +/// Tato partial třída obsahuje inicializaci, herní smyčku a správu stavů. +/// +/// POUŽITÍ: +/// 1. Přidejte tento skript na prázdný GameObject +/// 2. Nastavte serverHost a serverPort v Inspectoru +/// 3. Spusťte hru +/// +public partial class UnityTestClient : MonoBehaviour +{ + #region ═══════════════════════════════════════════════════════════════════ + // KONFIGURACE SERVERU + // ════════════════════════════════════════════════════════════════════════ + // Tyto hodnoty nastavte v Unity Inspectoru nebo zde přímo v kódu. + // ServerHost je IP adresa nebo hostname serveru. + // ServerPort je TCP port pro herní komunikaci (výchozí 7777). + // HttpPort je port pro REST API statistik (výchozí 8088). + #endregion + + [Header("Nastavení serveru")] + [Tooltip("IP adresa nebo hostname GeoSus serveru")] + public string serverHost = "127.0.0.1"; + + [Tooltip("TCP port pro herní komunikaci")] + public int serverPort = 7777; + + [Tooltip("HTTP port pro statistiky API (ignorováno pokud useHttps=true)")] + public int httpPort = 8088; + + [Tooltip("Použít HTTPS pro Stats API (pro produkční server)")] + public bool useHttps = false; + + #region ═══════════════════════════════════════════════════════════════════ + // HERNÍ STAVY + // ════════════════════════════════════════════════════════════════════════ + // AppState určuje, která obrazovka se zobrazuje a jaká logika běží. + // Toto je KLIENTSKÝ stav, nezaměňovat s GamePhase ze serveru. + #endregion + + ///

+ /// Stav aplikace určující aktuální obrazovku. + /// MainMenu -> Lobby -> Loading -> InGame -> GameEnded + /// + public enum AppState + { + /// Hlavní menu - připojení, vytvoření/vstup do lobby + MainMenu, + /// V lobby - čekání na hráče, nastavení hry + Lobby, + /// Načítání - stahování mapových dat + Loading, + /// Ve hře - hlavní gameplay + InGame, + /// Konec hry - zobrazení výsledků + GameEnded + } + + #region ═══════════════════════════════════════════════════════════════════ + // HLAVNÍ PROMĚNNÉ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Aktuální stav aplikace (která obrazovka je aktivní) + /// + [HideInInspector] + public AppState currentState = AppState.MainMenu; + + /// + /// Instance herního klienta z ClientSDK. + /// Obsahuje veškerou síťovou logiku a herní data. + /// + protected GameClient client; + + /// + /// Unikátní ID tohoto klienta (generuje se při startu) + /// + protected string clientUuid; + + /// + /// Zobrazované jméno hráče + /// + protected string displayName = "Hráč"; + + /// + /// Fronta notifikací k zobrazení na obrazovce + /// + protected Queue notifications = new Queue(); + + /// + /// Aktuálně zobrazená notifikace (null = žádná) + /// + protected NotificationData currentNotification; + + /// + /// Čas konce zobrazení aktuální notifikace + /// + protected float notificationEndTime; + + /// + /// Chybová zpráva k zobrazení (null = žádná chyba) + /// + protected string errorMessage; + + /// + /// Čas zmizení chybové zprávy + /// + protected float errorMessageEndTime; + + /// + /// Hlavní herní kamera + /// + protected Camera playerCamera; + + /// + /// Výška kamery nad mapou + /// + protected float cameraHeight = 100f; + + #region ═══════════════════════════════════════════════════════════════════ + // DATOVÉ STRUKTURY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Struktura pro notifikaci na obrazovce + /// + protected struct NotificationData + { + /// Text notifikace + public string message; + /// Barva pozadí + public Color color; + /// Doba zobrazení v sekundách + public float duration; + /// Ikona (emoji nebo text) + public string icon; + } + + #region ═══════════════════════════════════════════════════════════════════ + // UNITY LIFECYCLE + // ════════════════════════════════════════════════════════════════════════ + // Unity volá tyto metody automaticky: + // Awake() - při vytvoření objektu (před Start) + // Start() - před prvním Update + // Update() - každý frame + // OnDestroy() - při zničení objektu + // OnGUI() - pro vykreslení IMGUI (může být vícekrát za frame) + #endregion + + /// + /// Inicializace při vytvoření objektu. + /// Generujeme unikátní UUID pro tohoto klienta. + /// + void Awake() + { + // Generujeme unikátní ID klienta + // V produkční hře byste toto ukládali do PlayerPrefs + clientUuid = Guid.NewGuid().ToString("N").Substring(0, 8); + + // Načteme uložené jméno hráče (pokud existuje) + displayName = PlayerPrefs.GetString("PlayerName", "Hráč" + UnityEngine.Random.Range(1, 999)); + + Debug.Log($"[GeoSus] Klient inicializován s UUID: {clientUuid}"); + } + + /// + /// Inicializace před prvním frame. + /// + void Start() + { + // Nastavení kamery pro top-down pohled + SetupCamera(); + + Debug.Log("[GeoSus] Test klient připraven"); + } + + /// + /// Hlavní herní smyčka - volá se každý frame. + /// DŮLEŽITÉ: Zde musíme volat client.Update() pro zpracování síťových událostí! + /// + void Update() + { + // ═══════════════════════════════════════════════════════════════════ + // KRITICKÉ: Zpracování síťových událostí + // ═══════════════════════════════════════════════════════════════════ + // GameClient používá EventDispatcher pro thread-safe přenos událostí + // ze síťového vlákna do hlavního Unity vlákna. + // BEZ TOHOTO VOLÁNÍ nebudete dostávat žádné události ze serveru! + if (client != null) + { + client.Update(); + } + + // Zpracování vstupu podle aktuálního stavu + switch (currentState) + { + case AppState.InGame: + // Herní logika - pohyb, akce + HandlePlayerInput(); + UpdateGameLogic(); + break; + } + + // Aktualizace notifikací + UpdateNotifications(); + + // Aktualizace herních objektů na mapě + if (currentState == AppState.InGame || currentState == AppState.Loading) + { + UpdateMapObjects(); + } + + // Automatické obnovování statistik + UpdateStatsAutoRefresh(); + } + + /// + /// Úklid při zničení objektu. + /// DŮLEŽITÉ: Vždy se odpojte od serveru při ukončení! + /// + void OnDestroy() + { + // Odpojení od serveru + if (client != null) + { + client.Disconnect("Aplikace ukončena"); + client.Dispose(); + client = null; + } + + // Úklid mapových objektů + CleanupMapObjects(); + + Debug.Log("[GeoSus] Klient ukončen"); + } + + /// Příznak, zda jsou GUI styly inicializované + private bool stylesInitialized = false; + + /// + /// Vykreslení IMGUI - volá se každý frame (může i vícekrát). + /// Veškeré UI se vykresluje zde. + /// + void OnGUI() + { + // Inicializace stylů při prvním volání OnGUI + if (!stylesInitialized) + { + InitializeUIStyles(); + stylesInitialized = true; + } + + // Aplikace škálování UI pro různé rozlišení + ApplyUIScaling(); + + // Vykreslení UI podle aktuálního stavu + switch (currentState) + { + case AppState.MainMenu: + DrawMainMenu(); + break; + case AppState.Lobby: + DrawLobbyScreen(); + break; + case AppState.Loading: + DrawLoadingScreen(); + break; + case AppState.InGame: + DrawGameHUD(); + DrawMeetingPanel(); // Zobrazí se jen pokud je meeting aktivní + break; + case AppState.GameEnded: + DrawGameEndScreen(); + break; + } + + // Overlay prvky (notifikace, chyby) - vždy navrchu + DrawNotifications(); + DrawErrorMessage(); + + // Debug panel (pouze v editoru) + #if UNITY_EDITOR + DrawDebugPanel(); + #endif + } + + #region ═══════════════════════════════════════════════════════════════════ + // POMOCNÉ METODY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Zobrazí notifikaci na obrazovce. + /// Notifikace se řadí do fronty a zobrazují postupně. + /// + /// Text zprávy + /// Barva pozadí + /// Ikona/emoji (volitelné) + protected void ShowNotification(string message, Color color, string icon) + { + ShowNotification(message, color, 3f, icon); + } + + /// + /// Zobrazí notifikaci na obrazovce. + /// Notifikace se řadí do fronty a zobrazují postupně. + /// + /// Text zprávy + /// Barva pozadí + /// Doba zobrazení v sekundách + /// Ikona/emoji (volitelné) + protected void ShowNotification(string message, Color color, float duration = 3f, string icon = "") + { + notifications.Enqueue(new NotificationData + { + message = message, + color = color, + duration = duration, + icon = icon + }); + } + + /// + /// Aktualizace fronty notifikací + /// + private void UpdateNotifications() + { + // Pokud aktuální notifikace vypršela, zobrazíme další + if (currentNotification.message != null && Time.time > notificationEndTime) + { + currentNotification = default; + } + + // Pokud není žádná notifikace a fronta není prázdná, zobrazíme další + if (currentNotification.message == null && notifications.Count > 0) + { + currentNotification = notifications.Dequeue(); + notificationEndTime = Time.time + currentNotification.duration; + } + } + + /// + /// Zobrazí chybovou zprávu + /// + /// Text chyby + /// Doba zobrazení + protected void ShowError(string message, float duration = 5f) + { + errorMessage = message; + errorMessageEndTime = Time.time + duration; + Debug.LogError($"[GeoSus] {message}"); + } + + /// + /// Nastavení kamery pro top-down pohled + /// + private void SetupCamera() + { + // Najdeme nebo vytvoříme hlavní kameru + playerCamera = Camera.main; + if (playerCamera == null) + { + GameObject camObj = new GameObject("MainCamera"); + playerCamera = camObj.AddComponent(); + camObj.tag = "MainCamera"; + } + + // Nastavení pro top-down pohled + playerCamera.orthographic = true; + playerCamera.orthographicSize = 50f; // Výchozí zoom (50 metrů od středu) + playerCamera.transform.position = new Vector3(0, cameraHeight, 0); + playerCamera.transform.rotation = Quaternion.Euler(90, 0, 0); + playerCamera.backgroundColor = new Color(0.2f, 0.3f, 0.2f); // Tmavě zelená + playerCamera.clearFlags = CameraClearFlags.SolidColor; + + // Přidáme naši komponentu pro ovládání kamery + if (playerCamera.GetComponent() == null) + { + playerCamera.gameObject.AddComponent(); + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // DEBUG PANEL + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Debug panel - zobrazuje interní stav klienta + /// Pouze v Unity Editoru + /// + private void DrawDebugPanel() + { + GUILayout.BeginArea(new Rect(10, Screen.height - 150, 300, 140)); + GUI.Box(new Rect(0, 0, 300, 140), ""); + + GUILayout.Label($"DEBUG", richTextStyle); + GUILayout.Label($"State: {currentState}"); + GUILayout.Label($"Connected: {client?.IsConnected ?? false}"); + GUILayout.Label($"LobbyId: {client?.LobbyId ?? "null"}"); + GUILayout.Label($"Phase: {client?.CurrentLobbyState?.Phase.ToString() ?? "null"}"); + GUILayout.Label($"Role: {client?.MyRole?.ToString() ?? "null"}"); + GUILayout.Label($"Ping: {client?.Ping ?? 0}ms"); + + GUILayout.EndArea(); + } +} + +/// +/// Jednoduchý kontroler kamery pro top-down pohled. +/// Umožňuje pohyb pomocí WASD/šipek a zoom pomocí kolečka myši. +/// +/// POZNÁMKA: V produkční hře byste toto pravděpodobně spojili +/// s pohybem hráče - kamera sleduje hráče. +/// +public class CameraController : MonoBehaviour +{ + [Header("Nastavení kamery")] + [Tooltip("Rychlost pohybu kamery")] + public float moveSpeed = 50f; + + [Tooltip("Rychlost zoomu")] + public float zoomSpeed = 10f; + + [Tooltip("Minimální zoom (orthographic size)")] + public float minZoom = 10f; + + [Tooltip("Maximální zoom (orthographic size)")] + public float maxZoom = 200f; + + private Camera cam; + + void Start() + { + cam = GetComponent(); + } + + void Update() + { + // Pohyb kamery - WASD nebo šipky + // POZNÁMKA: V plné hře se kamera obvykle pohybuje s hráčem + float h = Input.GetAxis("Horizontal"); + float v = Input.GetAxis("Vertical"); + + Vector3 movement = new Vector3(h, 0, v) * moveSpeed * Time.deltaTime; + transform.position += movement; + + // Zoom - kolečko myši + float scroll = Input.GetAxis("Mouse ScrollWheel"); + if (scroll != 0 && cam.orthographic) + { + cam.orthographicSize = Mathf.Clamp( + cam.orthographicSize - scroll * zoomSpeed, + minZoom, + maxZoom + ); + } + } +} diff --git a/Assets/UnityTestClient/UnityTestClient.cs.meta b/Assets/UnityTestClient/UnityTestClient.cs.meta new file mode 100644 index 0000000..692a765 --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 719b1c3bbdc301646b2dbfa653d9a455 \ No newline at end of file diff --git a/Assets/UnityTestClient/UnityTestClient_Game.cs b/Assets/UnityTestClient/UnityTestClient_Game.cs new file mode 100644 index 0000000..01763f7 --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_Game.cs @@ -0,0 +1,673 @@ +/* +╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ HERNÍ MECHANIKY - UnityTestClient_Game.cs ║ +║ ║ +║ Tento soubor obsahuje veškeré herní mechaniky: ║ +║ • Pohyb hráče (WASD + myš) ║ +║ • Interakce (úkoly, reporty, opravy) ║ +║ • Kill mechanika (pro impostory) ║ +║ • Sabotáže a jejich opravy ║ +║ • Emergency meeting ║ +║ • Hlasování ║ +║ ║ +║ OVLÁDÁNÍ: ║ +║ • WASD - pohyb hráče ║ +║ • Myš - otáčení kamery (volitelné) ║ +║ • E - interakce (úkoly, report, oprava) ║ +║ • Q - kill (pouze impostor) ║ +║ • Tab - mapa/statistiky ║ +║ • Escape - menu ║ +║ ║ +║ SYSTÉM ÚKOLŮ: ║ +║ • Všechny úkoly jsou INSTANT - stačí přijít na místo (do 5m) a stisknout E ║ +║ • Server validuje pozici hráče - nemůžete dokončit úkol na dálku ║ +║ • Duchové (mrtví crew) mohou dokončovat úkoly - pomáhají týmu vyhrát ║ +║ • Impostoři NEMOHOU dokončovat úkoly ║ +║ ║ +║ SABOTÁŽE: ║ +║ • CommsBlackout - blokuje reporty a emergency meetings ║ +║ - 1 opravná stanice, libovolný hráč opraví sám ║ +║ • CriticalMeltdown - časový limit na opravu! ║ +║ - 2 stanice musí být opravovány SOUČASNĚ dvěma hráči ║ +║ - Pokud čas vyprší, impostoři vyhrávají ║ +║ ║ +║ POZNÁMKA: ║ +║ V reálné mobilní hře by se pozice hráče aktualizovala podle GPS (Input.location). ║ +║ Tento test klient umožňuje simulovat pohyb pomocí WASD pro testování. ║ +║ ║ +╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ +*/ + +using UnityEngine; +using System; +using System.Collections.Generic; +using GeoSus.Client; + +// ═══════════════════════════════════════════════════════════════════════════════ +// LOKÁLNÍ TYPY PRO UNITY (kopie z Protocol.cs pro kompatibilitu) +// ═══════════════════════════════════════════════════════════════════════════════ + +/// +/// Reprezentace úkolu hráče +/// +[System.Serializable] +public class PlayerTask +{ + public string Id; + public string Name; + public string Description; + public TaskType Type; + public Position Location; + public bool IsCompleted; +} + +/// +/// Opravná stanice pro sabotáže +/// +[System.Serializable] +public class RepairStation +{ + public string Id; + public string Name; + public Position Position; + public bool IsActive; +} + +public partial class UnityTestClient +{ + #region ═══════════════════════════════════════════════════════════════════ + // GAME PROMĚNNÉ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + // ───────────────────────────────────────────────────────────────────────── + // Pohyb + // ───────────────────────────────────────────────────────────────────────── + + /// Rychlost pohybu hráče (Unity jednotky/s) + protected float moveSpeed = 10f; + + /// Aktuální pozice hráče v Unity souřadnicích + protected Vector3 currentPlayerPosition; + + /// Interval odesílání pozice na server (sekundy) + protected float positionUpdateInterval = 0.5f; + + /// Čas posledního odeslání pozice + protected float lastPositionUpdate; + + // ───────────────────────────────────────────────────────────────────────── + // Opravy + // ───────────────────────────────────────────────────────────────────────── + + /// Právě opravujeme? + protected bool isRepairing = false; + + /// ID aktivní opravné stanice + protected string activeRepairStation = null; + + /// Progress opravy (0-1) + protected float repairProgress = 0f; + + // ───────────────────────────────────────────────────────────────────────── + // Sabotáže + // ───────────────────────────────────────────────────────────────────────── + + /// Aktuální sabotáž (null = žádná) + protected SabotageStartedPayload currentSabotage = null; + + // ───────────────────────────────────────────────────────────────────────── + // Kill + // ───────────────────────────────────────────────────────────────────────── + + /// Cooldown killu (sekundy) + protected float killCooldown = 25f; + + /// Čas posledního killu + protected float lastKillTime = -100f; + + // ───────────────────────────────────────────────────────────────────────── + // Konec hry + // ───────────────────────────────────────────────────────────────────────── + + /// Data o konci hry + protected GameEndedPayload gameEndData = null; + + #region ═══════════════════════════════════════════════════════════════════ + // POHYB HRÁČE + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Zpracování vstupu hráče. + /// Volá se každý frame v Update(). + /// + /// POZNÁMKA PRO STUDENTY: + /// Input.GetAxis vrací hodnotu -1 až 1 pro plynulý pohyb. + /// "Horizontal" = A/D nebo šipky vlevo/vpravo + /// "Vertical" = W/S nebo šipky nahoru/dolů + /// + protected void HandlePlayerInput() + { + // Pohyb pouze během hraní + if (currentState != AppState.InGame) return; + + // Kontrola, zda jsme naživu - bezpečný přístup + if (client?.PlayerPositions != null && + client.PlayerPositions.TryGetValue(clientUuid, out var myInfo)) + { + if (myInfo.State != PlayerState.Alive) + { + // Duch může létat, ale neposílá pozici + HandleGhostMovement(); + return; + } + } + + // Kontrola, zda není meeting - ale během arrival fáze se můžeme hýbat + var phase = client?.CurrentLobbyState?.Phase; + if (phase == GamePhase.Meeting) + { + // Během arrival fáze (před ArrivalDeadline) se můžeme hýbat + if (currentMeeting != null && DateTime.UtcNow < currentMeeting.ArrivalDeadline) + { + // OK - můžeme se hýbat k meeting pointu + } + else + { + return; // Po arrival deadline se nehýbeme + } + } + + // ═══════════════════════════════════════════════════════════════════ + // WASD POHYB + // ═══════════════════════════════════════════════════════════════════ + + float horizontal = Input.GetAxis("Horizontal"); // A/D + float vertical = Input.GetAxis("Vertical"); // W/S + + if (horizontal != 0 || vertical != 0) + { + // Směr pohybu + Vector3 movement = new Vector3(horizontal, 0, vertical).normalized; + + // Aplikace rychlosti a delta time + Vector3 newPosition = currentPlayerPosition + movement * moveSpeed * Time.deltaTime; + + // Kontrola hranic herní oblasti + if (IsPositionInPlayArea(newPosition)) + { + currentPlayerPosition = newPosition; + UpdateCameraPosition(); + } + } + + // ═══════════════════════════════════════════════════════════════════ + // ODESÍLÁNÍ POZICE NA SERVER + // ═══════════════════════════════════════════════════════════════════ + + if (Time.time - lastPositionUpdate >= positionUpdateInterval) + { + SendPositionToServer(); + lastPositionUpdate = Time.time; + } + + // ═══════════════════════════════════════════════════════════════════ + // KLÁVESOVÉ ZKRATKY PRO AKCE + // ═══════════════════════════════════════════════════════════════════ + + // E - Interakce (USE) + if (Input.GetKeyDown(KeyCode.E)) + { + PerformPrimaryAction(); + } + + // Q - Kill (pouze impostor) + if (Input.GetKeyDown(KeyCode.Q) && client?.MyRole == PlayerRole.Impostor) + { + TryKillNearbyPlayer(); + } + + // R - Emergency meeting + if (Input.GetKeyDown(KeyCode.R)) + { + CallEmergencyMeeting(); + } + + // Escape - Menu + if (Input.GetKeyDown(KeyCode.Escape)) + { + // TODO: Toggle pause menu + } + } + + /// + /// Pohyb ducha (mrtvý hráč) + /// + private void HandleGhostMovement() + { + float horizontal = Input.GetAxis("Horizontal"); + float vertical = Input.GetAxis("Vertical"); + + if (horizontal != 0 || vertical != 0) + { + Vector3 movement = new Vector3(horizontal, 0, vertical).normalized; + currentPlayerPosition += movement * moveSpeed * 1.5f * Time.deltaTime; // Duch je rychlejší + UpdateCameraPosition(); + } + } + + /// + /// Kontrola, zda je pozice v hrací oblasti + /// + private bool IsPositionInPlayArea(Vector3 position) + { + // Vzdálenost od středu + float distance = Vector3.Distance(position, Vector3.zero); + return distance <= (float)mapRadius; + } + + /// + /// Aktualizace pozice kamery podle hráče + /// + private void UpdateCameraPosition() + { + if (playerCamera == null) return; + + // Top-down kamera následuje hráče + Vector3 cameraPos = currentPlayerPosition; + cameraPos.y = cameraHeight; + playerCamera.transform.position = cameraPos; + + // Aktualizace lokálního hráče vizuálu + UpdateLocalPlayerVisual(); + } + + /// + /// Inicializace pozice hráče při startu hry + /// + protected void InitializePlayerPosition() + { + // Nastavíme hráče na střed mapy + currentPlayerPosition = Vector3.zero; + UpdateCameraPosition(); + Debug.Log("[GeoSus] Pozice hráče inicializována na střed mapy"); + } + + /// + /// Odeslání pozice hráče na server + /// + private void SendPositionToServer() + { + if (client == null || !client.IsConnected) return; + + // Převod na GPS + Position gpsPosition = UnityToGPS(currentPlayerPosition); + + // Odeslání + client.UpdatePosition(gpsPosition); + } + + #region ═══════════════════════════════════════════════════════════════════ + // INTERAKCE + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Provede primární akci (USE/REPORT/REPAIR) + /// + protected void PerformPrimaryAction() + { + if (client == null) return; + + // Kontrola stavu hráče - bezpečný přístup + if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var myState)) + { + if (myState.State != PlayerState.Alive) + { + ShowNotification("Jsi mrtvý!", Color.gray, 2f, "💀"); + return; + } + } + + // ═══════════════════════════════════════════════════════════════════ + // PRIORITA 1: Report těla + // ═══════════════════════════════════════════════════════════════════ + + var body = client.FindNearbyBody(5.0); + if (body != null) + { + // Použijeme přímo BodyId property + ReportBody(body.BodyId); + return; + } + + // ═══════════════════════════════════════════════════════════════════ + // PRIORITA 2: Oprava stanice + // ═══════════════════════════════════════════════════════════════════ + + if (currentSabotage != null) + { + var station = FindNearbyRepairStation(5.0); + if (station != null) + { + if (!isRepairing) + { + StartRepair(station.StationId); + } + return; + } + } + + // ═══════════════════════════════════════════════════════════════════ + // PRIORITA 3: Úkol (pouze crew) + // ═══════════════════════════════════════════════════════════════════ + + if (client.MyRole == PlayerRole.Crew) + { + var task = client.FindNearbyTask(5.0); + if (task != null) + { + TryCompleteTask(task); + return; + } + } + + ShowNotification("Nic v dosahu!", Color.yellow, "❓"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // ÚKOLY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Pokus o dokončení úkolu - pošle CompleteTask na server. + /// Server ověří pozici a označí jako dokončený. + /// + private void TryCompleteTask(object taskObj) + { + if (client == null) return; + + // Dynamicky získáme vlastnosti úkolu přes reflexi + var taskType = taskObj.GetType(); + string taskId = taskType.GetField("TaskId")?.GetValue(taskObj) as string ?? + taskType.GetProperty("TaskId")?.GetValue(taskObj) as string ?? "unknown"; + string taskName = taskType.GetField("Name")?.GetValue(taskObj) as string ?? + taskType.GetProperty("Name")?.GetValue(taskObj) as string ?? "Úkol"; + + // Pošleme CompleteTask na server + client.CompleteTask(taskId); + + ShowNotification($"Provádím: {taskName}...", Color.cyan, "📋"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // REPORT TĚLA + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Nahlášení těla + /// + /// ID těla + protected void ReportBody(string bodyId) + { + if (client == null) return; + + client.ReportBody(bodyId); + + ShowNotification("Tělo nahlášeno!", Color.red, "🚨"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // KILL + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Pokus o zabití blízkého hráče + /// + private void TryKillNearbyPlayer() + { + if (client?.MyRole != PlayerRole.Impostor) return; + + // Kontrola cooldownu + if (Time.time - lastKillTime < killCooldown) + { + float remaining = killCooldown - (Time.time - lastKillTime); + ShowNotification($"Cooldown: {remaining:F0}s", Color.yellow, "⏳"); + return; + } + + // Najdi blízkého hráče + string targetId = client.FindNearbyPlayer(5.0, true); + + if (targetId != null) + { + AttemptKill(targetId); + } + else + { + ShowNotification("Nikdo v dosahu!", Color.yellow, "🔪"); + } + } + + /// + /// Pokus o zabití konkrétního hráče + /// + /// UUID cíle + protected void AttemptKill(string targetId) + { + if (client == null) return; + + client.Kill(targetId); + lastKillTime = Time.time; + + string targetName = GetPlayerName(targetId); + ShowNotification($"Útočíš na {targetName}!", Color.red, "🔪"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // SABOTÁŽE + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Spuštění sabotáže + /// + /// Typ sabotáže + protected void StartSabotage(SabotageType type) + { + if (client?.MyRole != PlayerRole.Impostor) return; + if (currentSabotage != null) return; // Již běží sabotáž + + client.Send(new StartSabotage { SabotageType = type }); + + string sabName = type == SabotageType.CommsBlackout ? "Comms Blackout" : "Critical Meltdown"; + ShowNotification($"Sabotáž: {sabName}!", new Color(1f, 0.5f, 0f), "⚠"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // OPRAVY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Oprava stanice - INSTANT + /// Server opraví stanici okamžitě při přijetí ActivateRepairStation. + /// Budoucí pokročilý klient může simulovat progress bar a poslat request až po uplynutí času. + /// + /// ID opravné stanice + private void StartRepair(string stationId) + { + if (currentSabotage == null) return; + + // Pošleme serveru - ten opraví INSTANT + client.Send(new ActivateRepairStation { StationId = stationId }); + + ShowNotification("Stanice opravena!", Color.green, "✅"); + } + + /// + /// Aktualizace průběhu opravy - NEPOUŽÍVÁ SE (oprava je instant) + /// Ponecháno pro budoucí implementaci s progress barem. + /// + protected void UpdateRepairProgress() + { + // Oprava je nyní instant - tato metoda není potřeba + // Budoucí klient může implementovat lokální progress bar: + // if (!isRepairing) return; + // repairProgress += Time.deltaTime / 3f; // 3 sekundy + // if (repairProgress >= 1f) { + // client.Send(new ActivateRepairStation { StationId = activeRepairStation }); + // isRepairing = false; + // } + } + + /// + /// Ukončení opravy (úspěšné) - NEPOUŽÍVÁ SE (oprava je instant) + /// + private void StopRepair() + { + // Oprava je instant - tato metoda není potřeba + } + + /// + /// Zrušení opravy - NEPOUŽÍVÁ SE (oprava je instant) + /// + private void CancelRepair() + { + // Oprava je instant - tato metoda není potřeba + } + + #region ═══════════════════════════════════════════════════════════════════ + // EMERGENCY MEETING + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Svolání emergency meetingu + /// + protected void CallEmergencyMeeting() + { + if (client == null) return; + + // Kontrola sabotáže - nelze volat během comms blackout + if (currentSabotage?.Type == SabotageType.CommsBlackout) + { + ShowNotification("Komunikace odpojeny!", Color.red, "📡"); + return; + } + + client.CallEmergencyMeeting(); + + ShowNotification("Emergency Meeting!", Color.red, "🔔"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // HLASOVÁNÍ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Odeslání hlasu + /// + /// UUID hráče nebo null pro skip + protected void CastVote(string targetId) + { + if (client == null) + { + Debug.LogError("[GeoSus] CastVote: client je null!"); + return; + } + + if (!canVote) + { + Debug.LogWarning("[GeoSus] CastVote: canVote je false!"); + return; + } + + string voteText = targetId == null ? "SKIP" : GetPlayerName(targetId); + Debug.Log($"[GeoSus] Hlasování: {voteText} (targetId: {targetId ?? "null"})"); + + client.Vote(targetId); + myVote = targetId ?? "skip"; + canVote = false; + + ShowNotification($"Hlasoval jsi: {voteText}", Color.cyan, 2f, "🗳"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // POMOCNÉ METODY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Získání jména hráče podle UUID + /// + protected string GetPlayerName(string playerId) + { + if (client?.CurrentLobbyState?.Players == null) return playerId; + + foreach (var player in client.CurrentLobbyState.Players) + { + if (player.ClientUuid == playerId) + { + return player.DisplayName; + } + } + + return playerId.Substring(0, 8) + "..."; + } + + /// + /// Inicializace herního stavu při startu hry + /// + protected void InitializeGameState() + { + // Reset pozice na střed + currentPlayerPosition = Vector3.zero; + + // Reset úkolů + totalTasksCompleted = 0; + totalTasksRequired = client?.MyTasks?.Count ?? 0; + myCompletedTaskIds.Clear(); + + // Reset oprav + isRepairing = false; + activeRepairStation = null; + repairProgress = 0f; + + // Reset kill cooldownu + lastKillTime = Time.time; // Cooldown na začátku + + // Reset sabotáže + currentSabotage = null; + + // Reset meetingu + currentMeeting = null; + meetingVotes.Clear(); + myVote = null; + canVote = false; + + // Reset konce hry + gameEndData = null; + } + + /// + /// Game loop - aktualizace herní logiky. + /// Volá se v Update(). + /// + protected void UpdateGameLogic() + { + if (currentState != AppState.InGame) return; + + // Aktualizace opravy + UpdateRepairProgress(); + + // Aktualizace mapových objektů + UpdateMapObjects(); + } +} diff --git a/Assets/UnityTestClient/UnityTestClient_Game.cs.meta b/Assets/UnityTestClient/UnityTestClient_Game.cs.meta new file mode 100644 index 0000000..43156f8 --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_Game.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 829e2eaa53b86d347912031dba7711da \ No newline at end of file diff --git a/Assets/UnityTestClient/UnityTestClient_Map.cs b/Assets/UnityTestClient/UnityTestClient_Map.cs new file mode 100644 index 0000000..eb9a663 --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_Map.cs @@ -0,0 +1,1532 @@ +/* +╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ VYKRESLOVÁNÍ MAPY - UnityTestClient_Map.cs ║ +║ ║ +║ Tento soubor obsahuje veškeré vykreslování 3D mapy pomocí GameObjectů: ║ +║ • Budovy (Buildings) - 3D kvádry s výškou ║ +║ • Cesty (Pathways) - LineRenderer nebo extrudované polygony ║ +║ • Oblasti (Areas) - Ploché 3D objekty (parky, voda) ║ +║ • POI (Points of Interest) - Markery s ikonami ║ +║ • Hráči - Capsule s barvou podle role/stavu ║ +║ • Těla - Ležící capsule (po zabití) ║ +║ • Úkoly - Žluté diamanty (markery) ║ +║ • Opravné stanice - Oranžové věže (při sabotáži) ║ +║ ║ +║ GPS → UNITY PŘEVOD: ║ +║ 1. Střed mapy (PlayAreaCenter) = Vector3(0, 0, 0) ║ +║ 2. 1 metr GPS ≈ 1 Unity jednotka ║ +║ 3. Latitude → Z osa (sever = +Z) ║ +║ 4. Longitude → X osa (východ = +X) ║ +║ 5. Haversine formula pro přesný výpočet vzdáleností ║ +║ ║ +║ VRSTVY (Y osa): ║ +║ 0.00 - Zelený podklad ║ +║ 0.01 - Oblasti (parky, voda) ║ +║ 0.02 - Cesty ║ +║ 0.XX - Budovy (výška podle dat) ║ +║ 0.30 - Markery (úkoly, těla) ║ +║ 0.50 - POI ║ +║ 1.00 - Hráči ║ +║ 5.00+ - Opravné stanice (věže) ║ +║ ║ +║ MAPOVÁ DATA: ║ +║ Server stahuje data z OpenStreetMap (Overpass API) a posílá je klientovi. ║ +║ Pokud Overpass API není dostupné, mapa se vykreslí bez detailů. ║ +║ ║ +╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ +*/ + +using UnityEngine; +using System; +using System.Collections.Generic; +using System.Linq; +using GeoSus.Client; + +public partial class UnityTestClient +{ + #region ═══════════════════════════════════════════════════════════════════ + // MAP PROMĚNNÉ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + // ───────────────────────────────────────────────────────────────────────── + // Kontejnery pro mapové objekty + // ───────────────────────────────────────────────────────────────────────── + + /// Rodičovský objekt pro celou mapu + protected GameObject mapContainer; + + /// Kontejner pro budovy + protected GameObject buildingsContainer; + + /// Kontejner pro cesty + protected GameObject pathwaysContainer; + + /// Kontejner pro oblasti + protected GameObject areasContainer; + + /// Kontejner pro POI + protected GameObject poisContainer; + + /// Kontejner pro hráče + protected GameObject playersContainer; + + /// Kontejner pro těla + protected GameObject bodiesContainer; + + /// Kontejner pro úkoly + protected GameObject tasksContainer; + + /// Kontejner pro opravné stanice + protected GameObject repairStationsContainer; + + /// Emergency button marker (střed mapy) + protected GameObject emergencyButtonMarker; + + /// Meeting lokace marker (během meetingu) + protected GameObject meetingLocationMarker; + + // ───────────────────────────────────────────────────────────────────────── + // Mapování objektů na ID + // ───────────────────────────────────────────────────────────────────────── + + /// Mapování ID hráče na GameObject + protected Dictionary playerObjects = new Dictionary(); + + /// Mapování ID těla na GameObject + protected Dictionary bodyObjects = new Dictionary(); + + /// Mapování ID úkolu na GameObject + protected Dictionary taskObjects = new Dictionary(); + + /// Mapování ID opravné stanice na GameObject + protected Dictionary repairStationObjects = new Dictionary(); + + // ───────────────────────────────────────────────────────────────────────── + // GPS referenční bod + // ───────────────────────────────────────────────────────────────────────── + + /// Střed herní oblasti (GPS) + protected Position? mapCenter; + + /// Poloměr herní oblasti (metry) + protected double mapRadius; + + // ───────────────────────────────────────────────────────────────────────── + // Materiály + // ───────────────────────────────────────────────────────────────────────── + + /// Materiál pro budovy + protected Material buildingMaterial; + + /// Materiál pro cesty + protected Material pathwayMaterial; + + /// Materiál pro oblasti + protected Material areaMaterial; + + /// Materiál pro POI + protected Material poiMaterial; + + /// Materiál pro hráče + protected Material playerMaterial; + + /// Materiál pro těla + protected Material bodyMaterial; + + /// Materiál pro úkoly + protected Material taskMaterial; + + /// Materiál pro opravné stanice + protected Material repairMaterial; + + #region ═══════════════════════════════════════════════════════════════════ + // INICIALIZACE MAPY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vytvoření kontejnerů a materiálů pro mapu. + /// Volá se při startu hry. + /// + protected void InitializeMapSystem() + { + // Vytvoření hlavního kontejneru + if (mapContainer == null) + { + mapContainer = new GameObject("MapContainer"); + } + + // Vytvoření sub-kontejnerů pro lepší organizaci + buildingsContainer = CreateContainer("Buildings"); + pathwaysContainer = CreateContainer("Pathways"); + areasContainer = CreateContainer("Areas"); + poisContainer = CreateContainer("POIs"); + playersContainer = CreateContainer("Players"); + bodiesContainer = CreateContainer("Bodies"); + tasksContainer = CreateContainer("Tasks"); + repairStationsContainer = CreateContainer("RepairStations"); + + // Inicializace materiálů + InitializeMaterials(); + + // Vytvoření podlahy herní oblasti + CreateGroundPlane(); + } + + /// + /// Pomocná metoda pro vytvoření kontejneru + /// + private GameObject CreateContainer(string name) + { + var container = new GameObject(name); + container.transform.SetParent(mapContainer.transform); + return container; + } + + /// + /// Inicializace materiálů pro různé typy objektů. + /// BUILD-SAFE: Všechny materiály jsou odvozeny z primitiv. + /// + private void InitializeMaterials() + { + // Budovy - různé barvy podle typu + buildingMaterial = CreateUnlitMaterial(new Color(0.7f, 0.65f, 0.6f)); // Světle šedá/béžová + + // Cesty - tmavší šedá + pathwayMaterial = CreateUnlitMaterial(new Color(0.5f, 0.5f, 0.5f)); + + // Oblasti (parky) - zelená s průhledností + areaMaterial = CreateTransparentMaterial(new Color(0.3f, 0.7f, 0.3f, 0.4f)); + + // POI - jasně žlutá + poiMaterial = CreateUnlitMaterial(Color.yellow); + + // Hráči - modrá + playerMaterial = CreateUnlitMaterial(new Color(0.2f, 0.5f, 1f)); + + // Těla - červená + bodyMaterial = CreateUnlitMaterial(new Color(0.8f, 0.2f, 0.2f)); + + // Úkoly - cyan/tyrkysová + taskMaterial = CreateUnlitMaterial(new Color(0f, 0.9f, 0.9f)); + + // Opravné stanice - oranžová + repairMaterial = CreateUnlitMaterial(new Color(1f, 0.6f, 0.1f)); + } + + /// + /// Vytvoří průhledný materiál + /// BUILD-SAFE: Používá materiál z primitivu jako základ + /// + private Material CreateTransparentMaterial(Color color) + { + var mat = CreateUnlitMaterial(color); + + // Nastavení průhlednosti + mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + mat.SetInt("_ZWrite", 0); + mat.renderQueue = 3000; + + // Pro URP + if (mat.HasProperty("_Surface")) + { + mat.SetFloat("_Surface", 1); // 1 = Transparent + } + + return mat; + } + + /// + /// Nastaví materiál jako průhledný + /// + private void SetMaterialTransparent(Material mat) + { + mat.SetFloat("_Mode", 3); // Transparent + mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + mat.SetInt("_ZWrite", 0); + mat.DisableKeyword("_ALPHATEST_ON"); + mat.EnableKeyword("_ALPHABLEND_ON"); + mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + mat.renderQueue = 3000; + } + + /// + /// Nastaví materiál jako emisivní (svítící) + /// + private void SetMaterialEmissive(Material mat, Color emissionColor) + { + mat.EnableKeyword("_EMISSION"); + mat.SetColor("_EmissionColor", emissionColor); + } + + /// + /// Vytvoření podlahy herní oblasti + /// + private void CreateGroundPlane() + { + var ground = GameObject.CreatePrimitive(PrimitiveType.Plane); + ground.name = "Ground"; + ground.transform.SetParent(mapContainer.transform); + ground.transform.localPosition = Vector3.zero; + + // Škálování podle poloměru (Plane má základní velikost 10x10) + float scale = (float)mapRadius * 0.3f; // Mírně větší než poloměr + ground.transform.localScale = new Vector3(scale, 1, scale); + + // Materiál podlahy - použijeme Unlit shader pro konzistentní barvu + var groundMat = CreateUnlitMaterial(new Color(0.25f, 0.45f, 0.2f)); // Tráva + ground.GetComponent().material = groundMat; + + // Vytvoříme emergency button marker uprostřed mapy + CreateEmergencyButtonMarker(); + } + + /// + /// Vytvoří výrazný marker pro emergency button (střed mapy) + /// + private void CreateEmergencyButtonMarker() + { + emergencyButtonMarker = new GameObject("EmergencyButton"); + emergencyButtonMarker.transform.SetParent(mapContainer.transform); + emergencyButtonMarker.transform.localPosition = new Vector3(0, 0.1f, 0); // Střed mapy + + // Velký červený kruh na zemi + var platform = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + platform.name = "Platform"; + platform.transform.SetParent(emergencyButtonMarker.transform); + platform.transform.localPosition = Vector3.zero; + platform.transform.localScale = new Vector3(8f, 0.2f, 8f); + var platformMat = CreateEmissiveMaterial(new Color(0.8f, 0.2f, 0.2f), 1.5f); + platform.GetComponent().material = platformMat; + + // Tlačítko uprostřed + var button = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + button.name = "Button"; + button.transform.SetParent(emergencyButtonMarker.transform); + button.transform.localPosition = new Vector3(0, 0.5f, 0); + button.transform.localScale = new Vector3(3f, 0.5f, 3f); + var buttonMat = CreateEmissiveMaterial(Color.red, 3f); + button.GetComponent().material = buttonMat; + + // Světelná koule nahoře + var light = GameObject.CreatePrimitive(PrimitiveType.Sphere); + light.name = "Light"; + light.transform.SetParent(emergencyButtonMarker.transform); + light.transform.localPosition = new Vector3(0, 3f, 0); + light.transform.localScale = new Vector3(2f, 2f, 2f); + light.GetComponent().material = buttonMat; + } + + /// + /// Vytvoří nebo aktualizuje marker pro meeting lokaci + /// + protected void UpdateMeetingLocationMarker(Position? location, bool isActive) + { + if (!isActive || location == null) + { + if (meetingLocationMarker != null) + { + Destroy(meetingLocationMarker); + meetingLocationMarker = null; + } + return; + } + + if (meetingLocationMarker == null) + { + meetingLocationMarker = new GameObject("MeetingLocation"); + meetingLocationMarker.transform.SetParent(mapContainer.transform); + + // Velký pulsující kruh + var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + ring.name = "Ring"; + ring.transform.SetParent(meetingLocationMarker.transform); + ring.transform.localPosition = new Vector3(0, 0.1f, 0); + ring.transform.localScale = new Vector3(15f, 0.1f, 15f); + var ringMat = CreateEmissiveMaterial(new Color(1f, 1f, 0f, 0.6f), 2f); + ring.GetComponent().material = ringMat; + + // Šipka dolů + var arrow = GameObject.CreatePrimitive(PrimitiveType.Cube); + arrow.name = "Arrow"; + arrow.transform.SetParent(meetingLocationMarker.transform); + arrow.transform.localPosition = new Vector3(0, 10f, 0); + arrow.transform.localScale = new Vector3(2f, 8f, 0.5f); + var arrowMat = CreateEmissiveMaterial(Color.yellow, 4f); + arrow.GetComponent().material = arrowMat; + } + + Vector3 pos = GPSToUnity(location.Value); + meetingLocationMarker.transform.position = pos; + } + + /// + /// Cache pro základní materiál - získaný z primitivu při prvním volání + /// + private static Material _cachedBaseMaterial = null; + + /// + /// Vytvoří materiál pro konzistentní barvu + /// BUILD-SAFE: Používá Sprites/Default shader který je vždy zahrnut + /// + private Material CreateUnlitMaterial(Color color) + { + // Sprites/Default shader je VŽDY zahrnut v Unity buildech + var shader = Shader.Find("Sprites/Default"); + if (shader == null) + { + // Absolutní fallback - nemělo by nikdy nastat + Debug.LogError("[GeoSus] Sprites/Default shader nenalezen!"); + var tempCube = GameObject.CreatePrimitive(PrimitiveType.Cube); + var mat = new Material(tempCube.GetComponent().sharedMaterial); + DestroyImmediate(tempCube); + mat.color = color; + return mat; + } + + var material = new Material(shader); + material.color = color; + + return material; + } + + #region ═══════════════════════════════════════════════════════════════════ + // GPS KONVERZE + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Konverze GPS souřadnic na Unity pozici. + /// Střed mapy je na (0, 0, 0). + /// + /// MATEMATIKA: + /// - Latitude (zeměpisná šířka) → Z osa + /// - Longitude (zeměpisná délka) → X osa + /// - Používáme Haversine formuli pro přesnou vzdálenost + /// + /// GPS souřadnice + /// Unity pozice ve 3D prostoru + protected Vector3 GPSToUnity(Position position) + { + if (!mapCenter.HasValue) return Vector3.zero; + + // Výpočet vzdálenosti a směru od středu + // Zjednodušená verze - pro malé vzdálenosti je dost přesná + + double latDiff = position.Lat - mapCenter.Value.Lat; + double lonDiff = position.Lon - mapCenter.Value.Lon; + + // Převod stupňů na metry + // 1 stupeň latitude ≈ 111320 metrů + // 1 stupeň longitude závisí na latitude: 111320 * cos(latitude) + double metersPerDegreeLat = 111320.0; + double metersPerDegreeLon = 111320.0 * Math.Cos(mapCenter.Value.Lat * Math.PI / 180.0); + + float x = (float)(lonDiff * metersPerDegreeLon); + float z = (float)(latDiff * metersPerDegreeLat); + + return new Vector3(x, 0, z); + } + + /// + /// Konverze Unity pozice zpět na GPS souřadnice. + /// Inverzní funkce k GPSToUnity. + /// + /// Unity pozice + /// GPS souřadnice + protected Position UnityToGPS(Vector3 unityPos) + { + if (!mapCenter.HasValue) return new Position(0, 0); + + double metersPerDegreeLat = 111320.0; + double metersPerDegreeLon = 111320.0 * Math.Cos(mapCenter.Value.Lat * Math.PI / 180.0); + + double latDiff = unityPos.z / metersPerDegreeLat; + double lonDiff = unityPos.x / metersPerDegreeLon; + + return new Position(mapCenter.Value.Lat + latDiff, mapCenter.Value.Lon + lonDiff); + } + + #region ═══════════════════════════════════════════════════════════════════ + // BUDOVY (BUILDINGS) + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Získá barvu budovy podle typu + /// + private Color GetBuildingColor(string buildingType) + { + if (string.IsNullOrEmpty(buildingType)) return new Color(0.7f, 0.65f, 0.6f); + + switch (buildingType.ToLower()) + { + case "residential": + case "apartments": + case "house": + return new Color(0.85f, 0.75f, 0.65f); // Teplá béžová + case "commercial": + case "retail": + case "shop": + return new Color(0.7f, 0.7f, 0.85f); // Světle modrá + case "industrial": + case "warehouse": + return new Color(0.6f, 0.55f, 0.5f); // Tmavě šedá + case "school": + case "university": + return new Color(0.9f, 0.85f, 0.7f); // Světle žlutá + case "hospital": + case "clinic": + return new Color(0.9f, 0.9f, 0.95f); // Bílá + case "church": + case "cathedral": + return new Color(0.8f, 0.75f, 0.85f); // Světle fialová + case "garage": + case "garages": + return new Color(0.5f, 0.5f, 0.5f); // Šedá + default: + return new Color(0.75f, 0.7f, 0.65f); // Výchozí béžová + } + } + + /// + /// Vytvoření budovy z mapových dat. + /// Budova je vykreslena jako 3D kvádr s výškou. + /// + /// Data budovy z mapy + protected void CreateBuildingObject(MapBuilding building) + { + // Vytvoření GameObjectu + var buildingObj = new GameObject($"Building_{building.Name ?? "Unknown"}"); + buildingObj.transform.SetParent(buildingsContainer.transform); + + // Výpočet středu budovy + Vector3 center = CalculatePolygonCenter(building.Outline); + buildingObj.transform.position = center; + + // Vytvoření mesh pro budovu + MeshFilter meshFilter = buildingObj.AddComponent(); + MeshRenderer meshRenderer = buildingObj.AddComponent(); + + // Generování mesh z polygonu (výška implicitně 10m) + Mesh mesh = CreateExtrudedPolygonMesh(building.Outline, 10f); + meshFilter.mesh = mesh; + + // Použijeme barvu podle typu budovy + Color buildingColor = GetBuildingColor(building.BuildingType); + meshRenderer.material = CreateUnlitMaterial(buildingColor); + + // Přidání collideru pro interakci + buildingObj.AddComponent(); + } + + /// + /// Výpočet středu polygonu + /// + private Vector3 CalculatePolygonCenter(List points) + { + Vector3 center = Vector3.zero; + foreach (var point in points) + { + center += GPSToUnity(point); + } + return center / points.Count; + } + + /// + /// Vytvoření 3D mesh z polygonu vytažením do výšky. + /// + /// ALGORITMUS: + /// 1. Triangulace spodní podstavy (ear clipping) + /// 2. Kopie vrcholů nahoru pro horní podstavu + /// 3. Vytvoření bočních stěn spojením okrajů + /// + private Mesh CreateExtrudedPolygonMesh(List outline, float height) + { + Mesh mesh = new Mesh(); + + int vertexCount = outline.Count; + + // Vertices - spodní a horní podstava + Vector3[] vertices = new Vector3[vertexCount * 2]; + Vector3 center = CalculatePolygonCenter(outline); + + for (int i = 0; i < vertexCount; i++) + { + Vector3 pos = GPSToUnity(outline[i]) - center; + vertices[i] = pos; // Spodní + vertices[i + vertexCount] = pos + Vector3.up * height; // Horní + } + + // Triangles - jen boční stěny pro jednoduchost + List triangles = new List(); + + for (int i = 0; i < vertexCount; i++) + { + int next = (i + 1) % vertexCount; + + // Boční stěna - dva trojúhelníky + triangles.Add(i); + triangles.Add(i + vertexCount); + triangles.Add(next); + + triangles.Add(next); + triangles.Add(i + vertexCount); + triangles.Add(next + vertexCount); + } + + // Horní podstava - zjednodušená triangulace (fan) + if (vertexCount >= 3) + { + for (int i = 1; i < vertexCount - 1; i++) + { + triangles.Add(vertexCount); // Střed (první bod horní) + triangles.Add(vertexCount + i); + triangles.Add(vertexCount + i + 1); + } + } + + mesh.vertices = vertices; + mesh.triangles = triangles.ToArray(); + mesh.RecalculateNormals(); + mesh.RecalculateBounds(); + + return mesh; + } + + #region ═══════════════════════════════════════════════════════════════════ + // CESTY (PATHWAYS) + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vytvoření cesty z mapových dat. + /// Cesta je vykreslena jako LineRenderer nebo plochý polygon. + /// + /// Data cesty + protected void CreatePathwayObject(MapPathway pathway) + { + var pathObj = new GameObject($"Path_{pathway.Name ?? "Unknown"}"); + pathObj.transform.SetParent(pathwaysContainer.transform); + + // Použijeme LineRenderer pro jednoduchost + LineRenderer line = pathObj.AddComponent(); + line.material = pathwayMaterial; + line.widthMultiplier = GetPathWidth(pathway.PathType); + + // Nastavení bodů cesty + line.positionCount = pathway.Points.Count; + for (int i = 0; i < pathway.Points.Count; i++) + { + Vector3 pos = GPSToUnity(pathway.Points[i]); + pos.y = 0.1f; // Mírně nad zemí + line.SetPosition(i, pos); + } + } + + /// + /// Získání šířky cesty podle typu + /// + private float GetPathWidth(PathType type) + { + switch (type) + { + case PathType.Road: return 5f; + case PathType.Pedestrian: return 2f; // Sidewalk + case PathType.Path: return 1f; // Trail + case PathType.Footway: return 1.5f; + default: return 2f; + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // OBLASTI (AREAS) + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vytvoření oblasti z mapových dat. + /// Oblast je vykreslena jako plochý 3D polygon. + /// + /// Data oblasti + protected void CreateAreaObject(MapArea area) + { + var areaObj = new GameObject($"Area_{area.Name ?? "Unknown"}"); + areaObj.transform.SetParent(areasContainer.transform); + + MeshFilter meshFilter = areaObj.AddComponent(); + MeshRenderer meshRenderer = areaObj.AddComponent(); + + // Vytvoření plochého mesh + Mesh mesh = CreateFlatPolygonMesh(area.Outline); + meshFilter.mesh = mesh; + + // Materiál podle typu + Material mat = new Material(areaMaterial); + mat.color = GetAreaColor(area.AreaType); + meshRenderer.material = mat; + + areaObj.transform.position = new Vector3(0, 0.05f, 0); // Těsně nad zemí + } + + /// + /// Vytvoření plochého mesh z polygonu + /// + private Mesh CreateFlatPolygonMesh(List outline) + { + Mesh mesh = new Mesh(); + + int vertexCount = outline.Count; + Vector3[] vertices = new Vector3[vertexCount]; + Vector3 center = CalculatePolygonCenter(outline); + + for (int i = 0; i < vertexCount; i++) + { + vertices[i] = GPSToUnity(outline[i]) - center; + } + + // Triangulace - fan pattern + List triangles = new List(); + if (vertexCount >= 3) + { + for (int i = 1; i < vertexCount - 1; i++) + { + triangles.Add(0); + triangles.Add(i); + triangles.Add(i + 1); + } + } + + mesh.vertices = vertices; + mesh.triangles = triangles.ToArray(); + mesh.RecalculateNormals(); + + return mesh; + } + + /// + /// Získání barvy oblasti podle typu + /// + private Color GetAreaColor(MapAreaType type) + { + switch (type) + { + case MapAreaType.Park: return new Color(0.2f, 0.8f, 0.2f, 0.5f); + case MapAreaType.Garden: return new Color(0.3f, 0.7f, 0.3f, 0.5f); + case MapAreaType.Water: return new Color(0.2f, 0.5f, 0.9f, 0.7f); + case MapAreaType.Forest: return new Color(0.1f, 0.5f, 0.1f, 0.5f); + case MapAreaType.Grass: return new Color(0.4f, 0.8f, 0.4f, 0.5f); + case MapAreaType.Playground: return new Color(0.6f, 0.5f, 0.4f, 0.5f); + default: return new Color(0.5f, 0.5f, 0.5f, 0.5f); + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // POI (POINTS OF INTEREST) + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vytvoření POI markeru - VÝRAZNÝ marker s ikonou a popisem. + /// POI jsou body zájmu pro orientaci na mapě. + /// + /// Data POI + protected void CreatePOIObject(MapPOI poi) + { + var poiObj = new GameObject($"POI_{poi.Name ?? "Unknown"}"); + poiObj.transform.SetParent(poisContainer.transform); + + Vector3 pos = GPSToUnity(poi.Location); + pos.y = 0.5f; + poiObj.transform.position = pos; + + // Vytvoření vizuálu - větší a viditelnější + var visual = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + visual.transform.SetParent(poiObj.transform); + visual.transform.localPosition = new Vector3(0, 0.5f, 0); + visual.transform.localScale = new Vector3(1.2f, 0.5f, 1.2f); + + // Barva podle typu - svítící materiál + Color poiColor = GetPOIColor(poi.POIType); + Material mat = CreateEmissiveMaterial(poiColor, 1.5f); + visual.GetComponent().material = mat; + + // Ikona/symbol nahoře - malá koule + var icon = GameObject.CreatePrimitive(PrimitiveType.Sphere); + icon.transform.SetParent(poiObj.transform); + icon.transform.localPosition = new Vector3(0, 1.3f, 0); + icon.transform.localScale = new Vector3(0.8f, 0.8f, 0.8f); + icon.GetComponent().material = mat; + } + + /// + /// Získání barvy POI podle typu + /// + private Color GetPOIColor(MapPOIType type) + { + switch (type) + { + case MapPOIType.Shop: return Color.magenta; + case MapPOIType.FoodDrink: return new Color(1f, 0.5f, 0f); + case MapPOIType.Transport: return Color.blue; + case MapPOIType.Landmark: return Color.yellow; + case MapPOIType.Health: return Color.green; + case MapPOIType.Culture: return Color.cyan; + case MapPOIType.Recreation: return new Color(0.5f, 1f, 0.5f); + default: return Color.white; + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // HRÁČI + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Barvy pro hráče - každý hráč má unikátní barvu + /// + private static readonly Color[] PlayerColors = new Color[] + { + Color.red, + Color.blue, + Color.green, + Color.yellow, + Color.cyan, + Color.magenta, + new Color(1f, 0.5f, 0f), // Orange + new Color(0.5f, 0f, 1f), // Purple + new Color(0f, 1f, 0.5f), // Mint + new Color(1f, 0.75f, 0.8f), // Pink + }; + + /// + /// Vytvoření nebo aktualizace objektu hráče - VÝRAZNÝ marker. + /// Hráč je vykreslován jako Capsule s barvou a jménem. + /// + /// UUID hráče + /// GPS pozice + /// Stav hráče + protected void UpdatePlayerObject(string playerId, Position position, PlayerState state) + { + // Skip pro lokálního hráče - ten je vykreslen jinak + if (playerId == clientUuid) return; + + GameObject playerObj; + + if (!playerObjects.TryGetValue(playerId, out playerObj)) + { + // Kontejner pro hráče + playerObj = new GameObject($"Player_{GetPlayerName(playerId)}"); + playerObj.transform.SetParent(playersContainer.transform); + + // Tělo hráče - větší capsule + var body = GameObject.CreatePrimitive(PrimitiveType.Capsule); + body.name = "Body"; + body.transform.SetParent(playerObj.transform); + body.transform.localPosition = new Vector3(0, 2f, 0); + body.transform.localScale = new Vector3(2f, 2.5f, 2f); + + // Přidělení výrazné barvy + int colorIndex = playerObjects.Count % PlayerColors.Length; + var mat = CreateEmissiveMaterial(PlayerColors[colorIndex], 0.5f); + body.GetComponent().material = mat; + + // Kroužek pod hráčem + var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + ring.name = "Ring"; + ring.transform.SetParent(playerObj.transform); + ring.transform.localPosition = new Vector3(0, 0.1f, 0); + ring.transform.localScale = new Vector3(4f, 0.1f, 4f); + var ringMat = CreateEmissiveMaterial(PlayerColors[colorIndex] * 0.7f, 0.3f); + ring.GetComponent().material = ringMat; + + playerObjects[playerId] = playerObj; + } + + // Aktualizace pozice + Vector3 unityPos = GPSToUnity(position); + playerObj.transform.position = unityPos; + + // Viditelnost podle stavu + playerObj.SetActive(state == PlayerState.Alive); + } + + /// + /// Aktualizace lokálního hráče (kurzor/avatar) - VELMI VÝRAZNÝ + /// + protected void UpdateLocalPlayerVisual() + { + // Vytvoření lokálního hráče pokud neexistuje + if (localPlayerObject == null) + { + // Hlavní objekt hráče + localPlayerObject = new GameObject("LocalPlayer"); + localPlayerObject.transform.SetParent(playersContainer.transform); + + // Tělo hráče - VELKÁ jasně zelená capsule + var body = GameObject.CreatePrimitive(PrimitiveType.Capsule); + body.name = "Body"; + body.transform.SetParent(localPlayerObject.transform); + body.transform.localPosition = new Vector3(0, 2.5f, 0); + body.transform.localScale = new Vector3(3f, 3f, 3f); + + // Výrazná zelená barva se svícením + var bodyMat = CreateEmissiveMaterial(new Color(0.3f, 1f, 0.3f), 1.5f); + body.GetComponent().material = bodyMat; + + // VELKÝ indikátor - kroužek kolem hráče + var indicator = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + indicator.name = "Indicator"; + indicator.transform.SetParent(localPlayerObject.transform); + indicator.transform.localPosition = new Vector3(0, 0.15f, 0); + indicator.transform.localScale = new Vector3(8f, 0.15f, 8f); + + // Žlutý svítící kroužek + var indicatorMat = CreateEmissiveMaterial(new Color(1f, 1f, 0f), 2f); + indicator.GetComponent().material = indicatorMat; + + // Šipka směru + var arrow = GameObject.CreatePrimitive(PrimitiveType.Cube); + arrow.name = "Arrow"; + arrow.transform.SetParent(localPlayerObject.transform); + arrow.transform.localPosition = new Vector3(0, 0.3f, 3f); + arrow.transform.localScale = new Vector3(1.5f, 0.3f, 3f); + arrow.GetComponent().material = bodyMat; + + // Druhý vnější ring pro ještě lepší viditelnost + var outerRing = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + outerRing.name = "OuterRing"; + outerRing.transform.SetParent(localPlayerObject.transform); + outerRing.transform.localPosition = new Vector3(0, 0.1f, 0); + outerRing.transform.localScale = new Vector3(12f, 0.1f, 12f); + var outerMat = CreateEmissiveMaterial(new Color(0.5f, 1f, 0.5f, 0.3f), 0.5f); + outerRing.GetComponent().material = outerMat; + } + + // Pozice podle currentPlayerPosition (ne kamery!) + localPlayerObject.transform.position = new Vector3( + currentPlayerPosition.x, + 0, + currentPlayerPosition.z + ); + } + + /// Objekt lokálního hráče + protected GameObject localPlayerObject; + + #region ═══════════════════════════════════════════════════════════════════ + // TĚLA (BODIES) + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vytvoření objektu těla mrtvého hráče. + /// Tělo je vykresleno jako ležící capsule. + /// + /// ID těla + /// GPS pozice + /// Jméno oběti + protected void CreateBodyObject(string bodyId, Position position, string victimName) + { + if (bodyObjects.ContainsKey(bodyId)) return; + + // Kontejner pro tělo + var container = new GameObject($"Body_{victimName}"); + container.transform.SetParent(bodiesContainer.transform); + + Vector3 unityPos = GPSToUnity(position); + container.transform.position = unityPos; + + // Ležící tělo + var bodyObj = GameObject.CreatePrimitive(PrimitiveType.Capsule); + bodyObj.name = "Corpse"; + bodyObj.transform.SetParent(container.transform); + bodyObj.transform.localPosition = new Vector3(0, 0.3f, 0); + bodyObj.transform.rotation = Quaternion.Euler(90, 0, 0); + bodyObj.transform.localScale = new Vector3(1.5f, 2f, 1.5f); + + // Výrazná červená barva se svícením + var bodyMat = CreateEmissiveMaterial(new Color(1f, 0.2f, 0.2f), 1.5f); + bodyObj.GetComponent().material = bodyMat; + + // Výstražný kroužek + var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + ring.name = "WarningRing"; + ring.transform.SetParent(container.transform); + ring.transform.localPosition = new Vector3(0, 0.1f, 0); + ring.transform.localScale = new Vector3(6f, 0.1f, 6f); + var ringMat = CreateEmissiveMaterial(new Color(1f, 0f, 0f, 0.6f), 2f); + ring.GetComponent().material = ringMat; + + // Výstražná značka nahoře + var warning = GameObject.CreatePrimitive(PrimitiveType.Cube); + warning.name = "Warning"; + warning.transform.SetParent(container.transform); + warning.transform.localPosition = new Vector3(0, 4f, 0); + warning.transform.localScale = new Vector3(2f, 2f, 0.3f); + warning.GetComponent().material = bodyMat; + + bodyObjects[bodyId] = container; + } + + /// + /// Odstranění objektu těla (po reportu) + /// + protected void RemoveBodyObject(string bodyId) + { + if (bodyObjects.TryGetValue(bodyId, out var bodyObj)) + { + Destroy(bodyObj); + bodyObjects.Remove(bodyId); + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // ÚKOLY (TASKS) + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vytvoření markerů úkolů pro hráče. + /// Úkoly jsou vykresleny jako svítící válce. + /// + protected void CreateTaskMarkers() + { + if (client?.MyTasks == null) return; + + foreach (var task in client.MyTasks) + { + CreateTaskObject(task); + } + } + + /// + /// Vytvoření markeru úkolu - VÝRAZNÝ marker s popiskem + /// + private void CreateTaskObject(object taskObj) + { + // Dynamický přístup k vlastnostem + var taskType = taskObj.GetType(); + string taskId = taskType.GetField("TaskId")?.GetValue(taskObj) as string ?? + taskType.GetProperty("TaskId")?.GetValue(taskObj) as string ?? + taskType.GetField("Id")?.GetValue(taskObj) as string ?? + taskType.GetProperty("Id")?.GetValue(taskObj) as string ?? System.Guid.NewGuid().ToString(); + string taskName = taskType.GetField("Name")?.GetValue(taskObj) as string ?? + taskType.GetProperty("Name")?.GetValue(taskObj) as string ?? "Task"; + + // Získání lokace + object locationObj = taskType.GetField("Location")?.GetValue(taskObj) ?? + taskType.GetProperty("Location")?.GetValue(taskObj); + Position location = locationObj is Position pos ? pos : new Position(0, 0); + + if (taskObjects.ContainsKey(taskId)) return; + + // Kontejner pro task marker + var container = new GameObject($"Task_{taskName}"); + container.transform.SetParent(tasksContainer.transform); + + Vector3 unityPos = GPSToUnity(location); + container.transform.position = unityPos; + + // Hlavní marker - VĚTŠÍ svítící válec + var marker = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + marker.name = "Marker"; + marker.transform.SetParent(container.transform); + marker.transform.localPosition = new Vector3(0, 2f, 0); + marker.transform.localScale = new Vector3(4f, 2f, 4f); + + // Výrazný svítící cyan materiál + var mat = CreateEmissiveMaterial(new Color(0f, 1f, 1f), 2f); + marker.GetComponent().material = mat; + + // Vrchní kužel jako "šipka dolů" + var cone = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + cone.name = "Cone"; + cone.transform.SetParent(container.transform); + cone.transform.localPosition = new Vector3(0, 5f, 0); + cone.transform.localScale = new Vector3(2f, 1f, 2f); + cone.GetComponent().material = mat; + + // Pulsující ring na zemi + var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + ring.name = "Ring"; + ring.transform.SetParent(container.transform); + ring.transform.localPosition = new Vector3(0, 0.1f, 0); + ring.transform.localScale = new Vector3(8f, 0.1f, 8f); + var ringMat = CreateEmissiveMaterial(new Color(0f, 0.8f, 0.8f, 0.5f), 1f); + ring.GetComponent().material = ringMat; + + taskObjects[taskId] = container; + } + + /// + /// Vytvoří svítící materiál + /// BUILD-SAFE: Používá Sprites/Default - emise simulována jasnou barvou + /// + private Material CreateEmissiveMaterial(Color color, float intensity = 1f) + { + // Simulujeme emisi zjasnením barvy + Color brightColor = new Color( + Mathf.Min(1f, color.r * (1f + intensity * 0.5f)), + Mathf.Min(1f, color.g * (1f + intensity * 0.5f)), + Mathf.Min(1f, color.b * (1f + intensity * 0.5f)), + color.a + ); + + return CreateUnlitMaterial(brightColor); + } + + /// + /// Aktualizace stavu úkolu (dokončen = skryje marker) + /// + protected void UpdateTaskMarker(string taskId, bool completed) + { + if (taskObjects.TryGetValue(taskId, out var taskObj)) + { + if (completed) + { + // Skryjeme celý marker + taskObj.SetActive(false); + } + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // OPRAVNÉ STANICE + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vytvoření markerů opravných stanic. + /// Zobrazují se pouze při aktivní sabotáži. + /// + protected void CreateRepairStationMarkers() + { + if (currentSabotage?.RepairStations == null) return; + + foreach (var station in currentSabotage.RepairStations) + { + CreateRepairStationObject(station.StationId, station.Name, station.Location); + } + } + + /// + /// Vytvoření markeru opravné stanice - VELMI VÝRAZNÝ marker + /// + private void CreateRepairStationObject(string stationId, string stationName, Position stationPos) + { + if (string.IsNullOrEmpty(stationId)) stationId = System.Guid.NewGuid().ToString(); + if (string.IsNullOrEmpty(stationName)) stationName = "Repair"; + + if (repairStationObjects.ContainsKey(stationId)) return; + + Debug.Log($"[GeoSus] Creating repair station marker: {stationId} '{stationName}' @ {stationPos.Lat:F6}, {stationPos.Lon:F6}"); + + // Kontejner pro opravnou stanici + var container = new GameObject($"Repair_{stationName}"); + container.transform.SetParent(repairStationsContainer.transform); + + Vector3 unityPos = GPSToUnity(stationPos); + container.transform.position = unityPos; + + // VELKÝ hlavní marker - věž + var tower = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + tower.name = "Tower"; + tower.transform.SetParent(container.transform); + tower.transform.localPosition = new Vector3(0, 5f, 0); + tower.transform.localScale = new Vector3(3f, 5f, 3f); + + // Výrazná oranžová/žlutá barva + var towerMat = CreateEmissiveMaterial(new Color(1f, 0.6f, 0f), 3f); + tower.GetComponent().material = towerMat; + + // Výstražná koule nahoře + var beacon = GameObject.CreatePrimitive(PrimitiveType.Sphere); + beacon.name = "Beacon"; + beacon.transform.SetParent(container.transform); + beacon.transform.localPosition = new Vector3(0, 11f, 0); + beacon.transform.localScale = new Vector3(4f, 4f, 4f); + var beaconMat = CreateEmissiveMaterial(new Color(1f, 0.8f, 0f), 5f); + beacon.GetComponent().material = beaconMat; + + // Velký kroužek na zemi - blikající + var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + ring.name = "Ring"; + ring.transform.SetParent(container.transform); + ring.transform.localPosition = new Vector3(0, 0.15f, 0); + ring.transform.localScale = new Vector3(12f, 0.15f, 12f); + var ringMat = CreateEmissiveMaterial(new Color(1f, 0.5f, 0f, 0.6f), 2f); + ring.GetComponent().material = ringMat; + + // Druhá výstražná značka - šipka dolů + var arrow1 = GameObject.CreatePrimitive(PrimitiveType.Cube); + arrow1.name = "Arrow1"; + arrow1.transform.SetParent(container.transform); + arrow1.transform.localPosition = new Vector3(0, 15f, 0); + arrow1.transform.localScale = new Vector3(2f, 4f, 0.5f); + arrow1.GetComponent().material = beaconMat; + + repairStationObjects[stationId] = container; + } + + /// + /// Aktualizace stavu opravné stanice + /// + protected void UpdateRepairStationMarker(string stationId, bool active) + { + if (repairStationObjects.TryGetValue(stationId, out var stationObj)) + { + // Najdeme Beacon (kouli nahoře) pro blikání + var beacon = stationObj.transform.Find("Beacon"); + var ring = stationObj.transform.Find("Ring"); + + if (beacon != null) + { + var renderer = beacon.GetComponent(); + if (renderer != null) + { + float pulse = active ? Mathf.Abs(Mathf.Sin(Time.time * 8f)) : 0.5f; + Color emissionColor = new Color(1f, 0.8f, 0f) * (1f + pulse * 3f); + renderer.material.color = emissionColor; + } + } + + if (ring != null) + { + var renderer = ring.GetComponent(); + if (renderer != null) + { + float pulse = Mathf.Abs(Mathf.Sin(Time.time * 3f)); + Color ringColor = active ? new Color(1f, 1f, 0f, 0.6f + pulse * 0.4f) : new Color(1f, 0.5f, 0f, 0.4f); + renderer.material.color = ringColor; + } + } + } + } + + /// + /// Odstranění všech opravných stanic (po opravě sabotáže) + /// + protected void ClearRepairStationMarkers() + { + foreach (var kvp in repairStationObjects) + { + Destroy(kvp.Value); + } + repairStationObjects.Clear(); + } + + #region ═══════════════════════════════════════════════════════════════════ + // NAČTENÍ MAPY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Zpracování přijatých mapových dat. + /// Vytvoří všechny 3D objekty mapy. + /// + /// POZNÁMKA: MapDataPayload používá kompaktní formát: + /// - Buildings: List kde každý double[] je [lat1,lon1,lat2,lon2,...] + /// - Pathways: List podobně + /// - Areas: List podobně + /// - POIs: List ve formátu [lat,lon,type,lat,lon,type,...] + /// + /// Data z Overpass API + protected void ProcessMapData(MapDataPayload mapData) + { + if (mapData == null) return; + + // Uložení referenčních bodů + mapCenter = mapData.Center; + mapRadius = mapData.RadiusMeters; + + // Inicializace systému + InitializeMapSystem(); + + // Vytvoření budov z kompaktního formátu + if (mapData.Buildings != null) + { + loadingMessage = "Vytvářím budovy..."; + for (int i = 0; i < mapData.Buildings.Count; i++) + { + var coords = mapData.Buildings[i]; + string buildingType = (mapData.BuildingTypes != null && i < mapData.BuildingTypes.Count) + ? mapData.BuildingTypes[i] : null; + CreateBuildingFromCoords(coords, buildingType, i); + } + } + + // Vytvoření cest z kompaktního formátu + if (mapData.Pathways != null) + { + loadingMessage = "Vytvářím cesty..."; + for (int i = 0; i < mapData.Pathways.Count; i++) + { + var coords = mapData.Pathways[i]; + PathType pathType = (mapData.PathwayTypes != null && i < mapData.PathwayTypes.Count) + ? (PathType)mapData.PathwayTypes[i] : PathType.Footway; + CreatePathwayFromCoords(coords, pathType, i); + } + } + + // Vytvoření oblastí z kompaktního formátu + if (mapData.Areas != null) + { + loadingMessage = "Vytvářím oblasti..."; + for (int i = 0; i < mapData.Areas.Count; i++) + { + var coords = mapData.Areas[i]; + MapAreaType areaType = (mapData.AreaTypes != null && i < mapData.AreaTypes.Count) + ? (MapAreaType)mapData.AreaTypes[i] : MapAreaType.Other; + CreateAreaFromCoords(coords, areaType, i); + } + } + + // Vytvoření POI z kompaktního formátu [lat,lon,type,lat,lon,type,...] + if (mapData.POIs != null && mapData.POIs.Count >= 3) + { + loadingMessage = "Vytvářím body zájmu..."; + for (int i = 0; i + 2 < mapData.POIs.Count; i += 3) + { + double lat = mapData.POIs[i]; + double lon = mapData.POIs[i + 1]; + int typeIndex = (int)mapData.POIs[i + 2]; + MapPOIType poiType = (MapPOIType)typeIndex; + CreatePOIFromCoords(lat, lon, poiType, i / 3); + } + } + + loadingProgress = 1f; + loadingMessage = "Mapa připravena!"; + } + + /// + /// Vytvoření budovy z kompaktních souřadnic + /// + private void CreateBuildingFromCoords(double[] coords, string buildingType, int index) + { + if (coords == null || coords.Length < 6) return; // Potřeba alespoň 3 body + + // Převod na List + List outline = new List(); + for (int i = 0; i + 1 < coords.Length; i += 2) + { + outline.Add(new Position(coords[i], coords[i + 1])); + } + + // Vytvoření MapBuilding objektu + var building = new MapBuilding + { + Id = index, + Outline = outline, + Name = buildingType ?? "Building", + BuildingType = buildingType + }; + + CreateBuildingObject(building); + } + + /// + /// Vytvoření cesty z kompaktních souřadnic + /// + private void CreatePathwayFromCoords(double[] coords, PathType pathType, int index) + { + if (coords == null || coords.Length < 4) return; // Potřeba alespoň 2 body + + // Převod na List + List points = new List(); + for (int i = 0; i + 1 < coords.Length; i += 2) + { + points.Add(new Position(coords[i], coords[i + 1])); + } + + // Vytvoření MapPathway objektu + var pathway = new MapPathway + { + Id = index, + Points = points, + PathType = pathType, + Name = pathType.ToString() + }; + + CreatePathwayObject(pathway); + } + + /// + /// Vytvoření oblasti z kompaktních souřadnic + /// + private void CreateAreaFromCoords(double[] coords, MapAreaType areaType, int index) + { + if (coords == null || coords.Length < 6) return; // Potřeba alespoň 3 body + + // Převod na List + List outline = new List(); + for (int i = 0; i + 1 < coords.Length; i += 2) + { + outline.Add(new Position(coords[i], coords[i + 1])); + } + + // Vytvoření MapArea objektu + var area = new MapArea + { + Id = index, + Outline = outline, + AreaType = areaType, + Name = areaType.ToString() + }; + + CreateAreaObject(area); + } + + /// + /// Vytvoření POI z souřadnic + /// + private void CreatePOIFromCoords(double lat, double lon, MapPOIType poiType, int index) + { + var poi = new MapPOI + { + Id = index, + Location = new Position(lat, lon), + POIType = poiType, + Name = poiType.ToString() + }; + + CreatePOIObject(poi); + } + + #region ═══════════════════════════════════════════════════════════════════ + // ÚKLID + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vyčištění všech mapových objektů. + /// Volá se při ukončení hry nebo odpojení. + /// + protected void CleanupMapObjects() + { + // Zničení hlavního kontejneru zničí všechny potomky + if (mapContainer != null) + { + Destroy(mapContainer); + mapContainer = null; + } + + // Vyčištění slovníků + playerObjects.Clear(); + bodyObjects.Clear(); + taskObjects.Clear(); + repairStationObjects.Clear(); + + // Zničení lokálního hráče + if (localPlayerObject != null) + { + Destroy(localPlayerObject); + localPlayerObject = null; + } + } + + /// + /// Aktualizace všech dynamických objektů na mapě. + /// Volá se každý frame v Update(). + /// + protected void UpdateMapObjects() + { + if (client == null || currentState != AppState.InGame) return; + + // Aktualizace pozic hráčů - bezpečně kopírujeme klíče + if (client.PlayerPositions != null) + { + try + { + // Použijeme ToArray() místo new List() - je rychlejší a bezpečnější + var playerIds = client.PlayerPositions.Keys.ToArray(); + foreach (var playerId in playerIds) + { + if (client.PlayerPositions.TryGetValue(playerId, out var playerInfo)) + { + UpdatePlayerObject(playerId, playerInfo.Position, playerInfo.State); + } + } + } + catch (System.InvalidOperationException) + { + // Dictionary byl změněn během iterace - přeskočíme tento frame + } + } + + // Aktualizace lokálního hráče + UpdateLocalPlayerVisual(); + + // Aktualizace opravných stanic (blikání) - bezpečně kopírujeme klíče + if (currentSabotage != null) + { + try + { + var stationIds = repairStationObjects.Keys.ToArray(); + foreach (var stationId in stationIds) + { + bool isActive = activeRepairStation == stationId; + UpdateRepairStationMarker(stationId, isActive); + } + } + catch (System.InvalidOperationException) + { + // Dictionary byl změněn během iterace - přeskočíme tento frame + } + } + + // Aktualizace meeting location markeru + bool hasMeeting = currentMeeting != null && client?.CurrentLobbyState?.Phase == GamePhase.Meeting; + if (hasMeeting && currentMeeting != null) + { + UpdateMeetingLocationMarker(currentMeeting.MeetingLocation, true); + } + else + { + UpdateMeetingLocationMarker(null, false); + } + } + + /// + /// Hledání nejbližší opravné stanice + /// + protected RepairStationInfo FindNearbyRepairStation(double maxDistance) + { + if (currentSabotage?.RepairStations == null) return null; + if (localPlayerObject == null) return null; + + Position myPos = UnityToGPS(localPlayerObject.transform.position); + + foreach (var station in currentSabotage.RepairStations) + { + double dist = CalculateDistance(myPos, station.Location); + if (dist <= maxDistance) return station; + } + + return null; + } + + /// + /// Výpočet vzdálenosti mezi dvěma GPS body (Haversine) + /// + protected double CalculateDistance(Position a, Position b) + { + const double R = 6371000; // Poloměr Země v metrech + + double lat1 = a.Lat * Math.PI / 180; + double lat2 = b.Lat * Math.PI / 180; + double dLat = (b.Lat - a.Lat) * Math.PI / 180; + double dLon = (b.Lon - a.Lon) * Math.PI / 180; + + double h = Math.Sin(dLat/2) * Math.Sin(dLat/2) + + Math.Cos(lat1) * Math.Cos(lat2) * + Math.Sin(dLon/2) * Math.Sin(dLon/2); + + return 2 * R * Math.Asin(Math.Sqrt(h)); + } +} diff --git a/Assets/UnityTestClient/UnityTestClient_Map.cs.meta b/Assets/UnityTestClient/UnityTestClient_Map.cs.meta new file mode 100644 index 0000000..a0a811f --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_Map.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2d9f27ae239808c44b27975df7eefd38 \ No newline at end of file diff --git a/Assets/UnityTestClient/UnityTestClient_Network.cs b/Assets/UnityTestClient/UnityTestClient_Network.cs new file mode 100644 index 0000000..c00203d --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_Network.cs @@ -0,0 +1,1196 @@ +/* +╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ SÍŤOVÁ KOMUNIKACE - UnityTestClient_Network.cs ║ +║ ║ +║ Tento soubor obsahuje veškerou síťovou logiku: ║ +║ • Připojení k serveru (TCP s AES-256 šifrováním) ║ +║ • Zpracování příchozích zpráv a událostí ║ +║ • Odesílání herních akcí ║ +║ • Lobby management ║ +║ ║ +║ KOMUNIKAČNÍ PROTOKOL: ║ +║ 1. Handshake: ClientHello → ServerHello (RSA klíč) → KeyExchange (AES) → KeyExchangeAck ║ +║ 2. Po handshake: Veškerá komunikace je šifrovaná AES-256 ║ +║ 3. Formát zpráv: [4 bajty délka] + [JSON payload] ║ +║ ║ +║ DŮLEŽITÉ ZPRÁVY (klient → server): ║ +║ • CreateLobby/JoinLobby - vytvoření/vstup do lobby ║ +║ • StartGame - spuštění hry (pouze owner) ║ +║ • UpdatePosition - GPS pozice hráče (každých 100ms) ║ +║ • CompleteTask - dokončení úkolu (server validuje pozici!) ║ +║ • KillAttempt - pokus o zabití (pouze impostor) ║ +║ • ReportBody - report těla (svolá meeting) ║ +║ • CallEmergencyMeeting - emergency button ║ +║ • CastVote - hlasování ║ +║ • StartSabotage - spuštění sabotáže (pouze impostor) ║ +║ • ActivateRepairStation - oprava sabotáže ║ +║ ║ +║ DŮLEŽITÉ UDÁLOSTI (server → klient): ║ +║ • LobbyCreated/LobbyJoined - úspěšný vstup do lobby ║ +║ • GameStarted - hra začala ║ +║ • RoleAssigned - přiřazení role (Crew/Impostor) a seznamu úkolů ║ +║ • TaskCompleted - úkol dokončen (s globálním progress) ║ +║ • PlayerKilled - hráč byl zabit ║ +║ • MeetingStarted - začátek meetingu ║ +║ • VotingClosed - výsledky hlasování ║ +║ • SabotageStarted - sabotáž spuštěna (s lokacemi repair stations) ║ +║ • SabotageRepaired - sabotáž opravena ║ +║ • GameEnded - konec hry (s výsledky) ║ +║ ║ +╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ +*/ + +using UnityEngine; +using System; +using System.Threading.Tasks; +using GeoSus.Client; + +public partial class UnityTestClient +{ + #region ═══════════════════════════════════════════════════════════════════ + // SÍŤOVÉ PROMĚNNÉ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Příznak, zda probíhá připojování k serveru + /// + protected bool isConnecting = false; + + /// + /// Časovač pro pravidelné odesílání pozice (každých 100ms) + /// + protected float lastPositionUpdateTime = 0f; + + /// + /// Interval odesílání pozice v sekundách + /// + protected const float POSITION_UPDATE_INTERVAL = 0.1f; + + /// + /// Časovač pro ping + /// + protected float lastPingTime = 0f; + + /// + /// Interval pingu v sekundách + /// + protected const float PING_INTERVAL = 5f; + + /// + /// Aktuální pozice hráče (GPS) + /// + protected Position playerPosition; + + /// + /// ID opravné stanice, kterou právě opravujeme + /// + protected string currentRepairStationId; + + /// + /// Poslední chybová zpráva z ACK - pro prevenci spamu + /// + private string lastAckError = null; + + /// + /// Čas posledního zobrazení chyby z ACK + /// + private float lastAckErrorTime = 0f; + + #region ═══════════════════════════════════════════════════════════════════ + // PŘIPOJENÍ K SERVERU + // ════════════════════════════════════════════════════════════════════════ + // Připojení je asynchronní operace. Používáme async/await pattern. + // Unity vyžaduje volat síťové operace mimo hlavní vlákno, + // ale GameClient to řeší interně pomocí EventDispatcher. + #endregion + + /// + /// Připojení k serveru. + /// Tato metoda vytvoří novou instanci GameClient a pokusí se připojit. + /// + /// DŮLEŽITÉ: Po úspěšném připojení se automaticky registrují event handlery + /// pro příjem zpráv ze serveru. + /// + protected async void ConnectToServer() + { + // Ochrana proti vícenásobnému připojení + if (isConnecting || (client != null && client.IsConnected)) + { + ShowError("Již se připojujete nebo jste připojeni!"); + return; + } + + isConnecting = true; + Debug.Log($"[GeoSus] Připojuji k serveru {serverHost}:{serverPort}..."); + + try + { + // ═══════════════════════════════════════════════════════════════ + // KROK 1: Vytvoření instance GameClient + // ═══════════════════════════════════════════════════════════════ + // GameClient je hlavní třída z ClientSDK pro komunikaci se serverem. + // Vyžaduje unikátní UUID klienta a zobrazované jméno. + client = new GameClient(clientUuid, displayName); + + // ═══════════════════════════════════════════════════════════════ + // KROK 2: Registrace event handlerů + // ═══════════════════════════════════════════════════════════════ + // Události jsou volány na hlavním vlákně díky EventDispatcher. + // Registrujeme je PŘED připojením, abychom neminuli žádné události. + RegisterEventHandlers(); + + // ═══════════════════════════════════════════════════════════════ + // KROK 3: Připojení k serveru + // ═══════════════════════════════════════════════════════════════ + // ConnectAsync provede celý handshake proces: + // ClientHello -> ServerHello -> KeyExchange -> KeyExchangeAck + bool success = await client.ConnectAsync(serverHost, serverPort); + + if (success) + { + Debug.Log("[GeoSus] Úspěšně připojeno k serveru!"); + ShowNotification("Připojeno k serveru", Color.green, 2f, "✓"); + + // Načteme statistiky z HTTP API + FetchPlayerStats(); + FetchLeaderboard(5); + CheckServerHealth(); + } + else + { + ShowError("Nepodařilo se připojit k serveru. Zkontrolujte, zda server běží."); + client.Dispose(); + client = null; + } + } + catch (Exception ex) + { + ShowError($"Chyba připojení: {ex.Message}"); + Debug.LogError($"[GeoSus] Chyba připojení: {ex}"); + + if (client != null) + { + client.Dispose(); + client = null; + } + } + finally + { + isConnecting = false; + } + } + + /// + /// Odpojení od serveru. + /// Volá se při opuštění hry nebo při chybě. + /// + protected void DisconnectFromServer() + { + if (client != null) + { + client.Disconnect("Hráč se odpojil"); + client.Dispose(); + client = null; + } + + // Reset stavu + currentState = AppState.MainMenu; + CleanupMapObjects(); + + Debug.Log("[GeoSus] Odpojeno od serveru"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // EVENT HANDLERY + // ════════════════════════════════════════════════════════════════════════ + // Event handlery jsou volány když přijde zpráva ze serveru. + // GameClient má několik typů událostí: + // • OnConnected - úspěšné připojení + // • OnDisconnected - odpojení (s důvodem) + // • OnError - chyba + // • OnMessage - jakákoli zpráva (pro debug) + // • OnGameEvent - herní události (GameEvent) + #endregion + + /// + /// Registrace všech event handlerů. + /// Volá se při vytvoření klienta, PŘED připojením. + /// + private void RegisterEventHandlers() + { + // ═══════════════════════════════════════════════════════════════════ + // Základní síťové události + // ═══════════════════════════════════════════════════════════════════ + + client.OnConnected += OnClientConnected; + client.OnDisconnected += OnClientDisconnected; + client.OnError += OnClientError; + + // ═══════════════════════════════════════════════════════════════════ + // Herní události - toto je hlavní zdroj informací ze serveru + // ═══════════════════════════════════════════════════════════════════ + + client.OnGameEvent += OnGameEvent; + + // ═══════════════════════════════════════════════════════════════════ + // Zprávy - pro debug a speciální případy + // ═══════════════════════════════════════════════════════════════════ + + client.OnMessage += OnMessage; + } + + /// + /// Handler pro úspěšné připojení + /// + private void OnClientConnected() + { + Debug.Log("[GeoSus] Event: OnConnected"); + } + + /// + /// Handler pro odpojení od serveru + /// + /// Důvod odpojení + private void OnClientDisconnected(string reason) + { + Debug.Log($"[GeoSus] Event: OnDisconnected - {reason}"); + + ShowNotification($"Odpojeno: {reason}", Color.red, 5f, "⚠"); + + // Reset do hlavního menu + currentState = AppState.MainMenu; + CleanupMapObjects(); + + client = null; + } + + /// + /// Handler pro chyby + /// + /// Popis chyby + private void OnClientError(string error) + { + Debug.LogError($"[GeoSus] Event: OnError - {error}"); + ShowError(error); + } + + /// + /// Handler pro příchozí zprávy (všechny typy). + /// Používá se hlavně pro debug a speciální zprávy. + /// + /// Příchozí zpráva + private void OnMessage(Message message) + { + // ═══════════════════════════════════════════════════════════════════ + // Zpracování odpovědí na lobby operace + // ═══════════════════════════════════════════════════════════════════ + + switch (message) + { + case CreateLobbyResponse response: + HandleCreateLobbyResponse(response); + break; + + case JoinLobbyResponse response: + HandleJoinLobbyResponse(response); + break; + + case Ack ack: + HandleAck(ack); + break; + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // HERNÍ UDÁLOSTI (GameEvent) + // ════════════════════════════════════════════════════════════════════════ + // GameEvent je hlavní způsob, jak server informuje klienty o změnách. + // Každý event má: + // • EventId - sekvenční číslo pro řazení + // • EventType - typ události (string) + // • Payload - data události (JObject, deserializuje se na konkrétní typ) + // • Actor - UUID hráče, který akci provedl + #endregion + + /// + /// Hlavní handler pro herní události. + /// Zde se zpracovávají všechny události ze serveru. + /// + /// DŮLEŽITÉ: Payload se deserializuje pomocí GetPayload() + /// + /// Herní událost + private void OnGameEvent(GameEvent evt) + { + Debug.Log($"[GeoSus] GameEvent: {evt.EventType}"); + + // ═══════════════════════════════════════════════════════════════════ + // SWITCH podle typu události + // Každý typ má svůj vlastní payload s daty + // ═══════════════════════════════════════════════════════════════════ + + switch (evt.EventType) + { + // ─────────────────────────────────────────────────────────────── + // LOBBY UDÁLOSTI + // ─────────────────────────────────────────────────────────────── + + case "PlayerJoined": + HandlePlayerJoined(evt); + break; + + case "PlayerLeft": + HandlePlayerLeft(evt); + break; + + case "HostChanged": + HandleHostChanged(evt); + break; + + case "LobbySettingsChanged": + HandleLobbySettingsChanged(evt); + break; + + // ─────────────────────────────────────────────────────────────── + // LOADING FÁZE + // ─────────────────────────────────────────────────────────────── + + case "GameStarting": + HandleGameStarting(evt); + break; + + case "MapDataReady": + HandleMapDataReady(evt); + break; + + case "PlayerMapDataReceived": + HandlePlayerMapDataReceived(evt); + break; + + // ─────────────────────────────────────────────────────────────── + // HRA ZAČÍNÁ + // ─────────────────────────────────────────────────────────────── + + case "GameStarted": + HandleGameStarted(evt); + break; + + case "RoleAssigned": + HandleRoleAssigned(evt); + break; + + // ─────────────────────────────────────────────────────────────── + // HERNÍ AKCE + // ─────────────────────────────────────────────────────────────── + + case "PlayerKilled": + HandlePlayerKilled(evt); + break; + + case "BodyReported": + HandleBodyReported(evt); + break; + + case "EmergencyMeetingCalled": + HandleEmergencyMeetingCalled(evt); + break; + + // ─────────────────────────────────────────────────────────────── + // MEETING A HLASOVÁNÍ + // ─────────────────────────────────────────────────────────────── + + case "MeetingStarted": + HandleMeetingStarted(evt); + break; + + case "PlayerArrivedAtMeeting": + HandlePlayerArrivedAtMeeting(evt); + break; + + case "PlayerVoted": + HandlePlayerVoted(evt); + break; + + case "VotingClosed": + HandleVotingClosed(evt); + break; + + case "PlayerEjected": + HandlePlayerEjected(evt); + break; + + // ─────────────────────────────────────────────────────────────── + // ÚKOLY + // ─────────────────────────────────────────────────────────────── + + case "TaskCompleted": + HandleTaskCompleted(evt); + break; + + // ─────────────────────────────────────────────────────────────── + // SABOTÁŽE + // ─────────────────────────────────────────────────────────────── + + case "SabotageStarted": + HandleSabotageStarted(evt); + break; + + case "RepairStarted": + HandleRepairStarted(evt); + break; + + case "RepairStopped": + HandleRepairStopped(evt); + break; + + case "SabotageRepaired": + HandleSabotageRepaired(evt); + break; + + case "SabotageMeltdown": + HandleSabotageMeltdown(evt); + break; + + // ─────────────────────────────────────────────────────────────── + // KONEC HRY + // ─────────────────────────────────────────────────────────────── + + case "GameEnded": + HandleGameEnded(evt); + break; + + case "ReturnedToLobby": + HandleReturnedToLobby(evt); + break; + + // ─────────────────────────────────────────────────────────────── + // SYSTÉMOVÉ ZPRÁVY + // ─────────────────────────────────────────────────────────────── + + case "SystemMessage": + HandleSystemMessage(evt); + break; + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // HANDLERY JEDNOTLIVÝCH UDÁLOSTÍ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + // ───────────────────────────────────────────────────────────────────────── + // LOBBY HANDLERY + // ───────────────────────────────────────────────────────────────────────── + + /// + /// Zpracování odpovědi na vytvoření lobby + /// + private void HandleCreateLobbyResponse(CreateLobbyResponse response) + { + if (response.Success) + { + Debug.Log($"[GeoSus] Lobby vytvořeno: {response.JoinCode}"); + currentState = AppState.Lobby; + ShowNotification($"Lobby vytvořeno! Kód: {response.JoinCode}", Color.green, 3f, "🏠"); + } + else + { + ShowError($"Nelze vytvořit lobby: {response.Error}"); + } + } + + /// + /// Zpracování odpovědi na připojení do lobby + /// + private void HandleJoinLobbyResponse(JoinLobbyResponse response) + { + if (response.Success) + { + Debug.Log($"[GeoSus] Připojeno do lobby: {response.LobbyId}"); + currentState = AppState.Lobby; + ShowNotification("Připojeno do lobby!", Color.green, 2f, "✓"); + } + else + { + ShowError($"Nelze se připojit: {response.Error}"); + } + } + + /// + /// Zpracování ACK zprávy (potvrzení akce) + /// + private void HandleAck(Ack ack) + { + if (!ack.Success && !string.IsNullOrEmpty(ack.Error)) + { + // Prevence spamu - nezobrazujeme stejnou chybu opakovaně v krátkém čase + float currentTime = Time.time; + if (ack.Error == lastAckError && currentTime - lastAckErrorTime < 3f) + { + // Stejná chyba před méně než 3 sekundami - nezobrazovat + return; + } + + lastAckError = ack.Error; + lastAckErrorTime = currentTime; + + // Zobrazíme chybu uživateli + ShowNotification(ack.Error, new Color(1f, 0.5f, 0f), 3f, "⚠"); + } + } + + /// + /// Hráč se připojil do lobby + /// + private void HandlePlayerJoined(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // SDK automaticky aktualizuje CurrentLobbyState.Players + ShowNotification($"{payload.DisplayName} se připojil/a", Color.cyan, 2f, "→"); + } + } + + /// + /// Hráč opustil lobby + /// + private void HandlePlayerLeft(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // Zkontrolujeme jestli jsme byli kicknuti my + if (payload.ClientUuid == clientUuid) + { + // Jsme kicknuti! + string reason = payload.Reason ?? "Vyhozen administrátorem"; + Debug.Log($"[GeoSus] Byl jsem kicknut: {reason}"); + ShowNotification($"🚫 Byli jste vyhozeni: {reason}", Color.red, 10f, "🚫"); + + // Odpojíme se a vrátíme do menu + currentState = AppState.MainMenu; + client?.Disconnect(); + return; + } + + // SDK automaticky aktualizuje CurrentLobbyState.Players + // Najdeme jméno hráče pro zobrazení + string name = payload.ClientUuid; + if (client?.CurrentLobbyState?.Players != null) + { + foreach (var p in client.CurrentLobbyState.Players) + { + if (p.ClientUuid == payload.ClientUuid) + { + name = p.DisplayName; + break; + } + } + } + + // Zobrazíme jestli byl hráč kicknut nebo odešel sám + bool wasKicked = !string.IsNullOrEmpty(payload.Reason) && payload.Reason.Contains("Kicked"); + if (wasKicked) + { + ShowNotification($"{name} byl/a vyhozen/a", Color.red, 3f, "🚫"); + } + else + { + ShowNotification($"{name} odešel/a", Color.gray, 2f, "←"); + } + } + } + + /// + /// Vlastník lobby se změnil (host migration) + /// + private void HandleHostChanged(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // SDK automaticky aktualizuje CurrentLobbyState.OwnerId a IsOwner flags + bool iAmNewOwner = payload.NewHostId == clientUuid; + + if (iAmNewOwner) + { + ShowNotification("Jsi nyní vlastníkem lobby!", Color.yellow, 4f, "👑"); + Debug.Log("[GeoSus] Stal jsem se vlastníkem lobby"); + } + else + { + // Najdeme jméno nového vlastníka + string newOwnerName = payload.NewHostId; + if (client?.CurrentLobbyState?.Players != null) + { + foreach (var p in client.CurrentLobbyState.Players) + { + if (p.ClientUuid == payload.NewHostId) + { + newOwnerName = p.DisplayName; + break; + } + } + } + ShowNotification($"{newOwnerName} je nyní vlastníkem", Color.cyan, 3f, "👑"); + } + } + } + + /// + /// Nastavení lobby se změnilo + /// + private void HandleLobbySettingsChanged(GameEvent evt) + { + ShowNotification("Nastavení lobby změněno", Color.yellow, 2f, "⚙"); + } + + // ───────────────────────────────────────────────────────────────────────── + // LOADING FÁZE HANDLERY + // ───────────────────────────────────────────────────────────────────────── + + /// + /// Hra začíná - vstupujeme do loading fáze + /// + private void HandleGameStarting(GameEvent evt) + { + Debug.Log("[GeoSus] Hra začíná - načítání mapových dat..."); + currentState = AppState.Loading; + loadingMessage = "Načítání mapových dat..."; + loadingProgress = 0f; + + ShowNotification("Hra začíná!", Color.yellow, 2f, "🎮"); + } + + /// + /// Mapová data jsou připravena + /// + private void HandleMapDataReady(GameEvent evt) + { + var payload = evt.GetPayload(); + + Debug.Log("[GeoSus] Mapová data přijata"); + loadingMessage = "Mapová data přijata, čekám na ostatní hráče..."; + loadingProgress = 0.5f; + + // Vykreslíme mapu - ProcessMapData inicializuje mapu + if (payload?.MapData != null && client?.CurrentLobbyState != null) + { + mapCenter = client.CurrentLobbyState.PlayAreaCenter; + mapRadius = client.CurrentLobbyState.PlayAreaRadius; + ProcessMapData(payload.MapData); + } + + // POZNÁMKA: GameClient automaticky pošle MapDataReceived potvrzení + } + + /// + /// Hráč potvrdil příjem mapových dat + /// + private void HandlePlayerMapDataReceived(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + loadingMessage = $"Hráči připraveni: {payload.PlayersReady}/{payload.TotalPlayers}"; + loadingProgress = 0.5f + (0.5f * payload.PlayersReady / payload.TotalPlayers); + } + } + + // ───────────────────────────────────────────────────────────────────────── + // HRA HANDLERY + // ───────────────────────────────────────────────────────────────────────── + + /// + /// Hra oficiálně začala + /// + private void HandleGameStarted(GameEvent evt) + { + Debug.Log("[GeoSus] Hra začala!"); + currentState = AppState.InGame; + + // Reset herních proměnných + isRepairing = false; + repairProgress = 0f; + + // Inicializace pozice hráče + InitializePlayerPosition(); + + ShowNotification("HRA ZAČALA!", Color.green, 3f, "🎮"); + } + + /// + /// Přiřazení role hráči + /// + private void HandleRoleAssigned(GameEvent evt) + { + var payload = evt.GetPayload(); + + // Kontrola, zda je to naše role + if (payload != null && payload.ClientUuid == clientUuid) + { + Debug.Log($"[GeoSus] Moje role: {payload.Role}"); + + // Zobrazíme velkou notifikaci s rolí + if (payload.Role == PlayerRole.Impostor) + { + ShowNotification("JSI IMPOSTOR!", Color.red, 5f, "🔪"); + } + else + { + ShowNotification("Jsi Crewmate", Color.cyan, 3f, "👤"); + } + + // Aktualizujeme markery úkolů na mapě + CreateTaskMarkers(); + } + } + + /// + /// Hráč byl zabit + /// + private void HandlePlayerKilled(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // Vytvoříme marker těla na mapě + CreateBodyObject(payload.BodyId, payload.Location, GetPlayerName(payload.VictimId)); + + // Pokud jsme to my, kdo byl zabit + if (payload.VictimId == clientUuid) + { + ShowNotification("BYL JSI ZABIT!", Color.red, 5f, "💀"); + } + } + } + + /// + /// Tělo bylo nahlášeno + /// + private void HandleBodyReported(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // Najdeme jméno reportéra + string reporterName = GetPlayerName(payload.ReporterId); + ShowNotification($"{reporterName} nahlásil/a tělo!", Color.red, 3f, "🚨"); + } + } + + /// + /// Emergency meeting bylo svoláno + /// + private void HandleEmergencyMeetingCalled(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + string callerName = GetPlayerName(payload.CallerId); + ShowNotification($"{callerName} svolal/a emergency meeting!", Color.yellow, 3f, "🔔"); + } + } + + // ───────────────────────────────────────────────────────────────────────── + // MEETING HANDLERY + // ───────────────────────────────────────────────────────────────────────── + + /// + /// Meeting začal + /// + private void HandleMeetingStarted(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + Debug.Log($"[GeoSus] Meeting začal: {payload.Type}"); + + // Uložíme data meetingu + currentMeeting = payload; + meetingVotes.Clear(); + arrivedAtMeeting.Clear(); + iArrivedAtMeeting = false; + myVote = null; + canVote = false; + + // Určíme, zda můžeme hlasovat (jsme naživu) + bool amAlive = true; + if (client?.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var myInfo)) + { + amAlive = myInfo.State == PlayerState.Alive; + } + + canVoteInMeeting = amAlive; + + // Notifikace s informací o meeting lokaci + string meetingInfo = payload.Type == MeetingType.BodyReport + ? "TĚLO NALEZENO! Běž na meeting lokaci!" + : "EMERGENCY MEETING! Běž na meeting lokaci!"; + ShowNotification(meetingInfo, Color.yellow, 5f, "📢"); + } + } + + /// + /// Hráč dorazil na meeting lokaci + /// + private void HandlePlayerArrivedAtMeeting(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + arrivedAtMeeting.Add(payload.ClientUuid); + + if (payload.ClientUuid == clientUuid) + { + iArrivedAtMeeting = true; + ShowNotification("Dorazil/a jsi na meeting!", Color.green, 2f, "✓"); + } + else + { + string playerName = GetPlayerName(payload.ClientUuid); + Debug.Log($"[GeoSus] {playerName} dorazil na meeting"); + } + } + } + + /// + /// Hráč hlasoval (vidíme pouze, že hlasoval, ne koho) + /// + private void HandlePlayerVoted(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + meetingVotes[payload.VoterId] = true; // Označíme, že hlasoval + + string voterName = GetPlayerName(payload.VoterId); + ShowNotification($"{voterName} hlasoval/a", Color.gray, 1f, "✓"); + } + } + + /// + /// Hlasování skončilo + /// + private void HandleVotingClosed(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // Uložíme výsledky pro zobrazení + votingResults = payload; + showVotingResults = true; + votingResultsEndTime = Time.time + 5f; // Zobrazíme na 5 sekund + + if (payload.EjectedPlayerId != null) + { + string ejectedName = GetPlayerName(payload.EjectedPlayerId); + ShowNotification($"{ejectedName} byl/a vyhozen/a!", Color.red, 5f, "🚪"); + } + else if (payload.WasTie) + { + ShowNotification("Remíza - nikdo nebyl vyhozen", Color.gray, 3f, "⚖"); + } + else + { + ShowNotification("Nikdo nebyl vyhozen", Color.gray, 3f, "✗"); + } + } + } + + /// + /// Hráč byl vyhozen + /// + private void HandlePlayerEjected(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // Pokud jsme to my + if (payload.ClientUuid == clientUuid) + { + ShowNotification("BYL JSI VYHOZEN!", Color.red, 5f, "🚪"); + } + + // Zavřeme meeting panel + currentMeeting = null; + } + } + + // ───────────────────────────────────────────────────────────────────────── + // TASK HANDLERY + // ───────────────────────────────────────────────────────────────────────── + + /// + /// Úkol byl dokončen (kýmkoli) + /// + private void HandleTaskCompleted(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // Pokud jsme to my, označíme task jako splněný + if (payload.ClientUuid == clientUuid) + { + myCompletedTaskIds.Add(payload.TaskId); + ShowNotification("Úkol dokončen!", Color.green, 2f, "✓"); + + // Aktualizuj marker + UpdateTaskMarker(payload.TaskId, true); + } + + // Aktualizujeme globální progress bar úkolů + totalTasksCompleted = payload.TotalCompleted; + totalTasksRequired = payload.TotalTasks; + + Debug.Log($"[GeoSus] Task completed: {payload.TaskId}, progress {payload.TotalCompleted}/{payload.TotalTasks}"); + } + } + + // ───────────────────────────────────────────────────────────────────────── + // SABOTAGE HANDLERY + // ───────────────────────────────────────────────────────────────────────── + + /// + /// Sabotáž byla spuštěna + /// + private void HandleSabotageStarted(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + Debug.Log($"[GeoSus] Sabotáž spuštěna: {payload.Type}, stanic: {payload.RepairStations?.Count ?? 0}"); + + if (payload.RepairStations != null) + { + foreach (var station in payload.RepairStations) + { + Debug.Log($"[GeoSus] Repair station: {station.StationId} '{station.Name}' @ {station.Location.Lat:F6}, {station.Location.Lon:F6}"); + } + } + + currentSabotage = payload; + + // Vytvoříme markery opravných stanic + CreateRepairStationMarkers(); + + // Zobrazíme notifikaci + string message = payload.Type == SabotageType.CommsBlackout + ? "⚠ COMMS BLACKOUT - Opravte komunikaci!" + : "⚠ CRITICAL MELTDOWN - Opravte reaktor!"; + + Color color = payload.Type == SabotageType.CriticalMeltdown ? Color.red : Color.yellow; + ShowNotification(message, color, 5f, "⚠"); + } + } + + /// + /// Někdo začal opravovat stanici + /// + private void HandleRepairStarted(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + // Aktualizujeme marker stanice + UpdateRepairStationMarker(payload.StationId, true); + } + } + + /// + /// Někdo přestal opravovat stanici + /// + private void HandleRepairStopped(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + UpdateRepairStationMarker(payload.StationId, false); + } + } + + /// + /// Sabotáž byla opravena + /// + private void HandleSabotageRepaired(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + Debug.Log($"[GeoSus] Sabotáž opravena: {payload.Type}"); + + currentSabotage = null; + isRepairing = false; + + // Odstraníme markery opravných stanic + ClearRepairStationMarkers(); + + ShowNotification("Sabotáž opravena!", Color.green, 3f, "✓"); + } + } + + /// + /// Meltdown - impostoři vyhráli + /// + private void HandleSabotageMeltdown(GameEvent evt) + { + ShowNotification("MELTDOWN! Impostoři vyhráli!", Color.red, 5f, "💥"); + } + + // ───────────────────────────────────────────────────────────────────────── + // KONEC HRY + // ───────────────────────────────────────────────────────────────────────── + + /// + /// Hra skončila + /// + private void HandleGameEnded(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload != null) + { + Debug.Log($"[GeoSus] Hra skončila! Vítěz: {payload.WinningFaction}"); + + gameEndData = payload; + currentState = AppState.GameEnded; + + // Notifikace + bool weWon = payload.Winners.Contains(clientUuid); + if (weWon) + { + ShowNotification("VÍTĚZSTVÍ!", Color.green, 5f, "🏆"); + } + else + { + ShowNotification("Prohra...", Color.red, 5f, "💔"); + } + } + } + + /// + /// Vráceno do lobby po skončení hry + /// + private void HandleReturnedToLobby(GameEvent evt) + { + Debug.Log("[GeoSus] Vráceno do lobby"); + + gameEndData = null; + currentSabotage = null; // Reset sabotage stavu! + currentState = AppState.Lobby; + CleanupMapObjects(); + + ShowNotification("Zpět v lobby!", Color.cyan, 3f, "🔄"); + } + + // ───────────────────────────────────────────────────────────────────────── + // SYSTÉMOVÉ ZPRÁVY (Admin broadcast) + // ───────────────────────────────────────────────────────────────────────── + + /// + /// Zpracování systémové zprávy od administrátora + /// + private void HandleSystemMessage(GameEvent evt) + { + var payload = evt.GetPayload(); + if (payload == null) return; + + Debug.Log($"[GeoSus] Systémová zpráva: {payload.Message}"); + + // Zobrazíme výraznou notifikaci s delším trváním (oranžová barva pro admin zprávy) + ShowNotification($"📢 ADMIN: {payload.Message}", new Color(1f, 0.8f, 0f), 8f, "📢"); + } + + #region ═══════════════════════════════════════════════════════════════════ + // ODESÍLÁNÍ ZPRÁV + // ════════════════════════════════════════════════════════════════════════ + // Tyto metody obalují volání GameClient pro pohodlnější použití. + // GameClient.Send() automaticky šifruje a odesílá zprávy. + #endregion + + /// + /// Vytvoření nového lobby + /// + /// Střed herní oblasti (GPS) + /// Poloměr herní oblasti v metrech + /// Počet impostorů + /// Počet úkolů + /// Heslo (volitelné) + protected void CreateLobby(Position center, double radius, int impostorCount, int taskCount, string password = null) + { + if (client == null || !client.IsConnected) + { + ShowError("Nejste připojeni k serveru!"); + return; + } + + Debug.Log($"[GeoSus] Vytvářím lobby: center={center.Lat},{center.Lon}, radius={radius}m"); + client.CreateLobby(center, impostorCount, taskCount, password, radius); + } + + /// + /// Připojení do existujícího lobby + /// + /// 6-místný kód pro připojení + /// Heslo (pokud je vyžadováno) + protected void JoinLobby(string joinCode, string password = null) + { + if (client == null || !client.IsConnected) + { + ShowError("Nejste připojeni k serveru!"); + return; + } + + Debug.Log($"[GeoSus] Připojuji do lobby: {joinCode}"); + client.JoinLobby(joinCode, password); + } + + /// + /// Opuštění lobby + /// + protected void LeaveLobby() + { + if (client != null) + { + client.LeaveLobby(); + } + + currentState = AppState.MainMenu; + CleanupMapObjects(); + } + + /// + /// Spuštění hry (pouze owner) + /// + protected void StartGame() + { + if (client == null || !client.IsConnected) + { + ShowError("Nejste připojeni k serveru!"); + return; + } + + // Kontrola, zda jsme owner (používáme novou vlastnost IsOwner) + if (!client.IsOwner) + { + ShowError("Pouze vlastník lobby může spustit hru!"); + return; + } + + Debug.Log("[GeoSus] Spouštím hru..."); + client.StartGame(); + } + + /// + /// Odeslání aktualizace pozice + /// + protected void SendPositionUpdate() + { + if (client != null && client.IsConnected && currentState == AppState.InGame) + { + client.UpdatePosition(playerPosition); + } + } + +} diff --git a/Assets/UnityTestClient/UnityTestClient_Network.cs.meta b/Assets/UnityTestClient/UnityTestClient_Network.cs.meta new file mode 100644 index 0000000..39be8f4 --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_Network.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cdc71cfbe967d754189f3382fee78c6c \ No newline at end of file diff --git a/Assets/UnityTestClient/UnityTestClient_Stats.cs b/Assets/UnityTestClient/UnityTestClient_Stats.cs new file mode 100644 index 0000000..b47a9b3 --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_Stats.cs @@ -0,0 +1,589 @@ +/* +╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ STATISTIKY A HTTP API - UnityTestClient_Stats.cs ║ +║ ║ +║ Tento soubor obsahuje komunikaci s HTTP Stats API serveru: ║ +║ • Načítání statistik hráče ║ +║ • Načítání leaderboardu ║ +║ • Health check serveru ║ +║ ║ +║ API ENDPOINTY (port 8088): ║ +║ • GET /stats/{playerId} - statistiky konkrétního hráče ║ +║ • GET /leaderboard?limit=N - top N hráčů podle win rate ║ +║ • GET /health - stav serveru (aktivní lobby, hráči) ║ +║ ║ +║ STATISTIKY ZAHRNUJÍ: ║ +║ • Počet her, výher, proher ║ +║ • Win rate celkově, jako impostor, jako crew ║ +║ • Počet zabití, smrtí, K/D ratio ║ +║ • Dokončené úkoly ║ +║ • Přesnost hlasování (správně odhalení impostoři) ║ +║ ║ +║ POZNÁMKA PRO STUDENTY: ║ +║ Unity používá UnityWebRequest pro HTTP komunikaci. ║ +║ Všechny requesty musí běžet jako coroutiny (StartCoroutine). ║ +║ JSON parsing: JsonUtility (jednodušší) nebo Newtonsoft.Json (flexibilnější). ║ +║ ║ +║ Pro vlastní hru můžete rozšířit statistiky o: ║ +║ • Achievements/Badges ║ +║ • Sezónní ranky ║ +║ • Historii her ║ +║ ║ +╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ +*/ + +using UnityEngine; +using UnityEngine.Networking; +using System; +using System.Collections; +using System.Collections.Generic; + +public partial class UnityTestClient +{ + #region ═══════════════════════════════════════════════════════════════════ + // STATS PROMĚNNÉ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// Port pro Stats HTTP API + protected int statsApiPort = 8088; + + /// URL pro Stats API (automaticky používá HTTPS pokud je povoleno) + protected string StatsApiUrl => useHttps + ? $"https://{serverHost}" + : $"http://{serverHost}:{statsApiPort}"; + + /// Načtené statistiky hráče + protected PlayerStatistics playerStats = null; + + /// Načtený leaderboard + protected List leaderboard = null; + + /// Stav zdraví serveru + protected ServerHealthStatus healthStatus = null; + + /// Probíhá načítání statistik? + protected bool isLoadingStats = false; + + /// Probíhá načítání leaderboardu? + protected bool isLoadingLeaderboard = false; + + #region ═══════════════════════════════════════════════════════════════════ + // DATOVÉ STRUKTURY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Statistiky hráče. + /// Mapuje se z JSON response z /stats/{playerId}. + /// POZOR: Názvy polí musí přesně odpovídat JSON z serveru! + /// + [System.Serializable] + public class PlayerStatistics + { + /// UUID hráče + public string clientUuid; + + /// Zobrazované jméno + public string displayName; + + /// Celkový počet odehraných her + public int totalGames; + + /// Počet her jako crew + public int gamesAsCrew; + + /// Počet her jako impostor + public int gamesAsImpostor; + + /// Počet výher jako crew + public int crewWins; + + /// Počet výher jako impostor + public int impostorWins; + + /// Win rate jako crew (0-1) + public float crewWinRate; + + /// Win rate jako impostor (0-1) + public float impostorWinRate; + + /// Počet zabití (jako impostor) + public int totalKills; + + /// Počet smrtí + public int totalDeaths; + + /// Kill/Death ratio + public float killDeathRatio; + + /// Počet dokončených úkolů + public int tasksCompleted; + + /// Průměrný počet tasků za hru + public float averageTasksPerGame; + + /// Počet nahlášených těl + public int bodiesReported; + + /// Počet svolaných emergency meetingů + public int emergencyMeetingsCalled; + + /// Kolikrát byl vyhozen hlasováním + public int timesVotedOut; + + /// Počet úspěšných hlasů (správně identifikovaný impostor) + public int successfulVotes; + + /// Celkový čas hraní v sekundách + public long totalPlaytimeSeconds; + + /// Počet cheat incidentů + public int cheatIncidents; + + /// Kdy byl naposledy viděn + public string lastSeen; + + // Pomocné vlastnosti pro zobrazení v UI + public int GamesWon => crewWins + impostorWins; + public float WinRate => totalGames > 0 ? (float)GamesWon / totalGames : 0f; + } + + /// + /// Položka leaderboardu. + /// POZOR: Názvy polí musí přesně odpovídat JSON z serveru! + /// + [System.Serializable] + public class LeaderboardEntry + { + /// UUID hráče + public string clientUuid; + + /// Zobrazované jméno + public string displayName; + + /// Celkový počet her + public int totalGames; + + /// Výhry jako crew + public int crewWins; + + /// Výhry jako impostor + public int impostorWins; + + /// Počet zabití + public int totalKills; + + /// Počet dokončených tasků + public int tasksCompleted; + + // Pomocné vlastnosti + public int TotalWins => crewWins + impostorWins; + public float WinRate => totalGames > 0 ? (float)TotalWins / totalGames : 0f; + } + + /// + /// Wrapper pro leaderboard response (JSON array). + /// + [System.Serializable] + public class LeaderboardResponse + { + public List entries; + } + + /// + /// Stav serveru. + /// + [System.Serializable] + public class ServerHealthStatus + { + /// Stav: "healthy" nebo "unhealthy" + public string status; + + /// Verze serveru + public string version; + + /// Uptime v sekundách + public long uptimeSeconds; + + /// Počet aktivních lobby + public int activeLobbies; + + /// Počet připojených hráčů + public int connectedPlayers; + } + + #region ═══════════════════════════════════════════════════════════════════ + // NAČÍTÁNÍ STATISTIK + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Spustí načítání statistik hráče. + /// Výsledek bude v playerStats. + /// + protected void FetchPlayerStats() + { + Debug.Log($"[Stats] FetchPlayerStats called. isLoadingStats={isLoadingStats}, clientUuid={clientUuid}"); + + if (isLoadingStats) + { + Debug.Log("[Stats] Already loading, skipping"); + return; + } + if (string.IsNullOrEmpty(clientUuid)) + { + Debug.Log("[Stats] clientUuid is empty, skipping"); + return; + } + + Debug.Log($"[Stats] Starting coroutine for player {clientUuid}"); + StartCoroutine(FetchPlayerStatsCoroutine(clientUuid)); + } + + /// + /// Coroutine pro načtení statistik hráče. + /// + /// POZNÁMKA PRO STUDENTY: + /// Coroutiny v Unity umožňují asynchronní operace. + /// yield return čeká na dokončení operace před pokračováním. + /// UnityWebRequest.Get vytváří HTTP GET request. + /// + /// UUID hráče + private IEnumerator FetchPlayerStatsCoroutine(string playerId) + { + isLoadingStats = true; + + string url = $"{StatsApiUrl}/stats/{playerId}"; + Debug.Log($"[Stats] Fetching from: {url}"); + + using (UnityWebRequest request = UnityWebRequest.Get(url)) + { + // Nastavení timeoutu + request.timeout = 10; + + // Odeslání requestu a čekání na odpověď + yield return request.SendWebRequest(); + + Debug.Log($"[Stats] Request completed. Result: {request.result}, Code: {request.responseCode}"); + + // Kontrola chyb + if (request.result == UnityWebRequest.Result.Success) + { + // Parsování JSON + try + { + string json = request.downloadHandler.text; + playerStats = JsonUtility.FromJson(json); + + Debug.Log($"[Stats] Loaded stats for {playerStats.displayName}"); + } + catch (Exception e) + { + Debug.LogError($"[Stats] Failed to parse stats: {e.Message}"); + playerStats = null; + } + } + else + { + // Chyba při requestu + Debug.LogWarning($"[Stats] Failed to fetch stats: {request.error}"); + + // Pokud hráč neexistuje (404), vytvoříme prázdné statistiky + if (request.responseCode == 404) + { + playerStats = new PlayerStatistics + { + clientUuid = playerId, + displayName = displayName, + totalGames = 0, + gamesAsCrew = 0, + gamesAsImpostor = 0, + crewWins = 0, + impostorWins = 0, + crewWinRate = 0, + impostorWinRate = 0, + totalKills = 0, + totalDeaths = 0, + killDeathRatio = 0, + tasksCompleted = 0 + }; + } + } + } + + isLoadingStats = false; + } + + #region ═══════════════════════════════════════════════════════════════════ + // LEADERBOARD + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Spustí načítání leaderboardu. + /// + /// Počet hráčů k načtení (max 100) + protected void FetchLeaderboard(int limit = 10) + { + if (isLoadingLeaderboard) return; + + StartCoroutine(FetchLeaderboardCoroutine(limit)); + } + + /// + /// Coroutine pro načtení leaderboardu. + /// + private IEnumerator FetchLeaderboardCoroutine(int limit) + { + isLoadingLeaderboard = true; + + string url = $"{StatsApiUrl}/leaderboard?limit={limit}"; + + using (UnityWebRequest request = UnityWebRequest.Get(url)) + { + request.timeout = 10; + + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + try + { + string json = request.downloadHandler.text; + + // JsonUtility nemá přímou podporu pro arrays, potřebujeme wrapper + // nebo použijeme jednoduchý workaround + string wrappedJson = "{\"entries\":" + json + "}"; + LeaderboardResponse response = JsonUtility.FromJson(wrappedJson); + leaderboard = response.entries; + + Debug.Log($"[Stats] Loaded leaderboard with {leaderboard.Count} entries"); + } + catch (Exception e) + { + Debug.LogError($"[Stats] Failed to parse leaderboard: {e.Message}"); + leaderboard = null; + } + } + else + { + Debug.LogWarning($"[Stats] Failed to fetch leaderboard: {request.error}"); + } + } + + isLoadingLeaderboard = false; + } + + #region ═══════════════════════════════════════════════════════════════════ + // HEALTH CHECK + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Spustí health check serveru. + /// + protected void CheckServerHealth() + { + StartCoroutine(CheckServerHealthCoroutine()); + } + + /// + /// Coroutine pro health check. + /// + private IEnumerator CheckServerHealthCoroutine() + { + string url = $"{StatsApiUrl}/health"; + + using (UnityWebRequest request = UnityWebRequest.Get(url)) + { + request.timeout = 5; + + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + try + { + string json = request.downloadHandler.text; + healthStatus = JsonUtility.FromJson(json); + + Debug.Log($"[Stats] Server health: {healthStatus.status}, " + + $"uptime: {healthStatus.uptimeSeconds}s, " + + $"lobbies: {healthStatus.activeLobbies}, " + + $"players: {healthStatus.connectedPlayers}"); + } + catch (Exception e) + { + Debug.LogError($"[Stats] Failed to parse health: {e.Message}"); + healthStatus = null; + } + } + else + { + Debug.LogWarning($"[Stats] Health check failed: {request.error}"); + healthStatus = new ServerHealthStatus + { + status = "unreachable", + version = "unknown" + }; + } + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // UI PRO LEADERBOARD + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení leaderboard panelu. + /// Volá se z DrawStatsTab(). + /// + protected void DrawLeaderboardPanel() + { + if (leaderboard == null) + { + GUILayout.Label("Načítám leaderboard...", labelStyle); + + if (GUILayout.Button("Načíst", buttonStyle)) + { + FetchLeaderboard(10); + } + return; + } + + GUILayout.Label("TOP HRÁČI", subtitleStyle); + GUILayout.Space(5); + + // Hlavička + GUILayout.BeginHorizontal(); + GUILayout.Label("#", GUILayout.Width(30)); + GUILayout.Label("Jméno", GUILayout.Width(150)); + GUILayout.Label("Skóre", GUILayout.Width(80)); + GUILayout.Label("Win%", GUILayout.Width(60)); + GUILayout.EndHorizontal(); + + // Položky + int rank = 1; + foreach (var entry in leaderboard) + { + GUILayout.BeginHorizontal(); + + // Speciální barvy pro top 3 + if (rank == 1) GUI.color = Color.yellow; + else if (rank == 2) GUI.color = new Color(0.8f, 0.8f, 0.8f); + else if (rank == 3) GUI.color = new Color(0.8f, 0.5f, 0.2f); + else GUI.color = Color.white; + + GUILayout.Label($"{rank}.", GUILayout.Width(30)); + GUILayout.Label(entry.displayName, GUILayout.Width(150)); + GUILayout.Label(entry.TotalWins.ToString(), GUILayout.Width(80)); + GUILayout.Label($"{entry.WinRate * 100:F0}%", GUILayout.Width(60)); + + GUI.color = Color.white; + GUILayout.EndHorizontal(); + rank++; + } + + GUILayout.Space(10); + + if (GUILayout.Button("Obnovit", buttonStyle)) + { + leaderboard = null; + FetchLeaderboard(10); + } + } + + #region ═══════════════════════════════════════════════════════════════════ + // UI PRO SERVER HEALTH + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení panelu stavu serveru. + /// + protected void DrawServerHealthPanel() + { + GUILayout.BeginVertical(boxStyle); + + GUILayout.Label("STAV SERVERU", subtitleStyle); + + if (healthStatus == null) + { + GUILayout.Label("Neznámý", labelStyle); + + if (GUILayout.Button("Zkontrolovat", buttonStyle)) + { + CheckServerHealth(); + } + } + else + { + // Status + if (healthStatus.status == "healthy") + { + GUI.color = Color.green; + GUILayout.Label("● Online", labelStyle); + } + else + { + GUI.color = Color.red; + GUILayout.Label("● Offline", labelStyle); + } + GUI.color = Color.white; + + // Detaily + GUILayout.Label($"Verze: {healthStatus.version}", labelStyle); + + // Uptime + TimeSpan uptime = TimeSpan.FromSeconds(healthStatus.uptimeSeconds); + GUILayout.Label($"Uptime: {uptime.Days}d {uptime.Hours}h {uptime.Minutes}m", labelStyle); + + // Aktivita + GUILayout.Label($"Lobby: {healthStatus.activeLobbies}", labelStyle); + GUILayout.Label($"Hráči: {healthStatus.connectedPlayers}", labelStyle); + + GUILayout.Space(5); + + if (GUILayout.Button("Obnovit", buttonStyle)) + { + healthStatus = null; + CheckServerHealth(); + } + } + + GUILayout.EndVertical(); + } + + #region ═══════════════════════════════════════════════════════════════════ + // AUTOMATICKÉ NAČÍTÁNÍ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Interval automatického obnovení statistik (sekundy) + /// + protected float statsRefreshInterval = 60f; + + /// Čas posledního obnovení + protected float lastStatsRefresh = 0f; + + /// + /// Automatické načítání statistik. + /// Volá se v Update(). + /// + protected void UpdateStatsAutoRefresh() + { + if (currentState != AppState.MainMenu) return; + if (string.IsNullOrEmpty(clientUuid)) return; + + if (Time.time - lastStatsRefresh >= statsRefreshInterval) + { + FetchPlayerStats(); + CheckServerHealth(); + lastStatsRefresh = Time.time; + } + } +} diff --git a/Assets/UnityTestClient/UnityTestClient_Stats.cs.meta b/Assets/UnityTestClient/UnityTestClient_Stats.cs.meta new file mode 100644 index 0000000..2a75687 --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_Stats.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8b7bb01435f46b842b79fe4836d1b44e \ No newline at end of file diff --git a/Assets/UnityTestClient/UnityTestClient_UI.cs b/Assets/UnityTestClient/UnityTestClient_UI.cs new file mode 100644 index 0000000..08d8e8a --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_UI.cs @@ -0,0 +1,1642 @@ +/* +╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ UŽIVATELSKÉ ROZHRANÍ - UnityTestClient_UI.cs ║ +║ ║ +║ Tento soubor obsahuje veškeré UI pomocí Unity IMGUI: ║ +║ • Hlavní menu (připojení, vytvoření/vstup do lobby, statistiky) ║ +║ • Lobby obrazovka (nastavení, seznam hráčů, chat) ║ +║ • Loading obrazovka (progress bar načítání mapových dat) ║ +║ • Herní HUD (role, úkoly, minimap, sabotáž timer) ║ +║ • Meeting panel (hlasování, diskuze) ║ +║ • Konec hry (výsledky, statistiky) ║ +║ ║ +║ HERNÍ HUD OBSAHUJE: ║ +║ • Panel role (nahoře) - vaše role, jméno, stav ║ +║ • Panel úkolů (vpravo) - globální progress bar + seznam VAŠICH úkolů ║ +║ - Zelený checkmark = VY jste dokončili tento úkol ║ +║ - Progress bar = celkový pokrok VŠECH hráčů ║ +║ • Sabotáž timer (při aktivní sabotáži) - zbývající čas ║ +║ • Akční tlačítka (dole) - USE, KILL, SABOTAGE, REPORT ║ +║ ║ +║ POZNÁMKA PRO STUDENTY: ║ +║ IMGUI je vhodné pro prototypování a debug, ale pro produkční hru ║ +║ doporučujeme použít Unity UI (Canvas) nebo UI Toolkit, které nabízejí: ║ +║ • Lepší výkon (batching, atlasy) ║ +║ • WYSIWYG editor v Unity ║ +║ • Lepší podporu animací a přechodů ║ +║ • Snadnější lokalizaci a škálování ║ +║ • Lepší podporu dotykového ovládání ║ +║ ║ +╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ +*/ + +using UnityEngine; +using System; +using System.Collections.Generic; +using GeoSus.Client; + +public partial class UnityTestClient +{ + #region ═══════════════════════════════════════════════════════════════════ + // UI PROMĚNNÉ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + // ───────────────────────────────────────────────────────────────────────── + // GUI Styly - inicializují se v InitializeUIStyles() + // ───────────────────────────────────────────────────────────────────────── + + /// Styl pro velké nadpisy + protected GUIStyle titleStyle; + + /// Styl pro podnadpisy + protected GUIStyle subtitleStyle; + + /// Styl pro běžný text + protected GUIStyle labelStyle; + + /// Styl pro tlačítka + protected GUIStyle buttonStyle; + + /// Styl pro velká tlačítka + protected GUIStyle bigButtonStyle; + + /// Styl pro textová pole + protected GUIStyle textFieldStyle; + + /// Styl pro boxy/panely + protected GUIStyle boxStyle; + + /// Styl pro rich text + protected GUIStyle richTextStyle; + + /// Styl pro notifikace + protected GUIStyle notificationStyle; + + // ───────────────────────────────────────────────────────────────────────── + // Vstupy pro formuláře + // ───────────────────────────────────────────────────────────────────────── + + /// Vstupní pole pro jméno hráče + protected string inputPlayerName = ""; + + /// Vstupní pole pro kód lobby + protected string inputJoinCode = ""; + + /// Vstupní pole pro heslo + protected string inputPassword = ""; + + /// Vstupní pole pro zeměpisnou šířku + protected string inputLatitude = "50.7735892"; + + /// Vstupní pole pro zeměpisnou délku + protected string inputLongitude = "15.0721653"; + + /// Vstupní pole pro poloměr + protected string inputRadius = "300"; + + /// Počet impostorů + protected int inputImpostorCount = 1; + + /// Počet úkolů + protected int inputTaskCount = 5; + + // ───────────────────────────────────────────────────────────────────────── + // UI stav + // ───────────────────────────────────────────────────────────────────────── + + /// Aktuální záložka v hlavním menu + protected int mainMenuTab = 0; + + /// Scroll pozice pro seznam hráčů + protected Vector2 playerListScroll; + + /// Scroll pozice pro statistiky + protected Vector2 statsScroll; + + /// Škálovací faktor UI + protected float uiScale = 1f; + + // ───────────────────────────────────────────────────────────────────────── + // Loading stav + // ───────────────────────────────────────────────────────────────────────── + + /// Zpráva během načítání + protected string loadingMessage = "Načítání..."; + + /// Progress načítání (0-1) + protected float loadingProgress = 0f; + + // ───────────────────────────────────────────────────────────────────────── + // Meeting stav + // ───────────────────────────────────────────────────────────────────────── + + /// Aktuální meeting data + protected MeetingStartedPayload currentMeeting; + + /// Kdo už hlasoval + protected Dictionary meetingVotes = new Dictionary(); + + /// Kdo dorazil na meeting + protected HashSet arrivedAtMeeting = new HashSet(); + + /// Dorazili jsme na meeting? + protected bool iArrivedAtMeeting = false; + + /// Náš hlas (UUID nebo null pro skip) + protected string myVote; + + /// Můžeme hlasovat? + protected bool canVote = false; + + /// Můžeme hlasovat v tomto meetingu (jsme naživu)? + protected bool canVoteInMeeting = false; + + /// Výsledky hlasování + protected VotingClosedPayload votingResults; + + /// Zobrazit výsledky hlasování? + protected bool showVotingResults = false; + + /// Čas konce zobrazení výsledků + protected float votingResultsEndTime; + + // ───────────────────────────────────────────────────────────────────────── + // Herní HUD stav + // ───────────────────────────────────────────────────────────────────────── + + /// Celkový počet dokončených úkolů (globálně pro všechny hráče) + protected int totalTasksCompleted = 0; + + /// Celkový počet požadovaných úkolů (globálně) + protected int totalTasksRequired = 0; + + /// ID mých splněných úkolů + protected HashSet myCompletedTaskIds = new HashSet(); + + /// Zobrazit sabotage panel? + protected bool showSabotagePanel = false; + + #region ═══════════════════════════════════════════════════════════════════ + // INICIALIZACE STYLŮ + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Inicializace GUI stylů. + /// DŮLEŽITÉ: Musí se volat v Start() nebo později, ne v Awake()! + /// GUISkin není dostupný v Awake. + /// + protected void InitializeUIStyles() + { + // Styl pro velké nadpisy + titleStyle = new GUIStyle(GUI.skin.label) + { + fontSize = 32, + fontStyle = FontStyle.Bold, + alignment = TextAnchor.MiddleCenter + }; + titleStyle.normal.textColor = Color.white; + + // Styl pro podnadpisy + subtitleStyle = new GUIStyle(GUI.skin.label) + { + fontSize = 20, + fontStyle = FontStyle.Bold, + alignment = TextAnchor.MiddleCenter + }; + subtitleStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f); + + // Styl pro běžný text + labelStyle = new GUIStyle(GUI.skin.label) + { + fontSize = 16, + alignment = TextAnchor.MiddleLeft + }; + labelStyle.normal.textColor = Color.white; + + // Styl pro tlačítka + buttonStyle = new GUIStyle(GUI.skin.button) + { + fontSize = 16, + fontStyle = FontStyle.Bold + }; + + // Styl pro velká tlačítka + bigButtonStyle = new GUIStyle(GUI.skin.button) + { + fontSize = 20, + fontStyle = FontStyle.Bold, + fixedHeight = 50 + }; + + // Styl pro textová pole + textFieldStyle = new GUIStyle(GUI.skin.textField) + { + fontSize = 16, + fixedHeight = 30 + }; + + // Styl pro boxy + boxStyle = new GUIStyle(GUI.skin.box) + { + padding = new RectOffset(10, 10, 10, 10) + }; + + // Styl pro rich text + richTextStyle = new GUIStyle(GUI.skin.label) + { + richText = true, + fontSize = 14 + }; + + // Styl pro notifikace + notificationStyle = new GUIStyle(GUI.skin.box) + { + fontSize = 18, + fontStyle = FontStyle.Bold, + alignment = TextAnchor.MiddleCenter + }; + notificationStyle.normal.textColor = Color.white; + + // Načteme uložené jméno + inputPlayerName = displayName; + } + + /// + /// Aplikace škálování UI pro různá rozlišení. + /// Základní rozlišení je 1920x1080. + /// + protected void ApplyUIScaling() + { + // Výpočet škálovacího faktoru + float baseWidth = 1920f; + float baseHeight = 1080f; + + // Použijeme menší z obou faktorů pro zachování poměru stran + float scaleX = Screen.width / baseWidth; + float scaleY = Screen.height / baseHeight; + uiScale = Mathf.Min(scaleX, scaleY); + + // Minimální škálování + uiScale = Mathf.Max(uiScale, 0.5f); + + // Aplikace matice pro škálování + GUI.matrix = Matrix4x4.TRS( + Vector3.zero, + Quaternion.identity, + new Vector3(uiScale, uiScale, 1f) + ); + } + + #region ═══════════════════════════════════════════════════════════════════ + // HLAVNÍ MENU + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení hlavního menu. + /// Obsahuje záložky pro připojení, vytvoření lobby a statistiky. + /// + protected void DrawMainMenu() + { + // Reinicializace stylů pokud je potřeba (po změně skinu) + if (titleStyle == null) InitializeUIStyles(); + + // Centrovaný panel + float panelWidth = 500; + float panelHeight = 600; + float panelX = (Screen.width / uiScale - panelWidth) / 2; + float panelY = (Screen.height / uiScale - panelHeight) / 2; + + GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight)); + + // Pozadí + GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle); + + // Použijeme try-catch pro ošetření změn stavu během renderování + try + { + // ═══════════════════════════════════════════════════════════════════ + // NADPIS + // ═══════════════════════════════════════════════════════════════════ + + GUILayout.Space(20); + GUILayout.Label("GeoSus", titleStyle); + GUILayout.Label("GPS Multiplayer Game", subtitleStyle); + GUILayout.Space(20); + + // ═══════════════════════════════════════════════════════════════════ + // STAV PŘIPOJENÍ + // ═══════════════════════════════════════════════════════════════════ + + // Cache stav na začátku renderování pro konzistenci + bool isConnected = client != null && client.IsConnected; + + if (isConnected) + { + GUI.color = Color.green; + GUILayout.Label("● Připojeno k serveru", labelStyle); + GUI.color = Color.white; + } + else if (isConnecting) + { + GUI.color = Color.yellow; + GUILayout.Label("● Připojuji...", labelStyle); + GUI.color = Color.white; + } + else + { + GUI.color = Color.red; + GUILayout.Label("● Nepřipojeno", labelStyle); + GUI.color = Color.white; + } + + GUILayout.Space(10); + + // ═══════════════════════════════════════════════════════════════════ + // JMÉNO HRÁČE + // ═══════════════════════════════════════════════════════════════════ + + GUILayout.Label("Vaše jméno:", labelStyle); + inputPlayerName = GUILayout.TextField(inputPlayerName, 20, textFieldStyle); + + // Uložení jména + if (inputPlayerName != displayName && !string.IsNullOrWhiteSpace(inputPlayerName)) + { + displayName = inputPlayerName; + PlayerPrefs.SetString("PlayerName", displayName); + if (client != null) client.DisplayName = displayName; + } + + GUILayout.Space(10); + + // ═══════════════════════════════════════════════════════════════════ + // PŘIPOJENÍ K SERVERU + // ═══════════════════════════════════════════════════════════════════ + + if (!isConnected) + { + GUI.enabled = !isConnecting; + if (GUILayout.Button("Připojit k serveru", bigButtonStyle)) + { + ConnectToServer(); + } + GUI.enabled = true; + } + else + { + // ═══════════════════════════════════════════════════════════════ + // ZÁLOŽKY + // ═══════════════════════════════════════════════════════════════ + + string[] tabs = { "Připojit se", "Vytvořit hru", "Statistiky" }; + mainMenuTab = GUILayout.Toolbar(mainMenuTab, tabs); + + GUILayout.Space(10); + + switch (mainMenuTab) + { + case 0: + DrawJoinLobbyTab(); + break; + case 1: + DrawCreateLobbyTab(); + break; + case 2: + DrawStatsTab(); + break; + } + + GUILayout.Space(20); + + // Tlačítko odpojení + GUI.color = new Color(1f, 0.5f, 0.5f); + if (GUILayout.Button("Odpojit", buttonStyle)) + { + DisconnectFromServer(); + } + GUI.color = Color.white; + } + + } // end try + catch (System.ArgumentException) + { + // Ignorujeme chyby při změně stavu GUI mezi Layout a Repaint + // Příští frame se vykreslí správně + } + + GUILayout.EndArea(); + } + + /// + /// Záložka pro připojení do existujícího lobby + /// + private void DrawJoinLobbyTab() + { + GUILayout.Label("Kód lobby:", labelStyle); + + // Vstup pro kód - automaticky uppercase + inputJoinCode = GUILayout.TextField(inputJoinCode.ToUpper(), 6, textFieldStyle); + + GUILayout.Space(5); + + GUILayout.Label("Heslo (volitelné):", labelStyle); + inputPassword = GUILayout.PasswordField(inputPassword, '*', textFieldStyle); + + GUILayout.Space(20); + + GUI.enabled = inputJoinCode.Length == 6; + if (GUILayout.Button("Připojit do lobby", bigButtonStyle)) + { + JoinLobby(inputJoinCode, string.IsNullOrWhiteSpace(inputPassword) ? null : inputPassword); + } + GUI.enabled = true; + } + + /// + /// Záložka pro vytvoření nového lobby + /// + private void DrawCreateLobbyTab() + { + // ───────────────────────────────────────────────────────────────── + // GPS souřadnice + // ───────────────────────────────────────────────────────────────── + + GUILayout.Label("Střed herní oblasti (GPS):", labelStyle); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Lat:", GUILayout.Width(40)); + inputLatitude = GUILayout.TextField(inputLatitude, textFieldStyle); + GUILayout.Label("Lon:", GUILayout.Width(40)); + inputLongitude = GUILayout.TextField(inputLongitude, textFieldStyle); + GUILayout.EndHorizontal(); + + GUILayout.Space(5); + + // ───────────────────────────────────────────────────────────────── + // Poloměr + // ───────────────────────────────────────────────────────────────── + + GUILayout.Label($"Poloměr herní oblasti: {inputRadius}m", labelStyle); + + // Slider pro poloměr + float radius = 300f; + float.TryParse(inputRadius, out radius); + radius = GUILayout.HorizontalSlider(radius, 100f, 1000f); + inputRadius = Mathf.RoundToInt(radius).ToString(); + + GUILayout.Space(10); + + // ───────────────────────────────────────────────────────────────── + // Počet impostorů + // ───────────────────────────────────────────────────────────────── + + GUILayout.Label($"Počet impostorů: {inputImpostorCount}", labelStyle); + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("-", GUILayout.Width(40))) inputImpostorCount = Mathf.Max(1, inputImpostorCount - 1); + GUILayout.HorizontalSlider(inputImpostorCount, 1, 3); + if (GUILayout.Button("+", GUILayout.Width(40))) inputImpostorCount = Mathf.Min(3, inputImpostorCount + 1); + GUILayout.EndHorizontal(); + + GUILayout.Space(5); + + // ───────────────────────────────────────────────────────────────── + // Počet úkolů + // ───────────────────────────────────────────────────────────────── + + GUILayout.Label($"Počet úkolů: {inputTaskCount}", labelStyle); + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("-", GUILayout.Width(40))) inputTaskCount = Mathf.Max(3, inputTaskCount - 1); + GUILayout.HorizontalSlider(inputTaskCount, 3, 10); + if (GUILayout.Button("+", GUILayout.Width(40))) inputTaskCount = Mathf.Min(10, inputTaskCount + 1); + GUILayout.EndHorizontal(); + + GUILayout.Space(5); + + // ───────────────────────────────────────────────────────────────── + // Heslo + // ───────────────────────────────────────────────────────────────── + + GUILayout.Label("Heslo (volitelné):", labelStyle); + inputPassword = GUILayout.PasswordField(inputPassword, '*', textFieldStyle); + + GUILayout.Space(20); + + // ───────────────────────────────────────────────────────────────── + // Tlačítko vytvoření + // ───────────────────────────────────────────────────────────────── + + if (GUILayout.Button("Vytvořit lobby", bigButtonStyle)) + { + double lat, lon, rad; + if (double.TryParse(inputLatitude, out lat) && + double.TryParse(inputLongitude, out lon) && + double.TryParse(inputRadius, out rad)) + { + Position center = new Position(lat, lon); + CreateLobby(center, rad, inputImpostorCount, inputTaskCount, + string.IsNullOrWhiteSpace(inputPassword) ? null : inputPassword); + } + else + { + ShowError("Neplatné souřadnice nebo poloměr!"); + } + } + } + + /// + /// Záložka pro statistiky hráče + /// + private void DrawStatsTab() + { + statsScroll = GUILayout.BeginScrollView(statsScroll, GUILayout.Height(350)); + + // ═══════════════════════════════════════════════════════════════ + // SERVER HEALTH + // ═══════════════════════════════════════════════════════════════ + GUILayout.Label("═══ SERVER STATUS ═══", labelStyle); + + if (healthStatus == null) + { + GUILayout.Label("Načítám stav serveru...", labelStyle); + } + else + { + Color statusColor = healthStatus.status == "ok" ? Color.green : Color.red; + GUI.color = statusColor; + DrawStatRow("Status", healthStatus.status.ToUpper()); + GUI.color = Color.white; + + if (healthStatus.uptimeSeconds > 0) + { + var uptime = TimeSpan.FromSeconds(healthStatus.uptimeSeconds); + DrawStatRow("Uptime", $"{uptime.Hours:D2}:{uptime.Minutes:D2}:{uptime.Seconds:D2}"); + } + DrawStatRow("Aktivní lobby", healthStatus.activeLobbies.ToString()); + DrawStatRow("Hráčů online", healthStatus.connectedPlayers.ToString()); + if (!string.IsNullOrEmpty(healthStatus.version)) + DrawStatRow("Verze", healthStatus.version); + } + + GUILayout.Space(10); + + // ═══════════════════════════════════════════════════════════════ + // MOJE STATISTIKY + // ═══════════════════════════════════════════════════════════════ + GUILayout.Label("═══ MOJE STATISTIKY ═══", labelStyle); + + if (playerStats == null) + { + GUILayout.Label("Načítám statistiky...", labelStyle); + } + else + { + DrawStatRow("Odehráno her", playerStats.totalGames.ToString()); + DrawStatRow("Výher", playerStats.GamesWon.ToString()); + DrawStatRow("Win rate", $"{(playerStats.WinRate * 100):F1}%"); + DrawStatRow("Zabití", playerStats.totalKills.ToString()); + DrawStatRow("Smrti", playerStats.totalDeaths.ToString()); + DrawStatRow("K/D ratio", $"{playerStats.killDeathRatio:F2}"); + DrawStatRow("Dokončené úkoly", playerStats.tasksCompleted.ToString()); + DrawStatRow("Her jako impostor", playerStats.gamesAsImpostor.ToString()); + DrawStatRow("Her jako crew", playerStats.gamesAsCrew.ToString()); + } + + GUILayout.Space(10); + + // ═══════════════════════════════════════════════════════════════ + // LEADERBOARD + // ═══════════════════════════════════════════════════════════════ + GUILayout.Label("═══ LEADERBOARD (TOP 5) ═══", labelStyle); + + if (leaderboard == null || leaderboard.Count == 0) + { + GUILayout.Label(isLoadingLeaderboard ? "Načítám leaderboard..." : "Žádní hráči v leaderboardu", labelStyle); + } + else + { + // Header + GUILayout.BeginHorizontal(); + GUILayout.Label("#", labelStyle, GUILayout.Width(25)); + GUILayout.Label("Hráč", labelStyle, GUILayout.Width(120)); + GUILayout.Label("Výhry", labelStyle, GUILayout.Width(50)); + GUILayout.Label("Win%", labelStyle, GUILayout.Width(50)); + GUILayout.EndHorizontal(); + + // Entries + int rank = 1; + foreach (var entry in leaderboard) + { + GUILayout.BeginHorizontal(); + + // Barvy pro top 3 + if (rank == 1) GUI.color = Color.yellow; + else if (rank == 2) GUI.color = new Color(0.8f, 0.8f, 0.8f); + else if (rank == 3) GUI.color = new Color(0.8f, 0.5f, 0.2f); + else GUI.color = Color.white; + + GUILayout.Label($"{rank}.", labelStyle, GUILayout.Width(25)); + GUILayout.Label(entry.displayName ?? "???", labelStyle, GUILayout.Width(120)); + GUILayout.Label(entry.TotalWins.ToString(), labelStyle, GUILayout.Width(50)); + GUILayout.Label($"{(entry.WinRate * 100):F0}%", labelStyle, GUILayout.Width(50)); + + GUI.color = Color.white; + GUILayout.EndHorizontal(); + rank++; + } + } + + GUILayout.EndScrollView(); + + // Refresh button + GUILayout.Space(5); + if (GUILayout.Button("Obnovit vše", buttonStyle)) + { + RefreshAllStats(); + } + } + + /// + /// Obnoví všechny statistiky (player stats, leaderboard, health) + /// + private void RefreshAllStats() + { + FetchPlayerStats(); + FetchLeaderboard(5); + CheckServerHealth(); + } + + /// + /// Pomocná metoda pro vykreslení řádku statistiky + /// + private void DrawStatRow(string label, string value) + { + GUILayout.BeginHorizontal(); + GUILayout.Label(label, labelStyle, GUILayout.Width(200)); + GUILayout.Label(value, labelStyle); + GUILayout.EndHorizontal(); + } + + #region ═══════════════════════════════════════════════════════════════════ + // LOBBY OBRAZOVKA + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení lobby obrazovky. + /// Zobrazuje seznam hráčů, nastavení a tlačítko start. + /// + protected void DrawLobbyScreen() + { + if (titleStyle == null) InitializeUIStyles(); + + float panelWidth = 600; + float panelHeight = 700; + float panelX = (Screen.width / uiScale - panelWidth) / 2; + float panelY = (Screen.height / uiScale - panelHeight) / 2; + + GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight)); + GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle); + + // ═══════════════════════════════════════════════════════════════════ + // NADPIS A KÓD + // ═══════════════════════════════════════════════════════════════════ + + GUILayout.Space(10); + GUILayout.Label("LOBBY", titleStyle); + + if (client?.JoinCode != null) + { + GUILayout.Label($"Kód: {client.JoinCode}", subtitleStyle); + } + + GUILayout.Space(10); + + // ═══════════════════════════════════════════════════════════════════ + // SEZNAM HRÁČŮ + // ═══════════════════════════════════════════════════════════════════ + + GUILayout.Label("Hráči:", labelStyle); + + playerListScroll = GUILayout.BeginScrollView(playerListScroll, GUILayout.Height(200)); + + if (client?.CurrentLobbyState?.Players != null) + { + foreach (var player in client.CurrentLobbyState.Players) + { + GUILayout.BeginHorizontal(boxStyle); + + // Ikona vlastníka + if (player.IsOwner) + { + GUILayout.Label("👑", GUILayout.Width(25)); + } + else + { + GUILayout.Space(25); + } + + // Jméno hráče + string nameColor = player.ClientUuid == clientUuid ? "yellow" : "white"; + GUILayout.Label($"{player.DisplayName}", richTextStyle); + + // Stav ready + if (player.IsReady) + { + GUI.color = Color.green; + GUILayout.Label("✓ Ready", GUILayout.Width(70)); + GUI.color = Color.white; + } + + GUILayout.EndHorizontal(); + } + } + + GUILayout.EndScrollView(); + + // ═══════════════════════════════════════════════════════════════════ + // NASTAVENÍ LOBBY (pouze pro vlastníka) + // ═══════════════════════════════════════════════════════════════════ + + bool isOwner = client?.IsOwner ?? false; + + GUILayout.Space(10); + GUILayout.Label("Nastavení hry:", labelStyle); + + GUILayout.BeginVertical(boxStyle); + + if (client?.CurrentLobbyState != null) + { + var state = client.CurrentLobbyState; + + GUILayout.Label($"Střed: {state.PlayAreaCenter.Lat:F4}, {state.PlayAreaCenter.Lon:F4}"); + GUILayout.Label($"Poloměr: {state.PlayAreaRadius}m"); + GUILayout.Label($"Impostorů: {state.ImpostorCount}"); + GUILayout.Label($"Heslo: {(state.HasPassword ? "Ano" : "Ne")}"); + } + + GUILayout.EndVertical(); + + // ═══════════════════════════════════════════════════════════════════ + // TLAČÍTKA + // ═══════════════════════════════════════════════════════════════════ + + GUILayout.Space(20); + + if (isOwner) + { + // Tlačítko START (pouze owner) + int playerCount = client?.CurrentLobbyState?.Players?.Count ?? 0; + GUI.enabled = playerCount >= 2; // Minimum 2 hráči + + GUI.color = Color.green; + if (GUILayout.Button($"SPUSTIT HRU ({playerCount} hráčů)", bigButtonStyle)) + { + StartGame(); + } + GUI.color = Color.white; + GUI.enabled = true; + + if (playerCount < 2) + { + GUILayout.Label("Potřeba minimálně 2 hráči", richTextStyle); + } + } + else + { + GUILayout.Label("Čekám na vlastníka lobby...", labelStyle); + } + + GUILayout.Space(10); + + // Tlačítko opuštění + GUI.color = new Color(1f, 0.5f, 0.5f); + if (GUILayout.Button("Opustit lobby", buttonStyle)) + { + LeaveLobby(); + } + GUI.color = Color.white; + + GUILayout.EndArea(); + } + + #region ═══════════════════════════════════════════════════════════════════ + // LOADING OBRAZOVKA + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení loading obrazovky. + /// Zobrazuje progress bar během načítání mapových dat. + /// + protected void DrawLoadingScreen() + { + if (titleStyle == null) InitializeUIStyles(); + + float panelWidth = 400; + float panelHeight = 200; + float panelX = (Screen.width / uiScale - panelWidth) / 2; + float panelY = (Screen.height / uiScale - panelHeight) / 2; + + GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight)); + GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle); + + GUILayout.Space(30); + GUILayout.Label("Načítání hry", titleStyle); + GUILayout.Space(20); + + // Progress bar + Rect progressRect = GUILayoutUtility.GetRect(panelWidth - 40, 30); + progressRect.x = 20; + + GUI.Box(progressRect, ""); + + Rect fillRect = new Rect(progressRect.x + 2, progressRect.y + 2, + (progressRect.width - 4) * loadingProgress, progressRect.height - 4); + GUI.color = Color.green; + GUI.DrawTexture(fillRect, Texture2D.whiteTexture); + GUI.color = Color.white; + + GUILayout.Space(10); + GUILayout.Label(loadingMessage, labelStyle); + + GUILayout.EndArea(); + } + + #region ═══════════════════════════════════════════════════════════════════ + // HERNÍ HUD + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení herního HUD. + /// Zobrazuje roli, úkoly, ovládání a stav hry. + /// + protected void DrawGameHUD() + { + if (titleStyle == null) InitializeUIStyles(); + + // ═══════════════════════════════════════════════════════════════════ + // HORNÍ PANEL - Role a stav + // ═══════════════════════════════════════════════════════════════════ + + GUILayout.BeginArea(new Rect(10, 10, 300, 150)); + GUI.Box(new Rect(0, 0, 300, 150), "", boxStyle); + + // Role + if (client?.MyRole != null) + { + string roleText = client.MyRole == PlayerRole.Impostor ? "IMPOSTOR" : "CREWMATE"; + Color roleColor = client.MyRole == PlayerRole.Impostor ? Color.red : Color.cyan; + + GUI.color = roleColor; + GUILayout.Label(roleText, subtitleStyle); + GUI.color = Color.white; + } + + // Stav (naživu/mrtvý) + if (client?.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var myState)) + { + if (myState.State == PlayerState.Dead) + { + GUI.color = Color.gray; + GUILayout.Label("💀 MRTVÝ (duch)", labelStyle); + GUI.color = Color.white; + } + } + + // Sabotáž varování + if (currentSabotage != null) + { + GUI.color = Color.red; + string sabText = currentSabotage.Type == SabotageType.CriticalMeltdown + ? "⚠ MELTDOWN!" : "⚠ COMMS DOWN!"; + GUILayout.Label(sabText, labelStyle); + + if (currentSabotage.Deadline.HasValue) + { + float remaining = (float)(currentSabotage.Deadline.Value - DateTime.UtcNow).TotalSeconds; + GUILayout.Label($"Zbývá: {remaining:F0}s", labelStyle); + } + GUI.color = Color.white; + } + + // Ping + GUILayout.Label($"Ping: {client?.Ping ?? 0}ms", labelStyle); + + GUILayout.EndArea(); + + // ═══════════════════════════════════════════════════════════════════ + // PRAVÝ PANEL - Úkoly + // ═══════════════════════════════════════════════════════════════════ + + float rightPanelX = Screen.width / uiScale - 260; + GUILayout.BeginArea(new Rect(rightPanelX, 10, 250, 400)); + GUI.Box(new Rect(0, 0, 250, 400), "", boxStyle); + + GUILayout.Label("ÚKOLY", subtitleStyle); + + // Progress bar úkolů + GUILayout.Label($"Dokončeno: {totalTasksCompleted}/{totalTasksRequired}"); + + Rect taskProgressRect = GUILayoutUtility.GetRect(230, 20); + GUI.Box(taskProgressRect, ""); + + if (totalTasksRequired > 0) + { + float taskProgress = (float)totalTasksCompleted / totalTasksRequired; + Rect taskFillRect = new Rect(taskProgressRect.x + 2, taskProgressRect.y + 2, + (taskProgressRect.width - 4) * taskProgress, taskProgressRect.height - 4); + GUI.color = Color.green; + GUI.DrawTexture(taskFillRect, Texture2D.whiteTexture); + GUI.color = Color.white; + } + + GUILayout.Space(10); + + // Seznam úkolů + if (client?.MyTasks != null) + { + foreach (var task in client.MyTasks) + { + bool completed = myCompletedTaskIds.Contains(task.TaskId); + string taskColor = completed ? "green" : "white"; + string checkmark = completed ? "✓ " : "○ "; + + GUILayout.Label($"{checkmark}{task.Name}", richTextStyle); + } + } + + GUILayout.EndArea(); + + // ═══════════════════════════════════════════════════════════════════ + // SPODNÍ PANEL - Akce + // ═══════════════════════════════════════════════════════════════════ + + float bottomPanelY = Screen.height / uiScale - 100; + GUILayout.BeginArea(new Rect(10, bottomPanelY, Screen.width / uiScale - 20, 90)); + + GUILayout.BeginHorizontal(); + + // Tlačítko USE (úkoly, reporty) + GUI.enabled = CanPerformAction(); + if (GUILayout.Button(GetActionButtonText(), bigButtonStyle, GUILayout.Width(150))) + { + PerformPrimaryAction(); + } + GUI.enabled = true; + + GUILayout.Space(20); + + // Tlačítko KILL (pouze impostor) + if (client?.MyRole == PlayerRole.Impostor) + { + string nearbyTarget = client.FindNearbyPlayer(5.0, true); + GUI.enabled = nearbyTarget != null && !IsOnCooldown(); + GUI.color = Color.red; + if (GUILayout.Button("🔪 KILL", bigButtonStyle, GUILayout.Width(120))) + { + if (nearbyTarget != null) + { + AttemptKill(nearbyTarget); + } + } + GUI.color = Color.white; + GUI.enabled = true; + + GUILayout.Space(10); + + // Tlačítko SABOTAGE + GUI.enabled = currentSabotage == null; + if (GUILayout.Button("⚠ SABOTAGE", bigButtonStyle, GUILayout.Width(150))) + { + showSabotagePanel = !showSabotagePanel; + } + GUI.enabled = true; + } + + GUILayout.FlexibleSpace(); + + // Tlačítko MEETING + GUI.enabled = CanCallMeeting(); + if (GUILayout.Button("🔔 MEETING", bigButtonStyle, GUILayout.Width(150))) + { + CallEmergencyMeeting(); + } + GUI.enabled = true; + + GUILayout.EndHorizontal(); + + GUILayout.EndArea(); + + // ═══════════════════════════════════════════════════════════════════ + // SABOTAGE PANEL (popup) + // ═══════════════════════════════════════════════════════════════════ + + if (showSabotagePanel && client?.MyRole == PlayerRole.Impostor) + { + DrawSabotagePanel(); + } + + // ═══════════════════════════════════════════════════════════════════ + // PROGRESS BAR OPRAVY + // ═══════════════════════════════════════════════════════════════════ + + if (isRepairing) + { + DrawProgressBar(); + } + } + + /// + /// Vykreslení panelu sabotáží + /// + private void DrawSabotagePanel() + { + float panelWidth = 200; + float panelHeight = 120; + float panelX = (Screen.width / uiScale - panelWidth) / 2; + float panelY = Screen.height / uiScale - 220; + + GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight)); + GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle); + + GUILayout.Label("Sabotáž", subtitleStyle); + + if (GUILayout.Button("📡 Comms Blackout", buttonStyle)) + { + StartSabotage(SabotageType.CommsBlackout); + showSabotagePanel = false; + } + + if (GUILayout.Button("☢ Critical Meltdown", buttonStyle)) + { + StartSabotage(SabotageType.CriticalMeltdown); + showSabotagePanel = false; + } + + if (GUILayout.Button("Zavřít", buttonStyle)) + { + showSabotagePanel = false; + } + + GUILayout.EndArea(); + } + + /// + /// Vykreslení progress baru pro úkoly/opravy + /// + private void DrawProgressBar() + { + float barWidth = 300; + float barHeight = 30; + float barX = (Screen.width / uiScale - barWidth) / 2; + float barY = Screen.height / uiScale / 2; + + Rect bgRect = new Rect(barX - 10, barY - 10, barWidth + 20, barHeight + 40); + GUI.Box(bgRect, "", boxStyle); + + GUILayout.BeginArea(new Rect(barX, barY, barWidth, barHeight + 20)); + + string label = "Opravuji..."; + GUILayout.Label(label, labelStyle); + + Rect progressRect = GUILayoutUtility.GetRect(barWidth, barHeight); + GUI.Box(progressRect, ""); + + Rect fillRect = new Rect(progressRect.x + 2, progressRect.y + 2, + (progressRect.width - 4) * repairProgress, progressRect.height - 4); + GUI.color = Color.yellow; + GUI.DrawTexture(fillRect, Texture2D.whiteTexture); + GUI.color = Color.white; + + GUILayout.EndArea(); + } + + #region ═══════════════════════════════════════════════════════════════════ + // MEETING PANEL + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení hlasovacího panelu. + /// Zobrazuje seznam hráčů a tlačítka pro hlasování. + /// + protected void DrawMeetingPanel() + { + // Kontrola, zda je meeting aktivní nebo zobrazujeme výsledky + if (currentMeeting == null && !showVotingResults) return; + + // Kontrola herní fáze (povolíme i Playing když zobrazujeme výsledky) + var phase = client?.CurrentLobbyState?.Phase; + bool isInMeeting = phase == GamePhase.Meeting || phase == GamePhase.Voting; + if (!isInMeeting && !showVotingResults) return; + + float panelWidth = 500; + float panelHeight = 600; + float panelX = (Screen.width / uiScale - panelWidth) / 2; + float panelY = (Screen.height / uiScale - panelHeight) / 2; + + GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight)); + GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle); + + // ═══════════════════════════════════════════════════════════════════ + // NADPIS + // ═══════════════════════════════════════════════════════════════════ + + GUILayout.Space(10); + + // Pokud zobrazujeme výsledky, jiný nadpis + if (showVotingResults) + { + GUI.color = Color.yellow; + GUILayout.Label("VÝSLEDKY HLASOVÁNÍ", titleStyle); + GUI.color = Color.white; + } + else if (currentMeeting != null) + { + string meetingType = currentMeeting.Type == MeetingType.BodyReport + ? "TĚLO NALEZENO!" : "EMERGENCY MEETING!"; + GUI.color = Color.red; + GUILayout.Label(meetingType, titleStyle); + GUI.color = Color.white; + } + + // ═══════════════════════════════════════════════════════════════════ + // ČASOVAČ A FÁZE MEETINGU + // ═══════════════════════════════════════════════════════════════════ + + if (currentMeeting != null && !showVotingResults) + { + DateTime now = DateTime.UtcNow; + bool isArrival = now < currentMeeting.ArrivalDeadline; + bool isDiscussion = !isArrival && currentMeeting.DiscussionEndTime.HasValue && now < currentMeeting.DiscussionEndTime.Value; + bool isVoting = !isArrival && !isDiscussion && now < currentMeeting.VotingEndTime; + + // Fáze 1: Příchod na meeting + if (isArrival) + { + float remaining = (float)(currentMeeting.ArrivalDeadline - now).TotalSeconds; + + if (!iArrivedAtMeeting) + { + GUI.color = Color.yellow; + GUILayout.Label($"⚠ BĚŽ NA MEETING! ({remaining:F0}s)", subtitleStyle); + GUI.color = Color.white; + GUILayout.Label("Pokud nedorazíš včas, nebudeš moci hlasovat!", labelStyle); + } + else + { + GUI.color = Color.green; + GUILayout.Label($"✓ Dorazil/a jsi! Čekání: {remaining:F0}s", subtitleStyle); + GUI.color = Color.white; + } + + // Zobraz počet dorazivších + int arrivedCount = arrivedAtMeeting.Count; + int totalAlive = 0; + if (client?.CurrentLobbyState?.Players != null) + { + foreach (var p in client.CurrentLobbyState.Players) + { + if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(p.ClientUuid, out var info)) + { + if (info.State == PlayerState.Alive) totalAlive++; + } + else + { + totalAlive++; + } + } + } + GUILayout.Label($"Dorazilo: {arrivedCount}/{totalAlive}", labelStyle); + + canVote = false; + } + // Fáze 2: Diskuze + else if (isDiscussion && currentMeeting.DiscussionEndTime.HasValue) + { + float remaining = (float)(currentMeeting.DiscussionEndTime.Value - now).TotalSeconds; + GUILayout.Label($"Diskuze: {remaining:F0}s", subtitleStyle); + canVote = false; + } + // Fáze 3: Hlasování + else if (isVoting) + { + float remaining = (float)(currentMeeting.VotingEndTime - now).TotalSeconds; + GUILayout.Label($"Hlasování: {remaining:F0}s", subtitleStyle); + + // Můžeme hlasovat pouze pokud jsme dorazili a jsme naživu + if (!iArrivedAtMeeting) + { + GUI.color = Color.red; + GUILayout.Label("❌ Nedorazil/a jsi včas - nemůžeš hlasovat!", labelStyle); + GUI.color = Color.white; + canVote = false; + } + else + { + canVote = canVoteInMeeting && myVote == null; + } + } + else + { + GUILayout.Label("Čekám na výsledky...", subtitleStyle); + canVote = false; + } + } + + GUILayout.Space(10); + + // ═══════════════════════════════════════════════════════════════════ + // VÝSLEDKY HLASOVÁNÍ + // ═══════════════════════════════════════════════════════════════════ + + if (showVotingResults && votingResults != null) + { + GUILayout.BeginVertical(boxStyle); + + GUI.color = Color.yellow; + GUILayout.Label("═══ VÝSLEDKY HLASOVÁNÍ ═══", subtitleStyle); + GUI.color = Color.white; + + GUILayout.Space(5); + + if (votingResults.VoteCounts != null) + { + foreach (var kvp in votingResults.VoteCounts) + { + // Server používá "__SKIP__" pro skip vote + string name = (kvp.Key == "__SKIP__" || kvp.Key == "skip") + ? "⏭ Přeskočit" + : $"👤 {GetPlayerName(kvp.Key)}"; + + // Zvýrazníme vyhozeného hráče + if (kvp.Key == votingResults.EjectedPlayerId) + { + GUI.color = Color.red; + GUILayout.Label($"🚪 {name}: {kvp.Value} hlasů", labelStyle); + GUI.color = Color.white; + } + else + { + GUILayout.Label($" {name}: {kvp.Value} hlasů", labelStyle); + } + } + } + + GUILayout.Space(10); + + // Výsledek hlasování + if (votingResults.EjectedPlayerId != null) + { + string ejectedName = GetPlayerName(votingResults.EjectedPlayerId); + GUI.color = Color.red; + GUILayout.Label($"🚪 {ejectedName} byl/a vyhozen/a!", subtitleStyle); + GUI.color = Color.white; + } + else if (votingResults.WasTie) + { + GUI.color = Color.yellow; + GUILayout.Label("⚖ Remíza - nikdo nebyl vyhozen", subtitleStyle); + GUI.color = Color.white; + } + else + { + GUI.color = Color.gray; + GUILayout.Label("✗ Nikdo nebyl vyhozen", subtitleStyle); + GUI.color = Color.white; + } + + GUILayout.EndVertical(); + + GUILayout.Space(10); + + if (Time.time > votingResultsEndTime) + { + showVotingResults = false; + currentMeeting = null; + } + } + + // ═══════════════════════════════════════════════════════════════════ + // SEZNAM HRÁČŮ PRO HLASOVÁNÍ + // ═══════════════════════════════════════════════════════════════════ + + if (!showVotingResults) + { + playerListScroll = GUILayout.BeginScrollView(playerListScroll, GUILayout.Height(350)); + + if (client?.CurrentLobbyState?.Players != null) + { + foreach (var player in client.CurrentLobbyState.Players) + { + // Získáme stav hráče - bezpečný přístup + bool isAlive = true; + if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(player.ClientUuid, out var pInfo)) + { + isAlive = pInfo.State == PlayerState.Alive; + } + + GUILayout.BeginHorizontal(boxStyle); + + // Ikona stavu + if (!isAlive) + { + GUI.color = Color.gray; + GUILayout.Label("💀", GUILayout.Width(25)); + } + else + { + GUILayout.Label("👤", GUILayout.Width(25)); + } + + // Jméno + string nameColor = player.ClientUuid == clientUuid ? "yellow" : (isAlive ? "white" : "gray"); + GUILayout.Label($"{player.DisplayName}", richTextStyle, GUILayout.Width(150)); + + // Indikátor hlasování + if (meetingVotes.ContainsKey(player.ClientUuid)) + { + GUILayout.Label("✓ Hlasoval", GUILayout.Width(80)); + } + + // Tlačítko hlasování + GUI.enabled = canVote && isAlive && player.ClientUuid != clientUuid; + if (GUILayout.Button("Hlasovat", GUILayout.Width(80))) + { + CastVote(player.ClientUuid); + } + GUI.enabled = true; + + GUI.color = Color.white; + GUILayout.EndHorizontal(); + } + } + + GUILayout.EndScrollView(); + + // ═══════════════════════════════════════════════════════════════ + // TLAČÍTKO SKIP + // ═══════════════════════════════════════════════════════════════ + + GUILayout.Space(10); + + GUI.enabled = canVote; + GUI.color = Color.gray; + if (GUILayout.Button("⏭ Přeskočit hlasování", bigButtonStyle)) + { + CastVote(null); // null = skip + } + GUI.color = Color.white; + GUI.enabled = true; + } + + GUILayout.EndArea(); + } + + #region ═══════════════════════════════════════════════════════════════════ + // KONEC HRY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení obrazovky konce hry. + /// Zobrazuje vítěze a statistiky. + /// + protected void DrawGameEndScreen() + { + if (titleStyle == null) InitializeUIStyles(); + + float panelWidth = 500; + float panelHeight = 400; + float panelX = (Screen.width / uiScale - panelWidth) / 2; + float panelY = (Screen.height / uiScale - panelHeight) / 2; + + GUILayout.BeginArea(new Rect(panelX, panelY, panelWidth, panelHeight)); + GUI.Box(new Rect(0, 0, panelWidth, panelHeight), "", boxStyle); + + GUILayout.Space(20); + + // ═══════════════════════════════════════════════════════════════════ + // VÍTĚZ + // ═══════════════════════════════════════════════════════════════════ + + if (gameEndData != null) + { + // Určíme, zda jsme vyhráli + bool weWon = gameEndData.Winners.Contains(clientUuid); + + if (weWon) + { + GUI.color = Color.green; + GUILayout.Label("🏆 VÍTĚZSTVÍ!", titleStyle); + } + else + { + GUI.color = Color.red; + GUILayout.Label("💔 PROHRA", titleStyle); + } + GUI.color = Color.white; + + GUILayout.Space(20); + + // Vítězná frakce + string faction = gameEndData.WinningFaction == "Impostor" ? "Impostoři" : "Posádka"; + GUILayout.Label($"Vyhráli: {faction}", subtitleStyle); + + GUILayout.Space(10); + + // Důvod + GUILayout.Label($"Důvod: {gameEndData.Reason}", labelStyle); + + GUILayout.Space(20); + + // Seznam vítězů + GUILayout.Label("Vítězové:", labelStyle); + foreach (var winnerId in gameEndData.Winners) + { + string name = GetPlayerName(winnerId); + GUILayout.Label($" • {name}", labelStyle); + } + } + + GUILayout.Space(30); + + // ═══════════════════════════════════════════════════════════════════ + // TLAČÍTKA + // ═══════════════════════════════════════════════════════════════════ + + // Pouze owner může restartovat hru + bool isOwner = client?.IsOwner ?? false; + + if (isOwner) + { + if (GUILayout.Button("🔄 Nová hra (zpět do lobby)", bigButtonStyle)) + { + // Pošleme serveru požadavek na návrat do lobby + client?.ReturnToLobby(); + } + } + else + { + GUI.enabled = false; + GUILayout.Button("Čekám na hostitele...", bigButtonStyle); + GUI.enabled = true; + } + + GUILayout.Space(10); + + if (GUILayout.Button("Hlavní menu", buttonStyle)) + { + LeaveLobby(); + gameEndData = null; + } + + GUILayout.EndArea(); + } + + #region ═══════════════════════════════════════════════════════════════════ + // NOTIFIKACE A CHYBY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Vykreslení notifikací + /// + protected void DrawNotifications() + { + if (currentNotification.message == null) return; + + float notifWidth = 400; + float notifHeight = 60; + float notifX = (Screen.width / uiScale - notifWidth) / 2; + float notifY = 50; + + // Fade efekt + float remaining = notificationEndTime - Time.time; + float alpha = Mathf.Clamp01(remaining); + + Color bgColor = currentNotification.color; + bgColor.a = alpha * 0.9f; + + GUI.color = bgColor; + GUI.Box(new Rect(notifX, notifY, notifWidth, notifHeight), "", notificationStyle); + + GUI.color = new Color(1, 1, 1, alpha); + GUI.Label(new Rect(notifX, notifY, notifWidth, notifHeight), + $"{currentNotification.icon} {currentNotification.message}", notificationStyle); + + GUI.color = Color.white; + } + + /// + /// Vykreslení chybové zprávy + /// + protected void DrawErrorMessage() + { + if (string.IsNullOrEmpty(errorMessage) || Time.time > errorMessageEndTime) + { + errorMessage = null; + return; + } + + float errorWidth = 500; + float errorHeight = 40; + float errorX = (Screen.width / uiScale - errorWidth) / 2; + float errorY = Screen.height / uiScale - 60; + + GUI.color = new Color(0.8f, 0.2f, 0.2f, 0.9f); + GUI.Box(new Rect(errorX, errorY, errorWidth, errorHeight), "", notificationStyle); + + GUI.color = Color.white; + GUI.Label(new Rect(errorX, errorY, errorWidth, errorHeight), + $"⚠ {errorMessage}", notificationStyle); + } + + #region ═══════════════════════════════════════════════════════════════════ + // POMOCNÉ UI METODY + // ════════════════════════════════════════════════════════════════════════ + #endregion + + /// + /// Získá text pro hlavní akční tlačítko + /// + private string GetActionButtonText() + { + // Kontrola těla poblíž + var body = client?.FindNearbyBody(5.0); + if (body != null) return "🚨 REPORT"; + + // Kontrola úkolu poblíž + var task = client?.FindNearbyTask(5.0); + if (task != null) return "✋ USE"; + + // Kontrola opravné stanice poblíž + if (currentSabotage != null) + { + var station = FindNearbyRepairStation(5.0); + if (station != null) return "🔧 REPAIR"; + } + + return "USE"; + } + + /// + /// Zjistí, zda můžeme provést akci + /// + private bool CanPerformAction() + { + if (client == null) return false; + + // Zjistíme, jestli jsme duch + bool isGhost = false; + if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var playerInfo)) + { + isGhost = playerInfo.State != PlayerState.Alive; + } + + // Duchové NEMOHOU reportovat těla ani volat meeting + // Ale MOHOU plnit úkoly! + if (!isGhost && client.FindNearbyBody(5.0) != null) return true; + + // Kontrola úkolu - duchové i živí crew mohou dělat úkoly + if (client.FindNearbyTask(5.0) != null && client.MyRole == PlayerRole.Crew) return true; + + // Kontrola opravné stanice - pouze živí mohou opravovat + if (!isGhost && currentSabotage != null && FindNearbyRepairStation(5.0) != null) return true; + + return false; + } + + /// + /// Zjistí, zda můžeme svolat meeting + /// + private bool CanCallMeeting() + { + if (client == null) return false; + if (currentSabotage?.Type == SabotageType.CommsBlackout) return false; + + // Kontrola stavu hráče - bezpečný přístup + if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var playerInfo)) + { + if (playerInfo.State != PlayerState.Alive) + return false; + } + + return true; + } + + /// + /// Zjistí, zda je kill na cooldownu + /// + private bool IsOnCooldown() + { + // TODO: Implementovat cooldown tracking + return false; + } +} diff --git a/Assets/UnityTestClient/UnityTestClient_UI.cs.meta b/Assets/UnityTestClient/UnityTestClient_UI.cs.meta new file mode 100644 index 0000000..d3b81d8 --- /dev/null +++ b/Assets/UnityTestClient/UnityTestClient_UI.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ebca6f90ee582ea448b7a028626d8636 \ No newline at end of file diff --git a/Packages/manifest.json b/Packages/manifest.json new file mode 100644 index 0000000..7e42b3d --- /dev/null +++ b/Packages/manifest.json @@ -0,0 +1,48 @@ +{ + "dependencies": { + "com.unity.ai.navigation": "2.0.9", + "com.unity.collab-proxy": "2.9.3", + "com.unity.ide.rider": "3.0.38", + "com.unity.ide.visualstudio": "2.0.23", + "com.unity.inputsystem": "1.14.2", + "com.unity.multiplayer.center": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.2.2", + "com.unity.render-pipelines.universal": "17.2.0", + "com.unity.test-framework": "1.6.0", + "com.unity.timeline": "1.8.9", + "com.unity.ugui": "2.0.0", + "com.unity.visualscripting": "1.9.7", + "com.unity.modules.accessibility": "1.0.0", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json new file mode 100644 index 0000000..c4507fb --- /dev/null +++ b/Packages/packages-lock.json @@ -0,0 +1,472 @@ +{ + "dependencies": { + "com.unity.ai.navigation": { + "version": "2.0.9", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.ai": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.burst": { + "version": "1.8.25", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collab-proxy": { + "version": "2.9.3", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.collections": { + "version": "2.5.7", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.burst": "1.8.19", + "com.unity.mathematics": "1.3.2", + "com.unity.test-framework": "1.4.6", + "com.unity.nuget.mono-cecil": "1.11.5", + "com.unity.test-framework.performance": "3.0.3" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "2.0.5", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.ide.rider": { + "version": "3.0.38", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.23", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" + }, + "com.unity.inputsystem": { + "version": "1.14.2", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.mathematics": { + "version": "1.3.2", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.multiplayer.center": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + } + }, + "com.unity.nuget.mono-cecil": { + "version": "1.11.5", + "depth": 3, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.nuget.newtonsoft-json": { + "version": "3.2.2", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.render-pipelines.core": { + "version": "17.2.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.14", + "com.unity.mathematics": "1.3.2", + "com.unity.ugui": "2.0.0", + "com.unity.collections": "2.4.3", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.rendering.light-transport": "1.0.1" + } + }, + "com.unity.render-pipelines.universal": { + "version": "17.2.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.2.0", + "com.unity.shadergraph": "17.2.0", + "com.unity.render-pipelines.universal-config": "17.0.3" + } + }, + "com.unity.render-pipelines.universal-config": { + "version": "17.0.3", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.0.3" + } + }, + "com.unity.rendering.light-transport": { + "version": "1.0.1", + "depth": 2, + "source": "builtin", + "dependencies": { + "com.unity.collections": "2.2.0", + "com.unity.mathematics": "1.2.4", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.searcher": { + "version": "4.9.3", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.shadergraph": { + "version": "17.2.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.2.0", + "com.unity.searcher": "4.9.3" + } + }, + "com.unity.test-framework": { + "version": "1.6.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.ext.nunit": "2.0.3", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.test-framework.performance": { + "version": "3.2.0", + "depth": 3, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.33", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.8.9", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "2.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.visualscripting": { + "version": "1.9.7", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.modules.accessibility": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.hierarchycore": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.hierarchycore": "1.0.0", + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} diff --git a/ProjectSettings/AudioManager.asset b/ProjectSettings/AudioManager.asset new file mode 100644 index 0000000..27287fe --- /dev/null +++ b/ProjectSettings/AudioManager.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 1024 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 + m_RequestedDSPBufferSize: 0 diff --git a/ProjectSettings/ClusterInputManager.asset b/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 0000000..e7886b2 --- /dev/null +++ b/ProjectSettings/ClusterInputManager.asset @@ -0,0 +1,6 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] diff --git a/ProjectSettings/DynamicsManager.asset b/ProjectSettings/DynamicsManager.asset new file mode 100644 index 0000000..fc90ab9 --- /dev/null +++ b/ProjectSettings/DynamicsManager.asset @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_ClothInterCollisionDistance: 0.1 + m_ClothInterCollisionStiffness: 0.2 + m_ContactsGeneration: 1 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_AutoSimulation: 1 + m_AutoSyncTransforms: 0 + m_ReuseCollisionCallbacks: 1 + m_ClothInterCollisionSettingsToggle: 0 + m_ClothGravity: {x: 0, y: -9.81, z: 0} + m_ContactPairsMode: 0 + m_BroadphaseType: 0 + m_WorldBounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 250, y: 250, z: 250} + m_WorldSubdivisions: 8 + m_FrictionType: 0 + m_EnableEnhancedDeterminism: 0 + m_EnableUnifiedHeightmaps: 1 + m_SolverType: 0 + m_DefaultMaxAngularSpeed: 50 diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000..d057ba3 --- /dev/null +++ b/ProjectSettings/EditorBuildSettings.asset @@ -0,0 +1,13 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: + - enabled: 1 + path: Assets/Scenes/SampleScene.unity + guid: 99c9720ab356a0642a771bea13969a05 + m_configObjects: + com.unity.input.settings.actions: {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3} + m_UseUCBPForAssetBundles: 0 diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset new file mode 100644 index 0000000..878c4cb --- /dev/null +++ b/ProjectSettings/EditorSettings.asset @@ -0,0 +1,50 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 15 + m_SerializationMode: 2 + m_LineEndingsForNewScripts: 0 + m_DefaultBehaviorMode: 0 + m_PrefabRegularEnvironment: {fileID: 0} + m_PrefabUIEnvironment: {fileID: 0} + m_SpritePackerMode: 0 + m_SpritePackerCacheSize: 10 + m_SpritePackerPaddingPower: 1 + m_Bc7TextureCompressor: 0 + m_EtcTextureCompressorBehavior: 1 + m_EtcTextureFastCompressor: 1 + m_EtcTextureNormalCompressor: 2 + m_EtcTextureBestCompressor: 4 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref + m_ProjectGenerationRootNamespace: + m_EnableTextureStreamingInEditMode: 1 + m_EnableTextureStreamingInPlayMode: 1 + m_EnableEditorAsyncCPUTextureLoading: 0 + m_AsyncShaderCompilation: 1 + m_PrefabModeAllowAutoSave: 1 + m_EnterPlayModeOptionsEnabled: 1 + m_EnterPlayModeOptions: 0 + m_GameObjectNamingDigits: 1 + m_GameObjectNamingScheme: 0 + m_AssetNamingUsesSpace: 1 + m_InspectorUseIMGUIDefaultInspector: 0 + m_UseLegacyProbeSampleCount: 0 + m_SerializeInlineMappingsOnOneLine: 1 + m_DisableCookiesInLightmapper: 0 + m_ShadowmaskStitching: 0 + m_AssetPipelineMode: 1 + m_RefreshImportMode: 0 + m_CacheServerMode: 0 + m_CacheServerEndpoint: + m_CacheServerNamespacePrefix: default + m_CacheServerEnableDownload: 1 + m_CacheServerEnableUpload: 1 + m_CacheServerEnableAuth: 0 + m_CacheServerEnableTls: 0 + m_CacheServerValidationMode: 2 + m_CacheServerDownloadBatchSize: 128 + m_EnableEnlightenBakedGI: 0 + m_ReferencedClipsExactNaming: 1 + m_ForceAssetUnloadAndGCOnSceneLoad: 1 diff --git a/ProjectSettings/GraphicsSettings.asset b/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 0000000..3a1a7b3 --- /dev/null +++ b/ProjectSettings/GraphicsSettings.asset @@ -0,0 +1,68 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 16 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} + m_VideoShadersIncludeMode: 2 + m_AlwaysIncludedShaders: + - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 24, guid: 0000000000000000f000000000000000, type: 0} + m_PreloadedShaders: [] + m_PreloadShadersBatchTimeLimit: -1 + m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} + m_CustomRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, type: 2} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_BrgStripping: 0 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_RenderPipelineGlobalSettingsMap: + UnityEngine.Rendering.Universal.UniversalRenderPipeline: {fileID: 11400000, guid: 18dc0cd2c080841dea60987a38ce93fa, type: 2} + m_LightsUseLinearIntensity: 1 + m_LightsUseColorTemperature: 1 + m_LogWhenShaderIsCompiled: 0 + m_LightProbeOutsideHullStrategy: 0 + m_CameraRelativeLightCulling: 0 + m_CameraRelativeShadowCulling: 0 diff --git a/ProjectSettings/InputManager.asset b/ProjectSettings/InputManager.asset new file mode 100644 index 0000000..b16147e --- /dev/null +++ b/ProjectSettings/InputManager.asset @@ -0,0 +1,487 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Enable Debug Button 1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: joystick button 8 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Enable Debug Button 2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: backspace + altNegativeButton: + altPositiveButton: joystick button 9 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Reset + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Next + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: page down + altNegativeButton: + altPositiveButton: joystick button 5 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Previous + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: page up + altNegativeButton: + altPositiveButton: joystick button 4 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Validate + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Persistent + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: right shift + altNegativeButton: + altPositiveButton: joystick button 2 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Multiplier + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: joystick button 3 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 2 + axis: 6 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 2 + axis: 5 + joyNum: 0 diff --git a/ProjectSettings/MemorySettings.asset b/ProjectSettings/MemorySettings.asset new file mode 100644 index 0000000..5b5face --- /dev/null +++ b/ProjectSettings/MemorySettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} diff --git a/ProjectSettings/MultiplayerManager.asset b/ProjectSettings/MultiplayerManager.asset new file mode 100644 index 0000000..2a93664 --- /dev/null +++ b/ProjectSettings/MultiplayerManager.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!655991488 &1 +MultiplayerManager: + m_ObjectHideFlags: 0 + m_EnableMultiplayerRoles: 0 + m_StrippingTypes: {} diff --git a/ProjectSettings/NavMeshAreas.asset b/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 0000000..3b0b7c3 --- /dev/null +++ b/ProjectSettings/NavMeshAreas.asset @@ -0,0 +1,91 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_SettingNames: + - Humanoid diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset new file mode 100644 index 0000000..be4a797 --- /dev/null +++ b/ProjectSettings/PackageManagerSettings.asset @@ -0,0 +1,43 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_EnablePreviewPackages: 0 + m_EnablePackageDependencies: 0 + m_AdvancedSettingsExpanded: 1 + m_ScopedRegistriesSettingsExpanded: 1 + oneTimeWarningShown: 0 + m_Registries: + - m_Id: main + m_Name: + m_Url: https://packages.unity.com + m_Scopes: [] + m_IsDefault: 1 + m_Capabilities: 7 + m_UserSelectedRegistryName: + m_UserAddingNewScopedRegistry: 0 + m_RegistryInfoDraft: + m_ErrorMessage: + m_Original: + m_Id: + m_Name: + m_Url: + m_Scopes: [] + m_IsDefault: 0 + m_Capabilities: 0 + m_Modified: 0 + m_Name: + m_Url: + m_Scopes: + - + m_SelectedScopeIndex: 0 diff --git a/ProjectSettings/Physics2DSettings.asset b/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 0000000..6c5cf8a --- /dev/null +++ b/ProjectSettings/Physics2DSettings.asset @@ -0,0 +1,56 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_VelocityThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_JobOptions: + serializedVersion: 2 + useMultithreading: 0 + useConsistencySorting: 0 + m_InterpolationPosesPerJob: 100 + m_NewContactsPerJob: 30 + m_CollideContactsPerJob: 100 + m_ClearFlagsPerJob: 200 + m_ClearBodyForcesPerJob: 200 + m_SyncDiscreteFixturesPerJob: 50 + m_SyncContinuousFixturesPerJob: 50 + m_FindNearestContactsPerJob: 100 + m_UpdateTriggerContactsPerJob: 100 + m_IslandSolverCostThreshold: 100 + m_IslandSolverBodyCostScale: 1 + m_IslandSolverContactCostScale: 10 + m_IslandSolverJointCostScale: 10 + m_IslandSolverBodiesPerJob: 50 + m_IslandSolverContactsPerJob: 50 + m_AutoSimulation: 1 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_CallbacksOnDisable: 1 + m_ReuseCollisionCallbacks: 0 + m_AutoSyncTransforms: 0 + m_AlwaysShowColliders: 0 + m_ShowColliderSleep: 1 + m_ShowColliderContacts: 0 + m_ShowColliderAABB: 0 + m_ContactArrowScale: 0.2 + m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} + m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} + m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} + m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/ProjectSettings/PresetManager.asset b/ProjectSettings/PresetManager.asset new file mode 100644 index 0000000..67a94da --- /dev/null +++ b/ProjectSettings/PresetManager.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1386491679 &1 +PresetManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_DefaultPresets: {} diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000..346db83 --- /dev/null +++ b/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,943 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 28 + productGUID: 786a0f5e10d1d11479dc64c9b196a360 + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: DefaultCompany + productName: GeoSusTestClient + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1024 + defaultScreenHeight: 768 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 1 + unsupportedMSAAFallback: 0 + m_SpriteBatchMaxVertexCount: 65535 + m_SpriteBatchVertexThreshold: 300 + m_MTRendering: 1 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + iosUseCustomAppBackgroundBehavior: 0 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 0 + androidDisplayOptions: 1 + androidBlitType: 0 + androidResizeableActivity: 1 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 1 + androidApplicationEntry: 2 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 1 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + dedicatedServerOptimizations: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 1 + meshDeformation: 2 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + fullscreenMode: 3 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchMaxWorkerMultiple: 8 + switchNVNGraphicsFirmwareMemory: 32 + switchGraphicsJobsSyncAfterKick: 1 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 1 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 + bundleVersion: 0.1.0 + preloadedAssets: + - {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3} + metroInputSource: 0 + wsaTransparentSwapchain: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 + useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.4 + androidMinAspectRatio: 1 + applicationIdentifier: + Android: com.UnityTechnologies.com.unity.template.urpblank + Standalone: com.Unity-Technologies.com.unity.template.urp-blank + iPhone: com.Unity-Technologies.com.unity.template.urp-blank + buildNumber: + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 1 + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 23 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + androidSplitApplicationBinary: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSSimulatorArchitecture: 0 + iOSTargetOSVersionString: 13.0 + tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 13.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + macOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + metalCompileShaderBinary: 0 + iOSRenderExtraFrameOnPause: 0 + iosCopyPluginsCodeInsteadOfSymlink: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: 3c72c65a16f0acb438eed22b8b16c24a + templatePackageId: com.unity.template.urp-blank@17.0.14 + templateDefaultScene: Assets/Scenes/SampleScene.unity + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 2 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidEnableArm64MTE: 0 + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + androidAppCategory: 3 + useAndroidAppCategory: 1 + androidAppCategoryOther: + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 150 + AndroidReportGooglePlayAppDependencies: 1 + androidSymbolsSizeThreshold: 800 + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: + - m_BuildTarget: tvOS + m_Icons: + - m_Textures: [] + m_Width: 1280 + m_Height: 768 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 800 + m_Height: 480 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 400 + m_Height: 240 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 4640 + m_Height: 1440 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 2320 + m_Height: 720 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 3840 + m_Height: 1440 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 1920 + m_Height: 720 + m_Kind: 1 + m_SubKind: + m_BuildTargetBatching: + - m_BuildTarget: Standalone + m_StaticBatching: 1 + m_DynamicBatching: 0 + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: [] + m_BuildTargetGraphicsJobMode: [] + m_BuildTargetGraphicsAPIs: + - m_BuildTarget: iOSSupport + m_APIs: 10000000 + m_Automatic: 1 + - m_BuildTarget: AndroidPlayer + m_APIs: 150000000b000000 + m_Automatic: 0 + m_BuildTargetVRSettings: [] + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - serializedVersion: 2 + m_BuildTarget: Android + m_EncodingQuality: 1 + m_BuildTargetGroupHDRCubemapEncodingQuality: [] + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: + - m_BuildTarget: Android + m_Encoding: 1 + m_BuildTargetDefaultTextureCompressionFormat: + - serializedVersion: 3 + m_BuildTarget: Android + m_Formats: 03000000 + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + editorGfxJobOverride: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 11.0 + switchNMETAOverride: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchCompilerFlags: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchRatingsInt_12: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 0 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 + switchUseNewStyleFilepaths: 0 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 + switchUpgradedPlayerSettingsToNMETA: 0 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 2 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 2 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 32 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 0 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLPowerPreference: 2 + webGLWebAssemblyTable: 0 + webGLWebAssemblyBigInt: 0 + webGLCloseOnQuit: 0 + webWasm2023: 0 + webEnableSubmoduleStrippingCompatibility: 0 + scriptingDefineSymbols: {} + additionalCompilerArguments: {} + platformArchitecture: {} + scriptingBackend: + Android: 1 + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + il2cppStacktraceInformation: {} + managedStrippingLevel: {} + incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 + apiCompatibilityLevelPerPlatform: {} + editorAssembliesCompatibilityLevel: 1 + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: GeoSusTestClient + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: GeoSusTestClient + wsaImages: {} + metroTileShortName: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} + metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} + cloudServicesEnabled: {} + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 1 + embeddedLinuxEnableGamepadInput: 0 + hmiCpuConfiguration: + hmiLogStartupTiming: 0 + qnxGraphicConfPath: + apiCompatibilityLevel: 6 + captureStartupLogs: {} + activeInputHandler: 0 + windowsGamepadBackendHint: 0 + cloudProjectId: 8a417728-efd8-4f20-8f11-a7b1c9ae4cd1 + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] + projectName: GeoSusTestClient + organizationId: unity_dvyy2bhl4lcthq + cloudEnabled: 0 + legacyClampBlendShapeWeights: 0 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 + androidVulkanDenyFilterList: [] + androidVulkanAllowFilterList: [] + androidVulkanDeviceFilterListAsset: {fileID: 0} + d3d12DeviceFilterListAsset: {fileID: 0} diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..a9844fe --- /dev/null +++ b/ProjectSettings/ProjectVersion.txt @@ -0,0 +1,2 @@ +m_EditorVersion: 6000.2.8f1 +m_EditorVersionWithRevision: 6000.2.8f1 (c9992ac36c34) diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset new file mode 100644 index 0000000..f55198a --- /dev/null +++ b/ProjectSettings/QualitySettings.asset @@ -0,0 +1,134 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 1 + m_QualitySettings: + - serializedVersion: 4 + name: Mobile + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + skinWeights: 2 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 1 + adaptiveVsync: 0 + vSyncCount: 0 + realtimeGICPUUsage: 100 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 5e6cbd92db86f4b18aec3ed561671858, + type: 2} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: + - Standalone + - serializedVersion: 4 + name: PC + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 4 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 2 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 1 + adaptiveVsync: 0 + vSyncCount: 0 + realtimeGICPUUsage: 100 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 2 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, + type: 2} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: + - Android + - iPhone + m_TextureMipmapLimitGroupNames: [] + m_PerPlatformDefaultQuality: + Android: 0 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Lumin: 0 + Nintendo Switch: 1 + PS4: 1 + PS5: 1 + Server: 0 + Stadia: 0 + Standalone: 1 + WebGL: 0 + Windows Store Apps: 0 + XboxOne: 0 + iPhone: 0 + tvOS: 0 diff --git a/ProjectSettings/SceneTemplateSettings.json b/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 0000000..ede5887 --- /dev/null +++ b/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,121 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "defaultInstantiationMode": 0 + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "defaultInstantiationMode": 1 + }, + "newSceneOverride": 0 +} \ No newline at end of file diff --git a/ProjectSettings/ShaderGraphSettings.asset b/ProjectSettings/ShaderGraphSettings.asset new file mode 100644 index 0000000..ce8c243 --- /dev/null +++ b/ProjectSettings/ShaderGraphSettings.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de02f9e1d18f588468e474319d09a723, type: 3} + m_Name: + m_EditorClassIdentifier: + shaderVariantLimit: 128 + overrideShaderVariantLimit: 0 + customInterpolatorErrorThreshold: 32 + customInterpolatorWarningThreshold: 16 + customHeatmapValues: {fileID: 0} diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset new file mode 100644 index 0000000..6413d11 --- /dev/null +++ b/ProjectSettings/TagManager.asset @@ -0,0 +1,76 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 2 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 + m_RenderingLayers: + - Default + - Light Layer 1 + - Light Layer 2 + - Light Layer 3 + - Light Layer 4 + - Light Layer 5 + - Light Layer 6 + - Light Layer 7 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - diff --git a/ProjectSettings/TimeManager.asset b/ProjectSettings/TimeManager.asset new file mode 100644 index 0000000..558a017 --- /dev/null +++ b/ProjectSettings/TimeManager.asset @@ -0,0 +1,9 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + Fixed Timestep: 0.02 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 diff --git a/ProjectSettings/URPProjectSettings.asset b/ProjectSettings/URPProjectSettings.asset new file mode 100644 index 0000000..64a8674 --- /dev/null +++ b/ProjectSettings/URPProjectSettings.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 247994e1f5a72c2419c26a37e9334c01, type: 3} + m_Name: + m_EditorClassIdentifier: + m_LastMaterialVersion: 10 diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 0000000..7a17e8f --- /dev/null +++ b/ProjectSettings/UnityConnectSettings.asset @@ -0,0 +1,40 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 1 + 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 + m_ConfigUrl: https://config.uca.cloud.unity3d.com + m_DashboardUrl: https://dashboard.unity3d.com + m_TestInitMode: 0 + InsightsSettings: + m_EngineDiagnosticsEnabled: 1 + m_Enabled: 0 + CrashReportingSettings: + serializedVersion: 2 + m_EventUrl: https://perf-events.cloud.unity3d.com + m_EnableCloudDiagnosticsReporting: 0 + m_LogBufferSize: 10 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_TestMode: 0 + m_InitializeOnStartup: 1 + m_PackageRequiringCoreStatsPresent: 0 + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 diff --git a/ProjectSettings/VFXManager.asset b/ProjectSettings/VFXManager.asset new file mode 100644 index 0000000..3a95c98 --- /dev/null +++ b/ProjectSettings/VFXManager.asset @@ -0,0 +1,12 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!937362698 &1 +VFXManager: + m_ObjectHideFlags: 0 + m_IndirectShader: {fileID: 0} + m_CopyBufferShader: {fileID: 0} + m_SortShader: {fileID: 0} + m_StripUpdateShader: {fileID: 0} + m_RenderPipeSettingsPath: + m_FixedTimeStep: 0.016666668 + m_MaxDeltaTime: 0.05 diff --git a/ProjectSettings/VersionControlSettings.asset b/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 0000000..dca2881 --- /dev/null +++ b/ProjectSettings/VersionControlSettings.asset @@ -0,0 +1,8 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 diff --git a/ProjectSettings/XRSettings.asset b/ProjectSettings/XRSettings.asset new file mode 100644 index 0000000..482590c --- /dev/null +++ b/ProjectSettings/XRSettings.asset @@ -0,0 +1,10 @@ +{ + "m_SettingKeys": [ + "VR Device Disabled", + "VR Device User Alert" + ], + "m_SettingValues": [ + "False", + "False" + ] +} \ No newline at end of file

ZJ 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 z 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 literal 0 HcmV?d00001 diff --git a/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.dll.meta b/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.dll.meta new file mode 100644 index 0000000..87cfe3f --- /dev/null +++ b/Assets/ClientSDK/bin/Debug/netstandard2.1/ClientSDK.dll.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 47eeb682cfff82d42906fbccdbd561d8 \ No newline at end of file diff --git a/Assets/ClientSDK/obj.meta b/Assets/ClientSDK/obj.meta new file mode 100644 index 0000000..c1a843e --- /dev/null +++ b/Assets/ClientSDK/obj.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 131d9de257c8edc49991d792c6e702f6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.dgspec.json b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.dgspec.json new file mode 100644 index 0000000..8def387 --- /dev/null +++ b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.dgspec.json @@ -0,0 +1,80 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\ClientSDK.csproj": {} + }, + "projects": { + "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\ClientSDK.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\ClientSDK.csproj", + "projectName": "ClientSDK", + "projectPath": "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\ClientSDK.csproj", + "packagesPath": "C:\\Users\\racek\\.nuget\\packages\\", + "outputPath": "C:\\Users\\racek\\Documents\\GeoSus\\ClientSDK\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\racek\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "netstandard2.1" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "C:\\Program Files\\dotnet\\library-packs": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "10.0.100" + }, + "frameworks": { + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "dependencies": { + "Newtonsoft.Json": { + "target": "Package", + "version": "[13.0.3, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "NETStandard.Library": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.dgspec.json.meta b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.dgspec.json.meta new file mode 100644 index 0000000..e9eb929 --- /dev/null +++ b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.dgspec.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e24090cfcd76d42498d81c9b779bae93 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.props b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.props new file mode 100644 index 0000000..aeb2090 --- /dev/null +++ b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\racek\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 7.0.0 + + + + + + \ No newline at end of file diff --git a/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.props.meta b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.props.meta new file mode 100644 index 0000000..73062bc --- /dev/null +++ b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.props.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6967c057762dbec4a80dc0381b9a50fd +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.targets b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.targets.meta b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.targets.meta new file mode 100644 index 0000000..4297d70 --- /dev/null +++ b/Assets/ClientSDK/obj/ClientSDK.csproj.nuget.g.targets.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: fe3bd5ad8f9636d41bde2abb0ebb7530 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug.meta b/Assets/ClientSDK/obj/Debug.meta new file mode 100644 index 0000000..4518b30 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fb4cee9375d0f244e8995615ecaab70c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1.meta new file mode 100644 index 0000000..c99f0f8 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 664b1b069bb17b84a9566b7c76536173 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/.NETStandard,Version=v2.1.AssemblyAttributes.cs b/Assets/ClientSDK/obj/Debug/netstandard2.1/.NETStandard,Version=v2.1.AssemblyAttributes.cs new file mode 100644 index 0000000..348b87f --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/.NETStandard,Version=v2.1.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfo.cs b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfo.cs new file mode 100644 index 0000000..ec2b5fc --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("ClientSDK")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6fe5fff60c8e5c83560302ffb9d5f5db433cb667")] +[assembly: System.Reflection.AssemblyProductAttribute("ClientSDK")] +[assembly: System.Reflection.AssemblyTitleAttribute("ClientSDK")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfo.cs.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfo.cs.meta new file mode 100644 index 0000000..0bad271 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 317c8ca649884794eace1306da9518f4 \ No newline at end of file diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfoInputs.cache b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfoInputs.cache new file mode 100644 index 0000000..b2a5916 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +909f73150fc5400d21d798cce0fdeae4562227aba62b3fefaf41c4ebe3479a28 diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfoInputs.cache.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfoInputs.cache.meta new file mode 100644 index 0000000..dbea25c --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.AssemblyInfoInputs.cache.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 793d203358b179c4d8174f2084b26bf7 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.GeneratedMSBuildEditorConfig.editorconfig b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..a2ae7bb --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,8 @@ +is_global = true +build_property.RootNamespace = GeoSus.Client +build_property.ProjectDir = C:\Users\racek\Documents\GeoSus\ClientSDK\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.CsWinRTUseWindowsUIXamlProjections = false +build_property.EffectiveAnalysisLevelStyle = +build_property.EnableCodeStyleSeverity = diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.GeneratedMSBuildEditorConfig.editorconfig.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.GeneratedMSBuildEditorConfig.editorconfig.meta new file mode 100644 index 0000000..28773de --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.GeneratedMSBuildEditorConfig.editorconfig.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9ab4374e13e28254dbd1261542fbfde3 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.assets.cache b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..fca9b0e8c8127f7706e5cb22bd1a1c973683358d GIT binary patch literal 989 zcmcIjK~EDw7%jL^3bZIS9-)_d+?EnZ^+q5xwYF&}5)&sT)7_6UFgw%C%qsl{n&_YK zf`^6!2X6!q-uMlk^+$N%n{+oU8xu}!@-ms3m+yV^zPH_0V{0*!$+XY@{{H#?8(sPL zXZ-2w!e#sHbN$`r_1j-RzMNl;zqc-?kraJXxRULcoS@83$b| z*XpPB&2sGpX){krYe(sT>5%eL&xAgcq<2IW_@omyz;q+?Y^{g&_%z`0Jgrdd@xu$_ zaINdHP%In|w}2N*h{uR0h-E|(k^G+GrjYcj>8EF3OkN*gNSPF~ley=+)EqTMWc72* zW94ohE2+HLJUSg3KsG8=Z0cq(HNySrrPy0?2L4;pnyWD0d9jUfuz1n4kt9)(iVqsraA1ZvfcH8?XQX literal 0 HcmV?d00001 diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.assets.cache.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.assets.cache.meta new file mode 100644 index 0000000..6085204 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.assets.cache.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4e8560c40c30c0a4797e7be274a73c54 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.AssemblyReference.cache b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..3d8a52527f42b3b239c11fa9cb25b29867025217 GIT binary patch literal 453 zcmZQ$WMp7qV2p9LiU}=FEh>&FN=#18j?v32O;0U}DM(DtPE1cNj>$_cFUij<&QB}R z%PP*#i!n6TGte`R$;nItN|h9sB<7_g7Nr>J8N~P@RC)nb>ZRo5fURKIB_+Q^{x%0A zBO^bW8b)5A6OaMONg%*j4_nXV-7qb}0&)*H`cy4xHE-YSiO(r;DXS`Tc|q z=Ph2eDvk!@KrzY%pY;agalD>cI(`d(s0Zm-wxoTkN`HSy99r@ry#|_U_)a zsL`YL+^gSnn^#TdHxjVlC#xyw6n(VBf$2CS2hdFhYyF>XTDI;jV=$UebriyYVVs$t zXA2HM13d#B1?SS7lG377+q~4$lA^>M9fg3>q@2uT@6^hW{Or^`TVsR76qB@6vm~QL Yv&7WY6rlM)z{pi*0*V3yV?#>@0G`i~y8r+H literal 0 HcmV?d00001 diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.AssemblyReference.cache.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.AssemblyReference.cache.meta new file mode 100644 index 0000000..086ac07 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.AssemblyReference.cache.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5172f98fca4ee924590563b51439fada +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.CoreCompileInputs.cache b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..7004694 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +d380e7a79749800c241c828bf7749c96ff851a709d793dc2a85f50651e2cc678 diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.CoreCompileInputs.cache.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.CoreCompileInputs.cache.meta new file mode 100644 index 0000000..5c2c67b --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.CoreCompileInputs.cache.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 13c4f3623cba4154ab87a8c2d1b77005 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.FileListAbsolute.txt b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..14f81f5 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.FileListAbsolute.txt @@ -0,0 +1,10 @@ +C:\Users\racek\Documents\GeoSus\ClientSDK\obj\Debug\netstandard2.1\ClientSDK.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\racek\Documents\GeoSus\ClientSDK\obj\Debug\netstandard2.1\ClientSDK.AssemblyInfoInputs.cache +C:\Users\racek\Documents\GeoSus\ClientSDK\obj\Debug\netstandard2.1\ClientSDK.AssemblyInfo.cs +C:\Users\racek\Documents\GeoSus\ClientSDK\obj\Debug\netstandard2.1\ClientSDK.csproj.CoreCompileInputs.cache +C:\Users\racek\Documents\GeoSus\ClientSDK\obj\Debug\netstandard2.1\ClientSDK.csproj.AssemblyReference.cache +C:\Users\racek\Documents\GeoSus\ClientSDK\bin\Debug\netstandard2.1\ClientSDK.deps.json +C:\Users\racek\Documents\GeoSus\ClientSDK\bin\Debug\netstandard2.1\ClientSDK.dll +C:\Users\racek\Documents\GeoSus\ClientSDK\bin\Debug\netstandard2.1\ClientSDK.pdb +C:\Users\racek\Documents\GeoSus\ClientSDK\obj\Debug\netstandard2.1\ClientSDK.dll +C:\Users\racek\Documents\GeoSus\ClientSDK\obj\Debug\netstandard2.1\ClientSDK.pdb diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.FileListAbsolute.txt.meta b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.FileListAbsolute.txt.meta new file mode 100644 index 0000000..9f7e090 --- /dev/null +++ b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.csproj.FileListAbsolute.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 61ddb0a15ce734542b4cabbb8941d1e8 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.dll b/Assets/ClientSDK/obj/Debug/netstandard2.1/ClientSDK.dll new file mode 100644 index 0000000000000000000000000000000000000000..884654c2481aea74cc7abfbb3f7bdf0d687b16d0 GIT binary patch literal 143360 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