mm with lrm

This commit is contained in:
Sewmina
2022-09-11 15:54:15 +05:30
parent dcddade210
commit 4194d2decc
54 changed files with 118619 additions and 118 deletions

View File

@@ -0,0 +1,74 @@
using Mirror;
using System;
namespace LightReflectiveMirror
{
public partial class LightReflectiveMirrorTransport : Transport
{
public void DirectAddClient(int clientID)
{
if (!_isServer)
return;
_connectedDirectClients.Add(clientID, _currentMemberId);
OnServerConnected?.Invoke(_currentMemberId);
_currentMemberId++;
}
public void DirectRemoveClient(int clientID)
{
if (!_isServer)
return;
OnServerDisconnected?.Invoke(_connectedDirectClients.GetByFirst(clientID));
_connectedDirectClients.Remove(clientID);
}
public void DirectReceiveData(ArraySegment<byte> data, int channel, int clientID = -1)
{
if (_isServer)
OnServerDataReceived?.Invoke(_connectedDirectClients.GetByFirst(clientID), data, channel);
if (_isClient)
OnClientDataReceived?.Invoke(data, channel);
}
public void DirectClientConnected()
{
_directConnected = true;
OnClientConnected?.Invoke();
}
public void DirectDisconnected()
{
if (_directConnected)
{
_isClient = false;
_directConnected = false;
OnClientDisconnected?.Invoke();
}
else
{
int pos = 0;
_directConnected = false;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer);
_clientSendBuffer.WriteString(ref pos, _cachedHostID);
_clientSendBuffer.WriteBool(ref pos, false); // Direct failed, use relay
_isClient = true;
#if MIRROR_40_0_OR_NEWER
clientToServerTransport.ClientSend(new System.ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
#else
clientToServerTransport.ClientSend(0, new System.ArraySegment<byte>(_clientSendBuffer, 0, pos));
#endif
}
if (_clientProxy != null)
{
_clientProxy.Dispose();
_clientProxy = null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e38dc8d43e06744484cf2962c017606
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
using Mirror;
using System;
using System.Collections;
using System.Net;
using UnityEngine;
namespace LightReflectiveMirror
{
public partial class LightReflectiveMirrorTransport : Transport
{
IEnumerator NATPunch(IPEndPoint remoteAddress)
{
for (int i = 0; i < 10; i++)
{
_NATPuncher.Send(_punchData, 1, remoteAddress);
yield return new WaitForSeconds(0.25f);
}
}
void RecvData(IAsyncResult result)
{
IPEndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
var data = _NATPuncher.EndReceive(result, ref newClientEP);
_NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher);
if (!newClientEP.Address.Equals(_relayPuncherIP.Address))
{
if (_isServer)
{
if (_serverProxies.TryGetByFirst(newClientEP, out SocketProxy foundProxy))
{
if (data.Length > 2)
foundProxy.RelayData(data, data.Length);
}
else
{
_serverProxies.Add(newClientEP, new SocketProxy(_NATIP.Port + 1, newClientEP));
_serverProxies.GetByFirst(newClientEP).dataReceived += ServerProcessProxyData;
}
}
if (_isClient)
{
if (_clientProxy == null)
{
_clientProxy = new SocketProxy(_NATIP.Port - 1);
_clientProxy.dataReceived += ClientProcessProxyData;
}
else
{
_clientProxy.ClientRelayData(data, data.Length);
}
}
}
}
void ServerProcessProxyData(IPEndPoint remoteEndpoint, byte[] data)
{
_NATPuncher.Send(data, data.Length, remoteEndpoint);
}
void ClientProcessProxyData(IPEndPoint _, byte[] data)
{
_NATPuncher.Send(data, data.Length, _directConnectEndpoint);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d493f36a877ab042875f198b110ebb3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,271 @@
using Mirror;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using UnityEngine;
namespace LightReflectiveMirror
{
public partial class LightReflectiveMirrorTransport : Transport
{
public override bool ServerActive() => _isServer;
public override bool Available() => _connectedToRelay;
public override void ClientConnect(Uri uri) => ClientConnect(uri.Host);
public override int GetMaxPacketSize(int channelId = 0) => clientToServerTransport.GetMaxPacketSize(channelId);
public override bool ClientConnected() => _isClient;
public override void ServerLateUpdate()
{
if (_directConnectModule != null)
_directConnectModule.directConnectTransport.ServerLateUpdate();
}
public override string ServerGetClientAddress(int connectionId)
{
if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId))
return relayId.ToString();
if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId))
return "DIRECT-" + directId;
// Shouldn't ever get here.
return "?";
}
public override void ClientEarlyUpdate()
{
clientToServerTransport.ClientEarlyUpdate();
if (_directConnectModule != null)
_directConnectModule.directConnectTransport.ClientEarlyUpdate();
}
public override void ClientLateUpdate()
{
clientToServerTransport.ClientLateUpdate();
if (_directConnectModule != null)
_directConnectModule.directConnectTransport.ClientLateUpdate();
}
public override void ServerEarlyUpdate()
{
if (_directConnectModule != null)
_directConnectModule.directConnectTransport.ServerEarlyUpdate();
}
public override void ClientConnect(string address)
{
if (!Available())
{
Debug.Log("Not connected to relay!");
OnClientDisconnected?.Invoke();
return;
}
if (_isClient || _isServer)
throw new Exception("Cannot connect while hosting/already connected!");
_cachedHostID = address;
var room = GetServerForID(address);
if (!useLoadBalancer)
{
int pos = 0;
_directConnected = false;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer);
_clientSendBuffer.WriteString(ref pos, address);
_clientSendBuffer.WriteBool(ref pos, _directConnectModule != null);
if (_directConnectModule == null)
{
_clientSendBuffer.WriteString(ref pos, "0.0.0.0");
}
else
{
_clientSendBuffer.WriteString(ref pos, GetLocalIp() ?? "0.0.0.0");
}
_isClient = true;
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
else
{
StartCoroutine(JoinOtherRelayAndMatch(room, address));
}
}
public override void ClientDisconnect()
{
_isClient = false;
// make sure we are even connected to a relay
if (Available())
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
if (_directConnectModule != null)
_directConnectModule.ClientDisconnect();
}
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
if (_directConnected)
{
_directConnectModule.ClientSend(segment, channelId);
}
else
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData);
_clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray());
_clientSendBuffer.WriteInt(ref pos, 0);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), channelId);
}
}
public override void ServerDisconnect(int connectionId)
{
if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId))
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer);
_clientSendBuffer.WriteInt(ref pos, relayId);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
return;
}
if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId))
_directConnectModule.KickClient(directId);
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
if (_directConnectModule != null && _connectedDirectClients.TryGetBySecond(connectionId, out int directId))
{
_directConnectModule.ServerSend(directId, segment, channelId);
}
else
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData);
_clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray());
_clientSendBuffer.WriteInt(ref pos, _connectedRelayClients.GetBySecond(connectionId));
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), channelId);
}
}
public override void ServerStart()
{
if (!Available())
{
Debug.Log("Not connected to relay! Server failed to start.");
return;
}
if (_isClient || _isServer)
{
Debug.Log("Cannot host while already hosting or connected!");
return;
}
_isServer = true;
_connectedRelayClients = new BiDictionary<int, int>();
_currentMemberId = 1;
_connectedDirectClients = new BiDictionary<int, int>();
var keys = new List<IPEndPoint>(_serverProxies.GetAllKeys());
for (int i = 0; i < keys.Count; i++)
{
_serverProxies.GetByFirst(keys[i]).Dispose();
_serverProxies.Remove(keys[i]);
}
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.CreateRoom);
_clientSendBuffer.WriteInt(ref pos, maxServerPlayers);
_clientSendBuffer.WriteString(ref pos, serverName);
_clientSendBuffer.WriteBool(ref pos, isPublicServer);
_clientSendBuffer.WriteString(ref pos, extraServerData);
// If we have direct connect module, and our local IP isnt null, tell server. Only time local IP is null is on cellular networks, such as IOS and Android.
_clientSendBuffer.WriteBool(ref pos, _directConnectModule != null ? GetLocalIp() != null : false);
if (_directConnectModule != null && GetLocalIp() != null && useNATPunch)
{
_clientSendBuffer.WriteString(ref pos, GetLocalIp());
// Transport port will be NAT port + 1 for the proxy connections.
_directConnectModule.StartServer(useNATPunch ? _NATIP.Port + 1 : -1);
}
else
_clientSendBuffer.WriteString(ref pos, "0.0.0.0");
if (useNATPunch)
{
_clientSendBuffer.WriteBool(ref pos, true);
_clientSendBuffer.WriteInt(ref pos, 0);
}
else
{
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteInt(ref pos, _directConnectModule == null ? 1 : _directConnectModule.SupportsNATPunch() ? _directConnectModule.GetTransportPort() : 1);
}
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
public override void ServerStop()
{
if (_isServer)
{
_isServer = false;
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
if (_directConnectModule != null)
_directConnectModule.StopServer();
var keys = new List<IPEndPoint>(_serverProxies.GetAllKeys());
for (int i = 0; i < keys.Count; i++)
{
_serverProxies.GetByFirst(keys[i]).Dispose();
_serverProxies.Remove(keys[i]);
}
}
}
public override Uri ServerUri()
{
UriBuilder builder = new UriBuilder
{
Scheme = "LRM",
Host = serverId.ToString()
};
return builder.Uri;
}
public override void Shutdown()
{
_isAuthenticated = false;
_isClient = false;
_isServer = false;
_connectedToRelay = false;
clientToServerTransport.Shutdown();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16602a520793b6044894096e873bc28d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,223 @@
using Mirror;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;
namespace LightReflectiveMirror
{
public partial class LightReflectiveMirrorTransport : Transport
{
public void RequestServerList(LRMRegions searchRegion = LRMRegions.Any)
{
if (_isAuthenticated && _connectedToRelay)
StartCoroutine(GetServerList(searchRegion));
else
Debug.Log("You must be connected to Relay to request server list!");
}
IEnumerator RelayConnect()
{
string url = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/join/";
serverStatus = "Waiting for LLB...";
using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
{
// Request and wait for the desired page.
webRequest.SetRequestHeader("x-Region", ((int)region).ToString());
webRequest.SetRequestHeader("Access-Control-Allow-Credentials", "true");
webRequest.SetRequestHeader("Access-Control-Allow-Headers", "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time");
webRequest.SetRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
webRequest.SetRequestHeader("Access-Control-Allow-Origin", "*");
yield return webRequest.SendWebRequest();
var result = webRequest.downloadHandler.text;
#if UNITY_2020_1_OR_NEWER
switch (webRequest.result)
{
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.DataProcessingError:
case UnityWebRequest.Result.ProtocolError:
Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer.");
break;
case UnityWebRequest.Result.Success:
var parsedAddress = JsonUtility.FromJson<RelayAddress>(result);
Connect(parsedAddress.address, parsedAddress.port);
endpointServerPort = parsedAddress.endpointPort;
break;
}
#else
if (webRequest.isNetworkError || webRequest.isHttpError)
{
Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer.");
}
else
{
// join here
var parsedAddress = JsonUtility.FromJson<RelayAddress>(result);
Connect(parsedAddress.address, parsedAddress.port);
endpointServerPort = parsedAddress.endpointPort;
}
#endif
}
}
IEnumerator JoinOtherRelayAndMatch(Room? roomValue, string ID)
{
var room = new Room();
// using load balancer, we NEED the server's relay address
if (roomValue.HasValue)
room = roomValue.Value;
else
{
_serverListUpdated = false;
RequestServerList();
yield return new WaitUntil(() => _serverListUpdated);
var foundRoom = GetServerForID(ID);
if (foundRoom.HasValue)
{
room = foundRoom.Value;
}
else
{
Debug.LogWarning("LRM | Client tried to join a server that does not exist!");
OnClientDisconnected?.Invoke();
yield break;
}
}
// Wait for disconnection
DisconnectFromRelay();
while (IsAuthenticated())
yield return null;
endpointServerPort = room.relayInfo.endpointPort;
Connect(room.relayInfo.address, room.relayInfo.port);
while (!IsAuthenticated())
yield return null;
int pos = 0;
_directConnected = false;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer);
_clientSendBuffer.WriteString(ref pos, room.serverId);
_clientSendBuffer.WriteBool(ref pos, _directConnectModule != null);
string local = GetLocalIp();
_clientSendBuffer.WriteString(ref pos, local ?? "0.0.0.0");
_isClient = true;
clientToServerTransport.ClientSend(new System.ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
IEnumerator GetServerList(LRMRegions region)
{
if (!useLoadBalancer)
{
string uri = $"http://{serverIP}:{endpointServerPort}/api/compressed/servers";
using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
{
webRequest.SetRequestHeader("Access-Control-Allow-Credentials", "true");
webRequest.SetRequestHeader("Access-Control-Allow-Headers", "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time");
webRequest.SetRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
webRequest.SetRequestHeader("Access-Control-Allow-Origin", "*");
// Request and wait for the desired page.
yield return webRequest.SendWebRequest();
var result = webRequest.downloadHandler.text;
#if UNITY_2020_1_OR_NEWER
switch (webRequest.result)
{
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.DataProcessingError:
case UnityWebRequest.Result.ProtocolError:
Debug.LogWarning("LRM | Network Error while retreiving the server list!");
break;
case UnityWebRequest.Result.Success:
relayServerList?.Clear();
relayServerList = JsonUtilityHelper.FromJson<Room>(result.Decompress()).ToList();
serverListUpdated?.Invoke();
break;
}
#else
if (webRequest.isNetworkError || webRequest.isHttpError)
{
Debug.LogWarning("LRM | Network Error while retreiving the server list!");
}
else
{
relayServerList?.Clear();
relayServerList = JsonUtilityHelper.FromJson<Room>(result.Decompress()).ToList();
serverListUpdated?.Invoke();
}
#endif
}
}
else // get master list from load balancer
{
yield return StartCoroutine(RetrieveMasterServerListFromLoadBalancer(region));
}
}
/// <summary>
/// Gets master list from the LB.
/// This can be optimized but for now it is it's
/// own separate method, so i can understand wtf is going on :D
/// </summary>
/// <returns></returns>
IEnumerator RetrieveMasterServerListFromLoadBalancer(LRMRegions region)
{
string uri = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/masterlist/";
using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
{
webRequest.SetRequestHeader("x-Region", ((int)region).ToString());
// Request and wait for the desired page.
yield return webRequest.SendWebRequest();
var result = webRequest.downloadHandler.text;
#if UNITY_2020_1_OR_NEWER
switch (webRequest.result)
{
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.DataProcessingError:
case UnityWebRequest.Result.ProtocolError:
Debug.LogWarning("LRM | Network Error while retreiving the server list!");
break;
case UnityWebRequest.Result.Success:
relayServerList?.Clear();
relayServerList = JsonUtilityHelper.FromJson<Room>(result).ToList();
serverListUpdated?.Invoke();
_serverListUpdated = true;
break;
}
#else
if (webRequest.isNetworkError || webRequest.isHttpError)
{
Debug.LogWarning("LRM | Network Error while retreiving the server list!");
}
else
{
relayServerList?.Clear();
relayServerList = JsonUtilityHelper.FromJson<Room>(result).ToList();
serverListUpdated?.Invoke();
_serverListUpdated = true;
}
#endif
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ce47885d25567b4a8c0d6f3727de7f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,75 @@
using Mirror;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine.Events;
namespace LightReflectiveMirror
{
public partial class LightReflectiveMirrorTransport : Transport
{
// 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;
public string authenticationKey = "Secret Auth Key";
public UnityEvent disconnectedFromRelay;
public UnityEvent connectedToRelay;
// NAT Puncher variables
public bool useNATPunch = false;
public int NATPunchtroughPort = -1;
private const int NAT_PUNCH_ATTEMPTS = 3;
// LLB variables (LRM Load Balancer)
public bool useLoadBalancer = false;
public ushort loadBalancerPort = 7070;
public string loadBalancerAddress = null;
// Server hosting variables
public string serverName = "My awesome server!";
public string extraServerData = "Map 1";
public int maxServerPlayers = 10;
public bool isPublicServer = true;
private const string LOCALHOST = "127.0.0.1";
// Server list variables
public UnityEvent serverListUpdated;
public List<Room> relayServerList { private set; get; } = new List<Room>();
// Current Server Information
public string serverStatus = "Not Started.";
public string serverId = string.Empty;
private LRMDirectConnectModule _directConnectModule;
public LRMRegions region = LRMRegions.NorthAmerica;
private byte[] _clientSendBuffer;
private bool _connectedToRelay = false;
public bool isConnectedToRelay => _connectedToRelay;
private bool _isClient = false;
private bool _isServer = false;
private bool _directConnected = false;
private bool _isAuthenticated = false;
private int _currentMemberId;
private bool _callbacksInitialized = false;
private string _cachedHostID;
private UdpClient _NATPuncher;
private IPEndPoint _NATIP;
private IPEndPoint _relayPuncherIP;
private byte[] _punchData = new byte[1] { 1 };
private IPEndPoint _directConnectEndpoint;
private SocketProxy _clientProxy;
private BiDictionary<IPEndPoint, SocketProxy> _serverProxies = new BiDictionary<IPEndPoint, SocketProxy>();
private BiDictionary<int, int> _connectedRelayClients = new BiDictionary<int, int>();
private BiDictionary<int, int> _connectedDirectClients = new BiDictionary<int, int>();
private bool _serverListUpdated = false;
}
public enum LRMRegions { Any, NorthAmerica, SouthAmerica, Europe, Asia, Africa, Oceania }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f823917eee8f54742b6b03e520f05730
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,464 @@
using kcp2k;
using Mirror;
using Mirror.SimpleWeb;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
namespace LightReflectiveMirror
{
[DefaultExecutionOrder(1001)]
public partial class LightReflectiveMirrorTransport : Transport
{
public bool IsAuthenticated() => _isAuthenticated;
private void Awake()
{
if (Application.platform == RuntimePlatform.WebGLPlayer)
useNATPunch = false;
else
_directConnectModule = GetComponent<LRMDirectConnectModule>();
if (clientToServerTransport is LightReflectiveMirrorTransport)
throw new Exception("Haha real funny... Use a different transport.");
if (_directConnectModule != null)
{
if (useNATPunch && !_directConnectModule.SupportsNATPunch())
{
Debug.LogWarning("LRM | NATPunch is turned on but the transport used does not support it. It will be disabled.");
useNATPunch = false;
}
}
SetupCallbacks();
if (connectOnAwake)
ConnectToRelay();
InvokeRepeating(nameof(SendHeartbeat), heartBeatInterval, heartBeatInterval);
}
private void SetupCallbacks()
{
if (_callbacksInitialized)
return;
_callbacksInitialized = true;
clientToServerTransport.OnClientConnected = OnConnectedToRelay;
clientToServerTransport.OnClientDataReceived = DataReceived;
clientToServerTransport.OnClientDisconnected = Disconnected;
clientToServerTransport.OnClientError = (e) => Debug.LogException(e);
}
private void Disconnected()
{
_connectedToRelay = false;
_isAuthenticated = false;
disconnectedFromRelay?.Invoke();
serverStatus = "Disconnected from relay.";
}
private void OnConnectedToRelay()
{
_connectedToRelay = true;
connectedToRelay?.Invoke();
}
public void ConnectToRelay()
{
if (!useLoadBalancer)
{
if (!_connectedToRelay)
{
Connect(serverIP, serverPort);
}
else
{
Debug.LogWarning("LRM | Already connected to relay!");
}
}
else
{
if (!_connectedToRelay)
{
StartCoroutine(RelayConnect());
}
else
{
Debug.LogWarning("LRM | Already connected to relay!");
}
}
}
/// <summary>
/// Connects to the desired relay
/// </summary>
/// <param name="serverIP"></param>
private void Connect(string serverIP, ushort port = 7777)
{
// need to implement custom port
if (clientToServerTransport is LightReflectiveMirrorTransport)
throw new Exception("LRM | Client to Server Transport cannot be LRM.");
SetTransportPort(port);
this.serverIP = serverIP;
serverStatus = "Connecting to relay...";
_clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()];
clientToServerTransport.ClientConnect(serverIP);
}
public void DisconnectFromRelay()
{
if (IsAuthenticated())
{
clientToServerTransport.ClientDisconnect();
}
}
private void SendHeartbeat()
{
if (_connectedToRelay)
{
// Send a blank message with just the opcode 200, which is heartbeat
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, 200);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
// If NAT Puncher is initialized, send heartbeat on that as well.
try
{
if (_NATPuncher != null)
_NATPuncher.Send(new byte[] { 0 }, 1, _relayPuncherIP);
}
catch (Exception e)
{
print(e);
}
// Check if any server-side proxies havent been used in 10 seconds, and timeout if so.
var keys = new List<IPEndPoint>(_serverProxies.GetAllKeys());
for (int i = 0; i < keys.Count; i++)
{
if (DateTime.Now.Subtract(_serverProxies.GetByFirst(keys[i]).lastInteractionTime).TotalSeconds > 10)
{
_serverProxies.GetByFirst(keys[i]).Dispose();
_serverProxies.Remove(keys[i]);
}
}
}
}
private void DataReceived(ArraySegment<byte> segmentData, int channel)
{
try
{
var data = segmentData.Array;
int pos = segmentData.Offset;
// Read the opcode of the incoming data, this allows us to know what its used for.
OpCodes opcode = (OpCodes)data.ReadByte(ref pos);
switch (opcode)
{
case OpCodes.Authenticated:
// Server authenticated us! That means we are fully ready to host and join servers.
serverStatus = "Authenticated! Good to go!";
_isAuthenticated = true;
RequestServerList();
break;
case OpCodes.AuthenticationRequest:
// Server requested that we send an authentication request, lets send our auth key.
serverStatus = "Sent authentication to relay...";
SendAuthKey();
break;
case OpCodes.GetData:
// Someone sent us a packet from their mirror over the relay
var recvData = data.ReadBytes(ref pos);
// If we are the server and the client is registered, invoke the callback
if (_isServer)
{
if (_connectedRelayClients.TryGetByFirst(data.ReadInt(ref pos), out int clientID))
OnServerDataReceived?.Invoke(clientID, new ArraySegment<byte>(recvData), channel);
}
// If we are the client, invoke the callback
if (_isClient)
OnClientDataReceived?.Invoke(new ArraySegment<byte>(recvData), channel);
break;
case OpCodes.ServerLeft:
// Called when server was closed.
if (_isClient)
{
_isClient = false;
OnClientDisconnected?.Invoke();
}
break;
case OpCodes.PlayerDisconnected:
// Called when another player left the room.
if (_isServer)
{
// Get their client ID and invoke the mirror callback
int user = data.ReadInt(ref pos);
if (_connectedRelayClients.TryGetByFirst(user, out int clientID))
{
OnServerDisconnected?.Invoke(clientID);
_connectedRelayClients.Remove(user);
}
}
break;
case OpCodes.RoomCreated:
// We successfully created the room, the server also gave us the serverId of the room!
serverId = data.ReadString(ref pos);
break;
case OpCodes.ServerJoined:
// Called when a player joins the room or when we joined a room.
int clientId = data.ReadInt(ref pos);
if (_isClient)
{
// We successfully joined a room, let mirror know.
OnClientConnected?.Invoke();
}
if (_isServer)
{
// A client joined our room, let mirror know and setup their ID in the dictionary.
_connectedRelayClients.Add(clientId, _currentMemberId);
OnServerConnected?.Invoke(_currentMemberId);
_currentMemberId++;
}
break;
case OpCodes.DirectConnectIP:
// Either a client is trying to join us via NAT Punch, or we are trying to join a host over NAT punch/Direct connect.
var ip = data.ReadString(ref pos);
int port = data.ReadInt(ref pos);
bool attemptNatPunch = data.ReadBool(ref pos);
_directConnectEndpoint = new IPEndPoint(IPAddress.Parse(ip), port);
// Both client and server will send data to each other to open the hole.
if (useNATPunch && attemptNatPunch)
{
StartCoroutine(NATPunch(_directConnectEndpoint));
}
if (!_isServer)
{
// We arent the server, so lets tell the direct connect module to attempt a connection and initializing our middle man socket.
if (_clientProxy == null && useNATPunch && attemptNatPunch)
{
_clientProxy = new SocketProxy(_NATIP.Port - 1);
_clientProxy.dataReceived += ClientProcessProxyData;
}
if (useNATPunch && attemptNatPunch)
{
if (ip == LOCALHOST)
_directConnectModule.JoinServer(LOCALHOST, port + 1);
else
_directConnectModule.JoinServer(LOCALHOST, _NATIP.Port - 1);
}
else
_directConnectModule.JoinServer(ip, port);
}
break;
case OpCodes.RequestNATConnection:
// Called when the LRM node would like us to establish a NAT puncher connection. Its safe to ignore if NAT punch is disabled.
if (useNATPunch && GetLocalIp() != null && _directConnectModule != null)
{
byte[] initalData = new byte[150];
int sendPos = 0;
initalData.WriteBool(ref sendPos, true);
initalData.WriteString(ref sendPos, data.ReadString(ref pos));
NATPunchtroughPort = data.ReadInt(ref pos);
if (_NATPuncher == null)
{
_NATPuncher = new UdpClient { ExclusiveAddressUse = false };
while (true)
{
try
{
_NATIP = new IPEndPoint(IPAddress.Parse(GetLocalIp()), UnityEngine.Random.Range(16000, 17000));
_NATPuncher.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_NATPuncher.Client.Bind(_NATIP);
break;
}
catch { } // Binding port is in use, keep trying :P
}
}
if (!IPAddress.TryParse(serverIP, out IPAddress serverAddr))
serverAddr = Dns.GetHostEntry(serverIP).AddressList[0];
_relayPuncherIP = new IPEndPoint(serverAddr, NATPunchtroughPort);
for (int attempts = 0; attempts < NAT_PUNCH_ATTEMPTS; attempts++)
_NATPuncher.Send(initalData, sendPos, _relayPuncherIP);
_NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher);
}
break;
}
}
catch (Exception e) { print(e); }
}
public void SetTransportPort(ushort port)
{
if (clientToServerTransport is KcpTransport kcp)
kcp.Port = port;
if (clientToServerTransport is TelepathyTransport telepathy)
telepathy.port = port;
if (clientToServerTransport is SimpleWebTransport swt)
swt.port = port;
}
public void UpdateRoomName(string newServerName = "My Awesome Server!")
{
if (_isServer)
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData);
_clientSendBuffer.WriteBool(ref pos, true);
_clientSendBuffer.WriteString(ref pos, newServerName);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, false);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
}
public void UpdateRoomData(string newServerData = "Extra Data!")
{
if (_isServer)
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, true);
_clientSendBuffer.WriteString(ref pos, newServerData);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, false);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
}
public void UpdateRoomVisibility(bool isPublic = true)
{
if (_isServer)
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, true);
_clientSendBuffer.WriteBool(ref pos, isPublic);
_clientSendBuffer.WriteBool(ref pos, false);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
}
public void UpdateRoomPlayerCount(int maxPlayers = 16)
{
if (_isServer)
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, false);
_clientSendBuffer.WriteBool(ref pos, true);
_clientSendBuffer.WriteInt(ref pos, maxPlayers);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
}
private Room? GetServerForID(string serverID)
{
for (int i = 0; i < relayServerList.Count; i++)
{
if (relayServerList[i].serverId == serverID)
return relayServerList[i];
}
return null;
}
private void SendAuthKey()
{
int pos = 0;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.AuthenticationResponse);
_clientSendBuffer.WriteString(ref pos, authenticationKey);
clientToServerTransport.ClientSend(new ArraySegment<byte>(_clientSendBuffer, 0, pos), 0);
}
public enum OpCodes
{
Default = 0, RequestID = 1, JoinServer = 2, SendData = 3, GetID = 4, ServerJoined = 5, GetData = 6, CreateRoom = 7, ServerLeft = 8, PlayerDisconnected = 9, RoomCreated = 10,
LeaveRoom = 11, KickPlayer = 12, AuthenticationRequest = 13, AuthenticationResponse = 14, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19, RequestNATConnection = 20,
DirectConnectIP = 21
}
private static string GetLocalIp()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
return ip.ToString();
}
}
return null;
}
}
[Serializable]
public struct Room
{
public string serverName;
public int maxPlayers;
public string serverId;
public string serverData;
public int hostId;
public List<int> clients;
public int currentPlayers;
public RelayAddress relayInfo;
}
[Serializable]
public struct RelayAddress
{
public ushort port;
public ushort endpointPort;
public string address;
public LRMRegions serverRegion;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7064b1b1d0671194baf55fa8d5f564d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f4000c50bdad7994f8425330b81e5d87, type: 3}
userData:
assetBundleName:
assetBundleVariant: