first commit

This commit is contained in:
techiesplash 2023-01-15 13:55:38 -08:00
commit 35c0f73129
42 changed files with 3266 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.meta
"Visual Scripting"/.meta
Client/.meta
Images/.meta

View file

BIN
.vs/Matchmaker/v17/.suo Executable file

Binary file not shown.

3
.vs/ProjectSettings.json Executable file
View file

@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}

8
.vs/VSWorkspaceState.json Executable file
View file

@ -0,0 +1,8 @@
{
"ExpandedNodes": [
"",
"\\Visual Scripting"
],
"SelectedNode": "\\Visual Scripting",
"PreviewInSolutionExplorer": false
}

BIN
.vs/slnx.sqlite Executable file

Binary file not shown.

471
Client/Client.cs Executable file
View file

@ -0,0 +1,471 @@
/*
* MIT License
*
* Copyright (c) 2020 Tom Weiland
* Copyright (c) 2022 Vincent Dowling
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Scripts.Matchmaker;
using UnityEngine;
// ReSharper disable once CheckNamespace
namespace Scripts.CustomServer
{
/// <summary>
/// Stores information about the connection.
/// </summary>
public class Client : MonoBehaviour
{
public ClientSend.StatusType? lastStatusType;
public int lastReceivedInt;
public ClientHandle.ClientAttrib lastReceivedAttrib;
public bool welcomeReceived;
private const int DataBufferSize = 4096;
/// <summary>
/// For editor readability.
/// </summary>
public string displayName;
public string ip = Config.Address;
public int port = 26950;
public int myId;
public Tcp tcp;
public UDP udp;
private bool isConnected;
/// <summary>
/// Checks if it is connected to a server
/// </summary>
public bool IsConnected
{
get
{
try
{
if (tcp.socket is { Client: { Connected: true } })
{
/* pear to the documentation on Poll:
* When passing SelectMode.SelectRead as a parameter to the Poll method it will return
* -either- true if Socket.Listen(Int32) has been called and a connection is pending;
* -or- true if data is available for reading;
* -or- true if the connection has been closed, reset, or terminated;
* otherwise, returns false
*/
// Detect if listClient disconnected
if (tcp.socket.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (tcp.socket.Client.Receive(buff, SocketFlags.Peek) == 0)
{
// Client disconnected
return false;
}
else
{
return true;
}
}
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
}
public void OnDestroy()
{
Disconnect();
}
public class ClientPackets
{
public delegate void PacketHandler(Client server, Packet packet);
// ReSharper disable once FieldCanBeMadeReadOnly.Global
public Dictionary<int, PacketHandler> packetHandlers;
public ClientPackets(Dictionary<int, PacketHandler> packetHandlers)
{
this.packetHandlers = packetHandlers;
packetHandlers.Add(int.MaxValue, ClientHandle.Welcome);
packetHandlers.Add(int.MaxValue - 1, ClientHandle.ServerDisconnect);
packetHandlers.Add(int.MaxValue - 2, ClientHandle.GetClientAttributesReceived);
packetHandlers.Add(int.MaxValue - 3, ClientHandle.StatusReceived);
}
}
private ClientPackets packets = new(new Dictionary<int, ClientPackets.PacketHandler>());
private void OnApplicationQuit()
{
Disconnect(); // Disconnect when the game is closed
}
/// <summary>Attempts to connect to the server.</summary>
public bool ConnectToServer(ClientPackets packetList)
{
welcomeReceived = false;
Debug.Log($"Attempting connection to server with ip={ip} port={port}");
this.packets = packetList;
tcp = new Tcp(this);
udp = new UDP(this);
packetList.packetHandlers.TryAdd(int.MaxValue, ClientHandle.Welcome);
packetList.packetHandlers.TryAdd(int.MaxValue - 1, ClientHandle.ServerDisconnect);
packetList.packetHandlers.TryAdd(int.MaxValue - 2, ClientHandle.GetClientAttributesReceived);
packetList.packetHandlers.TryAdd(int.MaxValue - 3, ClientHandle.StatusReceived);
InitializeClientData();
isConnected = true;
tcp.Connect(); // MatchmakerNode_Connect tcp, udp gets connected once tcp is done
// Wait for a response from the server
var startTime = DateTime.Now.Ticks;
while (!welcomeReceived &&
((DateTime.Now.Ticks - startTime) < ClientSend.timeOutTime))
{
}
// If timed out
if ((DateTime.Now.Ticks - startTime) >= ClientSend.timeOutTime)
{
Debug.LogError($"ConnectToServer: Request timed out!.");
return false;
}
return true;
}
/// <summary>
/// Subclass to handle TCP networking
/// </summary>
public class Tcp
{
public TcpClient socket;
private readonly Client instance;
private NetworkStream stream;
private Packet receivedData;
private byte[] receiveBuffer;
public Tcp(Client inst)
{
instance = inst;
}
/// <summary>Attempts to connect to the server via TCP.</summary>
public void Connect()
{
socket = new TcpClient
{
ReceiveBufferSize = DataBufferSize,
SendBufferSize = DataBufferSize
};
receiveBuffer = new byte[DataBufferSize];
socket.BeginConnect(instance.ip, instance.port, ConnectCallback, socket);
}
/// <summary>Initializes the newly connected listClient's TCP-related info.</summary>
private void ConnectCallback(IAsyncResult result)
{
socket.EndConnect(result);
if (!socket.Connected)
{
return;
}
stream = socket.GetStream();
receivedData = new Packet();
stream.BeginRead(receiveBuffer, 0, DataBufferSize, ReceiveCallback, null);
}
/// <summary>Sends data to the listClient via TCP.</summary>
/// <param name="packet">The packet to send.</param>
public void SendData(Packet packet)
{
try
{
if (socket != null)
{
stream.BeginWrite(packet.ToArray(), 0, packet.Length(), null, null); // Send data to server
}
}
catch (Exception ex)
{
Debug.Log($"Error sending data to server via TCP: {ex}");
}
}
/// <summary>Reads incoming data from the stream.</summary>
private void ReceiveCallback(IAsyncResult result)
{
Debug.Log("Received TCP data. Preparing to handle...");
try
{
if (instance.IsConnected)
{
int byteLength = stream.EndRead(result);
if (byteLength <= 0)
{
instance.Disconnect();
return;
}
byte[] data = new byte[byteLength];
Array.Copy(receiveBuffer, data, byteLength);
receivedData.Reset(HandleData(data)); // Reset receivedData if all data was handled
stream.BeginRead(receiveBuffer, 0, DataBufferSize, ReceiveCallback, null);
}
}
catch (Exception ex)
{
Debug.LogError($"Error in TCP Receive Callback: {ex}");
Disconnect();
}
}
/// <summary>Prepares received data to be used by the appropriate packet handler methods.</summary>
/// <param name="data">The received data.</param>
private bool HandleData(byte[] data)
{
Debug.Log("Now handling data (TCP)...");
int packetLength = 0;
receivedData.SetBytes(data);
if (receivedData.UnreadLength() >= 4)
{
// If listClient's received data contains a packet
packetLength = receivedData.ReadInt();
if (packetLength <= 0)
{
// If packet contains no data
return true; // Reset receivedData instance to allow it to be reused
}
}
while (packetLength > 0 && packetLength <= receivedData.UnreadLength())
{
if (socket != null)
{
// While packet contains data AND packet data length doesn't exceed the length of the packet we're reading
var packetBytes = receivedData.ReadBytes(packetLength);
// ThreadManager.Start();
// ThreadManager.ExecuteOnMainThread(() =>
// {
using var packet = new Packet(packetBytes);
int packetId = packet.ReadInt();
Debug.Log($"Received TCP Packet number {packetId}");
instance.packets.packetHandlers
[packetId](instance, packet); // Call appropriate method to handle the packet
// });
}
packetLength = 0; // Reset packet length
if (receivedData.UnreadLength() >= 4)
{
// If listClient's received data contains another packet
packetLength = receivedData.ReadInt();
if (packetLength <= 0)
{
// If packet contains no data
return true; // Reset receivedData instance to allow it to be reused
}
}
}
if (packetLength <= 1)
{
return true; // Reset receivedData instance to allow it to be reused
}
return false;
}
/// <summary>Disconnects from the server and cleans up the TCP connection.</summary>
private void Disconnect()
{
instance.Disconnect();
stream = null;
receivedData = null;
receiveBuffer = null;
socket = null;
}
}
/// <summary>
/// Subclass to handle UDP networking
/// </summary>
public class UDP
{
public UdpClient socket;
private IPEndPoint endPoint;
private readonly Client instance;
public UDP(Client inst)
{
instance = inst;
endPoint = new IPEndPoint(IPAddress.Parse(instance.ip), instance.port);
}
/// <summary>Attempts to connect to the server via UDP.</summary>
/// <param name="localPort">The port number to bind the UDP socket to.</param>
public void Connect(int localPort)
{
socket = new UdpClient(localPort);
socket.Connect(endPoint);
socket.BeginReceive(ReceiveCallback, null);
using Packet packet = new Packet();
SendData(packet);
}
/// <summary>Sends data to the listClient via UDP.</summary>
/// <param name="packet">The packet to send.</param>
public void SendData(Packet packet)
{
try
{
packet.InsertInt(instance.myId); // Insert the listClient's ID at the start of the packet
if (socket != null)
{
socket.BeginSend(packet.ToArray(), packet.Length(), null, null);
}
}
catch (Exception ex)
{
Debug.Log($"Error sending data to server via UDP: {ex}");
}
}
/// <summary>Receives incoming UDP data.</summary>
private void ReceiveCallback(IAsyncResult result)
{
Debug.Log("Received UDP data. Preparing to handle...");
try
{
if (instance.IsConnected)
{
byte[] data = socket.EndReceive(result, ref endPoint);
socket.BeginReceive(ReceiveCallback, null);
if (data.Length < 4)
{
instance.Disconnect();
return;
}
HandleData(data);
}
}
catch (Exception ex)
{
Debug.LogError($"Error in UDP Receive Callback: {ex}");
Disconnect();
}
}
/// <summary>Prepares received data to be used by the appropriate packet handler methods.</summary>
/// <param name="data">The received data.</param>
private void HandleData(byte[] data)
{
Debug.Log("Now handling data (UDP)...");
using var packet = new Packet(data);
var packetLength = packet.ReadInt();
data = packet.ReadBytes(packetLength);
if (packet == null) throw new ArgumentNullException(nameof(packet));
// ThreadManager.Start();
// ThreadManager.ExecuteOnMainThread(() =>
// {
using var packet2 = new Packet(data);
var packetId = packet2.ReadInt();
try
{
instance.packets.packetHandlers
[packetId](instance, packet2); // Call appropriate method to handle the packet
}
catch (Exception e)
{
Debug.LogError($"Error in UDP packet handler {packetId}: {e}");
throw;
}
// });
}
/// <summary>Disconnects from the server and cleans up the UDP connection.</summary>
private void Disconnect()
{
instance.Disconnect();
endPoint = null;
socket = null;
}
}
/// <summary>Initializes all necessary listClient data.</summary>
private void InitializeClientData()
{
}
/// <summary>Disconnects from the server and stops all network traffic.</summary>
public void Disconnect()
{
if (!isConnected) return;
isConnected = false;
tcp.socket.Close();
udp?.socket?.Close();
Debug.Log("Disconnected from server.");
}
}
}

