v1.0
This commit is contained in:
parent
658108031f
commit
af5e74f36a
9 changed files with 1399 additions and 0 deletions
15
ServerProject-DONT-IMPORT-INTO-UNITY/Config.cs
Normal file
15
ServerProject-DONT-IMPORT-INTO-UNITY/Config.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LightReflectiveMirror
|
||||
{
|
||||
class Config
|
||||
{
|
||||
public string TransportDLL = "SimpleWebSocketTransportCompiled.dll";
|
||||
public string TransportClass = "Mirror.SimpleWeb.SimpleWebTransport";
|
||||
public string AuthenticationKey = "Secret Auth Key";
|
||||
public int UpdateLoopTime = 50;
|
||||
public int UpdateHeartbeatInterval = 20;
|
||||
}
|
||||
}
|
||||
120
ServerProject-DONT-IMPORT-INTO-UNITY/DataHandler.cs
Normal file
120
ServerProject-DONT-IMPORT-INTO-UNITY/DataHandler.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LightReflectiveMirror
|
||||
{
|
||||
public static class DataHandler
|
||||
{
|
||||
public static void WriteByte(this byte[] data, ref int position, byte value)
|
||||
{
|
||||
data[position] = value;
|
||||
position += 1;
|
||||
}
|
||||
|
||||
public static byte ReadByte(this byte[] data, ref int position)
|
||||
{
|
||||
byte value = data[position];
|
||||
position += 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteBool(this byte[] data, ref int position, bool value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed(byte* dataPtr = &data[position])
|
||||
{
|
||||
bool* valuePtr = (bool*)dataPtr;
|
||||
*valuePtr = value;
|
||||
position += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ReadBool(this byte[] data, ref int position)
|
||||
{
|
||||
bool value = BitConverter.ToBoolean(data, position);
|
||||
position += 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteString(this byte[] data, ref int position, string value)
|
||||
{
|
||||
data.WriteInt(ref position, value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteChar(ref position, value[i]);
|
||||
}
|
||||
|
||||
public static string ReadString(this byte[] data, ref int position)
|
||||
{
|
||||
string value = default;
|
||||
|
||||
int stringSize = data.ReadInt(ref position);
|
||||
|
||||
for (int i = 0; i < stringSize; i++)
|
||||
value += data.ReadChar(ref position);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteBytes(this byte[] data, ref int position, byte[] value)
|
||||
{
|
||||
data.WriteInt(ref position, value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteByte(ref position, value[i]);
|
||||
}
|
||||
|
||||
public static byte[] ReadBytes(this byte[] data, ref int position)
|
||||
{
|
||||
int byteSize = data.ReadInt(ref position);
|
||||
|
||||
byte[] value = new byte[byteSize];
|
||||
|
||||
for (int i = 0; i < byteSize; i++)
|
||||
value[i] = data.ReadByte(ref position);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteChar(this byte[] data, ref int position, char value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = &data[position])
|
||||
{
|
||||
char* valuePtr = (char*)dataPtr;
|
||||
*valuePtr = value;
|
||||
position += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static char ReadChar(this byte[] data, ref int position)
|
||||
{
|
||||
char value = BitConverter.ToChar(data, position);
|
||||
position += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteInt(this byte[] data, ref int position, int value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = &data[position])
|
||||
{
|
||||
int* valuePtr = (int*)dataPtr;
|
||||
*valuePtr = value;
|
||||
position += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int ReadInt(this byte[] data, ref int position)
|
||||
{
|
||||
int value = BitConverter.ToInt32(data, position);
|
||||
position += 4;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
154
ServerProject-DONT-IMPORT-INTO-UNITY/Program.cs
Normal file
154
ServerProject-DONT-IMPORT-INTO-UNITY/Program.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Mirror;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LightReflectiveMirror
|
||||
{
|
||||
class Program
|
||||
{
|
||||
public static Config conf;
|
||||
RelayHandler relay;
|
||||
public static Transport transport;
|
||||
MethodInfo awakeMethod;
|
||||
MethodInfo startMethod;
|
||||
MethodInfo updateMethod;
|
||||
MethodInfo lateUpdateMethod;
|
||||
List<int> _currentConnections = new List<int>();
|
||||
int _currentHeartbeatTimer = 0;
|
||||
|
||||
public static void Main(string[] args)
|
||||
=> new Program().MainAsync().GetAwaiter().GetResult();
|
||||
|
||||
public async Task MainAsync()
|
||||
{
|
||||
|
||||
if (!File.Exists("config.json"))
|
||||
{
|
||||
File.WriteAllText("config.json", JsonConvert.SerializeObject(new Config(), Formatting.Indented));
|
||||
WriteLogMessage("A config.json file was generated. Please configure it to the proper settings and re-run!", ConsoleColor.Yellow);
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
conf = JsonConvert.DeserializeObject<Config>(File.ReadAllText("config.json"));
|
||||
try
|
||||
{
|
||||
Console.WriteLine(Directory.GetCurrentDirectory());
|
||||
var asm = Assembly.LoadFile(Directory.GetCurrentDirectory() + @"\" + conf.TransportDLL);
|
||||
WriteLogMessage($"Loaded Assembly: {asm.FullName}", ConsoleColor.Green);
|
||||
transport = (Transport)asm.CreateInstance(conf.TransportClass);
|
||||
|
||||
if (transport != null)
|
||||
{
|
||||
WriteLogMessage($"Loaded Transport: {asm.GetType(conf.TransportClass).Name}! Loading Methods...", ConsoleColor.Green);
|
||||
CheckMethods(asm.GetType(conf.TransportClass));
|
||||
|
||||
if (awakeMethod != null)
|
||||
{
|
||||
awakeMethod.Invoke(transport, null);
|
||||
WriteLogMessage("Called Awake on transport.", ConsoleColor.Yellow);
|
||||
}
|
||||
|
||||
if (startMethod != null)
|
||||
{
|
||||
awakeMethod.Invoke(transport, null);
|
||||
WriteLogMessage("Called Start on transport.", ConsoleColor.Yellow);
|
||||
}
|
||||
|
||||
WriteLogMessage("Starting Transport...", ConsoleColor.Green);
|
||||
|
||||
transport.OnServerError = (clientID, error) => {
|
||||
WriteLogMessage($"Transport Error, Client: {clientID}, Error: {error}", ConsoleColor.Red);
|
||||
};
|
||||
|
||||
transport.OnServerConnected = (clientID) =>
|
||||
{
|
||||
WriteLogMessage($"Transport Connected, Client: {clientID}", ConsoleColor.Cyan);
|
||||
_currentConnections.Add(clientID);
|
||||
relay.ClientConnected(clientID);
|
||||
};
|
||||
|
||||
relay = new RelayHandler(transport.GetMaxPacketSize(0));
|
||||
|
||||
transport.OnServerDataReceived = relay.HandleMessage;
|
||||
transport.OnServerDisconnected = (clientID) =>
|
||||
{
|
||||
_currentConnections.Remove(clientID);
|
||||
relay.HandleDisconnect(clientID);
|
||||
};
|
||||
|
||||
transport.ServerStart();
|
||||
|
||||
WriteLogMessage("Transport Started!", ConsoleColor.Green);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLogMessage("Transport Class not found! Please make sure to include namespaces.", ConsoleColor.Red);
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
WriteLogMessage("Exception: " + e, ConsoleColor.Red);
|
||||
Console.ReadKey();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (updateMethod != null)
|
||||
updateMethod.Invoke(transport, null);
|
||||
|
||||
if (lateUpdateMethod != null)
|
||||
lateUpdateMethod.Invoke(transport, null);
|
||||
|
||||
_currentHeartbeatTimer++;
|
||||
|
||||
if(_currentHeartbeatTimer >= conf.UpdateHeartbeatInterval)
|
||||
{
|
||||
_currentHeartbeatTimer = 0;
|
||||
|
||||
for(int i = 0; i < _currentConnections.Count; i++)
|
||||
{
|
||||
transport.ServerSend(_currentConnections[i], 0, new ArraySegment<byte>(new byte[] { 200 }));
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(conf.UpdateLoopTime);
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteLogMessage(string message, ConsoleColor color = ConsoleColor.White)
|
||||
{
|
||||
Console.ForegroundColor = color;
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
|
||||
void CheckMethods(Type type)
|
||||
{
|
||||
awakeMethod = type.GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
startMethod = type.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
updateMethod = type.GetMethod("Update", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
lateUpdateMethod = type.GetMethod("LateUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (awakeMethod != null)
|
||||
WriteLogMessage("'Awake' Loaded!", ConsoleColor.Yellow);
|
||||
|
||||
if (startMethod != null)
|
||||
WriteLogMessage("'Start' Loaded!", ConsoleColor.Yellow);
|
||||
|
||||
if (updateMethod != null)
|
||||
WriteLogMessage("'Update' Loaded!", ConsoleColor.Yellow);
|
||||
|
||||
if (lateUpdateMethod != null)
|
||||
WriteLogMessage("'LateUpdate' Loaded!", ConsoleColor.Yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
307
ServerProject-DONT-IMPORT-INTO-UNITY/RelayHandler.cs
Normal file
307
ServerProject-DONT-IMPORT-INTO-UNITY/RelayHandler.cs
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LightReflectiveMirror
|
||||
{
|
||||
public class RelayHandler
|
||||
{
|
||||
List<Room> rooms = new List<Room>();
|
||||
List<int> pendingAuthentication = new List<int>();
|
||||
ArrayPool<byte> sendBuffers;
|
||||
int maxPacketSize = 0;
|
||||
|
||||
public RelayHandler(int maxPacketSize)
|
||||
{
|
||||
this.maxPacketSize = maxPacketSize;
|
||||
sendBuffers = ArrayPool<byte>.Create(maxPacketSize, 50);
|
||||
}
|
||||
|
||||
public void ClientConnected(int clientId)
|
||||
{
|
||||
pendingAuthentication.Add(clientId);
|
||||
var buffer = sendBuffers.Rent(1);
|
||||
int pos = 0;
|
||||
buffer.WriteByte(ref pos, (byte)OpCodes.AuthenticationRequest);
|
||||
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(buffer, 0, pos));
|
||||
sendBuffers.Return(buffer);
|
||||
}
|
||||
|
||||
public void HandleMessage(int clientId, ArraySegment<byte> segmentData, int channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = segmentData.Array;
|
||||
int pos = 0;
|
||||
|
||||
OpCodes opcode = (OpCodes)data.ReadByte(ref pos);
|
||||
|
||||
if (pendingAuthentication.Contains(clientId))
|
||||
{
|
||||
if (opcode == OpCodes.AuthenticationResponse)
|
||||
{
|
||||
string authResponse = data.ReadString(ref pos);
|
||||
if (authResponse == Program.conf.AuthenticationKey)
|
||||
{
|
||||
pendingAuthentication.Remove(clientId);
|
||||
int writePos = 0;
|
||||
var sendBuffer = sendBuffers.Rent(1);
|
||||
sendBuffer.WriteByte(ref writePos, (byte)OpCodes.Authenticated);
|
||||
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, writePos));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case OpCodes.CreateRoom:
|
||||
CreateRoom(clientId, data.ReadInt(ref pos), data.ReadString(ref pos), data.ReadBool(ref pos), data.ReadString(ref pos));
|
||||
break;
|
||||
case OpCodes.RequestID:
|
||||
SendClientID(clientId);
|
||||
break;
|
||||
case OpCodes.LeaveRoom:
|
||||
LeaveRoom(clientId);
|
||||
break;
|
||||
case OpCodes.JoinServer:
|
||||
JoinRoom(clientId, data.ReadInt(ref pos));
|
||||
break;
|
||||
case OpCodes.KickPlayer:
|
||||
LeaveRoom(data.ReadInt(ref pos), clientId);
|
||||
break;
|
||||
case OpCodes.SendData:
|
||||
ProcessData(clientId, data.ReadBytes(ref pos), channel, data.ReadInt(ref pos));
|
||||
break;
|
||||
case OpCodes.RequestServers:
|
||||
SendServerList(clientId);
|
||||
break;
|
||||
case OpCodes.UpdateRoomData:
|
||||
var room = GetRoomForPlayer(clientId);
|
||||
if (room == null)
|
||||
return;
|
||||
|
||||
var plyRoom = room.Value;
|
||||
|
||||
bool newName = data.ReadBool(ref pos);
|
||||
if (newName)
|
||||
plyRoom.serverName = data.ReadString(ref pos);
|
||||
|
||||
bool newData = data.ReadBool(ref pos);
|
||||
if (newData)
|
||||
plyRoom.serverData = data.ReadString(ref pos);
|
||||
|
||||
bool newPublicStatus = data.ReadBool(ref pos);
|
||||
if (newPublicStatus)
|
||||
plyRoom.isPublic = data.ReadBool(ref pos);
|
||||
|
||||
bool newPlayerCap = data.ReadBool(ref pos);
|
||||
if (newPlayerCap)
|
||||
plyRoom.maxPlayers = data.ReadInt(ref pos);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Do Nothing. Client probably sent some invalid data.
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleDisconnect(int clientId)
|
||||
{
|
||||
LeaveRoom(clientId);
|
||||
}
|
||||
|
||||
void SendServerList(int clientId)
|
||||
{
|
||||
int pos = 0;
|
||||
var buffer = sendBuffers.Rent(500);
|
||||
buffer.WriteByte(ref pos, (byte)OpCodes.ServerListReponse);
|
||||
for(int i = 0; i < rooms.Count; i++)
|
||||
{
|
||||
if (rooms[i].isPublic)
|
||||
{
|
||||
buffer.WriteBool(ref pos, true);
|
||||
buffer.WriteString(ref pos, rooms[i].serverName);
|
||||
buffer.WriteString(ref pos, rooms[i].serverData);
|
||||
buffer.WriteInt(ref pos, rooms[i].hostId);
|
||||
buffer.WriteInt(ref pos, rooms[i].maxPlayers);
|
||||
buffer.WriteInt(ref pos, rooms[i].clients.Count + 1);
|
||||
}
|
||||
}
|
||||
buffer.WriteBool(ref pos, false);
|
||||
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(buffer, 0, pos));
|
||||
sendBuffers.Return(buffer);
|
||||
}
|
||||
|
||||
void ProcessData(int clientId, byte[] clientData, int channel, int sendTo = -1)
|
||||
{
|
||||
Room? playersRoom = GetRoomForPlayer(clientId);
|
||||
|
||||
if(playersRoom != null)
|
||||
{
|
||||
Room room = playersRoom.Value;
|
||||
|
||||
if(room.hostId == clientId)
|
||||
{
|
||||
if (room.clients.Contains(sendTo))
|
||||
{
|
||||
int pos = 0;
|
||||
byte[] sendBuffer = sendBuffers.Rent(maxPacketSize);
|
||||
|
||||
sendBuffer.WriteByte(ref pos, (byte)OpCodes.GetData);
|
||||
sendBuffer.WriteBytes(ref pos, clientData);
|
||||
|
||||
Program.transport.ServerSend(sendTo, channel, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||
sendBuffers.Return(sendBuffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are not the host, so send the data to the host.
|
||||
int pos = 0;
|
||||
byte[] sendBuffer = sendBuffers.Rent(maxPacketSize);
|
||||
|
||||
sendBuffer.WriteByte(ref pos, (byte)OpCodes.GetData);
|
||||
sendBuffer.WriteBytes(ref pos, clientData);
|
||||
sendBuffer.WriteInt(ref pos, clientId);
|
||||
|
||||
Program.transport.ServerSend(room.hostId, channel, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||
sendBuffers.Return(sendBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Room? GetRoomForPlayer(int clientId)
|
||||
{
|
||||
for(int i = 0; i < rooms.Count; i++)
|
||||
{
|
||||
if (rooms[i].hostId == clientId)
|
||||
return rooms[i];
|
||||
|
||||
if (rooms[i].clients.Contains(clientId))
|
||||
return rooms[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void JoinRoom(int clientId, int serverId)
|
||||
{
|
||||
LeaveRoom(clientId);
|
||||
|
||||
for(int i = 0; i < rooms.Count; i++)
|
||||
{
|
||||
if(rooms[i].hostId == serverId)
|
||||
{
|
||||
if(rooms[i].clients.Count < rooms[i].maxPlayers)
|
||||
{
|
||||
rooms[i].clients.Add(clientId);
|
||||
|
||||
int sendJoinPos = 0;
|
||||
byte[] sendJoinBuffer = sendBuffers.Rent(5);
|
||||
|
||||
sendJoinBuffer.WriteByte(ref sendJoinPos, (byte)OpCodes.ServerJoined);
|
||||
sendJoinBuffer.WriteInt(ref sendJoinPos, clientId);
|
||||
|
||||
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendJoinBuffer, 0, sendJoinPos));
|
||||
Program.transport.ServerSend(serverId, 0, new ArraySegment<byte>(sendJoinBuffer, 0, sendJoinPos));
|
||||
sendBuffers.Return(sendJoinBuffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it got to here, then the server was not found, or full. Tell the client.
|
||||
int pos = 0;
|
||||
byte[] sendBuffer = sendBuffers.Rent(1);
|
||||
|
||||
sendBuffer.WriteByte(ref pos, (byte)OpCodes.ServerLeft);
|
||||
|
||||
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||
sendBuffers.Return(sendBuffer);
|
||||
}
|
||||
|
||||
void CreateRoom(int clientId, int maxPlayers, string serverName, bool isPublic, string serverData)
|
||||
{
|
||||
LeaveRoom(clientId);
|
||||
|
||||
Room room = new Room();
|
||||
room.hostId = clientId;
|
||||
room.maxPlayers = maxPlayers;
|
||||
room.serverName = serverName;
|
||||
room.isPublic = isPublic;
|
||||
room.serverData = serverData;
|
||||
room.clients = new List<int>();
|
||||
|
||||
rooms.Add(room);
|
||||
|
||||
int pos = 0;
|
||||
byte[] sendBuffer = sendBuffers.Rent(5);
|
||||
|
||||
sendBuffer.WriteByte(ref pos, (byte)OpCodes.RoomCreated);
|
||||
sendBuffer.WriteInt(ref pos, clientId);
|
||||
|
||||
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||
sendBuffers.Return(sendBuffer);
|
||||
}
|
||||
|
||||
void LeaveRoom(int clientId, int requiredHostId = -1)
|
||||
{
|
||||
for(int i = 0; i < rooms.Count; i++)
|
||||
{
|
||||
if(rooms[i].hostId == clientId)
|
||||
{
|
||||
int pos = 0;
|
||||
byte[] sendBuffer = sendBuffers.Rent(1);
|
||||
sendBuffer.WriteByte(ref pos, (byte)OpCodes.ServerLeft);
|
||||
|
||||
for(int x = 0; x < rooms[i].clients.Count; x++)
|
||||
Program.transport.ServerSend(rooms[i].clients[x], 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||
|
||||
sendBuffers.Return(sendBuffer);
|
||||
rooms[i].clients.Clear();
|
||||
rooms.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requiredHostId >= 0 && rooms[i].hostId != requiredHostId)
|
||||
continue;
|
||||
|
||||
if(rooms[i].clients.RemoveAll(x => x == clientId) > 0)
|
||||
{
|
||||
int pos = 0;
|
||||
byte[] sendBuffer = sendBuffers.Rent(5);
|
||||
|
||||
sendBuffer.WriteByte(ref pos, (byte)OpCodes.PlayerDisconnected);
|
||||
sendBuffer.WriteInt(ref pos, clientId);
|
||||
|
||||
Program.transport.ServerSend(rooms[i].hostId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||
sendBuffers.Return(sendBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendClientID(int clientId)
|
||||
{
|
||||
int pos = 0;
|
||||
byte[] sendBuffer = sendBuffers.Rent(5);
|
||||
|
||||
sendBuffer.WriteByte(ref pos, (byte)OpCodes.GetID);
|
||||
sendBuffer.WriteInt(ref pos, clientId);
|
||||
|
||||
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||
sendBuffers.Return(sendBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
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, RequestServers = 15, ServerListReponse = 16, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19
|
||||
}
|
||||
}
|
||||
16
ServerProject-DONT-IMPORT-INTO-UNITY/Room.cs
Normal file
16
ServerProject-DONT-IMPORT-INTO-UNITY/Room.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LightReflectiveMirror
|
||||
{
|
||||
struct Room
|
||||
{
|
||||
public int hostId;
|
||||
public string serverName;
|
||||
public string serverData;
|
||||
public bool isPublic;
|
||||
public int maxPlayers;
|
||||
public List<int> clients;
|
||||
}
|
||||
}
|
||||
244
ServerProject-DONT-IMPORT-INTO-UNITY/Transport.cs
Normal file
244
ServerProject-DONT-IMPORT-INTO-UNITY/Transport.cs
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
using System;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract transport layer component
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <h2>
|
||||
/// Transport Rules
|
||||
/// </h2>
|
||||
/// <list type="bullet">
|
||||
/// <listheader><description>
|
||||
/// All transports should follow these rules so that they work correctly with mirror
|
||||
/// </description></listheader>
|
||||
/// <item><description>
|
||||
/// When Monobehaviour is disabled the Transport should not invoke callbacks
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// Callbacks should be invoked on main thread. It is best to do this from LateUpdate
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// Callbacks can be invoked after <see cref="ServerStop"/> or <see cref="ClientDisconnect"/> as been called
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// <see cref="ServerStop"/> or <see cref="ClientDisconnect"/> can be called by mirror multiple times
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// <see cref="Available"/> should check the platform and 32 vs 64 bit if the transport only works on some of them
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// <see cref="GetMaxPacketSize"/> should return size even if transport is not running
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// Default channel should be reliable <see cref="Channels.DefaultReliable"/>
|
||||
/// </description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public abstract class Transport
|
||||
{
|
||||
/// <summary>
|
||||
/// The current transport used by Mirror.
|
||||
/// </summary>
|
||||
public static Transport activeTransport;
|
||||
|
||||
/// <summary>
|
||||
/// Is this transport available in the current platform?
|
||||
/// <para>Some transports might only be available in mobile</para>
|
||||
/// <para>Many will not work in webgl</para>
|
||||
/// <para>Example usage: return Application.platform == RuntimePlatform.WebGLPlayer</para>
|
||||
/// </summary>
|
||||
/// <returns>True if this transport works in the current platform</returns>
|
||||
public abstract bool Available();
|
||||
|
||||
#region Client
|
||||
/// <summary>
|
||||
/// Notify subscribers when when this client establish a successful connection to the server
|
||||
/// <para>callback()</para>
|
||||
/// </summary>
|
||||
public Action OnClientConnected = () => Console.WriteLine("OnClientConnected called with no handler");
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers when this client receive data from the server
|
||||
/// <para>callback(ArraySegment<byte> data, int channel)</para>
|
||||
/// </summary>
|
||||
public Action<ArraySegment<byte>, int> OnClientDataReceived = (data, channel) => Console.WriteLine("OnClientDataReceived called with no handler");
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers when this client encounters an error communicating with the server
|
||||
/// <para>callback(Exception e)</para>
|
||||
/// </summary>
|
||||
public Action<Exception> OnClientError = (error) => Console.WriteLine("OnClientError called with no handler");
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers when this client disconnects from the server
|
||||
/// <para>callback()</para>
|
||||
/// </summary>
|
||||
public Action OnClientDisconnected = () => Console.WriteLine("OnClientDisconnected called with no handler");
|
||||
|
||||
/// <summary>
|
||||
/// Determines if we are currently connected to the server
|
||||
/// </summary>
|
||||
/// <returns>True if a connection has been established to the server</returns>
|
||||
public abstract bool ClientConnected();
|
||||
|
||||
/// <summary>
|
||||
/// Establish a connection to a server
|
||||
/// </summary>
|
||||
/// <param name="address">The IP address or FQDN of the server we are trying to connect to</param>
|
||||
public abstract void ClientConnect(string address);
|
||||
|
||||
/// <summary>
|
||||
/// Establish a connection to a server
|
||||
/// </summary>
|
||||
/// <param name="uri">The address of the server we are trying to connect to</param>
|
||||
public virtual void ClientConnect(Uri uri)
|
||||
{
|
||||
// By default, to keep backwards compatibility, just connect to the host
|
||||
// in the uri
|
||||
ClientConnect(uri.Host);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data to the server
|
||||
/// </summary>
|
||||
/// <param name="channelId">The channel to use. 0 is the default channel,
|
||||
/// but some transports might want to provide unreliable, encrypted, compressed, or any other feature
|
||||
/// as new channels</param>
|
||||
/// <param name="segment">The data to send to the server. Will be recycled after returning, so either use it directly or copy it internally. This allows for allocation-free sends!</param>
|
||||
public abstract void ClientSend(int channelId, ArraySegment<byte> segment);
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect this client from the server
|
||||
/// </summary>
|
||||
public abstract void ClientDisconnect();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Server
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the address of this server.
|
||||
/// Useful for network discovery
|
||||
/// </summary>
|
||||
/// <returns>the url at which this server can be reached</returns>
|
||||
public abstract Uri ServerUri();
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers when a client connects to this server
|
||||
/// <para>callback(int connId)</para>
|
||||
/// </summary>
|
||||
public Action<int> OnServerConnected = (connId) => Console.WriteLine("OnServerConnected called with no handler");
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers when this server receives data from the client
|
||||
/// <para>callback(int connId, ArraySegment<byte> data, int channel)</para>
|
||||
/// </summary>
|
||||
public Action<int, ArraySegment<byte>, int> OnServerDataReceived = (connId, data, channel) => Console.WriteLine("OnServerDataReceived called with no handler");
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers when this server has some problem communicating with the client
|
||||
/// <para>callback(int connId, Exception e)</para>
|
||||
/// </summary>
|
||||
public Action<int, Exception> OnServerError = (connId, error) => Console.WriteLine("OnServerError called with no handler");
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers when a client disconnects from this server
|
||||
/// <para>callback(int connId)</para>
|
||||
/// </summary>
|
||||
public Action<int> OnServerDisconnected = (connId) => Console.WriteLine("OnServerDisconnected called with no handler");
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the server is up and running
|
||||
/// </summary>
|
||||
/// <returns>true if the transport is ready for connections from clients</returns>
|
||||
public abstract bool ServerActive();
|
||||
|
||||
/// <summary>
|
||||
/// Start listening for clients
|
||||
/// </summary>
|
||||
public abstract void ServerStart();
|
||||
|
||||
/// <summary>
|
||||
/// Send data to a client.
|
||||
/// </summary>
|
||||
/// <param name="connectionId">The client connection id to send the data to</param>
|
||||
/// <param name="channelId">The channel to be used. Transports can use channels to implement
|
||||
/// other features such as unreliable, encryption, compression, etc...</param>
|
||||
/// <param name="data"></param>
|
||||
public abstract void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment);
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect a client from this server. Useful to kick people out.
|
||||
/// </summary>
|
||||
/// <param name="connectionId">the id of the client to disconnect</param>
|
||||
/// <returns>true if the client was kicked</returns>
|
||||
public abstract bool ServerDisconnect(int connectionId);
|
||||
|
||||
/// <summary>
|
||||
/// Get the client address
|
||||
/// </summary>
|
||||
/// <param name="connectionId">id of the client</param>
|
||||
/// <returns>address of the client</returns>
|
||||
public abstract string ServerGetClientAddress(int connectionId);
|
||||
|
||||
/// <summary>
|
||||
/// Stop listening for clients and disconnect all existing clients
|
||||
/// </summary>
|
||||
public abstract void ServerStop();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The maximum packet size for a given channel. Unreliable transports
|
||||
/// usually can only deliver small packets. Reliable fragmented channels
|
||||
/// can usually deliver large ones.
|
||||
///
|
||||
/// GetMaxPacketSize needs to return a value at all times. Even if the
|
||||
/// Transport isn't running, or isn't Available(). This is because
|
||||
/// Fallback and Multiplex transports need to find the smallest possible
|
||||
/// packet size at runtime.
|
||||
/// </summary>
|
||||
/// <param name="channelId">channel id</param>
|
||||
/// <returns>the size in bytes that can be sent via the provided channel</returns>
|
||||
public abstract int GetMaxPacketSize(int channelId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Shut down the transport, both as client and server
|
||||
/// </summary>
|
||||
public abstract void Shutdown();
|
||||
|
||||
// block Update() to force Transports to use LateUpdate to avoid race
|
||||
// conditions. messages should be processed after all the game state
|
||||
// was processed in Update.
|
||||
// -> in other words: use LateUpdate!
|
||||
// -> uMMORPG 480 CCU stress test: when bot machine stops, it causes
|
||||
// 'Observer not ready for ...' log messages when using Update
|
||||
// -> occupying a public Update() function will cause Warnings if a
|
||||
// transport uses Update.
|
||||
//
|
||||
// IMPORTANT: set script execution order to >1000 to call Transport's
|
||||
// LateUpdate after all others. Fixes race condition where
|
||||
// e.g. in uSurvival Transport would apply Cmds before
|
||||
// ShoulderRotation.LateUpdate, resulting in projectile
|
||||
// spawns at the point before shoulder rotation.
|
||||
#pragma warning disable UNT0001 // Empty Unity message
|
||||
public void Update() { }
|
||||
#pragma warning restore UNT0001 // Empty Unity message
|
||||
|
||||
/// <summary>
|
||||
/// called when quitting the application by closing the window / pressing stop in the editor
|
||||
/// <para>virtual so that inheriting classes' OnApplicationQuit() can call base.OnApplicationQuit() too</para>
|
||||
/// </summary>
|
||||
public virtual void OnApplicationQuit()
|
||||
{
|
||||
// stop transport (e.g. to shut down threads)
|
||||
// (when pressing Stop in the Editor, Unity keeps threads alive
|
||||
// until we press Start again. so if Transports use threads, we
|
||||
// really want them to end now and not after next start)
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
51
UnityTransport/BiDictionary.cs
Normal file
51
UnityTransport/BiDictionary.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// 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<TFirst, TSecond>
|
||||
{
|
||||
IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
|
||||
IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();
|
||||
|
||||
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 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
121
UnityTransport/LRMTools.cs
Normal file
121
UnityTransport/LRMTools.cs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LightReflectiveMirror
|
||||
{
|
||||
public static class LRMTools
|
||||
{
|
||||
public static void WriteByte(this byte[] data, ref int position, byte value)
|
||||
{
|
||||
data[position] = value;
|
||||
position += 1;
|
||||
}
|
||||
|
||||
public static byte ReadByte(this byte[] data, ref int position)
|
||||
{
|
||||
byte value = data[position];
|
||||
position += 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteBool(this byte[] data, ref int position, bool value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = &data[position])
|
||||
{
|
||||
bool* valuePtr = (bool*)dataPtr;
|
||||
*valuePtr = value;
|
||||
position += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ReadBool(this byte[] data, ref int position)
|
||||
{
|
||||
bool value = BitConverter.ToBoolean(data, position);
|
||||
position += 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteString(this byte[] data, ref int position, string value)
|
||||
{
|
||||
data.WriteInt(ref position, value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteChar(ref position, value[i]);
|
||||
}
|
||||
|
||||
public static string ReadString(this byte[] data, ref int position)
|
||||
{
|
||||
string value = default;
|
||||
|
||||
int stringSize = data.ReadInt(ref position);
|
||||
|
||||
for (int i = 0; i < stringSize; i++)
|
||||
value += data.ReadChar(ref position);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteBytes(this byte[] data, ref int position, byte[] value)
|
||||
{
|
||||
data.WriteInt(ref position, value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteByte(ref position, value[i]);
|
||||
}
|
||||
|
||||
public static byte[] ReadBytes(this byte[] data, ref int position)
|
||||
{
|
||||
int byteSize = data.ReadInt(ref position);
|
||||
|
||||
byte[] value = new byte[byteSize];
|
||||
|
||||
for (int i = 0; i < byteSize; i++)
|
||||
value[i] = data.ReadByte(ref position);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteChar(this byte[] data, ref int position, char value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = &data[position])
|
||||
{
|
||||
char* valuePtr = (char*)dataPtr;
|
||||
*valuePtr = value;
|
||||
position += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static char ReadChar(this byte[] data, ref int position)
|
||||
{
|
||||
char value = BitConverter.ToChar(data, position);
|
||||
position += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void WriteInt(this byte[] data, ref int position, int value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = &data[position])
|
||||
{
|
||||
int* valuePtr = (int*)dataPtr;
|
||||
*valuePtr = value;
|
||||
position += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int ReadInt(this byte[] data, ref int position)
|
||||
{
|
||||
int value = BitConverter.ToInt32(data, position);
|
||||
position += 4;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
371
UnityTransport/LightReflectiveMirrorTransport.cs
Normal file
371
UnityTransport/LightReflectiveMirrorTransport.cs
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
using Mirror;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace LightReflectiveMirror
|
||||
{
|
||||
public class LightReflectiveMirrorTransport : Transport
|
||||
{
|
||||
[Header("Connection Variables")]
|
||||
public Transport clientToServerTransport;
|
||||
public string serverIP;
|
||||
public float heartBeatInterval = 3;
|
||||
public bool connectOnAwake = true;
|
||||
public string authenticationKey = "Secret Auth Key";
|
||||
[Header("Server Hosting Data")]
|
||||
public string serverName = "My awesome server!";
|
||||
public string extraServerData = "Map 1";
|
||||
public int maxServerPlayers = 10;
|
||||
public bool isPublicServer = true;
|
||||
[Header("Server List")]
|
||||
public UnityEvent serverListUpdated;
|
||||
public List<RelayServerInfo> relayServerList { private set; get; } = new List<RelayServerInfo>();
|
||||
[Header("Server Information")]
|
||||
public int serverId = -1;
|
||||
|
||||
private byte[] _clientSendBuffer;
|
||||
private bool _connectedToRelay = false;
|
||||
private bool _isClient = false;
|
||||
private bool _isServer = false;
|
||||
private bool _isAuthenticated = false;
|
||||
private int _currentMemberId;
|
||||
private BiDictionary<int, int> _connectedRelayClients = new BiDictionary<int, int>();
|
||||
public bool IsAuthenticated() => _isAuthenticated;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (clientToServerTransport is LightReflectiveMirrorTransport)
|
||||
{
|
||||
throw new Exception("Haha real funny... Use a different transport.");
|
||||
}
|
||||
|
||||
if (connectOnAwake)
|
||||
ConnectToRelay();
|
||||
|
||||
InvokeRepeating(nameof(SendHeartbeat), heartBeatInterval, heartBeatInterval);
|
||||
}
|
||||
|
||||
public void ConnectToRelay()
|
||||
{
|
||||
if (!_connectedToRelay)
|
||||
{
|
||||
clientToServerTransport.OnClientConnected = ConnectedToRelay;
|
||||
clientToServerTransport.OnClientDataReceived = DataReceived;
|
||||
_clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()];
|
||||
|
||||
clientToServerTransport.ClientConnect(serverIP);
|
||||
}
|
||||
}
|
||||
|
||||
void SendHeartbeat()
|
||||
{
|
||||
if (_connectedToRelay)
|
||||
{
|
||||
int pos = 0;
|
||||
_clientSendBuffer.WriteByte(ref pos, 200);
|
||||
clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectedToRelay()
|
||||
{
|
||||
_connectedToRelay = true;
|
||||
}
|
||||
|
||||
public void RequestServerList()
|
||||
{
|
||||
if (_isAuthenticated)
|
||||
{
|
||||
int pos = 0;
|
||||
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.RequestServers);
|
||||
clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
}
|
||||
|
||||
void DataReceived(ArraySegment<byte> segmentData, int channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = segmentData.Array;
|
||||
int pos = 0;
|
||||
|
||||
OpCodes opcode = (OpCodes)data.ReadByte(ref pos);
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case OpCodes.Authenticated:
|
||||
_isAuthenticated = true;
|
||||
break;
|
||||
case OpCodes.AuthenticationRequest:
|
||||
SendAuthKey();
|
||||
break;
|
||||
case OpCodes.GetData:
|
||||
var recvData = data.ReadBytes(ref pos);
|
||||
|
||||
if (_isServer)
|
||||
OnServerDataReceived?.Invoke(_connectedRelayClients.GetByFirst(data.ReadInt(ref pos)), new ArraySegment<byte>(recvData), channel);
|
||||
|
||||
if (_isClient)
|
||||
OnClientDataReceived?.Invoke(new ArraySegment<byte>(recvData), channel);
|
||||
break;
|
||||
case OpCodes.ServerLeft:
|
||||
if (_isClient)
|
||||
{
|
||||
_isClient = false;
|
||||
OnClientDisconnected?.Invoke();
|
||||
}
|
||||
break;
|
||||
case OpCodes.PlayerDisconnected:
|
||||
if (_isServer)
|
||||
{
|
||||
int user = data.ReadInt(ref pos);
|
||||
OnServerDisconnected?.Invoke(_connectedRelayClients.GetByFirst(user));
|
||||
_connectedRelayClients.Remove(user);
|
||||
}
|
||||
break;
|
||||
case OpCodes.RoomCreated:
|
||||
serverId = data.ReadInt(ref pos);
|
||||
break;
|
||||
case OpCodes.ServerJoined:
|
||||
int clientId = data.ReadInt(ref pos);
|
||||
if (_isClient)
|
||||
{
|
||||
OnClientConnected?.Invoke();
|
||||
}
|
||||
if (_isServer)
|
||||
{
|
||||
_connectedRelayClients.Add(clientId, _currentMemberId);
|
||||
OnServerConnected?.Invoke(_currentMemberId);
|
||||
_currentMemberId++;
|
||||
}
|
||||
break;
|
||||
case OpCodes.ServerListReponse:
|
||||
relayServerList.Clear();
|
||||
while(data.ReadBool(ref pos))
|
||||
{
|
||||
relayServerList.Add(new RelayServerInfo()
|
||||
{
|
||||
serverName = data.ReadString(ref pos),
|
||||
serverData = data.ReadString(ref pos),
|
||||
serverId = data.ReadInt(ref pos),
|
||||
maxPlayers = data.ReadInt(ref pos),
|
||||
currentPlayers = data.ReadInt(ref pos)
|
||||
});
|
||||
}
|
||||
serverListUpdated?.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void UpdateRoomInfo(string newServerName = null, string newServerData = null, bool? newServerIsPublic = null, int? newPlayerCap = null)
|
||||
{
|
||||
if (_isServer)
|
||||
{
|
||||
int pos = 0;
|
||||
if (!string.IsNullOrEmpty(newServerName))
|
||||
{
|
||||
_clientSendBuffer.WriteBool(ref pos, true);
|
||||
_clientSendBuffer.WriteString(ref pos,newServerName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(newServerData))
|
||||
{
|
||||
_clientSendBuffer.WriteBool(ref pos, true);
|
||||
_clientSendBuffer.WriteString(ref pos, newServerData);
|
||||
}
|
||||
|
||||
if (newServerIsPublic != null)
|
||||
{
|
||||
_clientSendBuffer.WriteBool(ref pos, true);
|
||||
_clientSendBuffer.WriteBool(ref pos, newServerIsPublic.Value);
|
||||
}
|
||||
|
||||
if (newPlayerCap != null)
|
||||
{
|
||||
_clientSendBuffer.WriteBool(ref pos, true);
|
||||
_clientSendBuffer.WriteInt(ref pos, newPlayerCap.Value);
|
||||
}
|
||||
|
||||
clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
}
|
||||
|
||||
void SendAuthKey()
|
||||
{
|
||||
int pos = 0;
|
||||
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.AuthenticationResponse);
|
||||
_clientSendBuffer.WriteString(ref pos, authenticationKey);
|
||||
clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
|
||||
public override bool Available() => _connectedToRelay;
|
||||
|
||||
public override void ClientConnect(string address)
|
||||
{
|
||||
int hostId = 0;
|
||||
if (!Available() || !int.TryParse(address, out hostId))
|
||||
{
|
||||
Debug.Log("Not connected to relay or invalid server id!");
|
||||
OnClientDisconnected?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isClient || _isServer)
|
||||
{
|
||||
throw new Exception("Cannot connect while hosting/already connected!");
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer);
|
||||
_clientSendBuffer.WriteInt(ref pos, hostId);
|
||||
|
||||
_isClient = true;
|
||||
|
||||
clientToServerTransport.ClientSend(0, new System.ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
|
||||
public override void ClientConnect(Uri uri)
|
||||
{
|
||||
ClientConnect(uri.Host);
|
||||
}
|
||||
|
||||
public override bool ClientConnected() => _isClient;
|
||||
|
||||
public override void ClientDisconnect()
|
||||
{
|
||||
_isClient = false;
|
||||
|
||||
int pos = 0;
|
||||
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom);
|
||||
|
||||
clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
|
||||
public override void ClientSend(int channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
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(channelId, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
|
||||
public override int GetMaxPacketSize(int channelId = 0)
|
||||
{
|
||||
return clientToServerTransport.GetMaxPacketSize(channelId);
|
||||
}
|
||||
|
||||
public override bool ServerActive() => _isServer;
|
||||
|
||||
public override bool ServerDisconnect(int connectionId)
|
||||
{
|
||||
int relayId;
|
||||
|
||||
if(_connectedRelayClients.TryGetBySecond(connectionId, out relayId))
|
||||
{
|
||||
int pos = 0;
|
||||
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer);
|
||||
_clientSendBuffer.WriteInt(ref pos, relayId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ServerGetClientAddress(int connectionId)
|
||||
{
|
||||
return _connectedRelayClients.GetBySecond(connectionId).ToString();
|
||||
}
|
||||
|
||||
public override void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
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(channelId, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
|
||||
public override void ServerStop()
|
||||
{
|
||||
if (_isServer)
|
||||
{
|
||||
_isServer = false;
|
||||
int pos = 0;
|
||||
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom);
|
||||
|
||||
clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
|
||||
}
|
||||
}
|
||||
|
||||
public override Uri ServerUri()
|
||||
{
|
||||
UriBuilder builder = new UriBuilder();
|
||||
builder.Scheme = "LRM";
|
||||
builder.Host = serverId.ToString();
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_isAuthenticated = false;
|
||||
_isClient = false;
|
||||
_isServer = false;
|
||||
_connectedToRelay = false;
|
||||
clientToServerTransport.Shutdown();
|
||||
}
|
||||
|
||||
|
||||
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, RequestServers = 15, ServerListReponse = 16, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct RelayServerInfo
|
||||
{
|
||||
public string serverName;
|
||||
public int currentPlayers;
|
||||
public int maxPlayers;
|
||||
public int serverId;
|
||||
public string serverData;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue