diff --git a/unity/tanks-fishnet/.dockerignore b/unity/tanks-fishnet/.dockerignore index 75becf1..1515742 100644 --- a/unity/tanks-fishnet/.dockerignore +++ b/unity/tanks-fishnet/.dockerignore @@ -1,2 +1,2 @@ ** -!build/LinuxServer/ +!builds/LinuxServer/ diff --git a/unity/tanks-fishnet/.vscode/extensions.json b/unity/tanks-fishnet/.vscode/extensions.json new file mode 100644 index 0000000..ddb6ff8 --- /dev/null +++ b/unity/tanks-fishnet/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "visualstudiotoolsforunity.vstuc" + ] +} diff --git a/unity/tanks-fishnet/.vscode/launch.json b/unity/tanks-fishnet/.vscode/launch.json new file mode 100644 index 0000000..da60e25 --- /dev/null +++ b/unity/tanks-fishnet/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Unity", + "type": "vstuc", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/unity/tanks-fishnet/.vscode/settings.json b/unity/tanks-fishnet/.vscode/settings.json index 6a0a9db..e6fa078 100644 --- a/unity/tanks-fishnet/.vscode/settings.json +++ b/unity/tanks-fishnet/.vscode/settings.json @@ -1,57 +1,56 @@ { - "files.exclude": - { - "**/.DS_Store":true, - "**/.git":true, - "**/.gitmodules":true, - "**/*.booproj":true, - "**/*.pidb":true, - "**/*.suo":true, - "**/*.user":true, - "**/*.userprefs":true, - "**/*.unityproj":true, - "**/*.dll":true, - "**/*.exe":true, - "**/*.pdf":true, - "**/*.mid":true, - "**/*.midi":true, - "**/*.wav":true, - "**/*.gif":true, - "**/*.ico":true, - "**/*.jpg":true, - "**/*.jpeg":true, - "**/*.png":true, - "**/*.psd":true, - "**/*.tga":true, - "**/*.tif":true, - "**/*.tiff":true, - "**/*.3ds":true, - "**/*.3DS":true, - "**/*.fbx":true, - "**/*.FBX":true, - "**/*.lxo":true, - "**/*.LXO":true, - "**/*.ma":true, - "**/*.MA":true, - "**/*.obj":true, - "**/*.OBJ":true, - "**/*.asset":true, - "**/*.cubemap":true, - "**/*.flare":true, - "**/*.mat":true, - "**/*.meta":true, - "**/*.prefab":true, - "**/*.unity":true, - "build/":true, - "Build/":true, - "Library/":true, - "library/":true, - "obj/":true, - "Obj/":true, - "ProjectSettings/":true, - "temp/":true, - "Temp/":true + "files.exclude": { + "**/.DS_Store": true, + "**/.git": true, + "**/.gitmodules": true, + "**/*.booproj": true, + "**/*.pidb": true, + "**/*.suo": true, + "**/*.user": true, + "**/*.userprefs": true, + "**/*.unityproj": true, + "**/*.dll": true, + "**/*.exe": true, + "**/*.pdf": true, + "**/*.mid": true, + "**/*.midi": true, + "**/*.wav": true, + "**/*.gif": true, + "**/*.ico": true, + "**/*.jpg": true, + "**/*.jpeg": true, + "**/*.png": true, + "**/*.psd": true, + "**/*.tga": true, + "**/*.tif": true, + "**/*.tiff": true, + "**/*.3ds": true, + "**/*.3DS": true, + "**/*.fbx": true, + "**/*.FBX": true, + "**/*.lxo": true, + "**/*.LXO": true, + "**/*.ma": true, + "**/*.MA": true, + "**/*.obj": true, + "**/*.OBJ": true, + "**/*.asset": true, + "**/*.cubemap": true, + "**/*.flare": true, + "**/*.mat": true, + "**/*.meta": true, + "**/*.prefab": true, + "**/*.unity": true, + "build/": true, + "Build/": true, + "Library/": true, + "library/": true, + "obj/": true, + "Obj/": true, + "ProjectSettings/": true, + "temp/": true, + "Temp/": true }, - "dotnet.defaultSolution": "Assets/FishNet/CodeGenerating/cecil-0.11.4/Mono.Cecil.sln", + "dotnet.defaultSolution": "tanks-fishnet.sln", "cmake.sourceDirectory": "C:/Users/micro/Unity Projects/example-tanks-unity-mirror/Library/PackageCache/com.unity.ide.visualstudio@2.0.18/Editor/COMIntegration/COMIntegration~" } \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/FishNet/Plugins/CodeAnalysis/FishNet.CodeAnalysis.Analyzers.dll.meta b/unity/tanks-fishnet/Assets/FishNet/Plugins/CodeAnalysis/FishNet.CodeAnalysis.Analyzers.dll.meta deleted file mode 100644 index 73913eb..0000000 --- a/unity/tanks-fishnet/Assets/FishNet/Plugins/CodeAnalysis/FishNet.CodeAnalysis.Analyzers.dll.meta +++ /dev/null @@ -1,72 +0,0 @@ -fileFormatVersion: 2 -guid: 1907658b89c1bbe42a0063df40b7ca24 -labels: -- RoslynAnalyzer -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: - - UNITY_2020_3_OR_NEWER - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: None - - first: - Windows Store Apps: WindowsStoreApps - second: - enabled: 0 - settings: - CPU: AnyCPU - userData: - assetBundleName: - assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/FishNet/Plugins/CodeAnalysis/FishNet.CodeAnalysis.dll.meta b/unity/tanks-fishnet/Assets/FishNet/Plugins/CodeAnalysis/FishNet.CodeAnalysis.dll.meta deleted file mode 100644 index 9d99248..0000000 --- a/unity/tanks-fishnet/Assets/FishNet/Plugins/CodeAnalysis/FishNet.CodeAnalysis.dll.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: 620557a5e202e644cb322b8fcc9422ea -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 1 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - - first: - Windows Store Apps: WindowsStoreApps - second: - enabled: 0 - settings: - CPU: AnyCPU - userData: - assetBundleName: - assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/FishNet/Runtime/Transporting/Transports/Tugboat/LiteNetLib/NetPeer.cs b/unity/tanks-fishnet/Assets/FishNet/Runtime/Transporting/Transports/Tugboat/LiteNetLib/NetPeer.cs index 0ebf508..1085b81 100644 --- a/unity/tanks-fishnet/Assets/FishNet/Runtime/Transporting/Transports/Tugboat/LiteNetLib/NetPeer.cs +++ b/unity/tanks-fishnet/Assets/FishNet/Runtime/Transporting/Transports/Tugboat/LiteNetLib/NetPeer.cs @@ -17,11 +17,11 @@ namespace LiteNetLib [Flags] public enum ConnectionState : byte { - Outgoing = 1 << 1, - Connected = 1 << 2, + Outgoing = 1 << 1, + Connected = 1 << 2, ShutdownRequested = 1 << 3, - Disconnected = 1 << 4, - EndPointChange = 1 << 5, + Disconnected = 1 << 4, + EndPointChange = 1 << 5, Any = Outgoing | Connected | ShutdownRequested | EndPointChange } @@ -161,7 +161,7 @@ private class IncomingFragments /// /// Current one-way ping (RTT/2) in milliseconds /// - public int Ping => _avgRtt/2; + public int Ping => _avgRtt / 2; /// /// Round trip time in milliseconds @@ -212,7 +212,7 @@ internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id) _connectionState = ConnectionState.Connected; _mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize); _pongPacket = new NetPacket(PacketProperty.Pong, 0); - _pingPacket = new NetPacket(PacketProperty.Ping, 0) {Sequence = 1}; + _pingPacket = new NetPacket(PacketProperty.Ping, 0) { Sequence = 1 }; _unreliableChannel = new Queue(); _holdedFragments = new Dictionary(); @@ -268,7 +268,7 @@ private void OverrideMtu(int mtuValue) public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered) { int idx = channelNumber * NetConstants.ChannelTypeCount + - (byte) (ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered); + (byte)(ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered); var channel = _channels[idx]; return channel != null ? ((ReliableChannel)channel).PacketsInQueue : 0; } @@ -312,7 +312,7 @@ public void SendPooledPacket(PooledPacket packet, int userDataSize) } else { - lock(_unreliableChannel) + lock (_unreliableChannel) _unreliableChannel.Enqueue(packet._packet); } } @@ -626,7 +626,7 @@ private void Send_Internal( ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId); - for(ushort partIdx = 0; partIdx < totalPackets; partIdx++) + for (ushort partIdx = 0; partIdx < totalPackets; partIdx++) { int sendLength = length > packetDataSize ? packetDataSize : length; @@ -654,7 +654,7 @@ private void Send_Internal( if (channel == null) //unreliable { - lock(_unreliableChannel) + lock (_unreliableChannel) _unreliableChannel.Enqueue(packet); } else @@ -861,7 +861,7 @@ internal ShutdownResult Shutdown(byte[] data, int start, int length, bool force) Interlocked.Exchange(ref _timeSinceLastPacket, 0); //send shutdown packet - _shutdownPacket = new NetPacket(PacketProperty.Disconnect, length) {ConnectionNumber = _connectNum}; + _shutdownPacket = new NetPacket(PacketProperty.Disconnect, length) { ConnectionNumber = _connectNum }; FastBitConverter.GetBytes(_shutdownPacket.RawData, 1, _connectTime); if (_shutdownPacket.Size >= _mtu) { @@ -883,7 +883,7 @@ private void UpdateRoundTripTime(int roundTripTime) { _rtt += roundTripTime; _rttCount++; - _avgRtt = _rtt/_rttCount; + _avgRtt = _rtt / _rttCount; _resendDelay = 25.0 + _avgRtt * 2.1; // 25 ms + double rtt } @@ -939,7 +939,7 @@ internal void AddReliablePacket(DeliveryMethod method, NetPacket p) var fragment = fragments[i]; int writtenSize = fragment.Size - NetConstants.FragmentedHeaderTotalSize; - if (pos+writtenSize > resultingPacket.RawData.Length) + if (pos + writtenSize > resultingPacket.RawData.Length) { _holdedFragments.Remove(packetFragId); NetDebug.WriteError("Fragment error pos: {0} >= resultPacketSize: {1} , totalSize: {2}", @@ -1003,7 +1003,7 @@ private void ProcessMtuPacket(NetPacket packet) packet.Property = PacketProperty.MtuOk; NetManager.SendRawAndRecycle(packet, _remoteEndPoint); } - else if(receivedMtu > _mtu && !_finishMtu) //MtuOk + else if (receivedMtu > _mtu && !_finishMtu) //MtuOk { //invalid packet if (receivedMtu != NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer) @@ -1011,7 +1011,7 @@ private void ProcessMtuPacket(NetPacket packet) lock (_mtuMutex) { - SetMtu(_mtuIdx+1); + SetMtu(_mtuIdx + 1); } //if maxed - finish. if (_mtuIdx == NetConstants.PossibleMtu.Length - 1) @@ -1073,7 +1073,7 @@ internal ConnectRequestResult ProcessConnectRequest(NetConnectRequestPacket conn { var remoteBytes = _remoteEndPoint.Serialize(); var localBytes = connRequest.TargetAddress; - for (int i = remoteBytes.Size-1; i >= 0; i--) + for (int i = remoteBytes.Size - 1; i >= 0; i--) { byte rb = remoteBytes[i]; if (rb == localBytes[i]) @@ -1173,7 +1173,7 @@ internal void ProcessPacket(NetPacket packet) { _pingTimer.Stop(); int elapsedMs = (int)_pingTimer.ElapsedMilliseconds; - _remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (elapsedMs * TimeSpan.TicksPerMillisecond ) / 2 - DateTime.UtcNow.Ticks; + _remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (elapsedMs * TimeSpan.TicksPerMillisecond) / 2 - DateTime.UtcNow.Ticks; UpdateRoundTripTime(elapsedMs); NetManager.ConnectionLatencyUpdated(this, elapsedMs / 2); NetDebug.Write("[PP]Ping: {0} - {1} - {2}", packet.Sequence, elapsedMs, _remoteDelta); diff --git a/unity/tanks-fishnet/Assets/LobbyConfig.cs b/unity/tanks-fishnet/Assets/LobbyConfig.cs index f01ffc4..cb52342 100644 --- a/unity/tanks-fishnet/Assets/LobbyConfig.cs +++ b/unity/tanks-fishnet/Assets/LobbyConfig.cs @@ -1,5 +1,6 @@ using FishNet.Object; using FishNet.Object.Synchronizing; +using Rivet; using UnityEngine; public enum GameMode @@ -12,6 +13,7 @@ public enum GameMode public class LobbyConfig : NetworkBehaviour { private RivetManager _rm; + private Server _server; [SyncVar] public GameMode gameMode = GameMode.ModeA; [SyncVar] public float moveSpeed = 5f; @@ -19,6 +21,7 @@ public class LobbyConfig : NetworkBehaviour void Start() { _rm = FindObjectOfType(); + _server = FindObjectOfType(); if (IsServer) { @@ -31,7 +34,7 @@ void Start() public override void OnStartNetwork() { base.OnStartNetwork(); - + // Update UI if (IsClient) { @@ -50,7 +53,7 @@ private void UpdateConfig() // Update game mode if (!Application.isEditor) { - switch (_rm.gameModeName) + switch (_server.gameModeName) { case "mode-a": gameMode = GameMode.ModeA; @@ -62,13 +65,13 @@ private void UpdateConfig() gameMode = GameMode.Custom; break; default: - Debug.LogError("Invalid game mode name: " + _rm.gameModeName); + Debug.LogError("Invalid game mode name: " + _server.gameModeName); break; } } // Update properties - var lc = _rm.LobbyConfig; + var lc = _server.LobbyConfig; if (lc == null) return; moveSpeed = (float)lc["move_speed"]; } diff --git a/unity/tanks-fishnet/Assets/Rivet.meta b/unity/tanks-fishnet/Assets/Rivet.meta new file mode 100644 index 0000000..6f55048 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 340c7fefc29a24a948e0af4516b94e7f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Dockerfile b/unity/tanks-fishnet/Assets/Rivet/Dockerfile new file mode 100644 index 0000000..d7e395d --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Dockerfile @@ -0,0 +1,22 @@ +# MARK: Runner +FROM debian:12 +WORKDIR /app + +# Install necessary libraries for Unity +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && update-ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + && useradd -ms /bin/bash rivet + +# Copy the precompiled Unity server files +COPY builds/LinuxServer/ /app +RUN ls /app && chmod +x /app/LinuxServer.x86_64 + +# Change to user rivet +USER rivet + +ENV UNITY_SERVER=1 + +# Run the server +ENTRYPOINT ["/app/LinuxServer.x86_64", "-batchmode", "-nographics"] diff --git a/unity/tanks-fishnet/Assets/FishNet/CodeGenerating/cecil-0.11.4/Mono.Cecil.sln.meta b/unity/tanks-fishnet/Assets/Rivet/Dockerfile.meta similarity index 74% rename from unity/tanks-fishnet/Assets/FishNet/CodeGenerating/cecil-0.11.4/Mono.Cecil.sln.meta rename to unity/tanks-fishnet/Assets/Rivet/Dockerfile.meta index aa8a077..1a0b7a3 100644 --- a/unity/tanks-fishnet/Assets/FishNet/CodeGenerating/cecil-0.11.4/Mono.Cecil.sln.meta +++ b/unity/tanks-fishnet/Assets/Rivet/Dockerfile.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 246f31a0e00fea74a93125fec6d80da8 +guid: 13b71fb9c7cd14e2294b40b89dcef233 DefaultImporter: externalObjects: {} userData: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor.meta b/unity/tanks-fishnet/Assets/Rivet/Editor.meta new file mode 100644 index 0000000..4e5583c --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5043f2f84714f4e3ca72b93f6a18c333 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/BuildPipeline.cs b/unity/tanks-fishnet/Assets/Rivet/Editor/BuildPipeline.cs new file mode 100644 index 0000000..0d01747 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/BuildPipeline.cs @@ -0,0 +1,54 @@ +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; +using System.IO; + +namespace Rivet +{ + public class BuildScript : IPreprocessBuildWithReport, IPostprocessBuildWithReport + { + public int callbackOrder { get { return 0; } } + + public void OnPreprocessBuild(BuildReport report) + { + // Check if StreamingAssets folder exists and create it if it doesn't + string streamingAssetsPath = Application.streamingAssetsPath; + if (!Directory.Exists(streamingAssetsPath)) + { + Directory.CreateDirectory(streamingAssetsPath); + } + + // If either is null, add a build error + if (string.IsNullOrEmpty(ExtensionData.ApiEndpoint)) + { + Debug.LogError("Rivet API endpoint is not set. Please set the API endpoint in the Rivet settings."); + } + + if (string.IsNullOrEmpty(ExtensionData.RivetToken)) + { + Debug.LogError("Rivet token is not set. Please set the Rivet token in the Rivet settings."); + } + + // Create the asset file before the build + RivetSettings data = new RivetSettings + { + ApiEndpoint = ExtensionData.ApiEndpoint, + RivetToken = ExtensionData.RivetToken + }; + + string json = JsonUtility.ToJson(data); + string filePath = Path.Combine(Application.streamingAssetsPath, "rivet_export.json"); + File.WriteAllText(filePath, json); + } + + public void OnPostprocessBuild(BuildReport report) + { + // Delete the asset file after the build + string filePath = Path.Combine(Application.streamingAssetsPath, "rivet_export.json"); + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + } + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/BuildPipeline.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/BuildPipeline.cs.meta new file mode 100644 index 0000000..e0c7a96 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/BuildPipeline.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 31fc2096397c426c68832f49183d6abf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/ExportPlugin.cs b/unity/tanks-fishnet/Assets/Rivet/Editor/ExportPlugin.cs new file mode 100644 index 0000000..6fde49c --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/ExportPlugin.cs @@ -0,0 +1,15 @@ +using System.Linq; +using UnityEditor; +using UnityEngine; + +public class ExportPackage +{ + [MenuItem("Assets/Export My Plugin")] + public static void Export() + { + string[] projectContent = AssetDatabase.GetAllAssetPaths(); + var assetsToExport = projectContent.Where(path => path.StartsWith("Assets/Rivet")).ToArray(); + AssetDatabase.ExportPackage(assetsToExport, "Rivet.unitypackage", ExportPackageOptions.Recurse); + Debug.Log("Project Exported"); + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/ExportPlugin.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/ExportPlugin.cs.meta new file mode 100644 index 0000000..8605280 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/ExportPlugin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54ef17bce8e7748758018b7081a61f0f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Images.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/Images.meta new file mode 100644 index 0000000..c3303eb --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Images.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05e473322652c4d059fa03b7d47a12fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-black.png b/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-black.png new file mode 100644 index 0000000..c91fbd1 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-black.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8938891db55c77a3cf7a1b10858076271f047639aaee4ae429ee5e953ad6ff5f +size 2831 diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-black.png.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-black.png.meta new file mode 100644 index 0000000..76b3a14 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-black.png.meta @@ -0,0 +1,140 @@ +fileFormatVersion: 2 +guid: 292132c72bd824b278283c5351e3cad0 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-white.png b/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-white.png new file mode 100644 index 0000000..d3947b4 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-white.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5aac287ec895eeeacb00b390033b0b3a9817c1000f1367ba70361a5c980527d +size 2828 diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-white.png.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-white.png.meta new file mode 100644 index 0000000..a96d648 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Images/icon-text-white.png.meta @@ -0,0 +1,140 @@ +fileFormatVersion: 2 +guid: c8a6d5bc1169a4c7ead9581401233b7b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Result.cs b/unity/tanks-fishnet/Assets/Rivet/Editor/Result.cs new file mode 100644 index 0000000..a2515af --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Result.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; + +namespace Rivet +{ + // https://josef.codes/my-take-on-the-result-class-in-c-sharp/ + public abstract class Result + { + public bool Success { get; protected set; } + public bool Failure => !Success; + } + + public abstract class Result : Result + { + private T _data; + + protected Result(T data) + { + Data = data; + } + + public T Data + { + get => Success ? _data : throw new Exception($"You can't access .{nameof(Data)} when .{nameof(Success)} is false"); + set => _data = value; + } + } + + public class SuccessResult : Result + { + public SuccessResult() + { + Success = true; + } + } + + public class SuccessResult : Result + { + public SuccessResult(T data) : base(data) + { + Success = true; + } + } + + public class ErrorResult : Result, IErrorResult + { + public ErrorResult(string message) : this(message, Array.Empty()) + { + } + + public ErrorResult(string message, IReadOnlyCollection errors) + { + Message = message; + Success = false; + Errors = errors ?? Array.Empty(); + } + + public string Message { get; } + public IReadOnlyCollection Errors { get; } + } + + public class ErrorResult : Result, IErrorResult + { + public ErrorResult(string message) : this(message, Array.Empty()) + { + } + + public ErrorResult(string message, IReadOnlyCollection errors) : base(default) + { + Message = message; + Success = false; + Errors = errors ?? Array.Empty(); + } + + public string Message { get; set; } + public IReadOnlyCollection Errors { get; } + } + + public class Error + { + public Error(string details) : this(null, details) + { + + } + + public Error(string code, string details) + { + Code = code; + Details = details; + } + + public string Code { get; } + public string Details { get; } + } + + internal interface IErrorResult + { + string Message { get; } + IReadOnlyCollection Errors { get; } + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Result.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/Result.cs.meta new file mode 100644 index 0000000..095a9cb --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Result.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2e6722c072dc45198427642016e356d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/RivetCLI.cs b/unity/tanks-fishnet/Assets/Rivet/Editor/RivetCLI.cs new file mode 100644 index 0000000..ed96c29 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/RivetCLI.cs @@ -0,0 +1,120 @@ +using System.IO; +using Newtonsoft.Json.Linq; + +namespace Rivet +{ + public static class RivetCLI + { + public const string REQUIRED_RIVET_CLI_VERSION = "v1.0.1"; + public const string RIVET_CLI_PATH_SETTING = "RivetCLIPath"; + + public static bool CLIInstalled() + { + string editorRivetPath = GetRivetCLIPath(); + + if (string.IsNullOrEmpty(editorRivetPath)) + { + return false; + } + + var result = RunCommand("sidekick", "get-cli-version"); + switch (result) + { + case SuccessResult successResult: + // Verify that the version that came back is correct + if (successResult.Data["Ok"] == null) + { + return false; + } + var cliVersion = successResult.Data["Ok"]["version"].ToString(); + if (cliVersion != REQUIRED_RIVET_CLI_VERSION) + { + return false; + } + return true; + default: + return false; + } + } + + public static Result RunCommand(params string[] args) + { + return RunRivetCLI(args); + } + + public static string GetBinDir() + { + string homePath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile); + return Path.Combine(homePath, ".rivet", REQUIRED_RIVET_CLI_VERSION, "bin"); + } + + public static string GetRivetCLIPath() + { + return Path.Combine(GetBinDir(), "rivet"); + } + + public static Result RunRivetCLI(params string[] args) + { + // TODO: Turn this on if debug is enabled + // UnityEngine.Debug.Log($"Running Rivet CLI: {GetRivetCLIPath()} {string.Join(" ", args)}"); + + if (!File.Exists(GetRivetCLIPath())) + { + // TODO: Turn this on if debug is enabled + // UnityEngine.Debug.LogError("File does not exist: " + GetRivetCLIPath()); + return new ErrorResult("File does not exist: " + GetRivetCLIPath()); + } + + var startInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = GetRivetCLIPath(), + Arguments = string.Join(" ", args), + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + try + { + using var process = System.Diagnostics.Process.Start(startInfo); + var output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + return new SuccessResult(JObject.Parse(output)); + } + catch (System.Exception ex) + { + return new ErrorResult("Failed to start process: " + ex.Message); + } + } + + public static Result Install() + { + string bin_dir = GetBinDir(); + + System.Environment.SetEnvironmentVariable("RIVET_CLI_VERSION", REQUIRED_RIVET_CLI_VERSION); + System.Environment.SetEnvironmentVariable("BIN_DIR", bin_dir); + + var process = new System.Diagnostics.Process(); + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + { + process.StartInfo.FileName = "powershell.exe"; + process.StartInfo.Arguments = "-Command \"iwr https://raw.githubusercontent.com/rivet-gg/cli/$env:RIVET_CLI_VERSION/install/windows.ps1 -useb | iex\""; + } + else + { + process.StartInfo.FileName = "/bin/sh"; + process.StartInfo.Arguments = "-c \"curl -fsSL https://raw.githubusercontent.com/rivet-gg/cli/${RIVET_CLI_VERSION}/install/unix.sh | sh\""; + } + + process.Start(); + string output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + return new SuccessResult(output); + } + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/RivetCLI.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/RivetCLI.cs.meta new file mode 100644 index 0000000..3f3aeeb --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/RivetCLI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 111d28c48b6bf457380774e98e995473 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/RivetPlugin.cs b/unity/tanks-fishnet/Assets/Rivet/Editor/RivetPlugin.cs new file mode 100644 index 0000000..24b1adb --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/RivetPlugin.cs @@ -0,0 +1,140 @@ +using UnityEngine; +using UnityEditor; + +namespace Rivet +{ + /// + /// Provides extension data for the Rivet plugin. + /// + public static class ExtensionData + { + private static string rivetToken; + private static string apiEndpoint = "https://api.rivet.gg"; + + /// + /// Gets or sets the Rivet token. + /// + /// + /// The Rivet token is used for authentication with the Rivet API. + /// When the token is set, it is also stored in the PlayerPrefs for persistence. + /// + public static string RivetToken + { + get { return rivetToken; } + set + { + rivetToken = value; + // This might not be called from the main thread, so we need to + // delay the call to PlayerPrefs + UnityEditor.EditorApplication.delayCall += () => + { + PlayerPrefs.SetString("RivetToken", value); + }; + } + } + + /// + /// Gets or sets the API endpoint. + /// + /// + /// The API endpoint is the base URL for the Rivet API. + /// When the endpoint is set, it is also stored in the PlayerPrefs for persistence. + /// + public static string ApiEndpoint + { + get { return apiEndpoint; } + set + { + apiEndpoint = value; + // This might not be called from the main thread, so we need to + // delay the call to PlayerPrefs + UnityEditor.EditorApplication.delayCall += () => + { + PlayerPrefs.SetString("ApiEndpoint", value); + }; + } + } + } + + namespace Rivet + { + public class RivetPluginWindow : EditorWindow + { + public string ApiEndpoint + { + get { return ExtensionData.ApiEndpoint; } + set { ExtensionData.ApiEndpoint = value; } + } + + public string RivetToken + { + get { return ExtensionData.RivetToken; } + set { ExtensionData.RivetToken = value; } + } + + // Define an interface for the states + public interface IState + { + void OnEnter(RivetPluginWindow pluginWindow); + void OnGUI(); + } + + [MenuItem("Window/Rivet Plugin")] + public static void ShowWindow() + { + GetWindow("Rivet Plugin"); + } + + // Add a variable to hold the current state + public IState currentState; + + // Add a method to handle the state transitions + public void TransitionToState(IState newState) + { + currentState = newState; + currentState.OnEnter(this); + } + + private Texture2D rivetLogo; + + void OnGUI() + { + // Create an outer vertical container + GUILayout.BeginVertical(); + + // Draw the Rivet logo + if (rivetLogo != null) + { + GUILayout.Label(rivetLogo); + } + else + { + // Load the Rivet logo + rivetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/Rivet/Editor/Images/icon-text-white.png", typeof(Texture2D)); + Debug.Log("Rivet logo not found"); + } + + // Draw the global Rivet buttons + + // Links + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Hub")) Application.OpenURL("https://hub.rivet.gg/"); + if (GUILayout.Button("Docs")) Application.OpenURL("https://rivet.gg/docs"); + if (GUILayout.Button("Discord")) Application.OpenURL("https://rivet.gg/discord"); + GUILayout.EndHorizontal(); + + // Call the OnGUI method of the current state + currentState.OnGUI(); + + // End the vertical container + GUILayout.EndVertical(); + } + + void OnEnable() + { + // Initialize the state machine + TransitionToState(new Installer()); + } + } + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/RivetPlugin.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/RivetPlugin.cs.meta new file mode 100644 index 0000000..6451381 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/RivetPlugin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d2c369bd409046d288bb3600eaa5c79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Windows.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows.meta new file mode 100644 index 0000000..a3314c0 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1ad51a974ce3f4d979ad6b27d2e252ba +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Installer.cs b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Installer.cs new file mode 100644 index 0000000..f17c5a5 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Installer.cs @@ -0,0 +1,74 @@ +using UnityEngine; +using UnityEditor; +using Rivet.Rivet; + +namespace Rivet +{ + public class Installer : RivetPluginWindow.IState + { + public RivetPluginWindow window; + public string installLabelText; + public bool installButtonEnabled; + + public Installer() + { + } + + public void OnEnter(RivetPluginWindow pluginWindow) + { + this.window = pluginWindow; + + // Prepare the installer + installLabelText = ReplacePlaceholders("%%version%% %%bin_dir%%"); + installButtonEnabled = false; + + // Start a new thread + new System.Threading.Thread(() => + { + if (RivetCLI.CLIInstalled()) + { + // Change to the Login screen + window.TransitionToState(new Login()); + } + else + { + installButtonEnabled = true; + } + }).Start(); + } + + public void OnGUI() + { + // Code to run every frame in the Installer state + EditorGUILayout.LabelField(installLabelText); + EditorGUI.BeginDisabledGroup(!installButtonEnabled); + if (GUILayout.Button("Install")) + { + // Handle the Install button click + new System.Threading.Thread(() => + { + var result = RivetCLI.Install(); + + switch (result) + { + case SuccessResult successResult: + // Change to the Login screen + window.TransitionToState(new Login()); + break; + case ErrorResult errorResult: + // Debug the error + UnityEngine.Debug.LogError(errorResult.Message); + break; + } + }).Start(); + } + EditorGUI.EndDisabledGroup(); + } + + public string ReplacePlaceholders(string text) + { + return text.Replace("%%version%%", RivetCLI.REQUIRED_RIVET_CLI_VERSION) + .Replace("%%bin_dir%%", RivetCLI.GetBinDir()); + } + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Installer.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Installer.cs.meta new file mode 100644 index 0000000..5b1e341 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Installer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f0ad3329b8084d5f9a5539b2eef9fa4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Login.cs b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Login.cs new file mode 100644 index 0000000..5163efb --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Login.cs @@ -0,0 +1,145 @@ +using UnityEngine; +using UnityEditor; +using Newtonsoft.Json.Linq; +using Rivet.Rivet; + +namespace Rivet +{ + public class Login : RivetPluginWindow.IState + { + public RivetPluginWindow window; + public string url; + bool loginButtonEnabled = true; + private bool showAdvancedOptions = false; + + public Login() + { + } + + public void OnEnter(RivetPluginWindow pluginWindow) + { + // Initialize the URL + url = ""; + + this.window = pluginWindow; + + // First, check if we're already logged in + var result = RivetCLI.RunCommand( + "sidekick", + "check-login-state"); + + switch (result) + { + case SuccessResult getLinkSuccessResult: + if (getLinkSuccessResult.Data["Ok"] == null) + { + // RivetPluginBridge.DisplayCliError(result); TODO: + UnityEngine.Debug.LogError("Error: " + result.Data); + loginButtonEnabled = true; + return; + } + + window.TransitionToState(new Plugin()); + + break; + } + } + + public void OnGUI() + { + UnityEditor.EditorGUI.BeginDisabledGroup(!loginButtonEnabled); + + if (GUILayout.Button("Sign in to Rivet")) + { + new System.Threading.Thread(() => + { + // Disable the button sign in button + loginButtonEnabled = false; + + var getLinkResult = RivetCLI.RunCommand( + "--api-endpoint", + window.ApiEndpoint, + "sidekick", + "get-link"); + + switch (getLinkResult) + { + case SuccessResult getLinkSuccessResult: + // Verify that the version that came back is correct + if (getLinkSuccessResult.Data["Ok"] == null) + { + // RivetPluginBridge.DisplayCliError(result); TODO: + UnityEngine.Debug.LogError("Error: " + getLinkResult.Data); + loginButtonEnabled = true; + return; + } + + var data = getLinkSuccessResult.Data["Ok"]; + + // Now that we have the link, open it in the user's browser + EditorApplication.delayCall += () => + { + Application.OpenURL(data["device_link_url"].ToString()); + }; + + // Long-poll the Rivet API until the user has logged in + var waitForLoginResult = RivetCLI.RunCommand( + "--api-endpoint", + window.ApiEndpoint, + "sidekick", + "wait-for-login", + "--device-link-token", + data["device_link_token"].ToString()); + + switch (getLinkResult) + { + case SuccessResult waitForLoginSuccessResult: + if (waitForLoginSuccessResult.Data["Ok"] == null) + { + // RivetPluginBridge.DisplayCliError(result); TODO: + UnityEngine.Debug.LogError("Error: " + getLinkResult.Data); + loginButtonEnabled = true; + return; + } + + window.TransitionToState(new Plugin()); + + break; + + case ErrorResult waitForLoginErrorResult: + // RivetPluginBridge.DisplayCliError(result); TODO: + UnityEngine.Debug.LogError("Error: " + getLinkResult.Data); + loginButtonEnabled = true; + return; + + } + + break; + + case ErrorResult errorResult: + // RivetPluginBridge.DisplayCliError(result); TODO: + UnityEngine.Debug.LogError("Error: " + getLinkResult.Data); + loginButtonEnabled = true; + return; + default: + UnityEngine.Debug.LogError("Error: " + getLinkResult.Data); + loginButtonEnabled = true; + return; + } + }).Start(); + } + + UnityEditor.EditorGUI.EndDisabledGroup(); + + // Display the Advanced Options dropdown + showAdvancedOptions = EditorGUILayout.Foldout(showAdvancedOptions, "Advanced Options"); + + if (showAdvancedOptions) + { + // Display the API Endpoint text box + EditorGUILayout.LabelField("API Endpoint:"); + window.ApiEndpoint = EditorGUILayout.TextField(window.ApiEndpoint); + } + } + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Login.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Login.cs.meta new file mode 100644 index 0000000..693cd8f --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Login.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 842ef04ebec2947d2ac7251e1d94731d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Plugin.cs b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Plugin.cs new file mode 100644 index 0000000..d913b12 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Plugin.cs @@ -0,0 +1,377 @@ +using UnityEngine; +using UnityEditor; +using Newtonsoft.Json.Linq; +using System.Linq; +using Newtonsoft.Json; +using System.Net.Http; +using System.Net; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.IO; +using Rivet.Rivet; + +namespace Rivet +{ + public struct BootstrapData + { + public string ApiEndpoint + { + get { return ExtensionData.ApiEndpoint; } + set { ExtensionData.ApiEndpoint = value; } + } + + [JsonProperty("game_id")] public string GameId; + [JsonProperty("token")] public string CloudToken; + } + + public class Region + { + public string provider; + public string provider_display_name; + public string region_display_name; + public string region_id; + public string region_name_id; + public string universal_region; + } + + public class Namespace + { + public string create_ts; + public string display_name; + public string name_id; + public string namespace_id; + public string version_id; + } + + public class Version + { + public string create_ts; + public string display_name; + public string version_id; + } + + public class Game + { + public List available_regions; + public string create_ts; + public string developer_group_id; + public string display_name; + public string game_id; + public string name_id; + public List namespaces; + public int total_player_count; + public List versions; + } + + public class Watch + { + public string index; + } + + public class Root + { + public Game game; + public Watch watch; + } + + public class GameData + { + public List<(Namespace, Version)> namespaces; + } + + + public class Plugin : RivetPluginWindow.IState + { + public Texture logoTexture; // Assign this in the Unity editor + public GameData gameData; + public int selectedIndex = 0; + public bool showPlaytestOptions = true; + public bool showDeployOptions = false; + public bool showSettingsOptions = false; + public RivetPluginWindow window; + public BootstrapData bootstrapData; + private bool thisMachineSelected = false; + + public void OnEnter(RivetPluginWindow pluginWindow) + { + this.window = pluginWindow; + new System.Threading.Thread(() => + { + GetBootstrapData(); + GetNamespaceToken(); + }).Start(); + } + + private void GetBootstrapData() + { + var getBootstrapResult = RivetCLI.RunCommand( + "sidekick", + "get-bootstrap-data" + ); + + switch (getBootstrapResult) + { + case SuccessResult successResult: + var data = successResult.Data["Ok"]; + // TODO: Deserialize this better + bootstrapData = new BootstrapData + { + ApiEndpoint = data["api_endpoint"].ToString(), + GameId = data["game_id"].ToString(), + CloudToken = data["token"].ToString() + }; + + // Update namespaces + FetchPluginData(); + + break; + case ErrorResult errorResult: + UnityEngine.Debug.LogError(errorResult.Message); + break; + } + } + + public void FetchPluginData() + { + using var httpClient = new HttpClient(); + var request = new HttpRequestMessage(HttpMethod.Get, $"{bootstrapData.ApiEndpoint}/cloud/games/{bootstrapData.GameId}"); + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", bootstrapData.CloudToken); + var response = httpClient.SendAsync(request).Result; + + if (response.StatusCode != HttpStatusCode.OK) + { + Debug.LogError("Failed to fetch plugin data"); + return; + } + + var responseBody = response.Content.ReadAsStringAsync().Result; + var root = JsonConvert.DeserializeObject(responseBody); + + var newGameData = new GameData + { + namespaces = new List<(Namespace, Version)>() + }; + + foreach (var ns in root.game.namespaces) + { + var version = root.game.versions.FirstOrDefault(version => version.version_id == ns.version_id); + + if (version != null) + { + newGameData.namespaces.Add((ns, version)); + } + } + + // Return the data to the main thread + gameData = newGameData; + } + + public void OnGUI() + { + GUILayout.BeginVertical(); + + try + { + // Logo + GUILayout.Label(logoTexture, GUILayout.Width(200), GUILayout.Height(200)); + + // Horizontal line + // GUILayout.Label("", GUI.skin.horizontalSlider); + + // Buttons + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Playtest")) + { + showPlaytestOptions = true; + showDeployOptions = false; + showSettingsOptions = false; + new System.Threading.Thread(() => + { + FetchPluginData(); + }).Start(); + } + + if (GUILayout.Button("Deploy")) + { + showDeployOptions = true; + showPlaytestOptions = false; + showSettingsOptions = false; + new System.Threading.Thread(() => + { + FetchPluginData(); + }).Start(); + } + + if (GUILayout.Button("Settings")) { showSettingsOptions = true; showDeployOptions = false; showPlaytestOptions = false; } + GUILayout.EndHorizontal(); + + GUILayout.Space(40); + + // Playtest options + if (showPlaytestOptions) + { + GUILayout.Label("Server"); + GUIStyle buttonStyle = new(GUI.skin.button); + GUIStyle toggleButtonStyle = new(GUI.skin.button) { normal = GUI.skin.button.active }; + + GUILayout.BeginHorizontal(); + bool thisMachineClicked = GUILayout.Button("This machine", thisMachineSelected ? toggleButtonStyle : buttonStyle); + if (thisMachineClicked) thisMachineSelected = true; + + bool rivetServersClicked = GUILayout.Button("Rivet servers", !thisMachineSelected ? toggleButtonStyle : buttonStyle); + if (rivetServersClicked) thisMachineSelected = false; + GUILayout.EndHorizontal(); + + GUILayout.Space(20); + + GUILayout.Label("Namespace"); + if (gameData == null) + { + GUILayout.Label("Loading..."); + } + else + { + var namespaces = gameData.namespaces.Select(space => space.Item1.display_name).ToArray(); + if (namespaces.Length == 0) + { + GUILayout.Label("No namespaces found"); + } + int oldSelectedIndex = selectedIndex; + selectedIndex = EditorGUILayout.Popup(selectedIndex, namespaces); + + if (thisMachineClicked || rivetServersClicked || oldSelectedIndex != selectedIndex) + { + new System.Threading.Thread(() => + { + GetNamespaceToken(); + }).Start(); + } + } + } + + // Deploy options + if (showDeployOptions) + { + GUILayout.Label("Build and deploy server"); + if (gameData == null) + { + GUILayout.Label("Loading..."); + } + else + { + var namespaces = gameData.namespaces.Select(space => space.Item1.display_name).ToArray(); + if (namespaces.Length == 0) + { + GUILayout.Label("No namespaces found"); + } + selectedIndex = EditorGUILayout.Popup(selectedIndex, namespaces); + } + + if (GUILayout.Button("Build and Deploy")) + { + // Update the game token + GetNamespaceToken(); + + // Check if Linux build support is installed + if (!BuildPipeline.IsBuildTargetSupported(BuildTargetGroup.Standalone, BuildTarget.StandaloneLinux64)) + { + Debug.LogError("Linux build support is not installed"); + return; + } + + // Set the build settings + BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions + { + scenes = EditorBuildSettings.scenes.Select(scene => scene.path).ToArray(), + locationPathName = "builds/LinuxServer/LinuxServer.x86_64", // Output path + target = BuildTarget.StandaloneLinux64, // Target platform + subtarget = (int)StandaloneBuildSubtarget.Server // Headless mode for server build + }; + + // Build the player + var result = BuildPipeline.BuildPlayer(buildPlayerOptions); + + // If the build failed, log an error, and don't continue + if (result.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded) + { + Debug.LogError("Build failed: " + result.summary.result); + return; + } + + // Run deploy with CLI + new System.Threading.Thread(() => + { + var result = RivetCLI.RunCommand( + "sidekick", + "--show-terminal", + "deploy", + "--namespace", + gameData.namespaces[selectedIndex].Item1.name_id + ); + }).Start(); + + return; + } + } + + // Settings options + if (showSettingsOptions) + { + if (GUILayout.Button("Unlink game")) + { + new System.Threading.Thread(() => + { + // First, check if we're already logged in + var result = RivetCLI.RunCommand( + "unlink"); + + switch (result) + { + case SuccessResult getLinkSuccessResult: + if (getLinkSuccessResult.Data["Ok"] == null) + { + UnityEngine.Debug.LogError("Error: " + result.Data); + return; + } + + break; + } + + window.TransitionToState(new Login()); + }).Start(); + }; + } + } + finally + { + GUILayout.EndVertical(); + } + } + + /// + /// Retrieves the namespace token based on the selected machine and namespace ID. + /// + private void GetNamespaceToken() + { + var command = thisMachineSelected ? "get-namespace-development-token" : "get-namespace-public-token"; + var namespaceId = gameData.namespaces[selectedIndex].Item1.name_id; + + var result = RivetCLI.RunCommand("sidekick", command, "--namespace", namespaceId); + + switch (result) + { + case SuccessResult successResult: + var token = successResult.Data["Ok"]["token"].ToString(); + window.RivetToken = token; + UnityEditor.EditorApplication.delayCall += () => + { + PlayerPrefs.SetString("RIVET_EDITOR_TOKEN", token); + }; + break; + case ErrorResult errorResult: + UnityEngine.Debug.LogError(errorResult.Message); + break; + } + } + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Plugin.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Plugin.cs.meta new file mode 100644 index 0000000..275eec8 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Editor/Windows/Plugin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eab40917f63af4e008a47dd59a7d6693 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/README.md b/unity/tanks-fishnet/Assets/Rivet/README.md new file mode 100644 index 0000000..85430b3 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/README.md @@ -0,0 +1,93 @@ +# Rivet Unity Plugin + +The Rivet Unity Plugin allows you to connect your game with Rivet, and easily +build and ship your multiplayer games to the world in under a minute. + +More learning content related to Unity and Rivet can be found in the [Rivet +Learning Center](https://rivet.gg/learn/unity). You can also find an example of +this plugin being used in the [Rivet examples +repository](https://github.com/rivet-gg/examples/tree/main/unity/tanks-fishnet). + +# 1.0 Table of Contents + +- [Rivet Unity Plugin](#rivet-unity-plugin) +- [1.0 Table of Contents](#10-table-of-contents) +- [2.0 Plugin Usage](#20-plugin-usage) + - [2.1 Linking](#21-linking) + - [2.2 Playtest Tab](#22-playtest-tab) + - [2.3 Deploy Tab](#23-deploy-tab) + - [2.4 Settings Tab](#24-settings-tab) + - [2.5 API Usage and Reference](#25-api-usage-and-reference) + +# 2.0 Plugin Usage + +## 2.1 Linking + +First, you'll need to link the Rivet Unity Plugin to your project on the Rivet +Hub. You can open the Rivet Unity Plugin window by going to `Window > Rivet +Plugin`. + +At this point, if the Rivet CLI isn't installed, the plugin shows a button that +handles the installation for you. + +After the installation, you'll be prompted to link your project with the Rivet +Hub. By clicking "Sign in with Rivet", a browser window will be opened and let +you select or create a project on the Rivet Hub. + +## 2.2 Playtest Tab + +The playtesting tab allows you to select how your local game and game builds +will connect to Rivet servers. There are two items you can change here: + +- **Server**: This allows you to select if you want to connect to a locally + hosted server with `This machine`, or Rivet servers with `Rivet servers`. More + often, you'll likely want to use Rivet servers so that you can quickly get + others testing the game with publically accessible lobbies. Local testing can + allow you to run a server on your machine for better debugging purposes. +- **Namespace**: This will allow you to choose which namespace you'd like to + connect to. By default, the options are "Production" and "Staging", but others + can be added on the Rivet Hub. + +## 2.3 Deploy Tab + +The Deploy tab allows you to build your game server and upload it to Rivet. You +can choose which namespace you'd like to deploy to. By default, the options are +"Production" and "Staging". + +Staging can be used for testing, or to playtest with others. After you upload to +staging, make sure to go back to the Playtest tab and select the "Staging" +namespace, and change the server to "Rivet Servers". + +Once you're ready to deploy to production, you can select the "Production" +namespace and deploy your server there. As soon as you deploy to production, +your updated server will be live for any future lobbies that are started. + +## 2.4 Settings Tab + +From the settings tab, you may unlink your game. This will bring you back to the +login screen, allowing you to re-link your project if needed. + +## 2.5 API Usage and Reference + +You can find the full Rivet API reference +[here](https://rivet.gg/docs/matchmaker). Items under the Matchmaker API +(`lobbies`, `players`, and `regions`) are all implemented in the Unity plugin. +Here's how you might use the `matchmaker/lobbies/find` endpoint: + +```csharp +// Find lobby and connect with FishNet +var rivetManager = FindObjectOfType(); +StartCoroutine(rivetManager.FindLobby(new FindLobbyRequest +{ + GameModes = new[] { gameMode }, +}, res => +{ + // Connect to server + var port = res.Ports["default"]; + Debug.Log("Connecting to " + port.Hostname + ":" + port.Port); + var networkManager = FindObjectOfType(); + networkManager.ClientManager.StartConnection(port.Hostname, port.Port); + + UpdateConnectionInfo(); +}, fail => { Debug.Log($"Failed to find lobby: {fail}"); })); +``` diff --git a/unity/tanks-fishnet/Assets/Rivet/README.md.meta b/unity/tanks-fishnet/Assets/Rivet/README.md.meta new file mode 100644 index 0000000..b8c5eec --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: eaf19bb4428f643bc9f606eed1d55715 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Runtime.meta b/unity/tanks-fishnet/Assets/Rivet/Runtime.meta new file mode 100644 index 0000000..0f1fc17 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ec74c9f56be464ac1997682685d4d5fd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.cs b/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.cs new file mode 100644 index 0000000..898e05c --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.cs @@ -0,0 +1,394 @@ +#nullable enable +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Serialization; +using UnityEngine; +using UnityEngine.Networking; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.IO; + +namespace Rivet +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum CreateLobbyRequestPublicity + { + [EnumMember(Value = "public")] Public, + [EnumMember(Value = "private")] Private, + } + + public struct FindLobbyRequest + { + [JsonProperty("game_modes")] public string[] GameModes; + [JsonProperty("regions")] public string[]? Regions; + } + + public struct JoinLobbyRequest + { + [JsonProperty("lobby_id")] public string LobbyId; + } + + public struct CreateLobbyRequest + { + [JsonProperty("game_mode")] public string GameMode; + [JsonProperty("region")] public string? Region; + [JsonProperty("publicity")] public CreateLobbyRequestPublicity Publicity; + [JsonProperty("lobby_config")] public JObject? LobbyConfig; + } + + public struct FindLobbyResponse + { + [JsonProperty("lobby")] public RivetLobby Lobby; + [JsonProperty("ports")] public Dictionary Ports; + [JsonProperty("player")] public RivetPlayer Player; + } + + public struct RivetLobby + { + [JsonProperty("lobby_id")] public string LobbyId; + [JsonProperty("host")] public string Host; + [JsonProperty("port")] public int Port; + } + + public struct RivetLobbyPort + { + [JsonProperty("hostname")] public string? Hostname; + [JsonProperty("port")] public ushort Port; + [JsonProperty("is_tls")] public bool IsTls; + } + + public struct RivetPlayer + { + [JsonProperty("token")] public string Token; + } + + [System.Serializable] + public class RivetSettings + { + public string? RivetToken; + public string? ApiEndpoint; + } + + public class RivetManager : MonoBehaviour + { + [HideInInspector] + public string? MatchmakerApiEndpoint => ApiEndpoint + "/matchmaker"; + + /// + /// The response from the last call. Used to maintain information about the Rivet player & + /// lobby. + /// + public FindLobbyResponse? FindLobbyResponse { get; private set; } + + [HideInInspector] + public string? RivetToken => GetRivetToken(); + + [HideInInspector] + public string? ApiEndpoint => GetApiEndpoint(); + + private string? GetRivetToken() + { + string? token = PlayerPrefs.GetString("RivetToken"); + if (string.IsNullOrEmpty(token)) + { + var rivetSettings = LoadRivetSettings(); + if (rivetSettings != null) + { + token = rivetSettings.RivetToken; + } + } + return token; + } + + private string? GetApiEndpoint() + { + string? endpoint = PlayerPrefs.GetString("ApiEndpoint"); + if (string.IsNullOrEmpty(endpoint)) + { + var rivetSettings = LoadRivetSettings(); + if (rivetSettings != null) + { + endpoint = rivetSettings.ApiEndpoint; + } + } + return endpoint; + } + + private RivetSettings? LoadRivetSettings() + { + string filePath = Path.Combine(Application.streamingAssetsPath, "rivet_export.json"); + if (File.Exists(filePath)) + { + string json = File.ReadAllText(filePath); + RivetSettings rivetSettings = JsonUtility.FromJson(json); + return rivetSettings; + } + else + { + Debug.LogError("File not found: " + filePath); + return null; + } + } + + // Start function that debugs the Rivet token and API endpoint + private void Start() + { + Debug.Log("Rivet Token: " + RivetToken); + Debug.Log("API Endpoint: " + ApiEndpoint); + } + + #region API: Matchmaker.Lobbies + + /// + /// Documentation + /// + /// + /// + /// + /// + /// + public IEnumerator FindLobby(FindLobbyRequest request, Action success, + Action fail) + { + yield return PostRequest(MatchmakerApiEndpoint + "/lobbies/find", + request, res => + { + // Save response + FindLobbyResponse = res; + success(res); + }, fail); + } + + /// + /// Documentation + /// + /// + /// + /// + /// + /// + public IEnumerator JoinLobby(JoinLobbyRequest request, Action success, + Action fail) + { + yield return PostRequest(MatchmakerApiEndpoint + "/lobbies/join", + request, res => + { + // Save response + FindLobbyResponse = res; + success(res); + }, fail); + } + + /// + /// Documentation + /// + /// + /// + /// + /// + /// + public IEnumerator CreateLobby(CreateLobbyRequest request, Action success, + Action fail) + { + yield return PostRequest(MatchmakerApiEndpoint + "/lobbies/create", + request, res => + { + // Save response + FindLobbyResponse = res; + success(res); + }, fail); + } + + /// + /// Documentation + /// + /// + /// + /// + /// + public IEnumerator LobbyReady(Action success, Action fail) + { + yield return PostRequest, object>(MatchmakerApiEndpoint + "/lobbies/ready", + new Dictionary(), (_) => success(), fail); + } + + #endregion + + #region API: Matchmaker.Players + + /// + /// Documentation + /// + /// + /// + /// + /// + /// + public IEnumerator PlayerConnected(string playerToken, Action success, Action fail) + { + yield return PostRequest, object>( + MatchmakerApiEndpoint + "/players/connected", + new Dictionary + { + { "player_token", playerToken }, + }, (_) => success(), fail); + } + + /// + /// Documentation + /// + /// + /// + /// + /// + /// + public IEnumerator PlayerDisconnected(string playerToken, Action success, Action fail) + { + yield return PostRequest, object>( + MatchmakerApiEndpoint + "/players/disconnected", new Dictionary + { + { "player_token", playerToken }, + }, (_) => success(), fail); + } + + #endregion + + #region API Requests + + private string GetToken() + { + // Try loading from environment + var token = Environment.GetEnvironmentVariable("RIVET_TOKEN"); + if (token != null && token.Length > 0) + { + return token; + } + + // Try loading from PlayerPrefs + string value = PlayerPrefs.GetString("RIVET_EDITOR_TOKEN"); + if (value.Length > 0) + { + return value; + } + + // Try loading from RivetSettings + if (RivetToken != null && RivetToken.Length > 0) + { + return RivetToken; + } + + throw new Exception("RIVET_TOKEN not set"); + } + + public IEnumerator PostRequest(string url, TReq requestBody, Action success, Action fail, string token = "") + { + if (token.Length == 0) + { + token = GetToken(); + } + + var debugRequestDescription = "POST " + url; + + var requestBodyStr = JsonConvert.SerializeObject(requestBody, + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + + var www = UnityWebRequest.Post(url, requestBodyStr, "application/json"); + www.SetRequestHeader("Authorization", "Bearer " + token); + + yield return www.SendWebRequest(); + + switch (www.result) + { + case UnityWebRequest.Result.InProgress: + break; + case UnityWebRequest.Result.Success: + if (www.responseCode == 200) + { + var responseBody = JsonConvert.DeserializeObject(www.downloadHandler.text); + success(responseBody!); + } + else + { + string statusError = "Error status " + www.responseCode + ": " + www.downloadHandler.text; + Debug.LogError(debugRequestDescription + " " + statusError); + fail(statusError); + } + + break; + case UnityWebRequest.Result.ConnectionError: + string connectionError = "ConnectionError: " + www.error; + Debug.LogError(debugRequestDescription + " " + connectionError + "\n" + Environment.StackTrace); + fail(connectionError); + break; + case UnityWebRequest.Result.ProtocolError: + string protocolError = "ProtocolError: " + www.error + " " + www.downloadHandler.text; + Debug.LogError(debugRequestDescription + " " + protocolError + "\n" + Environment.StackTrace); + fail(protocolError); + break; + case UnityWebRequest.Result.DataProcessingError: + string dpe = "DataProcessingError: " + www.error; + Debug.LogError(debugRequestDescription + " " + dpe + "\n" + Environment.StackTrace); + fail(dpe); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public IEnumerator GetRequest(string url, Action success, Action fail, string token = "") + { + if (token.Length == 0) + { + token = GetToken(); + } + + var debugRequestDescription = "GET " + url; + + var www = UnityWebRequest.Get(url); + www.SetRequestHeader("Authorization", "Bearer " + GetToken()); + + yield return www.SendWebRequest(); + + switch (www.result) + { + case UnityWebRequest.Result.InProgress: + break; + case UnityWebRequest.Result.Success: + if (www.responseCode == 200) + { + var responseBody = JsonConvert.DeserializeObject(www.downloadHandler.text); + success(responseBody!); + } + else + { + string statusError = "Error status " + www.responseCode + ": " + www.downloadHandler.text; + Debug.LogError(debugRequestDescription + " " + statusError); + fail(statusError); + } + + break; + case UnityWebRequest.Result.ConnectionError: + string connectionError = "ConnectionError: " + www.error; + Debug.LogError(debugRequestDescription + " " + connectionError + "\n" + Environment.StackTrace); + fail(connectionError); + break; + case UnityWebRequest.Result.ProtocolError: + string protocolError = "ProtocolError: " + www.error + " " + www.downloadHandler.text; + Debug.LogError(debugRequestDescription + " " + protocolError + "\n" + Environment.StackTrace); + fail(protocolError); + break; + case UnityWebRequest.Result.DataProcessingError: + string dpe = "DataProcessingError: " + www.error; + Debug.LogError(debugRequestDescription + " " + dpe + "\n" + Environment.StackTrace); + fail(dpe); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Scripts/RivetManager.cs.meta b/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.cs.meta similarity index 83% rename from unity/tanks-fishnet/Assets/Scripts/RivetManager.cs.meta rename to unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.cs.meta index d05f4b4..955eb8e 100644 --- a/unity/tanks-fishnet/Assets/Scripts/RivetManager.cs.meta +++ b/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 3781f297e673b0940943fedd28d18ef2 +guid: 968401380e77644ab8c094fa37084533 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.prefab b/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.prefab new file mode 100644 index 0000000..405bf28 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9f5631b2574644275381a81a796a8ea79dc3c48bf57fb9d45c3571b48a9dd27 +size 1372 diff --git a/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.prefab.meta b/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.prefab.meta new file mode 100644 index 0000000..9acc544 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/Runtime/RivetManager.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1757080393b12a4fda212c0494ff52f1 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Rivet/package.json b/unity/tanks-fishnet/Assets/Rivet/package.json new file mode 100644 index 0000000..d990de3 --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/package.json @@ -0,0 +1,16 @@ +{ + "name": "com.rivet.rivet", + "displayName": "Rivet", + "version": "24.0.0", + "unity": "2021.3", + "description": "Description of the Rivet package", + "keywords": ["rivet", "unity", "package"], + "author": { + "name": "Forest Anderson", + "email": "forest@rivet.gg", + "url": "https://rivet.gg" + }, + "dependencies": { + "com.unity.nuget.newtonsoft-json": "2.0.2" + } +} diff --git a/unity/tanks-fishnet/Assets/Rivet/package.json.meta b/unity/tanks-fishnet/Assets/Rivet/package.json.meta new file mode 100644 index 0000000..4d7c8fd --- /dev/null +++ b/unity/tanks-fishnet/Assets/Rivet/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0044b76306aa49a93a9b09e523a3a59d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/Scenes/Main.unity b/unity/tanks-fishnet/Assets/Scenes/Main.unity index cef6b68..5c8f384 100644 --- a/unity/tanks-fishnet/Assets/Scenes/Main.unity +++ b/unity/tanks-fishnet/Assets/Scenes/Main.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c814d24c202e78e7dfd356d7b23210392fee79dbe6624eb5bf4d0bb8ddf1663f -size 142302 +oid sha256:3d35461be48c919fed708ead7fec1fbf06438c29a26789533e85cd1aa1ffb717 +size 142452 diff --git a/unity/tanks-fishnet/Assets/Scripts/Player.cs b/unity/tanks-fishnet/Assets/Scripts/Player.cs index 8b99e11..9c6efee 100644 --- a/unity/tanks-fishnet/Assets/Scripts/Player.cs +++ b/unity/tanks-fishnet/Assets/Scripts/Player.cs @@ -1,5 +1,3 @@ -using System.Collections; -using System.Collections.Generic; using FishNet.Managing.Logging; using UnityEngine; using FishNet.Object; diff --git a/unity/tanks-fishnet/Assets/Scripts/RivetAuthenticator.cs b/unity/tanks-fishnet/Assets/Scripts/RivetAuthenticator.cs index a93ac1c..b95008e 100644 --- a/unity/tanks-fishnet/Assets/Scripts/RivetAuthenticator.cs +++ b/unity/tanks-fishnet/Assets/Scripts/RivetAuthenticator.cs @@ -5,7 +5,7 @@ using FishNet.Connection; using FishNet.Managing; using FishNet.Transporting; -using Unity.VisualScripting.Antlr3.Runtime; +using Rivet; using UnityEngine; public class RivetAuthenticator : Authenticator diff --git a/unity/tanks-fishnet/Assets/Scripts/RivetManager.cs b/unity/tanks-fishnet/Assets/Scripts/RivetManager.cs deleted file mode 100644 index 66b5b69..0000000 --- a/unity/tanks-fishnet/Assets/Scripts/RivetManager.cs +++ /dev/null @@ -1,386 +0,0 @@ -#nullable enable -using System; -using System.Collections; -using System.Collections.Generic; -using System.Runtime.Serialization; -using FishNet.Managing; -using FishNet.Transporting; -using UnityEngine; -using UnityEngine.Networking; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; -using UnityEngine.Serialization; - -[JsonConverter(typeof(StringEnumConverter))] -public enum CreateLobbyRequestPublicity -{ - [EnumMember(Value = "public")] Public, - [EnumMember(Value = "private")] Private, -} - -public struct FindLobbyRequest -{ - [JsonProperty("game_modes")] public string[] GameModes; - [JsonProperty("regions")] public string[]? Regions; -} - -public struct JoinLobbyRequest -{ - [JsonProperty("lobby_id")] public string LobbyId; -} - -public struct CreateLobbyRequest -{ - [JsonProperty("game_mode")] public string GameMode; - [JsonProperty("region")] public string? Region; - [JsonProperty("publicity")] public CreateLobbyRequestPublicity Publicity; - [JsonProperty("lobby_config")] public JObject? LobbyConfig; -} - -public struct FindLobbyResponse -{ - [JsonProperty("lobby")] public RivetLobby Lobby; - [JsonProperty("ports")] public Dictionary Ports; - [JsonProperty("player")] public RivetPlayer Player; -} - -public struct RivetLobby -{ - [JsonProperty("lobby_id")] public string LobbyId; - [JsonProperty("host")] public string Host; - [JsonProperty("port")] public int Port; -} - -public struct RivetLobbyPort -{ - [JsonProperty("hostname")] public string? Hostname; - [JsonProperty("port")] public ushort Port; - [JsonProperty("is_tls")] public bool IsTls; -} - -public struct RivetPlayer -{ - [JsonProperty("token")] public string Token; -} - -public class RivetManager : MonoBehaviour -{ - const string MatchmakerApiEndpoint = "https://api.staging2.gameinc.io/matchmaker"; - - public string? rivetToken = null; - - /// - /// The game mode to start the game with. - /// - /// Only available on the server. - /// - [HideInInspector] public string? gameModeName = null; - - /// - /// The lobby config provided for a custom lobby. - /// - private string? _lobbyConfigRaw = null; - - // Parse LobbyConfigRaw to JObject - public JObject? LobbyConfig => _lobbyConfigRaw != null ? JObject.Parse(_lobbyConfigRaw) : null; - - #region References - - private NetworkManager _networkManager = null!; - private RivetAuthenticator _authenticator = null!; - - #endregion - - /// - /// The response from the last call. Used to maintain information about the Rivet player & - /// lobby. - /// - public FindLobbyResponse? FindLobbyResponse { get; private set; } - - private void Start() - { - _networkManager = FindObjectOfType(); - - // Configure client authentication - _networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState; - _networkManager.ClientManager.RegisterBroadcast(OnTokenResponseBroadcast); - - // Start server if testing in editor or running from CLI - if ((Application.isEditor && GetToken().StartsWith("dev_")) || Application.isBatchMode) - { - StartServer(); - } - } - - - #region Server - - private void StartServer() - { - Debug.Log("Starting server on port " + GetServerPort()); - - // Read environment variables - gameModeName = Environment.GetEnvironmentVariable("RIVET_GAME_MODE_NAME"); - _lobbyConfigRaw = Environment.GetEnvironmentVariable("RIVET_LOBBY_CONFIG"); - - // Start server - _networkManager.TransportManager.Transport.SetServerBindAddress("0.0.0.0", IPAddressType.IPv4); - _networkManager.TransportManager.Transport.SetPort(GetServerPort()); - _networkManager.ServerManager.StartConnection(); - _networkManager.ServerManager.OnRemoteConnectionState += (conn, args) => - { - Debug.Log("Remote connection state: " + conn.ClientId + " " + conn.GetAddress() + " " + args.ConnectionState); - }; - - // Create authentication - _authenticator = gameObject.AddComponent(); - _networkManager.ServerManager.SetAuthenticator(_authenticator); - - // Notify Rivet this server can start accepting players - StartCoroutine(LobbyReady(() => { Debug.Log("Lobby ready"); }, _ => { })); - } - - private ushort GetServerPort() - { - var port = Environment.GetEnvironmentVariable("PORT_default"); - return port != null ? ushort.Parse(port) : (ushort) 7770; - } - - #endregion - - #region Authentication - - private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs args) - { - if (args.ConnectionState != LocalConnectionState.Started) - return; - - // Send request - var token = FindLobbyResponse?.Player.Token; - Debug.Log("Sending authenticate token request: " + token); - var pb = new RivetAuthenticator.TokenRequestBroadcast() - { - Token = token - }; - _networkManager.ClientManager.Broadcast(pb); - } - - private void OnTokenResponseBroadcast(RivetAuthenticator.TokenResponseBroadcast trb) - { - Debug.Log("Token response: " + trb.Valid); - string result = (trb.Valid) ? "Token authenticated." : "Token authentication failed."; - _networkManager.Log(result); - } - - #endregion - - #region API: Matchmaker.Lobbies - - /// - /// Documentation - /// - /// - /// - /// - /// - /// - public IEnumerator FindLobby(FindLobbyRequest request, Action success, - Action fail) - { - yield return PostRequest(MatchmakerApiEndpoint + "/lobbies/find", - request, res => - { - // Save response - FindLobbyResponse = res; - - // Connect to server - var port = res.Ports["default"]; - Debug.Log("Connecting to " + port.Hostname + ":" + port.Port); - _networkManager.ClientManager.StartConnection(port.Hostname, port.Port); - - success(res); - }, fail); - } - - /// - /// Documentation - /// - /// - /// - /// - /// - /// - public IEnumerator JoinLobby(JoinLobbyRequest request, Action success, - Action fail) - { - yield return PostRequest(MatchmakerApiEndpoint + "/lobbies/join", - request, res => - { - // Save response - FindLobbyResponse = res; - - // Connect to server - var port = res.Ports["default"]; - Debug.Log("Connecting to " + port.Hostname + ":" + port.Port); - _networkManager.ClientManager.StartConnection(port.Hostname, port.Port); - - success(res); - }, fail); - } - - /// - /// Documentation - /// - /// - /// - /// - /// - /// - public IEnumerator CreateLobby(CreateLobbyRequest request, Action success, - Action fail) - { - yield return PostRequest(MatchmakerApiEndpoint + "/lobbies/create", - request, res => - { - // Save response - FindLobbyResponse = res; - - // Connect to server - var port = res.Ports["default"]; - Debug.Log("Connecting to " + port.Hostname + ":" + port.Port); - _networkManager.ClientManager.StartConnection(port.Hostname, port.Port); - - success(res); - }, fail); - } - - /// - /// Documentation - /// - /// - /// - /// - /// - public IEnumerator LobbyReady(Action success, Action fail) - { - yield return PostRequest, object>(MatchmakerApiEndpoint + "/lobbies/ready", - new Dictionary(), (_) => success(), fail); - } - - #endregion - - #region API: Matchmaker.Players - - /// - /// Documentation - /// - /// - /// - /// - /// - /// - public IEnumerator PlayerConnected(string playerToken, Action success, Action fail) - { - yield return PostRequest, object>( - MatchmakerApiEndpoint + "/players/connected", - new Dictionary - { - { "player_token", playerToken }, - }, (_) => success(), fail); - } - - /// - /// Documentation - /// - /// - /// - /// - /// - /// - public IEnumerator PlayerDisconnected(string playerToken, Action success, Action fail) - { - yield return PostRequest, object>( - MatchmakerApiEndpoint + "/players/disconnected", new Dictionary - { - { "player_token", playerToken }, - }, (_) => success(), fail); - } - - #endregion - - #region API Requests - - private string GetToken() - { - #if UNITY_SERVER - var token = Environment.GetEnvironmentVariable("RIVET_TOKEN"); - if (token != null) - { - return token; - } - #endif - - if (rivetToken != null && rivetToken.Length > 0) - { - return rivetToken; - } - - throw new Exception("RIVET_TOKEN not set"); - } - - private IEnumerator PostRequest(string url, TReq requestBody, Action success, Action fail) - { - var debugRequestDescription = "POST " + url; - - var requestBodyStr = JsonConvert.SerializeObject(requestBody, - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); - Debug.Log(debugRequestDescription + " Request: " + requestBodyStr + "\n" + Environment.StackTrace); - - var www = UnityWebRequest.Post(url, requestBodyStr, "application/json"); - www.SetRequestHeader("Authorization", "Bearer " + GetToken()); - - yield return www.SendWebRequest(); - - switch (www.result) - { - case UnityWebRequest.Result.InProgress: - Debug.Log("In progress"); - break; - case UnityWebRequest.Result.Success: - if (www.responseCode == 200) - { - Debug.Log(debugRequestDescription + " Success: " + www.downloadHandler.text); - var responseBody = JsonConvert.DeserializeObject(www.downloadHandler.text); - success(responseBody!); - } - else - { - string statusError = "Error status " + www.responseCode + ": " + www.downloadHandler.text; - Debug.LogError(debugRequestDescription + " " + statusError); - fail(statusError); - } - - break; - case UnityWebRequest.Result.ConnectionError: - string connectionError = "ConnectionError: " + www.error; - Debug.LogError(debugRequestDescription + " " + connectionError + "\n" + Environment.StackTrace); - fail(connectionError); - break; - case UnityWebRequest.Result.ProtocolError: - string protocolError = "ProtocolError: " + www.error + " " + www.downloadHandler.text; - Debug.LogError(debugRequestDescription + " " + protocolError + "\n" + Environment.StackTrace); - fail(protocolError); - break; - case UnityWebRequest.Result.DataProcessingError: - string dpe = "DataProcessingError: " + www.error; - Debug.LogError(debugRequestDescription + " " + dpe + "\n" + Environment.StackTrace); - fail(dpe); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - #endregion -} \ No newline at end of file diff --git a/unity/tanks-fishnet/Assets/Scripts/RivetUI.cs b/unity/tanks-fishnet/Assets/Scripts/RivetUI.cs index 0d22c3e..4b6aeaf 100644 --- a/unity/tanks-fishnet/Assets/Scripts/RivetUI.cs +++ b/unity/tanks-fishnet/Assets/Scripts/RivetUI.cs @@ -2,9 +2,9 @@ using FishNet.Managing; using FishNet.Transporting; using Newtonsoft.Json.Linq; +using Rivet; using TMPro; using UnityEngine; -using UnityEngine.Serialization; using UnityEngine.UI; public class RivetUI : MonoBehaviour @@ -22,6 +22,7 @@ public class RivetUI : MonoBehaviour private void Start() { + Debug.Log("RivetU2I.Start"); _networkManager = FindObjectOfType(); _rivetManager = FindObjectOfType(); @@ -47,6 +48,7 @@ private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj public void OnClick_Find(string gameMode) { + Debug.Log("Finding lobby..."); // Hide menu joinMenuPanel.SetActive(false); @@ -54,9 +56,17 @@ public void OnClick_Find(string gameMode) StartCoroutine(_rivetManager.FindLobby(new FindLobbyRequest { GameModes = new[] { gameMode }, - }, _ => UpdateConnectionInfo(), fail => { Debug.Log($"Failed to find lobby: {fail}"); })); + }, res => + { + // Connect to server + var port = res.Ports["default"]; + Debug.Log("Connecting to " + port.Hostname + ":" + port.Port); + _networkManager.ClientManager.StartConnection(port.Hostname, port.Port); + + UpdateConnectionInfo(); + }, fail => { Debug.Log($"Failed to find lobby: {fail}"); })); } - + public void OnClick_Join() { // Hide menu @@ -68,7 +78,7 @@ public void OnClick_Join() LobbyId = lobbyIdInputField.text, }, _ => UpdateConnectionInfo(), fail => { Debug.Log($"Failed to join lobby: {fail}"); })); } - + public void OnClick_Create() { // Hide menu @@ -84,7 +94,7 @@ public void OnClick_Create() }, }, _ => UpdateConnectionInfo(), fail => { Debug.Log($"Failed to create lobby: {fail}"); })); } - + public void OnClick_CopyLobbyId() { GUIUtility.systemCopyBuffer = _rivetManager.FindLobbyResponse?.Lobby.LobbyId ?? ""; diff --git a/unity/tanks-fishnet/Assets/Scripts/Server.cs b/unity/tanks-fishnet/Assets/Scripts/Server.cs new file mode 100644 index 0000000..ef8e37f --- /dev/null +++ b/unity/tanks-fishnet/Assets/Scripts/Server.cs @@ -0,0 +1,113 @@ +#nullable enable +using System; +using FishNet.Managing; +using FishNet.Transporting; +using Newtonsoft.Json.Linq; +using Rivet; +using UnityEngine; + +public class Server : MonoBehaviour +{ + + #region References + + private RivetAuthenticator _authenticator = null!; + private NetworkManager _networkManager = null!; + private RivetManager _rivetManager = null!; + + #endregion + + /// + /// The game mode to start the game with. + /// + /// Only available on the server. + /// + [HideInInspector] public string? gameModeName = null; + + /// + /// The lobby config provided for a custom lobby. + /// + private string? _lobbyConfigRaw = null; + + // Parse LobbyConfigRaw to JObject + public JObject? LobbyConfig => _lobbyConfigRaw != null ? JObject.Parse(_lobbyConfigRaw) : null; + + + // Start is called before the first frame update + void Start() + { + _networkManager = FindObjectOfType(); + _rivetManager = FindObjectOfType(); + + // Configure client authentication + _networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState; + _networkManager.ClientManager.RegisterBroadcast(OnTokenResponseBroadcast); + + // Start server if testing in editor or running from CLI + if (Application.isBatchMode) + { + StartServer(); + } + } + + #region Server + + private ushort GetServerPort() + { + var port = Environment.GetEnvironmentVariable("PORT_default"); + return port != null ? ushort.Parse(port) : (ushort)7770; + } + + private void StartServer() + { + Debug.Log("Starting server on port " + GetServerPort()); + + // Read environment variables + gameModeName = Environment.GetEnvironmentVariable("RIVET_GAME_MODE_NAME"); + _lobbyConfigRaw = Environment.GetEnvironmentVariable("RIVET_LOBBY_CONFIG"); + + // Start server + _networkManager.TransportManager.Transport.SetServerBindAddress("0.0.0.0", IPAddressType.IPv4); + _networkManager.TransportManager.Transport.SetPort(GetServerPort()); + _networkManager.ServerManager.StartConnection(); + _networkManager.ServerManager.OnRemoteConnectionState += (conn, args) => + { + Debug.Log("Remote connection state: " + conn.ClientId + " " + conn.GetAddress() + " " + args.ConnectionState); + }; + + // Create authentication + _authenticator = gameObject.AddComponent(); + _networkManager.ServerManager.SetAuthenticator(_authenticator); + + // Notify Rivet this server can start accepting players + StartCoroutine(_rivetManager.LobbyReady(() => { Debug.Log("Lobby ready"); }, _ => { })); + } + #endregion + + #region Authentication + + private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs args) + { + if (args.ConnectionState != LocalConnectionState.Started) + return; + + // Send request + var token = _rivetManager.FindLobbyResponse?.Player.Token; + Debug.Log("Sending authenticate token request: " + token); + var pb = new RivetAuthenticator.TokenRequestBroadcast() + { + Token = token + }; + _networkManager.ClientManager.Broadcast(pb); + } + + private void OnTokenResponseBroadcast(RivetAuthenticator.TokenResponseBroadcast trb) + { + Debug.Log("Token response: " + trb.Valid); + string result = (trb.Valid) ? "Token authenticated." : "Token authentication failed."; + _networkManager.Log(result); + } + + #endregion + +} diff --git a/unity/tanks-fishnet/Assets/Scripts/Server.cs.meta b/unity/tanks-fishnet/Assets/Scripts/Server.cs.meta new file mode 100644 index 0000000..4382b9f --- /dev/null +++ b/unity/tanks-fishnet/Assets/Scripts/Server.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 72c03012efa14404096f106a5024f251 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/StreamingAssets.meta b/unity/tanks-fishnet/Assets/StreamingAssets.meta new file mode 100644 index 0000000..0e34381 --- /dev/null +++ b/unity/tanks-fishnet/Assets/StreamingAssets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a4230ee43ae364ccfb51a20a1fd0eac0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Assets/rivet_export.asset b/unity/tanks-fishnet/Assets/rivet_export.asset new file mode 100644 index 0000000..2d9f221 --- /dev/null +++ b/unity/tanks-fishnet/Assets/rivet_export.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77c12b8992ce7ca6a2b0ac1d9b2df786d50991bd022da0e36a1223e5abed8f5f +size 640 diff --git a/unity/tanks-fishnet/Assets/rivet_export.asset.meta b/unity/tanks-fishnet/Assets/rivet_export.asset.meta new file mode 100644 index 0000000..30f7fa3 --- /dev/null +++ b/unity/tanks-fishnet/Assets/rivet_export.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 47f97534c6e20475185074e379260925 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/tanks-fishnet/Dockerfile b/unity/tanks-fishnet/Dockerfile deleted file mode 100644 index cadd628..0000000 --- a/unity/tanks-fishnet/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM debian:12 - -RUN apt update -y && \ - apt install -y ca-certificates && \ - update-ca-certificates - -RUN useradd -m server -USER server - -COPY --chown=server:server ./build/LinuxServer /build -RUN ls /build && chmod +x /build/LinuxServer.x86_64 - -EXPOSE 7777/udp -ENTRYPOINT ["/build/LinuxServer.x86_64", "-batchmode", "-nographics"] diff --git a/unity/tanks-fishnet/Packages/manifest.json b/unity/tanks-fishnet/Packages/manifest.json index 494f8f8..430c3e6 100644 --- a/unity/tanks-fishnet/Packages/manifest.json +++ b/unity/tanks-fishnet/Packages/manifest.json @@ -5,6 +5,7 @@ "com.unity.nuget.newtonsoft-json": "3.2.1", "com.unity.textmeshpro": "3.0.6", "com.unity.timeline": "1.7.6", + "com.unity.toolchain.macos-arm64-linux-x86_64": "2.0.0", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6", "com.unity.ugui": "1.0.0", "com.unity.visualscripting": "1.9.1", diff --git a/unity/tanks-fishnet/Packages/packages-lock.json b/unity/tanks-fishnet/Packages/packages-lock.json index ab288d7..0615d25 100644 --- a/unity/tanks-fishnet/Packages/packages-lock.json +++ b/unity/tanks-fishnet/Packages/packages-lock.json @@ -139,6 +139,16 @@ }, "url": "https://packages.unity.com" }, + "com.unity.toolchain.macos-arm64-linux-x86_64": { + "version": "2.0.0", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.7", + "com.unity.sysroot.linux-x86_64": "2.0.6" + }, + "url": "https://packages.unity.com" + }, "com.unity.toolchain.win-x86_64-linux-x86_64": { "version": "2.0.6", "depth": 0, diff --git a/unity/tanks-fishnet/ProjectSettings/ProjectSettings.asset b/unity/tanks-fishnet/ProjectSettings/ProjectSettings.asset index 52efcc9..837357e 100644 --- a/unity/tanks-fishnet/ProjectSettings/ProjectSettings.asset +++ b/unity/tanks-fishnet/ProjectSettings/ProjectSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b48ab78fbdbbd38f18ba4dc58a30ac0da160b2e0a863cec367b48f3ece5ad69 -size 21730 +oid sha256:84fd2b18885cac8a2029866dfa0802f8cbc67faded1df064460e562f3804fcbf +size 21941 diff --git a/unity/tanks-fishnet/ProjectSettings/ProjectVersion.txt b/unity/tanks-fishnet/ProjectSettings/ProjectVersion.txt index 21588a1..307ccd3 100644 --- a/unity/tanks-fishnet/ProjectSettings/ProjectVersion.txt +++ b/unity/tanks-fishnet/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.18f1 -m_EditorVersionWithRevision: 2022.3.18f1 (d29bea25151d) +m_EditorVersion: 2022.3.19f1 +m_EditorVersionWithRevision: 2022.3.19f1 (244b723c30a6) diff --git a/unity/tanks-fishnet/rivet.yaml b/unity/tanks-fishnet/rivet.yaml index a194542..c8f2396 100644 --- a/unity/tanks-fishnet/rivet.yaml +++ b/unity/tanks-fishnet/rivet.yaml @@ -29,7 +29,8 @@ matchmaker: enabled: true docker: - dockerfile: Dockerfile + dockerfile: Assets/Rivet/Dockerfile + # Specify that the build ports: default: port: 7770 diff --git a/unity/tanks-fishnet_clone_0/.clone b/unity/tanks-fishnet_clone_0/.clone new file mode 100644 index 0000000..e69de29 diff --git a/unity/tanks-fishnet_clone_0/.parrelsyncarg b/unity/tanks-fishnet_clone_0/.parrelsyncarg new file mode 100644 index 0000000..f7629b7 --- /dev/null +++ b/unity/tanks-fishnet_clone_0/.parrelsyncarg @@ -0,0 +1 @@ +client \ No newline at end of file diff --git a/unity/tanks-fishnet_clone_0/.vscode/extensions.json b/unity/tanks-fishnet_clone_0/.vscode/extensions.json new file mode 100644 index 0000000..ddb6ff8 --- /dev/null +++ b/unity/tanks-fishnet_clone_0/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "visualstudiotoolsforunity.vstuc" + ] +} diff --git a/unity/tanks-fishnet_clone_0/.vscode/launch.json b/unity/tanks-fishnet_clone_0/.vscode/launch.json new file mode 100644 index 0000000..da60e25 --- /dev/null +++ b/unity/tanks-fishnet_clone_0/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Unity", + "type": "vstuc", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/unity/tanks-fishnet_clone_0/.vscode/settings.json b/unity/tanks-fishnet_clone_0/.vscode/settings.json new file mode 100644 index 0000000..be9334c --- /dev/null +++ b/unity/tanks-fishnet_clone_0/.vscode/settings.json @@ -0,0 +1,55 @@ +{ + "files.exclude": { + "**/.DS_Store": true, + "**/.git": true, + "**/.gitmodules": true, + "**/*.booproj": true, + "**/*.pidb": true, + "**/*.suo": true, + "**/*.user": true, + "**/*.userprefs": true, + "**/*.unityproj": true, + "**/*.dll": true, + "**/*.exe": true, + "**/*.pdf": true, + "**/*.mid": true, + "**/*.midi": true, + "**/*.wav": true, + "**/*.gif": true, + "**/*.ico": true, + "**/*.jpg": true, + "**/*.jpeg": true, + "**/*.png": true, + "**/*.psd": true, + "**/*.tga": true, + "**/*.tif": true, + "**/*.tiff": true, + "**/*.3ds": true, + "**/*.3DS": true, + "**/*.fbx": true, + "**/*.FBX": true, + "**/*.lxo": true, + "**/*.LXO": true, + "**/*.ma": true, + "**/*.MA": true, + "**/*.obj": true, + "**/*.OBJ": true, + "**/*.asset": true, + "**/*.cubemap": true, + "**/*.flare": true, + "**/*.mat": true, + "**/*.meta": true, + "**/*.prefab": true, + "**/*.unity": true, + "build/": true, + "Build/": true, + "Library/": true, + "library/": true, + "obj/": true, + "Obj/": true, + "ProjectSettings/": true, + "temp/": true, + "Temp/": true + }, + "dotnet.defaultSolution": "tanks-fishnet_clone_0.sln" +} \ No newline at end of file diff --git a/unity/tanks-fishnet_clone_0/Assets b/unity/tanks-fishnet_clone_0/Assets new file mode 120000 index 0000000..bbdc66b --- /dev/null +++ b/unity/tanks-fishnet_clone_0/Assets @@ -0,0 +1 @@ +/Users/forest/Documents/git/examples/unity/tanks-fishnet/Assets \ No newline at end of file diff --git a/unity/tanks-fishnet_clone_0/Packages/manifest.json b/unity/tanks-fishnet_clone_0/Packages/manifest.json new file mode 100644 index 0000000..430c3e6 --- /dev/null +++ b/unity/tanks-fishnet_clone_0/Packages/manifest.json @@ -0,0 +1,45 @@ +{ + "dependencies": { + "com.unity.collab-proxy": "2.2.0", + "com.unity.feature.development": "1.0.1", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.textmeshpro": "3.0.6", + "com.unity.timeline": "1.7.6", + "com.unity.toolchain.macos-arm64-linux-x86_64": "2.0.0", + "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6", + "com.unity.ugui": "1.0.0", + "com.unity.visualscripting": "1.9.1", + "com.veriorpies.parrelsync": "https://github.com/VeriorPies/ParrelSync.git?path=/ParrelSync", + "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/unity/tanks-fishnet_clone_0/Packages/packages-lock.json b/unity/tanks-fishnet_clone_0/Packages/packages-lock.json new file mode 100644 index 0000000..0615d25 --- /dev/null +++ b/unity/tanks-fishnet_clone_0/Packages/packages-lock.json @@ -0,0 +1,432 @@ +{ + "dependencies": { + "com.unity.collab-proxy": { + "version": "2.2.0", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.editorcoroutines": { + "version": "1.0.0", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "1.0.6", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.feature.development": { + "version": "1.0.1", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.ide.visualstudio": "2.0.22", + "com.unity.ide.rider": "3.0.27", + "com.unity.ide.vscode": "1.2.5", + "com.unity.editorcoroutines": "1.0.0", + "com.unity.performance.profile-analyzer": "1.2.2", + "com.unity.test-framework": "1.1.33", + "com.unity.testtools.codecoverage": "1.2.5" + } + }, + "com.unity.ide.rider": { + "version": "3.0.27", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.22", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.vscode": { + "version": "1.2.5", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.nuget.newtonsoft-json": { + "version": "3.2.1", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.performance.profile-analyzer": { + "version": "1.2.2", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.settings-manager": { + "version": "2.0.1", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.sysroot": { + "version": "2.0.7", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.sysroot.linux-x86_64": { + "version": "2.0.6", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.7" + }, + "url": "https://packages.unity.com" + }, + "com.unity.test-framework": { + "version": "1.1.33", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.testtools.codecoverage": { + "version": "1.2.5", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.0.16", + "com.unity.settings-manager": "1.0.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.textmeshpro": { + "version": "3.0.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.7.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.toolchain.macos-arm64-linux-x86_64": { + "version": "2.0.0", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.7", + "com.unity.sysroot.linux-x86_64": "2.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.toolchain.win-x86_64-linux-x86_64": { + "version": "2.0.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.7", + "com.unity.sysroot.linux-x86_64": "2.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "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.visualscripting": { + "version": "1.9.1", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.veriorpies.parrelsync": { + "version": "https://github.com/VeriorPies/ParrelSync.git?path=/ParrelSync", + "depth": 0, + "source": "git", + "dependencies": {}, + "hash": "141b4e1e31966242a6fb7c495b52ac3d1221bc75" + }, + "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.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.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/unity/tanks-fishnet_clone_0/ProjectSettings b/unity/tanks-fishnet_clone_0/ProjectSettings new file mode 120000 index 0000000..9fdd261 --- /dev/null +++ b/unity/tanks-fishnet_clone_0/ProjectSettings @@ -0,0 +1 @@ +/Users/forest/Documents/git/examples/unity/tanks-fishnet/ProjectSettings \ No newline at end of file diff --git a/unity/tanks-fishnet_clone_0/collabignore.txt b/unity/tanks-fishnet_clone_0/collabignore.txt new file mode 100644 index 0000000..f59ec20 --- /dev/null +++ b/unity/tanks-fishnet_clone_0/collabignore.txt @@ -0,0 +1 @@ +* \ No newline at end of file