122
Client/ClientHandle.cs Executable file
View file

@ -0,0 +1,122 @@
/*
* MIT License
*
* Copyright (c) 2020 Tom Weiland
* Copyright (c) 2022 Vincent Dowling
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Net;
using Scripts.Matchmaker;
using UnityEngine;
// ReSharper disable once CheckNamespace
namespace Scripts.CustomServer
{
public static class ClientHandle
{
public class ClientAttrib
{
// ReSharper disable once MemberCanBePrivate.Global
public int id;
public readonly string name;
public readonly string value;
public ClientAttrib(int id, string name, string value)
{
this.id = id;
this.name = name;
this.value = value;
}
}
public static ClientAttrib lastReceivedAttrib;
public static void GetClientAttributesReceived(Client client, Packet packet)
{
var requestedId = packet.ReadInt();
var name = packet.ReadString();
var value = packet.ReadString();
Debug.Log("Received object attributes (Obj. ID: " + requestedId.ToString() + "): '" + name + "', '" +
value + "'.");
lastReceivedAttrib = new ClientAttrib(requestedId, name, value);
}
public static void Welcome(Client client, Packet packet)
{
var msg = packet.ReadString();
var myId = packet.ReadInt();
var serverAPIVersion = packet.ReadString();
var serverId = packet.ReadString();
var gameVersion = packet.ReadString();
// Version check
if (serverAPIVersion != Config.MatchmakerAPIVersion)
{
Debug.LogWarning(
$"Client API version mismatch! Server API version is {serverAPIVersion} while Client API version is {Config.MatchmakerAPIVersion}.");
client.Disconnect();
return;
}
if (serverId != Config.GameId)
{
Debug.LogWarning($"Client-Server Game ID Mismatch! Server ID is {serverId} while Client Game ID is {Config.GameId}.");
client.Disconnect();
return;
}
// Version check
if (gameVersion != Config.GameVersion)
{
Debug.LogWarning(
$"Client game version mismatch! Server game version is {gameVersion} while Client game version is {Config.GameVersion}.");
client.Disconnect();
return;
}
client.welcomeReceived = true;
Debug.Log($"Message from server: {msg}");
client.myId = myId;
ClientSend.WelcomeReceived(client);
client.udp.Connect(((IPEndPoint)client.tcp.socket.Client.LocalEndPoint).Port);
client.welcomeReceived = true;
//MatchmakerBehaviour.APIClientPackets.RequestAllLobbyIds(listClient);
}
public static void ServerDisconnect(Client client, Packet packet)
{
client.Disconnect();
}
public static void StatusReceived(Client client, Packet packet)
{
var type = packet.ReadInt();
Debug.Log($"Status received: {(ClientSend.StatusType)type}");
client.lastStatusType = (ClientSend.StatusType)type;
}
}
}

214
Client/ClientSend.cs Executable file
View file

@ -0,0 +1,214 @@
/*
* MIT License
*
* Copyright (c) 2020 Tom Weiland
* Copyright (c) 2022 Vincent Dowling
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using UnityEngine;
using Scripts.Matchmaker;
using DateTime = System.DateTime;
// ReSharper disable once CheckNamespace
namespace Scripts.CustomServer
{
public static class ClientSend
{
public enum StatusType
{
Ok,
Received,
Fail
}
// ReSharper disable once FieldCanBeMadeReadOnly.Global
// ReSharper disable once ConvertToConstant.Global
public static float timeOutTime = 30000000;
// ReSharper disable once MemberCanBePrivate.Global
public static void SendTcpDataNoSync(Client client, Packet packet)
{
packet.WriteLength();
client.tcp.SendData(packet);
}
// ReSharper disable once UnusedMember.Global
public static void SendUdpDataNoSync(Client client, Packet packet)
{
packet.WriteLength();
client.udp.SendData(packet);
}
public static bool SendTcpData(Client client, Packet packet)
{
if (client.IsConnected)
{
// Send the data
packet.WriteLength();
client.tcp.SendData(packet);
if (Config.Sync)
{
// Wait for a response from the server
var startTime = DateTime.Now.Ticks;
client.lastStatusType = null;
while (client.lastStatusType == null &&
((DateTime.Now.Ticks - startTime) < timeOutTime))
{
}
// If timed out
if ((DateTime.Now.Ticks - startTime) < timeOutTime)
{
Debug.LogError($"SendTCPData: Request timed out!.");
return false;
}
return client.lastStatusType is StatusType.Ok or StatusType.Received;
}
return true;
}
else
{
Debug.LogWarning("Attempted to send TCP data through a disconnected Client!");
return false;
}
}
// ReSharper disable once UnusedMember.Global
public static bool SendUdpData(Client client, Packet packet)
{
if (client.IsConnected)
{
// Send the data
packet.WriteLength();
client.udp.SendData(packet);
if (Config.Sync)
{
// Wait for a response from the server
var startTime = DateTime.Now.Ticks;
client.lastStatusType = null;
while (client.lastStatusType == null &&
((DateTime.Now.Ticks - startTime) < timeOutTime))
{
}
// If timed out
if ((DateTime.Now.Ticks - startTime) < timeOutTime)
{
Debug.LogError($"SendTCPData: Request timed out!.");
return false;
}
return client.lastStatusType is StatusType.Ok or StatusType.Received;
}
return true;
}
// If status received
else
{
Debug.LogWarning("Attempted to send UDP data through a disconnected Client!");
return false;
}
}
#region Built-in Packets
public static void WelcomeReceived(Client client)
{
Debug.Log("Sending 'Welcome Received' message...");
using var packet = new Packet(int.MaxValue);
packet.Write(client.myId);
packet.Write(Config.MatchmakerAPIVersion);
packet.Write(Config.GameVersion);
packet.Write(Config.GameId);
SendTcpData(client, packet);
}
public static bool SetClientAttribute(Client client, String name, String value)
{
Debug.Log("Sending request to change a Client Attribute...");
using var packet = new Packet(int.MaxValue - 1);
packet.Write(client.myId);
packet.Write(name);
packet.Write(value);
if (ClientSend.SendTcpData(client, packet))
{
return true;
}
return false;
}
public static ClientHandle.ClientAttrib GetClientAttribute(Client client, int id, String name)
{
Debug.Log("Sending request to be sent a Client Attribute...");
using var packet = new Packet(int.MaxValue - 2);
packet.Write(client.myId);
packet.Write(id);
packet.Write(name);
ClientHandle.lastReceivedAttrib = null;
ClientSend.SendTcpData(client, packet);
var startTime = DateTime.Now.Ticks;
while (ClientHandle.lastReceivedAttrib == null && ((DateTime.Now.Ticks - startTime) < timeOutTime))
{
}
if (!((DateTime.Now.Ticks - startTime) < timeOutTime))
{
Debug.LogError("GetClientAttribute: Request timed out!");
}
return ClientHandle.lastReceivedAttrib;
}
/// <summary>
/// Respond to the listClient with a status
/// </summary>
/// <param name="client">What Client is sending it</param>
/// <param name="status">What status</param>
public static void Status(Client client, StatusType status)
{
using var packet = new Packet(int.MaxValue - 3);
packet.Write((int)status);
SendTcpDataNoSync(client, packet);
}
#endregion
}
}

403
Client/Packet.cs Executable file
View file

@ -0,0 +1,403 @@
/*
* MIT License
*
* Copyright (c) 2020 Tom Weiland
* Copyright (c) 2022 Vincent Dowling
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
// ReSharper disable once CheckNamespace
namespace Scripts.CustomServer
{
public sealed class Packet : IDisposable
{
private List<byte> buffer;
private byte[] readableBuffer;
private int readPos;
/// <summary>Creates a new empty packet (without an ID).</summary>
public Packet()
{
buffer = new List<byte>(); // Initialize buffer
readPos = 0; // Set readPos to 0
}
/// <summary>Creates a new packet with a given ID. Used for sending.</summary>
/// <param name="id">The packet ID.</param>
public Packet(int id)
{
buffer = new List<byte>(); // Initialize buffer
readPos = 0; // Set readPos to 0
Write(id); // Write packet id to the buffer
}
/// <summary>Creates a packet from which data can be read. Used for receiving.</summary>
/// <param name="data">The bytes to add to the packet.</param>
public Packet(byte[] data)
{
buffer = new List<byte>(); // Initialize buffer
readPos = 0; // Set readPos to 0
SetBytes(data);
}
#region Functions
/// <summary>Sets the packet's content and prepares it to be read.</summary>
/// <param name="data">The bytes to add to the packet.</param>
public void SetBytes(byte[] data)
{
Write(data);
readableBuffer = buffer.ToArray();
}
/// <summary>Inserts the length of the packet's content at the start of the buffer.</summary>
public void WriteLength()
{
buffer.InsertRange(0, BitConverter.GetBytes(buffer.Count)); // Insert the byte length of the packet at the very beginning
}
/// <summary>Inserts the given int at the start of the buffer.</summary>
/// <param name="value">The int to insert.</param>
public void InsertInt(int value)
{
buffer.InsertRange(0, BitConverter.GetBytes(value)); // Insert the int at the start of the buffer
}
/// <summary>Gets the packet's content in array form.</summary>
public byte[] ToArray()
{
readableBuffer = buffer.ToArray();
return readableBuffer;
}
/// <summary>Gets the length of the packet's content.</summary>
public int Length()
{
return buffer.Count; // Return the length of buffer
}
/// <summary>Gets the length of the unread data contained in the packet.</summary>
public int UnreadLength()
{
return Length() - readPos; // Return the remaining length (unread)
}
/// <summary>Resets the packet instance to allow it to be reused.</summary>
/// <param name="shouldReset">Whether or not to reset the packet.</param>
public void Reset(bool shouldReset = true)
{
if (shouldReset)
{
buffer.Clear(); // Clear buffer
readableBuffer = null;
readPos = 0; // Reset readPos
}
else
{
readPos -= 4; // "Unread" the last read int
}
}
#endregion
#region Write Data
/// <summary>Adds a byte to the packet.</summary>
/// <param name="value">The byte to add.</param>
public void Write(byte value)
{
buffer.Add(value);
}
/// <summary>Adds an array of bytes to the packet.</summary>
/// <param name="value">The byte array to add.</param>
// ReSharper disable once MemberCanBePrivate.Global
public void Write(IEnumerable<byte> value)
{
buffer.AddRange(value);
}
/// <summary>Adds a short to the packet.</summary>
/// <param name="value">The short to add.</param>
public void Write(short value)
{
buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds an int to the packet.</summary>
/// <param name="value">The int to add.</param>
public void Write(int value)
{
buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds a long to the packet.</summary>
/// <param name="value">The long to add.</param>
public void Write(long value)
{
buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds a float to the packet.</summary>
/// <param name="value">The float to add.</param>
public void Write(float value)
{
buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds a bool to the packet.</summary>
/// <param name="value">The bool to add.</param>
public void Write(bool value)
{
buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds a string to the packet.</summary>
/// <param name="value">The string to add.</param>
public void Write(string value)
{
Write(value.Length); // Add the length of the string to the packet
buffer.AddRange(Encoding.ASCII.GetBytes(value)); // Add the string itself
}
/// <summary>Adds a Vector3 to the packet.</summary>
/// <param name="value">The Vector3 to add.</param>
public void Write(Vector3 value)
{
Write(value.x);
Write(value.y);
Write(value.z);
}
/// <summary>Adds a Quaternion to the packet.</summary>
/// <param name="value">The Quaternion to add.</param>
public void Write(Quaternion value)
{
Write(value.x);
Write(value.y);
Write(value.z);
Write(value.w);
}
#endregion
#region Read Data
/// <summary>Reads a byte from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public byte ReadByte(bool moveReadPos = true)
{
if (buffer.Count > readPos)
{
// If there are unread bytes
var value = readableBuffer[readPos]; // Get the byte at readPos' position
if (moveReadPos)
{
// If _moveReadPos is true
readPos += 1; // Increase readPos by 1
}
return value; // Return the byte
}
else
{
throw new Exception("Could not read value of type 'byte'!");
}
}
/// <summary>Reads an array of bytes from the packet.</summary>
/// <param name="length">The length of the byte array.</param>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public byte[] ReadBytes(int length, bool moveReadPos = true)
{
if (buffer.Count > readPos)
{
// If there are unread bytes
var value = buffer.GetRange(readPos, length).ToArray(); // Get the bytes at readPos' position with a range of _length
if (moveReadPos)
{
// If _moveReadPos is true
readPos += length; // Increase readPos by _length
}
return value; // Return the bytes
}
else
{
throw new Exception("Could not read value of type 'byte[]'!");
}
}
/// <summary>Reads a short from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public short ReadShort(bool moveReadPos = true)
{
if (buffer.Count > readPos)
{
// If there are unread bytes
var value = BitConverter.ToInt16(readableBuffer, readPos); // Convert the bytes to a short
if (moveReadPos)
{
// If _moveReadPos is true and there are unread bytes
readPos += 2; // Increase readPos by 2
}
return value; // Return the short
}
else
{
throw new Exception("Could not read value of type 'short'!");
}
}
/// <summary>Reads an int from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public int ReadInt(bool moveReadPos = true)
{
if (buffer.Count > readPos)
{
// If there are unread bytes
var value = BitConverter.ToInt32(readableBuffer, readPos); // Convert the bytes to an int
if (moveReadPos)
{
// If _moveReadPos is true
readPos += 4; // Increase readPos by 4
}
return value; // Return the int
}
else
{
throw new Exception("Could not read value of type 'int'!");
}
}
/// <summary>Reads a long from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public long ReadLong(bool moveReadPos = true)
{
if (buffer.Count > readPos)
{
// If there are unread bytes
var value = BitConverter.ToInt64(readableBuffer, readPos); // Convert the bytes to a long
if (moveReadPos)
{
// If _moveReadPos is true
readPos += 8; // Increase readPos by 8
}
return value; // Return the long
}
else
{
throw new Exception("Could not read value of type 'long'!");
}
}
/// <summary>Reads a float from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public float ReadFloat(bool moveReadPos = true)
{
if (buffer.Count > readPos)
{
// If there are unread bytes
var value = BitConverter.ToSingle(readableBuffer, readPos); // Convert the bytes to a float
if (moveReadPos)
{
// If _moveReadPos is true
readPos += 4; // Increase readPos by 4
}
return value; // Return the float
}
else
{
throw new Exception("Could not read value of type 'float'!");
}
}
/// <summary>Reads a bool from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public bool ReadBool(bool moveReadPos = true)
{
if (buffer.Count > readPos)
{
// If there are unread bytes
var value = BitConverter.ToBoolean(readableBuffer, readPos); // Convert the bytes to a bool
if (moveReadPos)
{
// If _moveReadPos is true
readPos += 1; // Increase readPos by 1
}
return value; // Return the bool
}
else
{
throw new Exception("Could not read value of type 'bool'!");
}
}
/// <summary>Reads a string from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public string ReadString(bool moveReadPos = true)
{
try
{
var length = ReadInt(); // Get the length of the string
var value = Encoding.ASCII.GetString(readableBuffer, readPos, length); // Convert the bytes to a string
if (moveReadPos && value.Length > 0)
{
// If _moveReadPos is true string is not empty
readPos += length; // Increase readPos by the length of the string
}
return value; // Return the string
}
catch
{
throw new Exception("Could not read value of type 'string'!");
}
}
/// <summary>Reads a Vector3 from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public Vector3 ReadVector3(bool moveReadPos = true)
{
return new Vector3(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos));
}
/// <summary>Reads a Quaternion from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public Quaternion ReadQuaternion(bool moveReadPos = true)
{
return new Quaternion(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos));
}
#endregion
private bool disposed;
private void Dispose(bool disposing)
{
if (disposed) return;
if (disposing)
{
buffer = null;
readableBuffer = null;
readPos = 0;
}
disposed = true;
}
public void Dispose()
{
Dispose(true);
// ReSharper disable once GCSuppressFinalizeForTypeWithoutDestructor
GC.SuppressFinalize(this);
}
}
}

119
Client/ThreadManager.cs Executable file
View file

@ -0,0 +1,119 @@
/*
* MIT License
*
* Copyright (c) 2020 Tom Weiland
* Copyright (c) 2022 Vincent Dowling
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
// ReSharper disable once CheckNamespace
namespace Scripts.CustomServer
{
public static class ThreadManager
{
private static readonly List<Action> ActionsForMainThread = new();
private static readonly List<Action> ExecuteCopiedOnMainThread = new();
private static bool _actionToExecuteOnMainThread;
private const int TicksPerSec = 40;
private const float MsPerTick = 1000f / TicksPerSec;
private static bool _isRunning;
private static readonly Thread MainThreadObject = new(MainThread);
public static void Start()
{
if (_isRunning) return;
_isRunning = true;
MainThreadObject.Start();
}
public static void Stop()
{
_isRunning = false;
}
/// <summary>Sets an action to be executed on the main thread.</summary>
/// <param name="action">The action to be executed on the main thread.</param>
public static void ExecuteOnMainThread(Action action)
{
if (action == null)
{
Debug.Log("No action to execute on main thread!");
return;
}
lock (ActionsForMainThread)
{
ActionsForMainThread.Add(action);
_actionToExecuteOnMainThread = true;
}
}
/// <summary>Executes all code meant to run on the main thread. NOTE: Call this ONLY from the main thread.</summary>
public static void UpdateMain()
{
if (!_actionToExecuteOnMainThread) return;
ExecuteCopiedOnMainThread.Clear();
lock (ActionsForMainThread)
{
ExecuteCopiedOnMainThread.AddRange(ActionsForMainThread);
ActionsForMainThread.Clear();
_actionToExecuteOnMainThread = false;
}
foreach (var t in ExecuteCopiedOnMainThread)
{
t();
}
}
private static void MainThread()
{
Debug.Log($"Main thread started. Running at {TicksPerSec} ticks per second.");
var nextLoop = DateTime.Now;
while (_isRunning)
{
while (nextLoop < DateTime.Now)
{
UpdateMain();
nextLoop = nextLoop.AddMilliseconds(MsPerTick);
if (nextLoop > DateTime.Now)
{
Thread.Sleep(nextLoop - DateTime.Now);
}
}
}
}
}
}

36
Config.cs Executable file
View file

@ -0,0 +1,36 @@
namespace Scripts.Matchmaker
{
public static class Config
{
/// <summary>
/// This allows you to name the game so your game cannot connect to servers it is not supposed to.
/// </summary>
public const string GameId = "My Game Name";
/// <summary>
/// The version of the matchmaker APII. This must match here and on the server for a connection.
/// </summary>
public const string MatchmakerAPIVersion = "1.1.6";
/// <summary>
/// The version of the game itself. Must match on the server for a connection.
/// Name is also valid, as it is a string.
/// </summary>
public const string GameVersion = "0.0.1";
/// <summary>
/// What port the server will be on (0-65535). The lobby server will use a port two (2) above this.
/// </summary>
public const ushort Port = 26950;
/// <summary>
/// What address the server is at.
/// </summary>
public const string Address = "192.168.1.204";
/// <summary>
/// Makes it so it waits for a response from the server every time it sends data. Slow, advise use for debugging.
/// </summary>
public const bool Sync = false;
}
}

93
IMGUIMenuDemo.cs Executable file
View file

@ -0,0 +1,93 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Mirror;
using Scripts.CustomServer;
using Scripts.Matchmaker;
using UnityEngine;
[RequireComponent(typeof(Client))]
public class IMGUIMenuDemo : MonoBehaviour, IMatchmaker
{
private List<string> ServerList = new();
public static bool CreatePublic;
private string connectText;
private void Start()
{
IMatchmaker.listClient = gameObject.GetComponent<Client>();
}
private void Refresh()
{
IMatchmaker.Connect();
/* var x = IMatchmaker.GetAllLobbyIdsByAttribute("public", "True");
foreach (var y in x)
{
ServerList.Add(IMatchmaker.GetLobbyAttribute(y, "_auto_ip"));
}*/
// IMatchmaker.Disconnect();
}
private void OnGUI()
{
GUI.Box(new Rect(10, 10, 200, 500), "Connection Manager");
connectText = GUI.TextField(new Rect(30, 30, 160, 20), connectText);
if (GUI.Button(new Rect(30, 60, 160, 20), "Connect"))
{
// Check if it is an IP
NetworkManager.singleton.networkAddress = connectText;
NetworkManager.singleton.StartClient();
// Otherwise
IMatchmaker.Connect();
var uuidLobby = IMatchmaker.GetLobbyIdByUuid(connectText);
var address = "";
if (uuidLobby > 0)
{
NetworkManager.singleton.networkAddress = IMatchmaker.GetLobbyAttribute(uuidLobby, "_auto_uuid");
IMatchmaker.Disconnect();
NetworkManager.singleton.StartClient();
}
else
{
IMatchmaker.Disconnect();
}
}
if (GUI.Button(new Rect(30, 100, 160, 20), "Start Host (Public)"))
{
CreatePublic = true;
NetworkManager.singleton.StartHost();
}
if (GUI.Button(new Rect(30, 130, 160, 20), "Start Host (Private)"))
{
CreatePublic = false;
NetworkManager.singleton.StartHost();
}
if (GUI.Button(new Rect(30, 170, 160, 20), "Refresh"))
{
Refresh();
}
int yPos = 200;
foreach (var x in ServerList)
{
if(GUI.Button(new Rect(30, yPos+=30, 160, 20), x))
{
NetworkManager.singleton.networkAddress = x;
NetworkManager.singleton.StartClient();
}
}
}
}

BIN
Images/preview.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

22
LICENSE.md Executable file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2020 Tom Weiland
Copyright (c) 2022 Techiesplash
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

24
LobbyManager.cs Executable file
View file

@ -0,0 +1,24 @@
using System.Collections;
using System.Collections.Generic;
using Mirror;
using Scripts.CustomServer;
using Scripts.Matchmaker;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(Client))]
public class LobbyManager : NetworkBehaviour
{
public Text text;
public override void OnStartServer()
{
if (isServer)
{
IMatchmaker.lobbyClient = gameObject.GetComponent<Client>();
IMatchmaker.ConnectLobby();
IMatchmaker.SetLobbyAttribute("public", IMGUIMenuDemo.CreatePublic.ToString());
text.text = IMatchmaker.GetLobbyAttributeDirect("_auto_uuid");
}
}
}

654
Matchmaker.cs Executable file
View file

@ -0,0 +1,654 @@
/*
* MIT License
*
* Copyright (c) 2020 Tom Weiland
* Copyright (c) 2022 Vincent Dowling
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Threading;
using JetBrains.Annotations;
using Scripts.CustomServer;
using UnityEngine;
using Client = Scripts.CustomServer.Client;
// ReSharper disable once CheckNamespace
namespace Scripts.Matchmaker
{
public interface IMatchmaker
{
/**************************************************************************************************************
* Matchmaker Programming Interface - External
**************************************************************************************************************/
// Global Variables --------------------------------------------------------------------------------------------
public static Client listClient;
public static Client lobbyClient;
public static int port = Config.Port;
public static string ip = Config.Address;
public static string GameId = Config.GameId;
public static readonly Client.ClientPackets apiPackets = new Client.ClientPackets(
new Dictionary<int, Client.ClientPackets.PacketHandler>()
{
{ 10, API.APIPacketHandlers.ReceiveLobbyId },
{ 11, API.APIPacketHandlers.GetLobbyAttributesReceived },
{ 12, API.APIPacketHandlers.FinishedSendingLobbyIds },
});
// API Functions - Connection Management -----------------------------------------------------------------------
/// <summary>
/// MatchmakerNode_Connect the List Client.
/// </summary>
public static bool Connect()
{
if (listClient != null)
{
listClient.ip = ip;
listClient.port = port;
listClient.welcomeReceived = false;
if (!listClient.ConnectToServer(apiPackets)) return false;
var startTime = DateTime.Now.Ticks;
while (listClient.welcomeReceived == false && ((DateTime.Now.Ticks - startTime) < 30000000))
{
}
if ((DateTime.Now.Ticks - startTime) < 30000000) return true;
Debug.LogError("Failed to connect to List Server: Connection timed out!");
}
else
{
Debug.LogError(
"Attempted to connect to the List Server while listClient is null. Please set Matchmaker.listClient first!");
}
return false;
}
/// <summary>
/// MatchmakerNode_Connect the Lobby Client.
/// </summary>
public static bool ConnectLobby()
{
lobbyClient.ip = ip;
lobbyClient.port = port + 2;
lobbyClient.welcomeReceived = false;
if (lobbyClient.ConnectToServer(new Client.ClientPackets(new Dictionary<int, Client.ClientPackets.PacketHandler>())))
{
var startTime = DateTime.Now.Ticks;
while (lobbyClient.welcomeReceived == false && ((DateTime.Now.Ticks - startTime) < 30000000))
{
}
if ((DateTime.Now.Ticks - startTime) < 30000000) return true;
Debug.LogError("Failed to connect to to Lobby Server: Connection timed out!");
return false;
}
return false;
}
/// <summary>
/// Disconnect the List Client.
/// </summary>
public static void Disconnect()
{
if (listClient != null)
{
listClient.Disconnect();
}
else
{
Debug.LogWarning(
"Attempted to disconnect a List Client while listClient is null. Please set Matchmaker.listClient first!");
}
}
/// <summary>
/// Disconnect the Lobby Client.
/// </summary>
public static void DisconnectLobby()
{
Debug.Log("Disconnecting from Lobby server...");
// SetLobbyAttribute("_auto_uuid", "DISCONNECTED_LOBBY");
// SetLobbyAttribute("_auto_ip", "DISCONNECTED_LOBBY");
// SetLobbyAttribute("_auto_port", "DISCONNECTED_LOBBY");
lobbyClient.Disconnect();
}
// API Functions - Client Attributes ---------------------------------------------------------------------------
/// <summary>
/// Set an Attribute on the List Client.
/// </summary>
/// <param name="name">Name of the Attribute to set</param>
/// <param name="value">Value of the Attribute to set to</param>
public static bool SetClientAttribute(string name, string value)
{
if (listClient != null)
{
if (listClient.IsConnected)
{
if (ClientSend.SetClientAttribute(listClient, name, value))
{
return true;
}
}
}
else
{
Debug.LogWarning(
"Attempted to set a List Client Attribute while listClient is null. Please set Matchmaker.listClient first!");
}
return false;
}
/// <summary>
/// Get a List Client's Attribute.
/// </summary>
/// <param name="clientId">What List Client to find the Attribute in</param>
/// <param name="name">Name of the Attribute to get.</param>
/// <returns>The value of the Attribute. Returns null on failure.</returns>
public static string GetClientAttribute(int clientId, string name)
{
if (listClient != null)
{
if (listClient.IsConnected)
{
var foo = ClientSend.GetClientAttribute(listClient, clientId, name);
// Debug check - Is the received Attributes Name not the same as the requested one?
if (foo.name == name) return foo.value;
Debug.LogError("Requested Attribute Name and Received Attribute Name do not match!");
return null;
}
}
else
{
Debug.LogWarning(
"Attempted to get a List Client Attribute while listClient is null. Please set Matchmaker.listClient first!");
}
return null;
}
// API Functions - Lobby Attributes ----------------------------------------------------------------------------
/// <summary>
/// Get a Lobby Client's Attribute straight from the Lobby Server.
/// </summary>
/// <param name="clientId">What Lobby Client to find the Attribute in</param>
/// <param name="name">Name of the Attribute to get.</param>
/// <returns>The value of the Attribute. Returns null on failure.</returns>
public static string GetLobbyAttributeDirect(string name)
{
if (lobbyClient != null)
{
if (lobbyClient.IsConnected)
{
var foo = ClientSend.GetClientAttribute(lobbyClient, lobbyClient.myId, name);
// Debug check - Is the received Attributes Name not the same as the requested one?
if (foo.name == name) return foo.value;
Debug.LogError("Requested Attribute Name and Received Attribute Name do not match!");
return null;
}
}
else
{
Debug.LogWarning(
"Attempted to get a Lobby Client's Attribute *directly* while lobbyClient is null. Please set Matchmaker.lobbyClient first!");
}
return null;
}
/// <summary>
/// Set a Attribute on the Lobby Client.
/// </summary>
/// <param name="name">Name of the Attribute to set.</param>
/// <param name="value">What value to set the Attribute to.</param>
public static bool SetLobbyAttribute(string name, string value)
{
if (lobbyClient != null)
{
if (lobbyClient.IsConnected)
{
return ClientSend.SetClientAttribute(lobbyClient, name, value);
}
}
else
{
Debug.LogWarning(
"Attempted to set a Lobby Client Attribute while lobbyClient is null. Please set Matchmaker.lobbyClient first!");
}
return false;
}
/// <summary>
/// Get a Lobby Client Attribute by passing through the List Server.
/// </summary>
/// <param name="clientId">What Lobby Client to find the Attribute in</param>
/// <param name="name">Name of the Attribute to get.</param>
/// <returns>The value of the Attribute. Returns null on failure.</returns>
public static string GetLobbyAttribute(int clientId, string name)
{
if (listClient != null)
{
if (listClient.IsConnected)
{
var foo = API.APIClientPackets.GetLobbyAttribute(listClient, clientId, name);
if (foo == null)
{
Debug.LogWarning("Received Lobby Attribute is null!");
return null;
}
// Debug check - Is the received Attributes Name not the same as the requested one?
if (foo.name == name) return foo.value;
Debug.LogError(
$"Requested Attribute Name {foo.name} and Received Attribute Name {name} do not match!");
return null;
}
}
else
{
Debug.LogWarning(
"Attempted to get a Lobby Client's Attribute while listClient is null. Please set Matchmaker.listClient first!");
}
return null;
}
// API Functions - Lobby Discovery -----------------------------------------------------------------------------
/// <summary>
/// Gets a Lobby Client's ID by its UUID.
/// </summary>
/// <param name="uuid">The UUID to find a Lobby Client with.</param>
/// <returns>A Lobby Client's ID with matching UUID.</returns>
public static int GetLobbyIdByUuid(string uuid)
{
if (listClient != null)
{
if (listClient.IsConnected)
{
API.APIPacketHandlers.lobbyIds.Clear();
var foo = GetAllLobbyIdsByAttribute("_auto_uuid", uuid);
if(foo.Count > 1)
{
Debug.LogWarning("Found more than one lobby with UUID!");
}
if (foo.Count <= 0)
{
Debug.LogWarning(
"Failed to find Lobby IDs with matching UUID!");
return 0;
}
else
{
return foo[0];
}
}
}
else
{
Debug.LogWarning(
"Attempted to get a Lobby Client's ID through a UUID while listClient is null. Please set Matchmaker.listClient first!");
}
return 0;
}
/// <summary>
/// Gets all Lobby IDs by matching Attribute
/// </summary>
/// <param name="attribName">The name of the Attribute</param>
/// <param name="attribValue">The value of the Attribute</param>
public static List<int> GetAllLobbyIdsByAttribute(string attribName, string attribValue)
{
API.APIPacketHandlers.lobbyIds.Clear();
if (listClient != null)
{
if (!listClient.IsConnected) return API.APIPacketHandlers.lobbyIds;
var foo = API.APIClientPackets.RequestLobbyIdsWithMatchingAttribute(listClient, attribName, attribValue);
if (!foo)
{
Debug.LogWarning(
"Failed to find Lobby IDs with matching Attrbute!");
}
return API.APIPacketHandlers.lobbyIds;
}
else
{
Debug.LogWarning(
"Attempted to get all lobby IDs with matching Attribute while listClient is null. Please set Matchmaker.listClient first!");
}
return API.APIPacketHandlers.lobbyIds;
}
/// <summary>
/// Gets a list of the IDs of all connected Lobby Clients. Also calls a callback at Matchmaker.API.lobbyCallback for every ID.
/// </summary>
/// <returns>List of Lobby Client IDs</returns>
[CanBeNull]
public static List<int> GetAllLobbyIds()
{
if (listClient != null)
{
API.APIPacketHandlers.lobbyIds.Clear();
Debug.Log("GetAllLobbyIds() called.");
if (listClient.IsConnected)
{
API.APIClientPackets.RequestAllLobbyIds(listClient);
}
Debug.LogWarning("Attempted to request all Lobby IDs while Client is disconnected!");
}
else
{
Debug.LogWarning(
"Attempted to get a list of all Lobby IDs while listClient is null. Please set Matchmaker.listClient first!");
}
Debug.Log($"Lobby ID list length: {API.APIPacketHandlers.lobbyIds.Count}");
return API.APIPacketHandlers.lobbyIds;
}
/**************************************************************************************************************
* Matchmaker Programming Interface - Internal
**************************************************************************************************************/
/// <summary>
/// Contains the internal functionality for the Matchmaker API
/// </summary>
public static class API
{
// API Internals - Global Variables ------------------------------------------------------------------------
public delegate void UserCallbackDelegate(int userId);
/// <summary>
/// Gets called back whenever a Lobby Client ID is received by GetAllLobbyIds().
/// </summary>
public static UserCallbackDelegate lobbyCallback;
private static readonly List<Action> ExecuteOnApiThread = new();
private static readonly List<Action> ExecuteCopiedOnApiThread = new();
private static bool _actionToExecuteOnApiThread;
private const int TicksPerSec = 30;
private const float MsPerTick = 1000f / TicksPerSec;
private static bool _isRunning = true;
private static Thread _apiThread;
// API Internals - API Thread Manager ----------------------------------------------------------------------
private static void APIThread(object data)
{
Debug.Log($"MatchmakerBehaviour thread started. Running at {TicksPerSec} ticks per second.");
var nextLoop = DateTime.Now;
while (_isRunning)
{
while (nextLoop < DateTime.Now)
{
UpdateAPI();
nextLoop = nextLoop.AddMilliseconds(MsPerTick);
if (nextLoop > DateTime.Now)
{
Thread.Sleep(nextLoop - DateTime.Now);
}
}
}
}
public static void StartThreads()
{
// if (apiThread != null || _isRunning != false) return;
_isRunning = true;
_apiThread = new Thread(APIThread);
_apiThread.Start(0);
}
public static void StopThreads()
{
_isRunning = false;
}
/// <summary>Sets an action to be executed on the main thread.</summary>
/// <param name="action">The action to be executed on the main thread.</param>
public static void ExecuteOnAPIThread(Action action)
{
if (action == null)
{
Debug.Log("No action to execute on MatchmakerBehaviour thread!");
return;
}
lock (ExecuteOnApiThread)
{
ExecuteOnApiThread.Add(action);
_actionToExecuteOnApiThread = true;
}
}
/// <summary>
/// Executes all code meant to run on the main thread. NOTE: Call this ONLY from the main thread.
/// </summary>
private static void UpdateAPI()
{
if (_actionToExecuteOnApiThread)
{
ExecuteCopiedOnApiThread.Clear();
lock (ExecuteOnApiThread)
{
ExecuteCopiedOnApiThread.AddRange(ExecuteOnApiThread);
ExecuteOnApiThread.Clear();
_actionToExecuteOnApiThread = false;
}
foreach (var t in ExecuteCopiedOnApiThread)
{
t();
}
}
}
// API Internals - Packet Handling -------------------------------------------------------------------------
public static class APIPacketHandlers
{
public static readonly List<int> lobbyIds = new();
public static bool finishedSendingIds;
public static bool receivedId;
public static void ReceiveLobbyId(Client client, Packet packet)
{
receivedId = true;
int userId = packet.ReadInt();
int mod = packet.ReadInt();
Debug.Log("Received lobby id: " + userId.ToString());
lobbyIds.Add(userId);
switch (mod)
{
case 0:
{
if (API.lobbyCallback != null)
{
API.lobbyCallback.Invoke(userId);
}
break;
}
default:
client.lastReceivedInt = userId;
break;
}
}
public static void GetLobbyAttributesReceived(Client client, Packet packet)
{
int requestedId = packet.ReadInt();
string name = packet.ReadString();
string value = packet.ReadString();
Debug.Log("Received lobby attributes (Obj. ID: " + requestedId.ToString() + "): '" + name + "', '" +
value + "'.");
client.lastReceivedAttrib = new ClientHandle.ClientAttrib(requestedId, name, value);
}
public static void FinishedSendingLobbyIds(Client client, Packet packet)
{
Debug.Log("Finished receiving Lobby IDs.");
finishedSendingIds = true;
}
}
// API Internals - Packet Sending --------------------------------------------------------------------------
public static class APIClientPackets
{
// ReSharper disable once FieldCanBeMadeReadOnly.Global
// ReSharper disable once ConvertToConstant.Global
// ReSharper disable once MemberCanBePrivate.Global
public static long timeOutTime = 300000000;
public static bool RequestAllLobbyIds(Client client)
{
if (client.IsConnected)
{
APIPacketHandlers.finishedSendingIds = false;
Debug.Log("Getting all the connected lobby IDs...");
using var packet = new Packet(10);
packet.Write(client.myId);
ClientSend.SendTcpData(client, packet);
var startTime = DateTime.Now.Ticks;
while (!APIPacketHandlers.finishedSendingIds &&
((DateTime.Now.Ticks - startTime) < timeOutTime))
{
if (!APIPacketHandlers.receivedId) continue;
startTime = DateTime.Now.Ticks;
APIPacketHandlers.receivedId = false;
}
if (!((DateTime.Now.Ticks - startTime) < timeOutTime))
{
Debug.LogError(
$"RequestAllLobbyIds: Request timed out! Returning {client.lastReceivedInt}");
return false;
}
return true;
}
else
{
Debug.LogWarning("Tried to request all Lobby IDs while the Client is disconnected!");
return false;
}
}
public static bool RequestLobbyIdsWithMatchingAttribute(Client client, string attribName,
string attribValue)
{
Debug.Log("Getting a lobby ID by UUID...");
using var packet = new Packet(11);
APIPacketHandlers.finishedSendingIds = false;
packet.Write(client.myId);
packet.Write(attribName);
packet.Write(attribValue);
ClientSend.SendTcpData(client, packet);
var startTime = DateTime.Now.Ticks;
while (!APIPacketHandlers.finishedSendingIds &&
((DateTime.Now.Ticks - startTime) < timeOutTime))
{
// if (!APIPacketHandlers.receivedId) continue;
// startTime = DateTime.Now.Ticks;
// APIPacketHandlers.receivedId = false;
}
if (!((DateTime.Now.Ticks - startTime) < timeOutTime))
{
Debug.LogError(
$"RequestLobbyIdsWithMatchingAttribute: Request timed out!");
return false;
}
return true;
}
public static ClientHandle.ClientAttrib GetLobbyAttribute(Client client, int requestedId, string name)
{
Debug.Log("Getting a lobby attribute...");
using var packet = new Packet(12);
packet.Write(client.myId);
packet.Write(requestedId);
packet.Write(name);
ClientSend.SendTcpData(client, packet);
var startTime = DateTime.Now.Ticks;
client.lastReceivedAttrib = null;
while (client.lastReceivedAttrib == null &&
((DateTime.Now.Ticks - startTime) < timeOutTime))
{
}
if (!((DateTime.Now.Ticks - startTime) < timeOutTime))
{
Debug.LogError($"GetLobbyAttribute: Request timed out!.");
}
return client.lastReceivedAttrib;
}
}
}
}
}

40
README.md Executable file
View file

@ -0,0 +1,40 @@
<div align="center">
# Matchmaker-API - Unity3D Client
<br />
![Unity](https://img.shields.io/badge/unity-%23000000.svg?style=for-the-badge&logo=unity&logoColor=white)
![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)
![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white)
![macOS](https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)
![GitHub forks](https://img.shields.io/github/forks/Techiesplash/Matchmaker-API-Client-Unity3d)
![GitHub repo size](https://img.shields.io/github/repo-size/Techiesplash/Matchmaker-API-Client-Unity3d)
![GitHub all releases](https://img.shields.io/github/downloads/Techiesplash/Matchmaker-API-Client-Unity3d/total)
![GitHub issues](https://img.shields.io/github/issues/Techiesplash/Matchmaker-API-Client-Unity3d)
![GitHub](https://img.shields.io/github/license/Techiesplash/Matchmaker-API-Client-Unity3d)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/Techiesplash/Matchmaker-API-Client-Unity3d)
<h2>Introduction</h2>
This is a project for implementing a Matchmaker API into Unity3D.
<br />
It can be expanded with custom packets as needed.
<br />
<br />
It depends on another project to be used in Unity. https://github.com/Techiesplash/Matchmaker-API-Server
<br /><br />
This project is built upon MIT-Licensed code by Tom Weiland meant for a tutorial series.
Please check out his work: https://github.com/tom-weiland/tcp-udp-networking
<br />
![UVS Preview](./Images/preview.png)
<h2>Setup</h2>
To start, you can either download this project as a .ZIP file and place the files in your project,
or you can click on your Project files in Unity Editor and use Import > Custom Package and import a package from Releases.
<br /><br />
This includes Visual Scripting support. If you want to use this feature, ensure VIsual Scripting is enabled in Project Settings > Visual Scripting. You may also need to regenerate the nodes in Project Settings > Visual Scripting > Regenerate Nodes.
<br /><br />
<h3>Anyone is free to use, copy, modify, merge, publish, distribute, sublicense, or and/or sell copies of the software.</h3>

19
ThreadObject.cs Executable file
View file

@ -0,0 +1,19 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Scripts.CustomServer;
public class ThreadObject : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
ThreadManager.Start();
}
// Update is called once per frame
void Update()
{
ThreadManager.UpdateMain();
}
}

View file

@ -0,0 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DummyScript_Icon : MonoBehaviour
{
}

View file

@ -0,0 +1,42 @@
using Unity.VisualScripting;
// ReSharper disable once CheckNamespace
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Matchmaking Server Address")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_Address : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputTrigger;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput set;
[DoNotSerialize] // No need to serialize ports
public ValueOutput get;
private bool state;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
IMatchmaker.ip = flow.GetValue<string>(set);
return outputTrigger;
});
//Making the ControlOutput port visible and setting its key.
outputTrigger = ControlOutput("");
set = ValueInput<string>("set");
get = ValueOutput<string>("get", (flow) => IMatchmaker.ip);
Succession(inputTrigger, outputTrigger);
}
}
}

View file

@ -0,0 +1,39 @@
using Scripts.Matchmaker;
using Unity.VisualScripting;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Connect to List Server")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_Connect : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnSuccess; //Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnFail; //Adding the ControlOutput port variable.
private bool resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("",
(flow) => { return IMatchmaker.Connect() ? outputOnSuccess : outputOnFail; });
//Making the ControlOutput port visible and setting its key.
outputOnSuccess = ControlOutput("onSuccess");
outputOnFail = ControlOutput("onFail");
Succession(inputTrigger, outputOnSuccess);
Succession(inputTrigger, outputOnFail);
}
}
}

View file

@ -0,0 +1,47 @@
using System.Collections;
using System.Collections.Generic;
using Scripts.Matchmaker;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Connect to Lobby Server")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_ConnectLobby : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnSuccess; //Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnFail; //Adding the ControlOutput port variable.
private bool resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
if (IMatchmaker.ConnectLobby())
{
return outputOnSuccess;
}
return outputOnFail;
});
//Making the ControlOutput port visible and setting its key.
outputOnSuccess = ControlOutput("onSuccess");
outputOnFail = ControlOutput("onFail");
Succession(inputTrigger, outputOnSuccess);
Succession(inputTrigger, outputOnFail);
}
}
}

View file

@ -0,0 +1,37 @@
using Unity.VisualScripting;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Disconnect from List Server")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_Disconnect : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputTrigger;//Adding the ControlOutput port variable.
private bool resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
IMatchmaker.Disconnect();
return outputTrigger;
});
//Making the ControlOutput port visible and setting its key.
outputTrigger = ControlOutput("");
Succession(inputTrigger, outputTrigger);
}
}
}

View file

@ -0,0 +1,37 @@
using Unity.VisualScripting;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Disconnect from Lobby Server")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_DisconnectLobby : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputTrigger;//Adding the ControlOutput port variable.
private bool resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
IMatchmaker.DisconnectLobby();
return outputTrigger;
});
//Making the ControlOutput port visible and setting its key.
outputTrigger = ControlOutput("");
Succession(inputTrigger, outputTrigger);
}
}
}

View file

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using Mirror;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Get All Lobby IDs with Attribute")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_GetAllLobbiesWithAttribute : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputIfSome;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputIfNone;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueOutput idList;
[DoNotSerialize] // No need to serialize ports
public ValueInput attribName;
[DoNotSerialize] // No need to serialize ports
public ValueInput attribValue;
public List<int> resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
try
{
resultValue = IMatchmaker.GetAllLobbyIdsByAttribute(flow.GetValue<string>(attribName),
flow.GetValue<string>(attribValue));
; if (resultValue is { Count: > 0 })
{
return outputIfSome;
}
return outputIfNone;
}
catch (Exception ex)
{
Debug.LogError($"Error in Get All Lobby IDs node: {ex}");
}
return outputIfNone;
});
//Making the ControlOutput port visible and setting its key.
outputIfNone = ControlOutput("ifNone");
outputIfSome = ControlOutput("ifSome");
idList = ValueOutput<List<int>>("result", (flow) => resultValue);
attribName = ValueInput<string>("attributeName");
attribValue = ValueInput<string>("attributeValue");
Succession(inputTrigger, outputIfSome);
Succession(inputTrigger, outputIfNone);
Assignment(inputTrigger, idList);
}
}
}

