diff --git a/README.md b/README.md index 1f2320c..2237d9c 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ To switch between them you have the following options: * Mirror.TelepathyTransport * kcp2k.KcpTransport * Mirror.SimpleWebTransport +* MultiCompiled.KcpWebCombined AuthenticationKey - This is the key the clients need to have on their inspector. It cannot be blank. diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Transport.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Transport.cs index 7e259f2..43b042d 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Transport.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Transport.cs @@ -225,7 +225,9 @@ namespace Mirror // ShoulderRotation.LateUpdate, resulting in projectile // spawns at the point before shoulder rotation. #pragma warning disable UNT0001 // Empty Unity message - public void Update() { } + public abstract void Update(); + + public abstract void Awake(); #pragma warning restore UNT0001 // Empty Unity message /// diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/BiDictionary.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/BiDictionary.cs new file mode 100644 index 0000000..507c85f --- /dev/null +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/BiDictionary.cs @@ -0,0 +1,56 @@ +// +// Source: https://stackoverflow.com/questions/255341/getting-multiple-keys-of-specified-value-of-a-generic-dictionary#255630 +// +using System; +using System.Collections.Generic; + +namespace LightReflectiveMirror +{ + class BiDictionary + { + IDictionary firstToSecond = new Dictionary(); + IDictionary secondToFirst = new Dictionary(); + + public void Add(TFirst first, TSecond second) + { + if (firstToSecond.ContainsKey(first) || + secondToFirst.ContainsKey(second)) + { + throw new ArgumentException("Duplicate first or second"); + } + firstToSecond.Add(first, second); + secondToFirst.Add(second, first); + } + + public bool TryGetByFirst(TFirst first, out TSecond second) + { + return firstToSecond.TryGetValue(first, out second); + } + + public void Remove(TFirst first) + { + secondToFirst.Remove(firstToSecond[first]); + firstToSecond.Remove(first); + } + + public ICollection GetAllKeys() + { + return secondToFirst.Values; + } + + public bool TryGetBySecond(TSecond second, out TFirst first) + { + return secondToFirst.TryGetValue(second, out first); + } + + public TSecond GetByFirst(TFirst first) + { + return firstToSecond[first]; + } + + public TFirst GetBySecond(TSecond second) + { + return secondToFirst[second]; + } + } +} \ No newline at end of file diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/KCP/KcpTransport.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/KCP/KcpTransport.cs index 5b16690..19d347c 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/KCP/KcpTransport.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/KCP/KcpTransport.cs @@ -39,7 +39,7 @@ namespace kcp2k // log statistics for headless servers that can't show them in GUI public bool statisticsLog; - void Awake() + public override void Awake() { KCPConfig conf = new KCPConfig(); @@ -169,7 +169,7 @@ namespace kcp2k public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId); public override void ServerStop() => server.Stop(); - public void Update() + public override void Update() { server.TickIncoming(); server.TickOutgoing(); diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/KcpWebCombined.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/KcpWebCombined.cs new file mode 100644 index 0000000..6ac148f --- /dev/null +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/KcpWebCombined.cs @@ -0,0 +1,268 @@ +using kcp2k; +using Mirror; +using System; +using System.Text; + +namespace MultiCompiled +{ + + public class KcpWebCombined : Transport + { + public Transport[] transports; + + Transport available; + + public override void Awake() + { + transports = new Transport[2] { new KcpTransport(), new SimpleWebTransport() }; + + foreach (Transport transport in transports) + transport.Awake(); + } + + + + public override void Update() + { + foreach (Transport transport in transports) + { + transport.Update(); + } + } + + public override bool Available() + { + // available if any of the transports is available + foreach (Transport transport in transports) + { + if (transport.Available()) + { + return true; + } + } + return false; + } + + #region Client + + public override void ClientConnect(string address) + { + foreach (Transport transport in transports) + { + if (transport.Available()) + { + available = transport; + transport.OnClientConnected = OnClientConnected; + transport.OnClientDataReceived = OnClientDataReceived; + transport.OnClientError = OnClientError; + transport.OnClientDisconnected = OnClientDisconnected; + transport.ClientConnect(address); + return; + } + } + throw new ArgumentException("No transport suitable for this platform"); + } + + public override void ClientConnect(Uri uri) + { + foreach (Transport transport in transports) + { + if (transport.Available()) + { + try + { + available = transport; + transport.OnClientConnected = OnClientConnected; + transport.OnClientDataReceived = OnClientDataReceived; + transport.OnClientError = OnClientError; + transport.OnClientDisconnected = OnClientDisconnected; + transport.ClientConnect(uri); + return; + } + catch (ArgumentException) + { + // transport does not support the schema, just move on to the next one + } + } + } + throw new ArgumentException("No transport suitable for this platform"); + } + + public override bool ClientConnected() + { + return (object)available != null && available.ClientConnected(); + } + + public override void ClientDisconnect() + { + if ((object)available != null) + available.ClientDisconnect(); + } + + public override void ClientSend(int channelId, ArraySegment segment) + { + available.ClientSend(channelId, segment); + } + + #endregion + + #region Server + // connection ids get mapped to base transports + // if we have 3 transports, then + // transport 0 will produce connection ids [0, 3, 6, 9, ...] + // transport 1 will produce connection ids [1, 4, 7, 10, ...] + // transport 2 will produce connection ids [2, 5, 8, 11, ...] + int FromBaseId(int transportId, int connectionId) + { + return connectionId * transports.Length + transportId; + } + + int ToBaseId(int connectionId) + { + return connectionId / transports.Length; + } + + int ToTransportId(int connectionId) + { + return connectionId % transports.Length; + } + + void AddServerCallbacks() + { + // wire all the base transports to my events + for (int i = 0; i < transports.Length; i++) + { + // this is required for the handlers, if I use i directly + // then all the handlers will use the last i + int locali = i; + Transport transport = transports[i]; + + transport.OnServerConnected = (baseConnectionId => + { + OnServerConnected.Invoke(FromBaseId(locali, baseConnectionId)); + }); + + transport.OnServerDataReceived = (baseConnectionId, data, channel) => + { + OnServerDataReceived.Invoke(FromBaseId(locali, baseConnectionId), data, channel); + }; + + transport.OnServerError = (baseConnectionId, error) => + { + OnServerError.Invoke(FromBaseId(locali, baseConnectionId), error); + }; + transport.OnServerDisconnected = baseConnectionId => + { + OnServerDisconnected.Invoke(FromBaseId(locali, baseConnectionId)); + }; + } + } + + // for now returns the first uri, + // should we return all available uris? + public override Uri ServerUri() + { + return transports[0].ServerUri(); + } + + + public override bool ServerActive() + { + // avoid Linq.All allocations + foreach (Transport transport in transports) + { + if (!transport.ServerActive()) + { + return false; + } + } + return true; + } + + public override string ServerGetClientAddress(int connectionId) + { + int baseConnectionId = ToBaseId(connectionId); + int transportId = ToTransportId(connectionId); + return transports[transportId].ServerGetClientAddress(baseConnectionId); + } + + public override bool ServerDisconnect(int connectionId) + { + int baseConnectionId = ToBaseId(connectionId); + int transportId = ToTransportId(connectionId); + return transports[transportId].ServerDisconnect(baseConnectionId); + } + + public override void ServerSend(int connectionId, int channelId, ArraySegment segment) + { + int baseConnectionId = ToBaseId(connectionId); + int transportId = ToTransportId(connectionId); + + for (int i = 0; i < transports.Length; ++i) + { + if (i == transportId) + { + transports[i].ServerSend(baseConnectionId, channelId, segment); + } + } + } + + public override void ServerStart(ushort port) + { + foreach (Transport transport in transports) + { + AddServerCallbacks(); + transport.ServerStart(port); + } + } + + public override void ServerStop() + { + foreach (Transport transport in transports) + { + transport.ServerStop(); + } + } + #endregion + + public override int GetMaxPacketSize(int channelId = 0) + { + // finding the max packet size in a multiplex environment has to be + // done very carefully: + // * servers run multiple transports at the same time + // * different clients run different transports + // * there should only ever be ONE true max packet size for everyone, + // otherwise a spawn message might be sent to all tcp sockets, but + // be too big for some udp sockets. that would be a debugging + // nightmare and allow for possible exploits and players on + // different platforms seeing a different game state. + // => the safest solution is to use the smallest max size for all + // transports. that will never fail. + int mininumAllowedSize = int.MaxValue; + foreach (Transport transport in transports) + { + int size = transport.GetMaxPacketSize(channelId); + mininumAllowedSize = Math.Min(size, mininumAllowedSize); + } + return mininumAllowedSize; + } + + public override void Shutdown() + { + foreach (Transport transport in transports) + { + transport.Shutdown(); + } + } + + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + foreach (Transport transport in transports) + { + builder.AppendLine(transport.ToString()); + } + return builder.ToString().Trim(); + } + } +} diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/SimpleWebTransport/SimpleWebTransport.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/SimpleWebTransport/SimpleWebTransport.cs index 9fe5808..748f6ff 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/SimpleWebTransport/SimpleWebTransport.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/SimpleWebTransport/SimpleWebTransport.cs @@ -78,7 +78,7 @@ namespace Mirror return maxMessageSize; } - void Awake() + public override void Awake() { Log.level = _logLevels; @@ -216,7 +216,7 @@ namespace Mirror return builder.Uri; } - public void Update() + public override void Update() { server?.ProcessMessageQueue(); } diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyTransport.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyTransport.cs index d4ec7cb..54978a5 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyTransport.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyTransport.cs @@ -44,7 +44,7 @@ namespace Mirror // let's only allocate it once. Func enabledCheck; - void Awake() + public override void Awake() { TelepathyConfig conf = new TelepathyConfig(); if (!File.Exists("TelepathyConfig.json")) @@ -162,7 +162,7 @@ namespace Mirror } public override void ServerStop() => server.Stop(); // messages should always be processed in early update - public void Update() + public override void Update() { // note: we need to check enabled in case we set it to false // when LateUpdate already started. diff --git a/UnityProject/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs b/UnityProject/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs index 7a825e2..b2f9a73 100644 --- a/UnityProject/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs +++ b/UnityProject/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs @@ -230,6 +230,7 @@ namespace LightReflectiveMirror GUI.enabled = false; } lrm.serverIP = EditorGUILayout.TextField("LRM Node IP", lrm.serverIP); + lrm.serverPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("LRM Node Port", lrm.serverPort), ushort.MinValue, ushort.MaxValue); lrm.endpointServerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Endpoint Port", lrm.endpointServerPort), ushort.MinValue, ushort.MaxValue); if (lrm.useLoadBalancer) diff --git a/UnityProject/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs b/UnityProject/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs index 5086d16..57cd725 100644 --- a/UnityProject/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs +++ b/UnityProject/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs @@ -11,6 +11,7 @@ namespace LightReflectiveMirror // Connection/auth variables public Transport clientToServerTransport; public string serverIP = null; + public ushort serverPort = 7777; public ushort endpointServerPort = 8080; public float heartBeatInterval = 3; public bool connectOnAwake = true; diff --git a/UnityProject/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs b/UnityProject/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs index a68f718..fb1377b 100644 --- a/UnityProject/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs +++ b/UnityProject/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs @@ -19,11 +19,14 @@ namespace LightReflectiveMirror throw new Exception("LRM | Please switch to .NET 4.x for LRM to function properly!"); #endif + if (Application.platform == RuntimePlatform.WebGLPlayer) + useNATPunch = false; + else + _directConnectModule = GetComponent(); + if (clientToServerTransport is LightReflectiveMirrorTransport) throw new Exception("Haha real funny... Use a different transport."); - _directConnectModule = GetComponent(); - if (_directConnectModule != null) { if (useNATPunch && !_directConnectModule.SupportsNATPunch()) @@ -71,7 +74,7 @@ namespace LightReflectiveMirror { if (!_connectedToRelay) { - Connect(serverIP); + Connect(serverIP, serverPort); } else { @@ -102,7 +105,6 @@ namespace LightReflectiveMirror throw new Exception("LRM | Client to Server Transport cannot be LRM."); SetTransportPort(port); - this.serverIP = serverIP; serverStatus = "Connecting to relay...";