commit 35c0f731293d6b78f3b5eb11b539aa7ff14c998f Author: techiesplash Date: Sun Jan 15 13:55:38 2023 -0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab45420 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.meta +"Visual Scripting"/.meta +Client/.meta +Images/.meta \ No newline at end of file diff --git a/.vs/Matchmaker/FileContentIndex/84f5bfc4-1178-4eb2-9ba0-96ef4f71fbdd.vsidx b/.vs/Matchmaker/FileContentIndex/84f5bfc4-1178-4eb2-9ba0-96ef4f71fbdd.vsidx new file mode 100755 index 0000000..ecb27b5 Binary files /dev/null and b/.vs/Matchmaker/FileContentIndex/84f5bfc4-1178-4eb2-9ba0-96ef4f71fbdd.vsidx differ diff --git a/.vs/Matchmaker/FileContentIndex/read.lock b/.vs/Matchmaker/FileContentIndex/read.lock new file mode 100755 index 0000000..e69de29 diff --git a/.vs/Matchmaker/v17/.suo b/.vs/Matchmaker/v17/.suo new file mode 100755 index 0000000..9cf9f50 Binary files /dev/null and b/.vs/Matchmaker/v17/.suo differ diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100755 index 0000000..f8b4888 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100755 index 0000000..6176ad1 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,8 @@ +{ + "ExpandedNodes": [ + "", + "\\Visual Scripting" + ], + "SelectedNode": "\\Visual Scripting", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100755 index 0000000..b0b025b Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/Client/Client.cs b/Client/Client.cs new file mode 100755 index 0000000..6fcf41e --- /dev/null +++ b/Client/Client.cs @@ -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 +{ + /// + /// Stores information about the connection. + /// + public class Client : MonoBehaviour + { + public ClientSend.StatusType? lastStatusType; + public int lastReceivedInt; + public ClientHandle.ClientAttrib lastReceivedAttrib; + + public bool welcomeReceived; + private const int DataBufferSize = 4096; + + + /// + /// For editor readability. + /// + public string displayName; + + public string ip = Config.Address; + public int port = 26950; + public int myId; + public Tcp tcp; + public UDP udp; + private bool isConnected; + + /// + /// Checks if it is connected to a server + /// + 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 packetHandlers; + + public ClientPackets(Dictionary 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()); + + private void OnApplicationQuit() + { + Disconnect(); // Disconnect when the game is closed + } + + /// Attempts to connect to the server. + 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; + } + + /// + /// Subclass to handle TCP networking + /// + 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; + } + + /// Attempts to connect to the server via TCP. + public void Connect() + { + socket = new TcpClient + { + ReceiveBufferSize = DataBufferSize, + SendBufferSize = DataBufferSize + }; + + receiveBuffer = new byte[DataBufferSize]; + socket.BeginConnect(instance.ip, instance.port, ConnectCallback, socket); + } + + /// Initializes the newly connected listClient's TCP-related info. + 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); + } + + /// Sends data to the listClient via TCP. + /// The packet to send. + 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}"); + } + } + + /// Reads incoming data from the stream. + 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(); + } + } + + /// Prepares received data to be used by the appropriate packet handler methods. + /// The received data. + 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; + } + + /// Disconnects from the server and cleans up the TCP connection. + private void Disconnect() + { + instance.Disconnect(); + + stream = null; + receivedData = null; + receiveBuffer = null; + socket = null; + } + } + + /// + /// Subclass to handle UDP networking + /// + 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); + } + + /// Attempts to connect to the server via UDP. + /// The port number to bind the UDP socket to. + public void Connect(int localPort) + { + socket = new UdpClient(localPort); + + socket.Connect(endPoint); + socket.BeginReceive(ReceiveCallback, null); + + using Packet packet = new Packet(); + SendData(packet); + } + + /// Sends data to the listClient via UDP. + /// The packet to send. + 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}"); + } + } + + /// Receives incoming UDP data. + 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(); + } + } + + /// Prepares received data to be used by the appropriate packet handler methods. + /// The received data. + 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; + } + + // }); + } + + /// Disconnects from the server and cleans up the UDP connection. + private void Disconnect() + { + instance.Disconnect(); + + endPoint = null; + socket = null; + } + } + + /// Initializes all necessary listClient data. + private void InitializeClientData() + { + } + + /// Disconnects from the server and stops all network traffic. + public void Disconnect() + { + if (!isConnected) return; + isConnected = false; + tcp.socket.Close(); + + udp?.socket?.Close(); + + Debug.Log("Disconnected from server."); + } + } +} \ No newline at end of file diff --git a/Client/ClientHandle.cs b/Client/ClientHandle.cs new file mode 100755 index 0000000..bdfd9e9 --- /dev/null +++ b/Client/ClientHandle.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Client/ClientSend.cs b/Client/ClientSend.cs new file mode 100755 index 0000000..b624f10 --- /dev/null +++ b/Client/ClientSend.cs @@ -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; + } + + /// + /// Respond to the listClient with a status + /// + /// What Client is sending it + /// What status + public static void Status(Client client, StatusType status) + { + using var packet = new Packet(int.MaxValue - 3); + packet.Write((int)status); + + SendTcpDataNoSync(client, packet); + } + + #endregion + } +} \ No newline at end of file diff --git a/Client/Packet.cs b/Client/Packet.cs new file mode 100755 index 0000000..47dfbec --- /dev/null +++ b/Client/Packet.cs @@ -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 buffer; + private byte[] readableBuffer; + private int readPos; + + /// Creates a new empty packet (without an ID). + public Packet() + { + buffer = new List(); // Initialize buffer + readPos = 0; // Set readPos to 0 + } + + /// Creates a new packet with a given ID. Used for sending. + /// The packet ID. + public Packet(int id) + { + buffer = new List(); // Initialize buffer + readPos = 0; // Set readPos to 0 + + Write(id); // Write packet id to the buffer + } + + /// Creates a packet from which data can be read. Used for receiving. + /// The bytes to add to the packet. + public Packet(byte[] data) + { + buffer = new List(); // Initialize buffer + readPos = 0; // Set readPos to 0 + + SetBytes(data); + } + + #region Functions + /// Sets the packet's content and prepares it to be read. + /// The bytes to add to the packet. + public void SetBytes(byte[] data) + { + Write(data); + readableBuffer = buffer.ToArray(); + } + + /// Inserts the length of the packet's content at the start of the buffer. + public void WriteLength() + { + buffer.InsertRange(0, BitConverter.GetBytes(buffer.Count)); // Insert the byte length of the packet at the very beginning + } + + /// Inserts the given int at the start of the buffer. + /// The int to insert. + public void InsertInt(int value) + { + buffer.InsertRange(0, BitConverter.GetBytes(value)); // Insert the int at the start of the buffer + } + + /// Gets the packet's content in array form. + public byte[] ToArray() + { + readableBuffer = buffer.ToArray(); + return readableBuffer; + } + + /// Gets the length of the packet's content. + public int Length() + { + return buffer.Count; // Return the length of buffer + } + + /// Gets the length of the unread data contained in the packet. + public int UnreadLength() + { + return Length() - readPos; // Return the remaining length (unread) + } + + /// Resets the packet instance to allow it to be reused. + /// Whether or not to reset the packet. + 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 + /// Adds a byte to the packet. + /// The byte to add. + public void Write(byte value) + { + buffer.Add(value); + } + /// Adds an array of bytes to the packet. + /// The byte array to add. + // ReSharper disable once MemberCanBePrivate.Global + public void Write(IEnumerable value) + { + buffer.AddRange(value); + } + /// Adds a short to the packet. + /// The short to add. + public void Write(short value) + { + buffer.AddRange(BitConverter.GetBytes(value)); + } + /// Adds an int to the packet. + /// The int to add. + public void Write(int value) + { + buffer.AddRange(BitConverter.GetBytes(value)); + } + /// Adds a long to the packet. + /// The long to add. + public void Write(long value) + { + buffer.AddRange(BitConverter.GetBytes(value)); + } + /// Adds a float to the packet. + /// The float to add. + public void Write(float value) + { + buffer.AddRange(BitConverter.GetBytes(value)); + } + /// Adds a bool to the packet. + /// The bool to add. + public void Write(bool value) + { + buffer.AddRange(BitConverter.GetBytes(value)); + } + /// Adds a string to the packet. + /// The string to add. + 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 + } + /// Adds a Vector3 to the packet. + /// The Vector3 to add. + public void Write(Vector3 value) + { + Write(value.x); + Write(value.y); + Write(value.z); + } + /// Adds a Quaternion to the packet. + /// The Quaternion to add. + public void Write(Quaternion value) + { + Write(value.x); + Write(value.y); + Write(value.z); + Write(value.w); + } + #endregion + + #region Read Data + /// Reads a byte from the packet. + /// Whether or not to move the buffer's read position. + 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'!"); + } + } + + /// Reads an array of bytes from the packet. + /// The length of the byte array. + /// Whether or not to move the buffer's read position. + 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[]'!"); + } + } + + /// Reads a short from the packet. + /// Whether or not to move the buffer's read position. + 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'!"); + } + } + + /// Reads an int from the packet. + /// Whether or not to move the buffer's read position. + 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'!"); + } + } + + /// Reads a long from the packet. + /// Whether or not to move the buffer's read position. + 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'!"); + } + } + + /// Reads a float from the packet. + /// Whether or not to move the buffer's read position. + 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'!"); + } + } + + /// Reads a bool from the packet. + /// Whether or not to move the buffer's read position. + 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'!"); + } + } + + /// Reads a string from the packet. + /// Whether or not to move the buffer's read position. + 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'!"); + } + } + + /// Reads a Vector3 from the packet. + /// Whether or not to move the buffer's read position. + public Vector3 ReadVector3(bool moveReadPos = true) + { + return new Vector3(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos)); + } + + /// Reads a Quaternion from the packet. + /// Whether or not to move the buffer's read position. + 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); + } +} +} \ No newline at end of file diff --git a/Client/ThreadManager.cs b/Client/ThreadManager.cs new file mode 100755 index 0000000..24a23f0 --- /dev/null +++ b/Client/ThreadManager.cs @@ -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 ActionsForMainThread = new(); + private static readonly List 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; + } + + + /// Sets an action to be executed on the main thread. + /// The action to be executed on the main thread. + 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; + } + } + + + /// Executes all code meant to run on the main thread. NOTE: Call this ONLY from the main thread. + 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); + } + } + } + } + } +} \ No newline at end of file diff --git a/Config.cs b/Config.cs new file mode 100755 index 0000000..ea6c6dd --- /dev/null +++ b/Config.cs @@ -0,0 +1,36 @@ +namespace Scripts.Matchmaker +{ + public static class Config + { + /// + /// This allows you to name the game so your game cannot connect to servers it is not supposed to. + /// + public const string GameId = "My Game Name"; + + /// + /// The version of the matchmaker APII. This must match here and on the server for a connection. + /// + public const string MatchmakerAPIVersion = "1.1.6"; + + /// + /// The version of the game itself. Must match on the server for a connection. + /// Name is also valid, as it is a string. + /// + public const string GameVersion = "0.0.1"; + + /// + /// What port the server will be on (0-65535). The lobby server will use a port two (2) above this. + /// + public const ushort Port = 26950; + + /// + /// What address the server is at. + /// + public const string Address = "192.168.1.204"; + + /// + /// Makes it so it waits for a response from the server every time it sends data. Slow, advise use for debugging. + /// + public const bool Sync = false; + } +} \ No newline at end of file diff --git a/IMGUIMenuDemo.cs b/IMGUIMenuDemo.cs new file mode 100755 index 0000000..914fd2c --- /dev/null +++ b/IMGUIMenuDemo.cs @@ -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 ServerList = new(); + + public static bool CreatePublic; + + private string connectText; + + private void Start() + { + IMatchmaker.listClient = gameObject.GetComponent(); + } + + 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(); + } + } + + } +} \ No newline at end of file diff --git a/Images/preview.png b/Images/preview.png new file mode 100755 index 0000000..13e78ff Binary files /dev/null and b/Images/preview.png differ diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 0000000..b32a8c9 --- /dev/null +++ b/LICENSE.md @@ -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. \ No newline at end of file diff --git a/LobbyManager.cs b/LobbyManager.cs new file mode 100755 index 0000000..4873ade --- /dev/null +++ b/LobbyManager.cs @@ -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(); + IMatchmaker.ConnectLobby(); + IMatchmaker.SetLobbyAttribute("public", IMGUIMenuDemo.CreatePublic.ToString()); + text.text = IMatchmaker.GetLobbyAttributeDirect("_auto_uuid"); + } + } +} diff --git a/Matchmaker.cs b/Matchmaker.cs new file mode 100755 index 0000000..a64f4b2 --- /dev/null +++ b/Matchmaker.cs @@ -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() + { + { 10, API.APIPacketHandlers.ReceiveLobbyId }, + { 11, API.APIPacketHandlers.GetLobbyAttributesReceived }, + { 12, API.APIPacketHandlers.FinishedSendingLobbyIds }, + }); + + // API Functions - Connection Management ----------------------------------------------------------------------- + + /// + /// MatchmakerNode_Connect the List Client. + /// + 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; + } + + /// + /// MatchmakerNode_Connect the Lobby Client. + /// + public static bool ConnectLobby() + { + + lobbyClient.ip = ip; + lobbyClient.port = port + 2; + lobbyClient.welcomeReceived = false; + if (lobbyClient.ConnectToServer(new Client.ClientPackets(new Dictionary()))) + { + 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; + } + + /// + /// Disconnect the List Client. + /// + 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!"); + } + } + + /// + /// Disconnect the Lobby Client. + /// + 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 --------------------------------------------------------------------------- + /// + /// Set an Attribute on the List Client. + /// + /// Name of the Attribute to set + /// Value of the Attribute to set to + 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; + } + + /// + /// Get a List Client's Attribute. + /// + /// What List Client to find the Attribute in + /// Name of the Attribute to get. + /// The value of the Attribute. Returns null on failure. + 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 ---------------------------------------------------------------------------- + /// + /// Get a Lobby Client's Attribute straight from the Lobby Server. + /// + /// What Lobby Client to find the Attribute in + /// Name of the Attribute to get. + /// The value of the Attribute. Returns null on failure. + 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; + } + + /// + /// Set a Attribute on the Lobby Client. + /// + /// Name of the Attribute to set. + /// What value to set the Attribute to. + 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; + } + + /// + /// Get a Lobby Client Attribute by passing through the List Server. + /// + /// What Lobby Client to find the Attribute in + /// Name of the Attribute to get. + /// The value of the Attribute. Returns null on failure. + 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 ----------------------------------------------------------------------------- + /// + /// Gets a Lobby Client's ID by its UUID. + /// + /// The UUID to find a Lobby Client with. + /// A Lobby Client's ID with matching UUID. + 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; + } + + /// + /// Gets all Lobby IDs by matching Attribute + /// + /// The name of the Attribute + /// The value of the Attribute + public static List 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; + } + + /// + /// Gets a list of the IDs of all connected Lobby Clients. Also calls a callback at Matchmaker.API.lobbyCallback for every ID. + /// + /// List of Lobby Client IDs + [CanBeNull] + public static List 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 + **************************************************************************************************************/ + /// + /// Contains the internal functionality for the Matchmaker API + /// + public static class API + { + // API Internals - Global Variables ------------------------------------------------------------------------ + public delegate void UserCallbackDelegate(int userId); + + /// + /// Gets called back whenever a Lobby Client ID is received by GetAllLobbyIds(). + /// + public static UserCallbackDelegate lobbyCallback; + + private static readonly List ExecuteOnApiThread = new(); + private static readonly List 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; + } + + /// Sets an action to be executed on the main thread. + /// The action to be executed on the main thread. + 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; + } + } + + + /// + /// Executes all code meant to run on the main thread. NOTE: Call this ONLY from the main thread. + /// + 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 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; + } + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..9c20fd0 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +
+ +# Matchmaker-API - Unity3D Client +
+ +![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) + +

Introduction

+This is a project for implementing a Matchmaker API into Unity3D. +
+It can be expanded with custom packets as needed. +
+
+It depends on another project to be used in Unity. https://github.com/Techiesplash/Matchmaker-API-Server +

+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 +
+ +![UVS Preview](./Images/preview.png) + +

Setup

+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. +

+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. +

+

Anyone is free to use, copy, modify, merge, publish, distribute, sublicense, or and/or sell copies of the software.

diff --git a/ThreadObject.cs b/ThreadObject.cs new file mode 100755 index 0000000..6331e8e --- /dev/null +++ b/ThreadObject.cs @@ -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(); + } +} diff --git a/Visual Scripting/DummyScript_Icon.cs b/Visual Scripting/DummyScript_Icon.cs new file mode 100755 index 0000000..98126f7 --- /dev/null +++ b/Visual Scripting/DummyScript_Icon.cs @@ -0,0 +1,7 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class DummyScript_Icon : MonoBehaviour +{ +} diff --git a/Visual Scripting/MatchmakerNode_Address.cs b/Visual Scripting/MatchmakerNode_Address.cs new file mode 100755 index 0000000..fea94cc --- /dev/null +++ b/Visual Scripting/MatchmakerNode_Address.cs @@ -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(set); + return outputTrigger; + }); + //Making the ControlOutput port visible and setting its key. + outputTrigger = ControlOutput(""); + + set = ValueInput("set"); + get = ValueOutput("get", (flow) => IMatchmaker.ip); + Succession(inputTrigger, outputTrigger); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_Connect.cs b/Visual Scripting/MatchmakerNode_Connect.cs new file mode 100755 index 0000000..a36438a --- /dev/null +++ b/Visual Scripting/MatchmakerNode_Connect.cs @@ -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); + + } + } + +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_ConnectLobby.cs b/Visual Scripting/MatchmakerNode_ConnectLobby.cs new file mode 100755 index 0000000..ca3125a --- /dev/null +++ b/Visual Scripting/MatchmakerNode_ConnectLobby.cs @@ -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); + + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_Disconnect.cs b/Visual Scripting/MatchmakerNode_Disconnect.cs new file mode 100755 index 0000000..1f3b1bc --- /dev/null +++ b/Visual Scripting/MatchmakerNode_Disconnect.cs @@ -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); + + } + } + +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_DisconnectLobby.cs b/Visual Scripting/MatchmakerNode_DisconnectLobby.cs new file mode 100755 index 0000000..e3671e7 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_DisconnectLobby.cs @@ -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); + + } + } + +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_GetAllLobbiesWithAttribute.cs b/Visual Scripting/MatchmakerNode_GetAllLobbiesWithAttribute.cs new file mode 100755 index 0000000..19ba457 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_GetAllLobbiesWithAttribute.cs @@ -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 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(attribName), + flow.GetValue(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>("result", (flow) => resultValue); + + attribName = ValueInput("attributeName"); + + attribValue = ValueInput("attributeValue"); + + + Succession(inputTrigger, outputIfSome); + Succession(inputTrigger, outputIfNone); + + Assignment(inputTrigger, idList); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_GetAllLobbyIds.cs b/Visual Scripting/MatchmakerNode_GetAllLobbyIds.cs new file mode 100755 index 0000000..7635466 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_GetAllLobbyIds.cs @@ -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 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>("result", (flow) => resultValue); + + Succession(inputTrigger, outputIfSome); + Succession(inputTrigger, outputIfNone); + + Assignment(inputTrigger, idList); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_GetClientAttribute.cs b/Visual Scripting/MatchmakerNode_GetClientAttribute.cs new file mode 100755 index 0000000..e02fd0d --- /dev/null +++ b/Visual Scripting/MatchmakerNode_GetClientAttribute.cs @@ -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(lobbyId), flow.GetValue(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("value", (flow) => resultValue); + + name = ValueInput("Name", ""); + + lobbyId = ValueInput("clientId"); + + + Succession(inputTrigger, outputOnFail); + Succession(inputTrigger, outputOnSuccess); + + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_GetLobbyAttribute.cs b/Visual Scripting/MatchmakerNode_GetLobbyAttribute.cs new file mode 100755 index 0000000..612f6c2 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_GetLobbyAttribute.cs @@ -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(clientId), flow.GetValue(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("value", (flow) => resultValue); + + name = ValueInput("Name", ""); + + clientId = ValueInput("lobbyId"); + + + Succession(inputTrigger, outputOnFail); + Succession(inputTrigger, outputOnSuccess); + + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_GetLobbyAttributeDirect.cs b/Visual Scripting/MatchmakerNode_GetLobbyAttributeDirect.cs new file mode 100755 index 0000000..8cf7795 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_GetLobbyAttributeDirect.cs @@ -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(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("lobbyId"); + + name = ValueInput("Name", ""); + + value = ValueOutput("Value", (flow) => resultValue); + + Succession(inputTrigger, outputOnFail); + Succession(inputTrigger, outputOnSuccess); + + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_GetLobbyByUuid.cs b/Visual Scripting/MatchmakerNode_GetLobbyByUuid.cs new file mode 100755 index 0000000..2b584f1 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_GetLobbyByUuid.cs @@ -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(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("uuid", ""); + id = ValueOutput("result", (flow) => resultValue); + + Succession(inputTrigger, outputOnFail); + Succession(inputTrigger, outputOnSuccess); + + Assignment(inputTrigger, id); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_IsClientConnected.cs b/Visual Scripting/MatchmakerNode_IsClientConnected.cs new file mode 100755 index 0000000..84bcd12 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_IsClientConnected.cs @@ -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("get", (flow) => + { + if (IMatchmaker.listClient == null) + { + Debug.LogError("Error in Is Client Connected node: listClient is null!"); + return false; + } + + return IMatchmaker.listClient.IsConnected; + }); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_IsLobbyConnected.cs b/Visual Scripting/MatchmakerNode_IsLobbyConnected.cs new file mode 100755 index 0000000..3e417dd --- /dev/null +++ b/Visual Scripting/MatchmakerNode_IsLobbyConnected.cs @@ -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("get", (flow) => + { + if (IMatchmaker.lobbyClient == null) + { + Debug.LogError("Error in Is Lobby Connected node: listClient is null!"); + return false; + } + + return IMatchmaker.lobbyClient.IsConnected; + }); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_Port.cs b/Visual Scripting/MatchmakerNode_Port.cs new file mode 100755 index 0000000..1d6942a --- /dev/null +++ b/Visual Scripting/MatchmakerNode_Port.cs @@ -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(set); + return outputTrigger; + }); + //Making the ControlOutput port visible and setting its key. + outputTrigger = ControlOutput(""); + + set = ValueInput("set"); + get = ValueOutput("get", (flow) => IMatchmaker.port); + Succession(inputTrigger, outputTrigger); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_SetClientAttribute.cs b/Visual Scripting/MatchmakerNode_SetClientAttribute.cs new file mode 100755 index 0000000..e30e801 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_SetClientAttribute.cs @@ -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(name), flow.GetValue(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("Name", ""); + + value = ValueInput("value", ""); + + Succession(inputTrigger, outputOnFail); + Succession(inputTrigger, outputOnSuccess); + + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_SetClientObjects.cs b/Visual Scripting/MatchmakerNode_SetClientObjects.cs new file mode 100755 index 0000000..14a2872 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_SetClientObjects.cs @@ -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(listclient) != null) + { + IMatchmaker.listClient = flow.GetValue(listclient); + } + if (flow.GetValue(lobbyclient) != null) + { + IMatchmaker.lobbyClient = flow.GetValue(lobbyclient); + } + + return outputTrigger; + }); + //Making the ControlOutput port visible and setting its key. + outputTrigger = ControlOutput(""); + + listclient = ValueInput("listClient", null); + + lobbyclient = ValueInput("lobbyClient", null); + + Succession(inputTrigger, outputTrigger); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_SetLobbyAttribute.cs b/Visual Scripting/MatchmakerNode_SetLobbyAttribute.cs new file mode 100755 index 0000000..e3e74da --- /dev/null +++ b/Visual Scripting/MatchmakerNode_SetLobbyAttribute.cs @@ -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(name), flow.GetValue(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("Name", ""); + + value = ValueInput("value", ""); + + Succession(inputTrigger, outputOnFail); + Succession(inputTrigger, outputOnSuccess); + + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_ThisClient.cs b/Visual Scripting/MatchmakerNode_ThisClient.cs new file mode 100755 index 0000000..b65dbfa --- /dev/null +++ b/Visual Scripting/MatchmakerNode_ThisClient.cs @@ -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("get", (flow) => + { + if (IMatchmaker.listClient == null) + { + Debug.LogError("Error in This Client's ID node: listClient is null!"); + return 0; + } + + return IMatchmaker.listClient.myId; + }); + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MatchmakerNode_ThisLobby.cs b/Visual Scripting/MatchmakerNode_ThisLobby.cs new file mode 100755 index 0000000..0c7c5d6 --- /dev/null +++ b/Visual Scripting/MatchmakerNode_ThisLobby.cs @@ -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("get", (flow) => + { + if (IMatchmaker.lobbyClient == null) + { + Debug.LogError("Error in This Lobby's ID node: lobbyClient is null!"); + return 0; + } + return IMatchmaker.lobbyClient.myId; + }); + + } + } +} \ No newline at end of file diff --git a/Visual Scripting/MirrorNodeHelper.cs b/Visual Scripting/MirrorNodeHelper.cs new file mode 100755 index 0000000..ecf1a12 --- /dev/null +++ b/Visual Scripting/MirrorNodeHelper.cs @@ -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); + + } + } +} \ No newline at end of file diff --git a/Visual Scripting/icon.png b/Visual Scripting/icon.png new file mode 100755 index 0000000..a76d7c7 Binary files /dev/null and b/Visual Scripting/icon.png differ