View file

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using Mirror;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Get All Lobby IDs")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_GetAllLobbyIds : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputIfSome;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputIfNone;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueOutput idList;
public List<int> resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
try
{
resultValue = IMatchmaker.GetAllLobbyIds();
if (resultValue is { Count: > 0 })
{
return outputIfSome;
}
return outputIfNone;
}
catch (Exception ex)
{
Debug.LogError($"Error in Get All Lobby IDs node: {ex}");
}
return outputIfNone;
});
//Making the ControlOutput port visible and setting its key.
outputIfNone = ControlOutput("ifNone");
outputIfSome = ControlOutput("ifSome");
idList = ValueOutput<List<int>>("result", (flow) => resultValue);
Succession(inputTrigger, outputIfSome);
Succession(inputTrigger, outputIfNone);
Assignment(inputTrigger, idList);
}
}
}

View file

@ -0,0 +1,76 @@
using System;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Get Client Attribute")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_GetClientAttribute : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnSuccess;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnFail;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput lobbyId;
[DoNotSerialize] // No need to serialize ports
public ValueInput name;
[DoNotSerialize] // No need to serialize ports
public ValueOutput value;
private string resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
try
{
resultValue =
IMatchmaker.GetLobbyAttribute(flow.GetValue<int>(lobbyId), flow.GetValue<string>(name));
if (resultValue != null)
{
return outputOnSuccess;
}
resultValue = "";
}
catch (Exception ex)
{
Debug.LogError($"Error in Get Lobby Attribute node: {ex}");
}
resultValue = "";
return outputOnFail;
});
//Making the ControlOutput port visible and setting its key.
outputOnSuccess = ControlOutput("onSuccess");
outputOnFail = ControlOutput("onFail");
value = ValueOutput<string>("value", (flow) => resultValue);
name = ValueInput<string>("Name", "");
lobbyId = ValueInput<int>("clientId");
Succession(inputTrigger, outputOnFail);
Succession(inputTrigger, outputOnSuccess);
}
}
}

View file

@ -0,0 +1,76 @@
using System;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Get Lobby Attribute")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_GetLobbyAttribute : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnSuccess;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnFail;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput clientId;
[DoNotSerialize] // No need to serialize ports
public ValueInput name;
[DoNotSerialize] // No need to serialize ports
public ValueOutput value;
private string resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
try
{
resultValue =
IMatchmaker.GetClientAttribute(flow.GetValue<int>(clientId), flow.GetValue<string>(name));
if (resultValue != null)
{
return outputOnSuccess;
}
resultValue = "";
}
catch (Exception ex)
{
Debug.LogError($"Error in Get Client Attribute node: {ex}");
}
resultValue = "";
return outputOnFail;
});
//Making the ControlOutput port visible and setting its key.
outputOnSuccess = ControlOutput("onSuccess");
outputOnFail = ControlOutput("onFail");
value = ValueOutput<string>("value", (flow) => resultValue);
name = ValueInput<string>("Name", "");
clientId = ValueInput<int>("lobbyId");
Succession(inputTrigger, outputOnFail);
Succession(inputTrigger, outputOnSuccess);
}
}
}

View file

@ -0,0 +1,72 @@
using System;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Get Lobby Attribute Directly")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_GetLobbyAttributeDirect : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnSuccess;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnFail;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput id;
[DoNotSerialize] // No need to serialize ports
public ValueInput name;
[DoNotSerialize] // No need to serialize ports
public ValueOutput value;
private string resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
try
{
resultValue =
IMatchmaker.GetLobbyAttributeDirect(flow.GetValue<string>(name));
if (resultValue != null)
{
return outputOnSuccess;
}
}
catch (Exception ex)
{
Debug.LogError($"Error in Get Lobby Attribute Direct node: {ex}");
}
return outputOnFail;
});
//Making the ControlOutput port visible and setting its key.
outputOnSuccess = ControlOutput("onSuccess");
outputOnFail = ControlOutput("onFail");
id = ValueInput<int>("lobbyId");
name = ValueInput<string>("Name", "");
value = ValueOutput<string>("Value", (flow) => resultValue);
Succession(inputTrigger, outputOnFail);
Succession(inputTrigger, outputOnSuccess);
}
}
}

View file

@ -0,0 +1,65 @@
using System;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Get Lobby By UUID")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_GetLobbyByUuid : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnSuccess;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnFail;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput uuid;
[DoNotSerialize] // No need to serialize ports
public ValueOutput id;
public int resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
try
{
resultValue = IMatchmaker.GetLobbyIdByUuid(flow.GetValue<string>(uuid).ToUpper());
if (resultValue > 0)
{
return outputOnSuccess;
}
}
catch (Exception ex)
{
Debug.LogError($"Error in Get Lobby By Uuid node: {ex}");
}
return outputOnFail;
});
//Making the ControlOutput port visible and setting its key.
outputOnSuccess = ControlOutput("onSuccess");
outputOnFail = ControlOutput("onFail");
uuid = ValueInput<string>("uuid", "");
id = ValueOutput<int>("result", (flow) => resultValue);
Succession(inputTrigger, outputOnFail);
Succession(inputTrigger, outputOnSuccess);
Assignment(inputTrigger, id);
}
}
}

View file

@ -0,0 +1,31 @@
using Mirror;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Is Client Connected")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_IsClientConnected : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports
public ValueOutput get;
protected override void Definition()
{
get = ValueOutput<bool>("get", (flow) =>
{
if (IMatchmaker.listClient == null)
{
Debug.LogError("Error in Is Client Connected node: listClient is null!");
return false;
}
return IMatchmaker.listClient.IsConnected;
});
}
}
}

View file

@ -0,0 +1,31 @@
using Mirror;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Is Lobby Connected")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_IsLobbyConnected : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports
public ValueOutput get;
protected override void Definition()
{
get = ValueOutput<bool>("get", (flow) =>
{
if (IMatchmaker.lobbyClient == null)
{
Debug.LogError("Error in Is Lobby Connected node: listClient is null!");
return false;
}
return IMatchmaker.lobbyClient.IsConnected;
});
}
}
}

View file

@ -0,0 +1,44 @@
using Mirror;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Matchmaking Server Port")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_Port : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputTrigger;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput set;
[DoNotSerialize] // No need to serialize ports
public ValueOutput get;
private bool state;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
IMatchmaker.port = flow.GetValue<int>(set);
return outputTrigger;
});
//Making the ControlOutput port visible and setting its key.
outputTrigger = ControlOutput("");
set = ValueInput<int>("set");
get = ValueOutput<int>("get", (flow) => IMatchmaker.port);
Succession(inputTrigger, outputTrigger);
}
}
}

View file

@ -0,0 +1,65 @@
using System;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Set Client Attribute")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_SetClientAttribute : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnSuccess;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnFail;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput name;
[DoNotSerialize] // No need to serialize ports
public ValueInput value;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
try
{
var result =
IMatchmaker.SetClientAttribute(flow.GetValue<string>(name), flow.GetValue<string>(value));
if (result)
{
return outputOnSuccess;
}
}
catch (Exception ex)
{
Debug.LogError($"Error in Set Client Attribute node: {ex}");
}
return outputOnFail;
});
//Making the ControlOutput port visible and setting its key.
outputOnSuccess = ControlOutput("oOnSuccess");
outputOnFail = ControlOutput("onFail");
name = ValueInput<string>("Name", "");
value = ValueInput<string>("value", "");
Succession(inputTrigger, outputOnFail);
Succession(inputTrigger, outputOnSuccess);
}
}
}

View file

@ -0,0 +1,55 @@
using Mirror;
using Scripts.CustomServer;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Set Client Objects")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_SetClientObjects : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputTrigger;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput listclient;
[DoNotSerialize] // No need to serialize ports
public ValueInput lobbyclient;
private bool resultValue;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
if (flow.GetValue<Client>(listclient) != null)
{
IMatchmaker.listClient = flow.GetValue<Client>(listclient);
}
if (flow.GetValue<Client>(lobbyclient) != null)
{
IMatchmaker.lobbyClient = flow.GetValue<Client>(lobbyclient);
}
return outputTrigger;
});
//Making the ControlOutput port visible and setting its key.
outputTrigger = ControlOutput("");
listclient = ValueInput<Client>("listClient", null);
lobbyclient = ValueInput<Client>("lobbyClient", null);
Succession(inputTrigger, outputTrigger);
}
}
}

View file

@ -0,0 +1,65 @@
using System;
using Unity.VisualScripting;
using UnityEngine;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("Set Lobby Attribute")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_SetLobbyAttribute : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports.
public ControlInput inputTrigger; //Adding the ControlInput port variable
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnSuccess;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports.
public ControlOutput outputOnFail;//Adding the ControlOutput port variable.
[DoNotSerialize] // No need to serialize ports
public ValueInput name;
[DoNotSerialize] // No need to serialize ports
public ValueInput value;
protected override void Definition()
{
//Making the ControlInput port visible, setting its key and running the anonymous action method to pass the flow to the outputTrigger port.
inputTrigger = ControlInput("", (flow) =>
{
try
{
var result =
IMatchmaker.SetLobbyAttribute(flow.GetValue<string>(name), flow.GetValue<string>(value));
if (result)
{
return outputOnSuccess;
}
}
catch (Exception ex)
{
Debug.LogError($"Error in Set Client Attribute node: {ex}");
}
return outputOnFail;
});
//Making the ControlOutput port visible and setting its key.
outputOnSuccess = ControlOutput("oOnSuccess");
outputOnFail = ControlOutput("onFail");
name = ValueInput<string>("Name", "");
value = ValueInput<string>("value", "");
Succession(inputTrigger, outputOnFail);
Succession(inputTrigger, outputOnSuccess);
}
}
}

View file

@ -0,0 +1,31 @@
using Mirror;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("This Client's ID")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_ThisClient : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports
public ValueOutput get;
protected override void Definition()
{
get = ValueOutput<int>("get", (flow) =>
{
if (IMatchmaker.listClient == null)
{
Debug.LogError("Error in This Client's ID node: listClient is null!");
return 0;
}
return IMatchmaker.listClient.myId;
});
}
}
}

View file

@ -0,0 +1,35 @@
using Mirror;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Scripts.Matchmaker.Visual_Scripting
{
[UnitTitle("This Lobby's ID")]
[UnitSubtitle("Matchmaker API")]
[UnitCategory("Matchmaking")]
[TypeIcon(typeof(DummyScript_Icon))]
public class MatchmakerNode_ThisLobby : Unit, IMatchmaker
{
[DoNotSerialize] // No need to serialize ports
public ValueOutput get;
private bool state;
protected override void Definition()
{
get = ValueOutput<int>("get", (flow) =>
{
if (IMatchmaker.lobbyClient == null)
{
Debug.LogError("Error in This Lobby's ID node: lobbyClient is null!");
return 0;
}
return IMatchmaker.lobbyClient.myId;
});
}
}
}

View file

@ -0,0 +1,37 @@
using Mirror;
using UnityEngine;
// ReSharper disable once CheckNamespace
namespace Scripts.Matchmaker
{
public class MirrorNodeHelper : NetworkBehaviour
{
[Client]
public GameObject SpawnObject(GameObject iG, Vector3 iPos, Quaternion iRot, Transform iParent, Vector3 iScale)
{
SpawnObjectCmd(NetworkManager.singleton.spawnPrefabs.IndexOf(iG), iPos, iRot, iParent, iScale);
return GameObject.FindGameObjectWithTag("MirNodeHelperSpawned");
}
[Command(requiresAuthority = false)]
void SpawnObjectCmd(int iG, Vector3 iPos, Quaternion iRot, Transform iParent, Vector3 iScale)
{
GameObject go;
Debug.Log("Creating object...");
if (iParent != null)
{
go = Instantiate(NetworkManager.singleton.spawnPrefabs[iG], iPos, iRot, iParent);
}
else
{
go = Instantiate(NetworkManager.singleton.spawnPrefabs[iG], iPos, iRot, iParent);
}
go.transform.localScale = iScale;
go.gameObject.tag = "MirNodeHelperSpawned";
NetworkServer.Spawn(go);
}
}
}

BIN
Visual Scripting/icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB