first commit
This commit is contained in:
commit
90b9eae405
43 changed files with 2245 additions and 0 deletions
2
.gitignore
vendored
Executable file
2
.gitignore
vendored
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
Matchmaker/bin/
|
||||||
|
Matchmaker/obj/
|
13
.idea/.idea.Matchmaker-API---Server.dir/.idea/.gitignore
generated
vendored
Executable file
13
.idea/.idea.Matchmaker-API---Server.dir/.idea/.gitignore
generated
vendored
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/contentModel.xml
|
||||||
|
/modules.xml
|
||||||
|
/.idea.Matchmaker-API---Server.iml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
4
.idea/.idea.Matchmaker-API---Server.dir/.idea/encodings.xml
generated
Executable file
4
.idea/.idea.Matchmaker-API---Server.dir/.idea/encodings.xml
generated
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
8
.idea/.idea.Matchmaker-API---Server.dir/.idea/indexLayout.xml
generated
Executable file
8
.idea/.idea.Matchmaker-API---Server.dir/.idea/indexLayout.xml
generated
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/.idea.Matchmaker-API---Server.dir/.idea/misc.xml
generated
Executable file
6
.idea/.idea.Matchmaker-API---Server.dir/.idea/misc.xml
generated
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
|
||||||
|
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/.idea.Matchmaker-API---Server.dir/.idea/vcs.xml
generated
Executable file
6
.idea/.idea.Matchmaker-API---Server.dir/.idea/vcs.xml
generated
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
13
.idea/.idea.Matchmaker/.idea/.gitignore
generated
vendored
Executable file
13
.idea/.idea.Matchmaker/.idea/.gitignore
generated
vendored
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/contentModel.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/.idea.Matchmaker.iml
|
||||||
|
/modules.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
4
.idea/.idea.Matchmaker/.idea/encodings.xml
generated
Executable file
4
.idea/.idea.Matchmaker/.idea/encodings.xml
generated
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
8
.idea/.idea.Matchmaker/.idea/indexLayout.xml
generated
Executable file
8
.idea/.idea.Matchmaker/.idea/indexLayout.xml
generated
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/.idea.Matchmaker/.idea/misc.xml
generated
Executable file
6
.idea/.idea.Matchmaker/.idea/misc.xml
generated
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
|
||||||
|
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/.idea.Matchmaker/.idea/vcs.xml
generated
Executable file
6
.idea/.idea.Matchmaker/.idea/vcs.xml
generated
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
BIN
.vs/Matchmaker/DesignTimeBuild/.dtbcache.v2
Executable file
BIN
.vs/Matchmaker/DesignTimeBuild/.dtbcache.v2
Executable file
Binary file not shown.
BIN
.vs/Matchmaker/FileContentIndex/f194b12e-6927-4aa2-b7a4-b9e614c2e2a2.vsidx
Executable file
BIN
.vs/Matchmaker/FileContentIndex/f194b12e-6927-4aa2-b7a4-b9e614c2e2a2.vsidx
Executable file
Binary file not shown.
BIN
.vs/Matchmaker/FileContentIndex/fd5857db-c473-4269-9c48-e4bf2f5b2f78.vsidx
Executable file
BIN
.vs/Matchmaker/FileContentIndex/fd5857db-c473-4269-9c48-e4bf2f5b2f78.vsidx
Executable file
Binary file not shown.
0
.vs/Matchmaker/FileContentIndex/read.lock
Executable file
0
.vs/Matchmaker/FileContentIndex/read.lock
Executable file
BIN
.vs/Matchmaker/v17/.futdcache.v1
Executable file
BIN
.vs/Matchmaker/v17/.futdcache.v1
Executable file
Binary file not shown.
BIN
.vs/Matchmaker/v17/.suo
Executable file
BIN
.vs/Matchmaker/v17/.suo
Executable file
Binary file not shown.
BIN
.vs/ProjectEvaluation/matchmaker.metadata.v2
Executable file
BIN
.vs/ProjectEvaluation/matchmaker.metadata.v2
Executable file
Binary file not shown.
BIN
.vs/ProjectEvaluation/matchmaker.projects.v2
Executable file
BIN
.vs/ProjectEvaluation/matchmaker.projects.v2
Executable file
Binary file not shown.
27
.vscode/launch.json
vendored
Executable file
27
.vscode/launch.json
vendored
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||||
|
// Use hover for the description of the existing attributes
|
||||||
|
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||||
|
"name": ".NET Core Launch (console)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
|
"program": "${workspaceFolder}/Matchmaker/bin/Debug/net7.0/Matchmaker.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/Matchmaker",
|
||||||
|
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||||
|
"console": "internalConsole",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
41
.vscode/tasks.json
vendored
Executable file
41
.vscode/tasks.json
vendored
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/Matchmaker/Matchmaker.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/Matchmaker/Matchmaker.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/Matchmaker/Matchmaker.csproj"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
Images/preview.png
Executable file
BIN
Images/preview.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
22
LICENSE.md
Executable file
22
LICENSE.md
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Tom Weiland
|
||||||
|
Copyright (c) 2022 Techiesplash
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
16
Matchmaker.sln
Executable file
16
Matchmaker.sln
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Matchmaker", "Matchmaker\Matchmaker.csproj", "{4C780A67-B2B0-4DE1-AD9A-BD26551A540C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4C780A67-B2B0-4DE1-AD9A-BD26551A540C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4C780A67-B2B0-4DE1-AD9A-BD26551A540C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4C780A67-B2B0-4DE1-AD9A-BD26551A540C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4C780A67-B2B0-4DE1-AD9A-BD26551A540C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
5
Matchmaker.sln.DotSettings.user
Executable file
5
Matchmaker.sln.DotSettings.user
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dowling/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=NONINFRINGEMENT/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Techiesplash_0027s/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Weiland/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
26
Matchmaker/.vscode/launch.json
vendored
Executable file
26
Matchmaker/.vscode/launch.json
vendored
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||||
|
// Use hover for the description of the existing attributes
|
||||||
|
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||||
|
"name": ".NET Core Launch (console)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
|
"program": "${workspaceFolder}/bin/Debug/net6.0/Matchmaker.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||||
|
"console": "internalConsole",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
41
Matchmaker/.vscode/tasks.json
vendored
Executable file
41
Matchmaker/.vscode/tasks.json
vendored
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/Matchmaker.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/Matchmaker.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/Matchmaker.csproj"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
50
Matchmaker/BaseServer/Attribute.cs
Executable file
50
Matchmaker/BaseServer/Attribute.cs
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
public class ClientAttributes
|
||||||
|
{
|
||||||
|
private const uint MaxAttributes = 64;
|
||||||
|
private class Attribute
|
||||||
|
{
|
||||||
|
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||||
|
public string AttributeName;
|
||||||
|
public string AttributeValue;
|
||||||
|
|
||||||
|
public Attribute(string name, string value)
|
||||||
|
{
|
||||||
|
AttributeName = name;
|
||||||
|
AttributeValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<Attribute> _attributes = new();
|
||||||
|
|
||||||
|
public void SetAttribute(string name, string value)
|
||||||
|
{
|
||||||
|
// Check if the attribute is already set
|
||||||
|
foreach (var attr in _attributes.Where(attr => attr.AttributeName.Equals(name)))
|
||||||
|
{
|
||||||
|
attr.AttributeValue = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is not
|
||||||
|
if (_attributes.Count < MaxAttributes)
|
||||||
|
{
|
||||||
|
_attributes.Add(new Attribute(name, value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to add attribute: Reached limit!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GetAttribute(string name)
|
||||||
|
{
|
||||||
|
return _attributes.Where(attr => attr.AttributeName.Equals(name)).Select(attr => attr.AttributeValue).FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_attributes.Clear();
|
||||||
|
}
|
||||||
|
}
|
306
Matchmaker/BaseServer/Client.cs
Executable file
306
Matchmaker/BaseServer/Client.cs
Executable file
|
@ -0,0 +1,306 @@
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
public class Client
|
||||||
|
{
|
||||||
|
public ServerSend.StatusType LastStatus;
|
||||||
|
|
||||||
|
public readonly ClientAttributes Attributes = new();
|
||||||
|
|
||||||
|
public readonly TCP? Tcp;
|
||||||
|
public readonly UDP Udp;
|
||||||
|
public readonly Server ServerParent;
|
||||||
|
|
||||||
|
public Client(int clientId, Server server)
|
||||||
|
{
|
||||||
|
Tcp = new TCP(clientId, server);
|
||||||
|
Udp = new UDP(clientId, server);
|
||||||
|
ServerParent = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Tcp?.Socket != null && Tcp.Socket.Client != null && Tcp.Socket.Client.Connected)
|
||||||
|
{
|
||||||
|
/* 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 client 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public class TCP
|
||||||
|
{
|
||||||
|
private readonly Server _serv;
|
||||||
|
public TcpClient? Socket;
|
||||||
|
|
||||||
|
private readonly int _id;
|
||||||
|
private NetworkStream? _stream;
|
||||||
|
private Packet? _receivedData;
|
||||||
|
private byte[]? _receiveBuffer;
|
||||||
|
|
||||||
|
public TCP(int id, Server server)
|
||||||
|
{
|
||||||
|
_serv = server;
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Connect(TcpClient? socket)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug("Connecting TCP...");
|
||||||
|
Socket = socket;
|
||||||
|
if (Socket != null)
|
||||||
|
{
|
||||||
|
Socket.ReceiveBufferSize = Constants.DataBufferSize;
|
||||||
|
Socket.SendBufferSize = Constants.DataBufferSize;
|
||||||
|
|
||||||
|
_stream = Socket.GetStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
_receivedData = new Packet();
|
||||||
|
_receiveBuffer = new byte[Constants.DataBufferSize];
|
||||||
|
|
||||||
|
_stream?.BeginRead(_receiveBuffer, 0, Constants.DataBufferSize, ReceiveCallback, null);
|
||||||
|
|
||||||
|
ServerSend.Welcome(_serv, _id, "Welcome to the server!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendData(Packet packet)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Socket != null)
|
||||||
|
{
|
||||||
|
_stream?.BeginWrite(packet.ToArray(), 0, packet.Length(), null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"Error sending data to player {_id} via TCP: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReceiveCallback(IAsyncResult result)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Socket?.Client.RemoteEndPoint is not IPEndPoint remoteIpEndPoint)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug($"[{_serv.DisplayName}]: RemoteIpEndPoint is NULL!");
|
||||||
|
_serv.Clients[_id].Attributes.Clear();
|
||||||
|
_serv.Clients[_id].Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_stream != null)
|
||||||
|
{
|
||||||
|
var byteLength = _stream.EndRead(result);
|
||||||
|
if (byteLength <= 0)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug("Client has left. Disconnecting...");
|
||||||
|
_serv.DisconnectClient(_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[byteLength];
|
||||||
|
if (_receiveBuffer != null) Array.Copy(_receiveBuffer, data, byteLength);
|
||||||
|
|
||||||
|
_receivedData?.Reset(HandleData(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_receiveBuffer != null)
|
||||||
|
{
|
||||||
|
_stream?.BeginRead(_receiveBuffer, 0, Constants.DataBufferSize, ReceiveCallback, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"[{_serv.DisplayName}] Error receiving TCP data: {ex}");
|
||||||
|
_serv.DisconnectClient(_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HandleData(IEnumerable<byte> data)
|
||||||
|
{
|
||||||
|
var packetLength = 0;
|
||||||
|
|
||||||
|
_receivedData?.SetBytes(data);
|
||||||
|
|
||||||
|
if (_receivedData?.UnreadLength() >= 4)
|
||||||
|
{
|
||||||
|
packetLength = _receivedData.ReadInt();
|
||||||
|
if (packetLength <= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (packetLength > 0 && packetLength <= _receivedData?.UnreadLength())
|
||||||
|
{
|
||||||
|
var packetBytes = _receivedData.ReadBytes(packetLength);
|
||||||
|
ThreadManager.ExecuteOnMainThread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var packet = new Packet(packetBytes);
|
||||||
|
|
||||||
|
var packetId = packet.ReadInt();
|
||||||
|
if ((packetId != int.MaxValue - 3) && Config.Sync)
|
||||||
|
{
|
||||||
|
ServerSend.Status(_serv, _id, ServerSend.StatusType.RECEIVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal.LogDebug($"[{_serv.DisplayName}] Received TCP Packet with ID: " + packetId);
|
||||||
|
var x = _serv.Packets?.PacketHandlers.ContainsKey(packetId);
|
||||||
|
if (x != null && x != false)
|
||||||
|
{
|
||||||
|
_serv.Packets?.PacketHandlers[packetId].DynamicInvoke(_serv, _id, packet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Terminal.LogError(
|
||||||
|
$"[{_serv.DisplayName}] Received unregistered TCP packet with ID: {packetId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"[{_serv.DisplayName}] Error processing packet: {e}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
packetLength = 0;
|
||||||
|
if (_receivedData.UnreadLength() < 4) continue;
|
||||||
|
packetLength = _receivedData.ReadInt();
|
||||||
|
if (packetLength <= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetLength <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
Socket?.Close();
|
||||||
|
_stream = null;
|
||||||
|
_receivedData = null;
|
||||||
|
_receiveBuffer = null;
|
||||||
|
Socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public class UDP
|
||||||
|
{
|
||||||
|
public IPEndPoint? EndPoint;
|
||||||
|
private readonly Server _serv;
|
||||||
|
private readonly int _id;
|
||||||
|
|
||||||
|
public UDP(int id, Server server)
|
||||||
|
{
|
||||||
|
_serv = server;
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect(IPEndPoint endPoint)
|
||||||
|
{
|
||||||
|
EndPoint = endPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendData(Packet packet)
|
||||||
|
{
|
||||||
|
if (EndPoint != null) _serv.SendUdpData(EndPoint, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleData(Packet packetData)
|
||||||
|
{
|
||||||
|
var packetLength = packetData.ReadInt();
|
||||||
|
var packetBytes = packetData.ReadBytes(packetLength);
|
||||||
|
|
||||||
|
ThreadManager.ExecuteOnMainThread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var packet = new Packet(packetBytes);
|
||||||
|
var packetId = packet.ReadInt();
|
||||||
|
if ((packetId != int.MaxValue - 3) && Config.Sync)
|
||||||
|
{
|
||||||
|
ServerSend.Status(_serv, _id, ServerSend.StatusType.RECEIVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal.LogDebug($"[{_serv.DisplayName}] Received UDP Packet with ID: " + packetId);
|
||||||
|
var x = _serv.Packets?.PacketHandlers.ContainsKey(packetId);
|
||||||
|
if (x != null && x != false)
|
||||||
|
{
|
||||||
|
_serv.Packets?.PacketHandlers[packetId].DynamicInvoke(_serv, _id, packet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Terminal.LogError(
|
||||||
|
$"[{_serv.DisplayName}] Received unregistered UDP packet with ID: {packetId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"[{_serv.DisplayName}] Error processing packet: {e}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
EndPoint = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{ServerParent.DisplayName}] Disconnecting Client ({Tcp?.Socket?.Client.RemoteEndPoint})...");
|
||||||
|
|
||||||
|
Tcp?.Disconnect();
|
||||||
|
Udp.Disconnect();
|
||||||
|
Attributes.Clear();
|
||||||
|
}
|
||||||
|
}
|
12
Matchmaker/BaseServer/Constants.cs
Executable file
12
Matchmaker/BaseServer/Constants.cs
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
internal static class Constants
|
||||||
|
{
|
||||||
|
public const int TicksPerSec = 30;
|
||||||
|
public const float MsPerTick = 1000f / TicksPerSec;
|
||||||
|
public const int DataBufferSize = 4096;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
12
Matchmaker/BaseServer/GameLogic.cs
Executable file
12
Matchmaker/BaseServer/GameLogic.cs
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
internal static class GameLogic
|
||||||
|
{
|
||||||
|
public static void Update()
|
||||||
|
{
|
||||||
|
ThreadManager.UpdateMain();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
398
Matchmaker/BaseServer/Packet.cs
Executable file
398
Matchmaker/BaseServer/Packet.cs
Executable file
|
@ -0,0 +1,398 @@
|
||||||
|
|
||||||
|
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
public sealed class Packet : IDisposable
|
||||||
|
{
|
||||||
|
private List<byte> _buffer;
|
||||||
|
private byte[] _readableBuffer = null!;
|
||||||
|
private int _readPos;
|
||||||
|
|
||||||
|
/// <summary>Creates a new empty packet (without an ID).</summary>
|
||||||
|
public Packet()
|
||||||
|
{
|
||||||
|
_buffer = new List<byte>(); // Initialize buffer
|
||||||
|
_readPos = 0; // Set readPos to 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Creates a new packet with a given ID. Used for sending.</summary>
|
||||||
|
/// <param name="id">The packet ID.</param>
|
||||||
|
public Packet(int id)
|
||||||
|
{
|
||||||
|
_buffer = new List<byte>(); // Initialize buffer
|
||||||
|
_readPos = 0; // Set readPos to 0
|
||||||
|
|
||||||
|
Write(id); // Write packet id to the buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Creates a packet from which data can be read. Used for receiving.</summary>
|
||||||
|
/// <param name="data">The bytes to add to the packet.</param>
|
||||||
|
public Packet(IEnumerable<byte> data)
|
||||||
|
{
|
||||||
|
_buffer = new List<byte>(); // Initialize buffer
|
||||||
|
_readPos = 0; // Set readPos to 0
|
||||||
|
|
||||||
|
SetBytes(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Functions
|
||||||
|
/// <summary>Sets the packet's content and prepares it to be read.</summary>
|
||||||
|
/// <param name="data">The bytes to add to the packet.</param>
|
||||||
|
public void SetBytes(IEnumerable<byte> data)
|
||||||
|
{
|
||||||
|
Write(data);
|
||||||
|
_readableBuffer = _buffer.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Inserts the length of the packet's content at the start of the buffer.</summary>
|
||||||
|
public void WriteLength()
|
||||||
|
{
|
||||||
|
_buffer.InsertRange(0, BitConverter.GetBytes(_buffer.Count)); // Insert the byte length of the packet at the very beginning
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Inserts the given int at the start of the buffer.</summary>
|
||||||
|
/// <param name="value">The int to insert.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void InsertInt(int value)
|
||||||
|
{
|
||||||
|
_buffer.InsertRange(0, BitConverter.GetBytes(value)); // Insert the int at the start of the buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the packet's content in array form.</summary>
|
||||||
|
public byte[] ToArray()
|
||||||
|
{
|
||||||
|
_readableBuffer = _buffer.ToArray();
|
||||||
|
return _readableBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the length of the packet's content.</summary>
|
||||||
|
public int Length()
|
||||||
|
{
|
||||||
|
return _buffer.Count; // Return the length of buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the length of the unread data contained in the packet.</summary>
|
||||||
|
public int UnreadLength()
|
||||||
|
{
|
||||||
|
return Length() - _readPos; // Return the remaining length (unread)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Resets the packet instance to allow it to be reused.</summary>
|
||||||
|
/// <param name="shouldReset">Whether or not to reset the packet.</param>
|
||||||
|
public void Reset(bool shouldReset = true)
|
||||||
|
{
|
||||||
|
if (shouldReset)
|
||||||
|
{
|
||||||
|
_buffer.Clear(); // Clear buffer
|
||||||
|
_readableBuffer = null!;
|
||||||
|
_readPos = 0; // Reset readPos
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_readPos -= 4; // "Unread" the last read int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Write Data
|
||||||
|
/// <summary>Adds a byte to the packet.</summary>
|
||||||
|
/// <param name="value">The byte to add.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void Write(byte value)
|
||||||
|
{
|
||||||
|
_buffer.Add(value);
|
||||||
|
}
|
||||||
|
/// <summary>Adds an array of bytes to the packet.</summary>
|
||||||
|
/// <param name="value">The byte array to add.</param>
|
||||||
|
// ReSharper disable once MemberCanBePrivate.Global
|
||||||
|
public void Write(IEnumerable<byte> value)
|
||||||
|
{
|
||||||
|
_buffer.AddRange(value);
|
||||||
|
}
|
||||||
|
/// <summary>Adds a short to the packet.</summary>
|
||||||
|
/// <param name="value">The short to add.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void Write(short value)
|
||||||
|
{
|
||||||
|
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||||
|
}
|
||||||
|
/// <summary>Adds an int to the packet.</summary>
|
||||||
|
/// <param name="value">The int to add.</param>
|
||||||
|
public void Write(int value)
|
||||||
|
{
|
||||||
|
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||||
|
}
|
||||||
|
/// <summary>Adds a long to the packet.</summary>
|
||||||
|
/// <param name="value">The long to add.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void Write(long value)
|
||||||
|
{
|
||||||
|
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||||
|
}
|
||||||
|
/// <summary>Adds a float to the packet.</summary>
|
||||||
|
/// <param name="value">The float to add.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void Write(float value)
|
||||||
|
{
|
||||||
|
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||||
|
}
|
||||||
|
/// <summary>Adds a bool to the packet.</summary>
|
||||||
|
/// <param name="value">The bool to add.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void Write(bool value)
|
||||||
|
{
|
||||||
|
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||||
|
}
|
||||||
|
/// <summary>Adds a string to the packet.</summary>
|
||||||
|
/// <param name="value">The string to add.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void Write(string value)
|
||||||
|
{
|
||||||
|
Write(value.Length); // Add the length of the string to the packet
|
||||||
|
_buffer.AddRange(Encoding.ASCII.GetBytes(value)); // Add the string itself
|
||||||
|
}
|
||||||
|
/// <summary>Adds a Vector3 to the packet.</summary>
|
||||||
|
/// <param name="value">The Vector3 to add.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void Write(Vector3 value)
|
||||||
|
{
|
||||||
|
Write(value.X);
|
||||||
|
Write(value.Y);
|
||||||
|
Write(value.Z);
|
||||||
|
}
|
||||||
|
/// <summary>Adds a Quaternion to the packet.</summary>
|
||||||
|
/// <param name="value">The Quaternion to add.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public void Write(Quaternion value)
|
||||||
|
{
|
||||||
|
Write(value.X);
|
||||||
|
Write(value.Y);
|
||||||
|
Write(value.Z);
|
||||||
|
Write(value.W);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Read Data
|
||||||
|
/// <summary>Reads a byte from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// ReSharper disable once RedundantIfElseBlock
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Could not read value of type 'byte'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads an array of bytes from the packet.</summary>
|
||||||
|
/// <param name="length">The length of the byte array.</param>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
public byte[] ReadBytes(int length, bool moveReadPos = true)
|
||||||
|
{
|
||||||
|
if (_buffer.Count > _readPos)
|
||||||
|
{
|
||||||
|
// If there are unread bytes
|
||||||
|
var value = _buffer.GetRange(_readPos, length).ToArray(); // Get the bytes at readPos' position with a range of _length
|
||||||
|
if (moveReadPos)
|
||||||
|
{
|
||||||
|
// If _moveReadPos is true
|
||||||
|
_readPos += length; // Increase readPos by _length
|
||||||
|
}
|
||||||
|
return value; // Return the bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once RedundantIfElseBlock
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Could not read value of type 'byte[]'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads a short from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// ReSharper disable once RedundantIfElseBlock
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Could not read value of type 'short'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads an int from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
public int ReadInt(bool moveReadPos = true)
|
||||||
|
{
|
||||||
|
if (_buffer.Count > _readPos)
|
||||||
|
{
|
||||||
|
// If there are unread bytes
|
||||||
|
var value = BitConverter.ToInt32(_readableBuffer, _readPos); // Convert the bytes to an int
|
||||||
|
if (moveReadPos)
|
||||||
|
{
|
||||||
|
// If _moveReadPos is true
|
||||||
|
_readPos += 4; // Increase readPos by 4
|
||||||
|
}
|
||||||
|
return value; // Return the int
|
||||||
|
}
|
||||||
|
// ReSharper disable once RedundantIfElseBlock
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Could not read value of type 'int'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads a long from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// ReSharper disable once RedundantIfElseBlock
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Could not read value of type 'long'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads a float from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
public float ReadFloat(bool moveReadPos = true)
|
||||||
|
{
|
||||||
|
if (_buffer.Count > _readPos)
|
||||||
|
{
|
||||||
|
// If there are unread bytes
|
||||||
|
var value = BitConverter.ToSingle(_readableBuffer, _readPos); // Convert the bytes to a float
|
||||||
|
if (moveReadPos)
|
||||||
|
{
|
||||||
|
// If _moveReadPos is true
|
||||||
|
_readPos += 4; // Increase readPos by 4
|
||||||
|
}
|
||||||
|
return value; // Return the float
|
||||||
|
}
|
||||||
|
// ReSharper disable once RedundantIfElseBlock
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Could not read value of type 'float'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads a bool from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// ReSharper disable once RedundantIfElseBlock
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Could not read value of type 'bool'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads a string from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
public string ReadString(bool moveReadPos = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var length = ReadInt(); // Get the length of the string
|
||||||
|
var value = Encoding.ASCII.GetString(_readableBuffer, _readPos, length); // Convert the bytes to a string
|
||||||
|
if (moveReadPos && value.Length > 0)
|
||||||
|
{
|
||||||
|
// If _moveReadPos is true string is not empty
|
||||||
|
_readPos += length; // Increase readPos by the length of the string
|
||||||
|
}
|
||||||
|
return value; // Return the string
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw new Exception("Could not read value of type 'string'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads a Vector3 from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public Vector3 ReadVector3(bool moveReadPos = true)
|
||||||
|
{
|
||||||
|
return new Vector3(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads a Quaternion from the packet.</summary>
|
||||||
|
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
159
Matchmaker/BaseServer/Server.cs
Executable file
159
Matchmaker/BaseServer/Server.cs
Executable file
|
@ -0,0 +1,159 @@
|
||||||
|
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
public class Server
|
||||||
|
{
|
||||||
|
public int MaxPlayers;
|
||||||
|
private int _port;
|
||||||
|
public readonly Dictionary<int, Client> Clients = new();
|
||||||
|
|
||||||
|
public string DisplayName = "Unnamed Server";
|
||||||
|
|
||||||
|
public class ServerPackets
|
||||||
|
{
|
||||||
|
public delegate void PacketHandler(Server server, int fromClient, Packet packet);
|
||||||
|
|
||||||
|
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||||
|
public Dictionary<int, PacketHandler> PacketHandlers;
|
||||||
|
|
||||||
|
public ServerPackets(Dictionary<int, PacketHandler> packetHandlers)
|
||||||
|
{
|
||||||
|
PacketHandlers = packetHandlers;
|
||||||
|
PacketHandlers.Add(int.MaxValue, ServerHandle.WelcomeReceived);
|
||||||
|
PacketHandlers.Add(int.MaxValue - 1, ServerHandle.SetClientAttribute);
|
||||||
|
PacketHandlers.Add(int.MaxValue - 2, ServerHandle.GetClientAttribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerPackets? Packets;
|
||||||
|
/*
|
||||||
|
public Dictionary<int, Delegate> packetHandlers = new Dictionary<int, Delegate>()
|
||||||
|
{
|
||||||
|
{ (int)ClientPackets.welcomeReceived, ServerHandle.WelcomeReceived },
|
||||||
|
{ (int)ClientPackets.setClientAttributes, ServerHandle.SetClientAttribute },
|
||||||
|
{ (int)ClientPackets.getClientAttributes, ServerHandle.GetClientAttribute }
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
private TcpListener _tcpListener = null!;
|
||||||
|
|
||||||
|
private UdpClient _udpListener = null!;
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||||
|
public bool Start(ServerPackets packets, int maxPlayers, int port, string displayName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DisplayName = displayName;
|
||||||
|
|
||||||
|
Packets = packets;
|
||||||
|
|
||||||
|
MaxPlayers = maxPlayers;
|
||||||
|
_port = port;
|
||||||
|
|
||||||
|
InitializeServerData();
|
||||||
|
|
||||||
|
_tcpListener = new TcpListener(IPAddress.Any, _port);
|
||||||
|
_tcpListener.Start();
|
||||||
|
_tcpListener.BeginAcceptTcpClient(TcpConnectCallback, null);
|
||||||
|
|
||||||
|
_udpListener = new UdpClient(_port);
|
||||||
|
_udpListener.BeginReceive(UdpReceiveCallback, null);
|
||||||
|
|
||||||
|
Terminal.LogSuccess($"[{DisplayName}] Server started on port {_port}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"[{DisplayName}] Failed to start server: {ex}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisconnectClient(int id)
|
||||||
|
{
|
||||||
|
Clients[id].Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void TcpConnectCallback(IAsyncResult result)
|
||||||
|
{
|
||||||
|
var client = _tcpListener.EndAcceptTcpClient(result);
|
||||||
|
_tcpListener.BeginAcceptTcpClient(TcpConnectCallback, null);
|
||||||
|
Terminal.LogInfo($"[{DisplayName}] Incoming connection from {client.Client.RemoteEndPoint}...");
|
||||||
|
|
||||||
|
for (var i = 1; i <= MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
if (Clients[i].Tcp!.Socket != null) continue;
|
||||||
|
Clients[i].Tcp!.Connect(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal.LogWarn($"[{DisplayName}] {client.Client.RemoteEndPoint} failed to connect: Server full!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UdpReceiveCallback(IAsyncResult result)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var clientEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
var data = _udpListener.EndReceive(result, ref clientEndPoint!);
|
||||||
|
_udpListener.BeginReceive(UdpReceiveCallback, null);
|
||||||
|
|
||||||
|
if (data.Length < 4)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var packet = new Packet(data);
|
||||||
|
var clientId = packet.ReadInt();
|
||||||
|
|
||||||
|
if (clientId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||||
|
if (Clients[clientId].Udp.EndPoint == null)
|
||||||
|
{
|
||||||
|
Clients[clientId].Udp.Connect(clientEndPoint);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Clients[clientId].Udp.EndPoint?.ToString() == clientEndPoint.ToString())
|
||||||
|
{
|
||||||
|
Clients[clientId].Udp.HandleData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"[{DisplayName}] Error receiving UDP data: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendUdpData(IPEndPoint clientEndPoint, Packet packet)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_udpListener.BeginSend(packet.ToArray(), packet.Length(), clientEndPoint, null, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"[{DisplayName}] Error sending data to {clientEndPoint} via UDP: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeServerData()
|
||||||
|
{
|
||||||
|
for (var i = 1; i <= MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
Clients.Add(i, new Client(i, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal.LogSuccess($"[{DisplayName}] Server data is now initialized.");
|
||||||
|
}
|
||||||
|
}
|
156
Matchmaker/BaseServer/ServerHandle.cs
Executable file
156
Matchmaker/BaseServer/ServerHandle.cs
Executable file
|
@ -0,0 +1,156 @@
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
// ReSharper disable once CheckNamespace
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
internal static class ServerHandle
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message received in response to the Welcome message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <param name="fromClient"></param>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public static void WelcomeReceived(Server server, int fromClient, Packet packet)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var clientIdCheck = packet.ReadInt();
|
||||||
|
var clientAPIVersion = packet.ReadString();
|
||||||
|
var clientGameVersion = packet.ReadString();
|
||||||
|
var clientGameId = packet.ReadString();
|
||||||
|
|
||||||
|
if (clientAPIVersion != Config.MatchmakerAPIVersion)
|
||||||
|
{
|
||||||
|
Terminal.LogError(
|
||||||
|
$"[{server.DisplayName}] Client API is not the same version as the Server! \n Server API version: {Config.MatchmakerAPIVersion}. \n Client API version: {clientAPIVersion}");
|
||||||
|
server.Clients[fromClient].Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientGameId != Config.GameId)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"Client Game ID and Server Game ID do not match: \n Server Game ID: {Config.GameId}. \n Client Game ID: {clientGameId}.");
|
||||||
|
server.Clients[fromClient].Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientGameVersion != Config.GameVersion)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"Client Game Version and Server Game Version do not match: \n Server Game Ver: {Config.GameVersion}. \n Client Game Ver: {clientGameVersion}.");
|
||||||
|
server.Clients[fromClient].Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcp = server.Clients[fromClient].Tcp;
|
||||||
|
if (tcp == null)
|
||||||
|
{
|
||||||
|
Terminal.LogWarn("TCP is null!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tcp is { Socket: { } })
|
||||||
|
Terminal.LogSuccess(
|
||||||
|
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} connected successfully and is now player {fromClient}.");
|
||||||
|
if (fromClient != clientIdCheck)
|
||||||
|
{
|
||||||
|
Terminal.LogWarn(
|
||||||
|
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var remoteIpEndPoint = server.Clients[fromClient].Tcp?.Socket?.Client.RemoteEndPoint as IPEndPoint;
|
||||||
|
if (remoteIpEndPoint == null)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug($"[{server.DisplayName}] WelcomeReceived: RemoteIpEndPoint is NULL!");
|
||||||
|
server.Clients[fromClient].Attributes.Clear();
|
||||||
|
server.Clients[fromClient].Disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uuidList = new List<string>();
|
||||||
|
for (var i = 1; i < server.Clients.Count; i++)
|
||||||
|
{
|
||||||
|
if (server.Clients[i].IsConnected)
|
||||||
|
{
|
||||||
|
var x = server.Clients[i].Attributes.GetAttribute("_auto_uuid");
|
||||||
|
if (x != null)
|
||||||
|
{
|
||||||
|
uuidList.Add(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
server.Clients[fromClient].Attributes.SetAttribute("_auto_ip", remoteIpEndPoint.Address.ToString());
|
||||||
|
server.Clients[fromClient].Attributes.SetAttribute("_auto_port", remoteIpEndPoint.Port.ToString());
|
||||||
|
server.Clients[fromClient].Attributes
|
||||||
|
.SetAttribute("_auto_uuid", new UUID(Config.UuidLength, uuidList).GetValue());
|
||||||
|
server.Clients[fromClient].Attributes
|
||||||
|
.SetAttribute("_auto_game_version", clientGameVersion);
|
||||||
|
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] Automatically assigned _auto_ip for client {fromClient}. _auto_ip = {server.Clients[fromClient].Attributes.GetAttribute("_auto_ip")}");
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] Automatically assigned _auto_port for client {fromClient}. _auto_port = {server.Clients[fromClient].Attributes.GetAttribute("_auto_port")}");
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] Automatically assigned _auto_uuid for client {fromClient}. _auto_uuid = {server.Clients[fromClient].Attributes.GetAttribute("_auto_uuid")}");
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] Automatically assigned _auto_game_version for client {fromClient} based on received Client data. _auto_game_version = {server.Clients[fromClient].Attributes.GetAttribute("_auto_game_version")}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"[{server.DisplayName}] Error in WelcomeReceived packet handler: {ex}");
|
||||||
|
ServerSend.Status(server, fromClient, ServerSend.StatusType.FAIL);
|
||||||
|
server.Clients[fromClient].Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetClientAttribute(Server server, int fromClient, Packet packet)
|
||||||
|
{
|
||||||
|
var clientIdCheck = packet.ReadInt();
|
||||||
|
var name = packet.ReadString();
|
||||||
|
var value = packet.ReadString();
|
||||||
|
|
||||||
|
var tcp = server.Clients[fromClient].Tcp;
|
||||||
|
if (tcp is { Socket: { } })
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} is changing Client Attribute {name} to {value}.");
|
||||||
|
if (fromClient != clientIdCheck)
|
||||||
|
{
|
||||||
|
Terminal.LogWarn(
|
||||||
|
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.Clients[fromClient].Attributes.SetAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetClientAttribute(Server server, int fromClient, Packet packet)
|
||||||
|
{
|
||||||
|
var clientIdCheck = packet.ReadInt();
|
||||||
|
var requestedId = packet.ReadInt();
|
||||||
|
var name = packet.ReadString();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tcp = server.Clients[fromClient].Tcp;
|
||||||
|
if (tcp is { Socket: { } })
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} requests Client Attribute {name}.");
|
||||||
|
if (fromClient != clientIdCheck)
|
||||||
|
{
|
||||||
|
Terminal.LogWarn(
|
||||||
|
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = server.Clients[requestedId].Attributes.GetAttribute(name);
|
||||||
|
ServerSend.GetClientAttributesReceived(server, fromClient, requestedId, name, value ?? "");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Terminal.LogError($"[{server.DisplayName}] Error in GetClientAttribute: {ex}");
|
||||||
|
ServerSend.GetClientAttributesReceived(server, fromClient, requestedId, name, "ERR_HANDLED_EXCEPTION");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
277
Matchmaker/BaseServer/ServerSend.cs
Executable file
277
Matchmaker/BaseServer/ServerSend.cs
Executable file
|
@ -0,0 +1,277 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
public static class ServerSend
|
||||||
|
{
|
||||||
|
|
||||||
|
public enum StatusType
|
||||||
|
{
|
||||||
|
OK,
|
||||||
|
RECEIVED,
|
||||||
|
FAIL
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends TCP data to a Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
public static void SendTcpData(Server server, int toClient, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
var tcp = server.Clients[toClient].Tcp;
|
||||||
|
tcp?.SendData(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends UDP data to a Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendUdpData(Server server, int toClient, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
var udp = server.Clients[toClient].Udp;
|
||||||
|
udp.SendData(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends TCP data to all Clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendTcpDataToAll(Server server, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
var tcp = server.Clients[i].Tcp;
|
||||||
|
tcp?.SendData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends TCP data to all Clients except one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// /// <param name="exceptClient">What Client to exclude</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendTcpDataToAll(Server server, int exceptClient, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
if (i == exceptClient) continue;
|
||||||
|
var tcp = server.Clients[i].Tcp;
|
||||||
|
tcp?.SendData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends UDP data to all Clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendUdpDataToAll(Server server, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
var udp = server.Clients[i].Udp;
|
||||||
|
udp.SendData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends UDP data to all Clients except one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="exceptClient">What Client to exclude</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendUdpDataToAll(Server server, int exceptClient, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
if (i == exceptClient) continue;
|
||||||
|
var udp = server.Clients[i].Udp;
|
||||||
|
udp.SendData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends TCP data to a Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
public static void SendTcpDataNoSync(Server server, int toClient, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
var tcp = server.Clients[toClient].Tcp;
|
||||||
|
tcp?.SendData(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends UDP data to a Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendUdpDataNoSync(Server server, int toClient, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
var udp = server.Clients[toClient].Udp;
|
||||||
|
udp.SendData(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends TCP data to all Clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendTcpDataToAllNoSync(Server server, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
var tcp = server.Clients[i].Tcp;
|
||||||
|
tcp?.SendData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends TCP data to all Clients except one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// /// <param name="exceptClient">What Client to exclude</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendTcpDataToAllNoSync(Server server, int exceptClient, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
if (i == exceptClient) continue;
|
||||||
|
var tcp = server.Clients[i].Tcp;
|
||||||
|
tcp?.SendData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends UDP data to all Clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendUdpDataToAllNoSync(Server server, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
var udp = server.Clients[i].Udp;
|
||||||
|
udp.SendData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends UDP data to all Clients except one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="exceptClient">What Client to exclude</param>
|
||||||
|
/// <param name="packet">Data to send</param>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void SendUdpDataToAllNoSync(Server server, int exceptClient, Packet packet)
|
||||||
|
{
|
||||||
|
packet.WriteLength();
|
||||||
|
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||||
|
{
|
||||||
|
if (i == exceptClient) continue;
|
||||||
|
var udp = server.Clients[i].Udp;
|
||||||
|
udp.SendData(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Built-in Packets
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Welcome message for Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="msg">Message to send</param>
|
||||||
|
public static void Welcome(Server server, int toClient, string msg)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug($"[{server.DisplayName}] Sending 'Welcome' message.");
|
||||||
|
using var packet = new Packet(int.MaxValue);
|
||||||
|
packet.Write(msg);
|
||||||
|
packet.Write(toClient);
|
||||||
|
packet.Write(Config.MatchmakerAPIVersion);
|
||||||
|
packet.Write(Config.GameId);
|
||||||
|
packet.Write(Config.GameVersion);
|
||||||
|
|
||||||
|
SendTcpData(server, toClient, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect a Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">What server the Client is in</param>
|
||||||
|
/// <param name="toClient">What Client to kick</param>
|
||||||
|
public static void DisconnectClient(Server server, int toClient)
|
||||||
|
{
|
||||||
|
using var packet = new Packet(int.MaxValue-1);
|
||||||
|
SendTcpData(server, toClient, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send an Attribute to the Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">What server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="requestedId">What Client the Attribute is from</param>
|
||||||
|
/// <param name="name">The Attribute name</param>
|
||||||
|
/// <param name="value">The Attribute value</param>
|
||||||
|
public static void GetClientAttributesReceived(BaseServer.Server server, int toClient, int requestedId, string name, string value)
|
||||||
|
{
|
||||||
|
using var packet = new Packet(int.MaxValue-2);
|
||||||
|
packet.Write(requestedId);
|
||||||
|
packet.Write(name);
|
||||||
|
packet.Write(value);
|
||||||
|
|
||||||
|
ServerSend.SendTcpData(server, toClient, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Respond to the client with a status
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">What server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="status">What status</param>
|
||||||
|
public static void Status(BaseServer.Server server, int toClient, StatusType status)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug($"[{server.DisplayName}] Sending status {status}...");
|
||||||
|
using var packet = new Packet(int.MaxValue-3);
|
||||||
|
packet.Write((int)status);
|
||||||
|
|
||||||
|
ServerSend.SendUdpDataNoSync(server, toClient, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
98
Matchmaker/BaseServer/Terminal.cs
Executable file
98
Matchmaker/BaseServer/Terminal.cs
Executable file
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
public static class Terminal
|
||||||
|
{
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void Log(string str)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
Console.Write(Config.LogText);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
|
||||||
|
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void LogError(string str)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
Console.Write(Config.LogErrorText);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
|
||||||
|
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void LogDebug(string str)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (Config.ShowTerminalDebug)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||||
|
Console.Write(Config.LogDebugText);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
|
||||||
|
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void LogWarn(string str)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.Write(Config.LogWarningText);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
|
||||||
|
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void LogSuccess(string str)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
|
Console.Write(Config.LogSuccessText);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
|
||||||
|
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static void LogInfo(string str)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Blue;
|
||||||
|
Console.Write(Config.LogInfoText);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
|
||||||
|
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
}
|
||||||
|
}
|
93
Matchmaker/BaseServer/ThreadManager.cs
Executable file
93
Matchmaker/BaseServer/ThreadManager.cs
Executable file
|
@ -0,0 +1,93 @@
|
||||||
|
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
internal static class ThreadManager
|
||||||
|
{
|
||||||
|
private static readonly List<Action> ExecuteOnMainThreadList = new();
|
||||||
|
private static readonly List<Action> ExecuteCopiedOnMainThread = new();
|
||||||
|
private static bool _actionToExecuteOnMainThread;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls whether or not the Main Thread should run. Set to FALSE to shut down Main Thread.
|
||||||
|
/// </summary>
|
||||||
|
private static bool _isRunning;
|
||||||
|
private static Thread? _thread;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Sets an action to be executed on the main thread.</summary>
|
||||||
|
/// <param name="action">The action to be executed on the main thread.</param>
|
||||||
|
public static void ExecuteOnMainThread(Action action)
|
||||||
|
{
|
||||||
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||||
|
if (action == null)
|
||||||
|
{
|
||||||
|
Terminal.LogWarn("No action to execute on main thread!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (ExecuteOnMainThreadList)
|
||||||
|
{
|
||||||
|
ExecuteOnMainThreadList.Add(action);
|
||||||
|
_actionToExecuteOnMainThread = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start the Main Thread so Packets can be acted upon
|
||||||
|
/// </summary>
|
||||||
|
public static void Start()
|
||||||
|
{
|
||||||
|
_isRunning = true;
|
||||||
|
_thread = new Thread(MainThread);
|
||||||
|
_thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop the Main Thread
|
||||||
|
/// </summary>
|
||||||
|
public static void Stop()
|
||||||
|
{
|
||||||
|
_isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes all code meant to run on the main thread. NOTE: Call this ONLY from the main thread.
|
||||||
|
/// </summary>
|
||||||
|
public static void UpdateMain()
|
||||||
|
{
|
||||||
|
if (!_actionToExecuteOnMainThread) return;
|
||||||
|
ExecuteCopiedOnMainThread.Clear();
|
||||||
|
lock (ExecuteOnMainThreadList)
|
||||||
|
{
|
||||||
|
ExecuteCopiedOnMainThread.AddRange(ExecuteOnMainThreadList);
|
||||||
|
ExecuteOnMainThreadList.Clear();
|
||||||
|
_actionToExecuteOnMainThread = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var t in ExecuteCopiedOnMainThread)
|
||||||
|
{
|
||||||
|
t();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MainThread()
|
||||||
|
{
|
||||||
|
|
||||||
|
var nextLoop = DateTime.Now;
|
||||||
|
|
||||||
|
while (_isRunning)
|
||||||
|
{
|
||||||
|
while (nextLoop < DateTime.Now)
|
||||||
|
{
|
||||||
|
GameLogic.Update();
|
||||||
|
|
||||||
|
nextLoop = nextLoop.AddMilliseconds(Constants.MsPerTick);
|
||||||
|
|
||||||
|
if (nextLoop > DateTime.Now)
|
||||||
|
{
|
||||||
|
Thread.Sleep(nextLoop - DateTime.Now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
Matchmaker/BaseServer/UUID.cs
Executable file
69
Matchmaker/BaseServer/UUID.cs
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
namespace Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A UUID Object
|
||||||
|
/// </summary>
|
||||||
|
public class UUID
|
||||||
|
{
|
||||||
|
private string value = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a random string of specified length
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">How long a random string to make</param>
|
||||||
|
/// <returns>A random string</returns>
|
||||||
|
public string GetRandomString(int length)
|
||||||
|
{
|
||||||
|
// Creating object of random class
|
||||||
|
var rand = new Random();
|
||||||
|
|
||||||
|
|
||||||
|
var str = "";
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Generating a random number.
|
||||||
|
var randValue = rand.Next(0, 26);
|
||||||
|
|
||||||
|
// Generating random character by converting
|
||||||
|
// the random number into character.
|
||||||
|
var letter = Convert.ToChar(randValue + 65);
|
||||||
|
|
||||||
|
// Appending the letter to string.
|
||||||
|
str = str + letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the UUID value
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The UUID's value</returns>
|
||||||
|
public string GetValue()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID(int length, List<string> usedIDs)
|
||||||
|
{
|
||||||
|
var tempValue = "";
|
||||||
|
var loop = true;
|
||||||
|
|
||||||
|
while (loop)
|
||||||
|
{
|
||||||
|
tempValue = GetRandomString(length);
|
||||||
|
loop = false;
|
||||||
|
foreach (var e in usedIDs)
|
||||||
|
{
|
||||||
|
if (e.Equals(tempValue))
|
||||||
|
{
|
||||||
|
loop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = tempValue;
|
||||||
|
}
|
||||||
|
}
|
90
Matchmaker/Config.cs
Executable file
90
Matchmaker/Config.cs
Executable file
|
@ -0,0 +1,90 @@
|
||||||
|
namespace Matchmaker.Server;
|
||||||
|
|
||||||
|
public static class Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This allows you to name the server so your game cannot connect to other ones.
|
||||||
|
/// Use the game name here and on the client as well.
|
||||||
|
/// </summary>
|
||||||
|
public const string GameId = "My Game Name";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of the matchmaker API. This must match here and on the client for a connection.
|
||||||
|
/// </summary>
|
||||||
|
public const string MatchmakerAPIVersion = "1.1.6";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What version the game is. This must match here and on the client for a connection.
|
||||||
|
/// </summary>
|
||||||
|
public const string GameVersion = "0.0.1";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many people can be connected to the List server instance at the same time.
|
||||||
|
/// </summary>
|
||||||
|
public const ushort ListMaxClients = 512;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum amount of Lobbies.
|
||||||
|
/// </summary>
|
||||||
|
public const ushort MaxLobbies = 1024;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What port the server will be on (0-65535). The lobby server will use a port two (2) above this.
|
||||||
|
/// </summary>
|
||||||
|
public const ushort Port = 26950;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The display name for the List server instance, if you want to change it.
|
||||||
|
/// </summary>
|
||||||
|
public const string ListServerDisplayName = "List Server";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The display name for the Lobby server instance, if you want to change it.
|
||||||
|
/// </summary>
|
||||||
|
public const string LobbyServerDisplayName = "Lobby Server";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long generated UUIDs are
|
||||||
|
/// </summary>
|
||||||
|
public const int UuidLength = 7;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a response to the Client whenever a message is received. Slow, for debugging.
|
||||||
|
/// </summary>
|
||||||
|
public const bool Sync = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not to show LogDebug() text.
|
||||||
|
/// </summary>
|
||||||
|
public const bool ShowTerminalDebug = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text that is prepended to a Log() text.
|
||||||
|
/// </summary>
|
||||||
|
public const string LogText = " LOG ";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text that is prepended to a LogDebug() text.
|
||||||
|
/// </summary>
|
||||||
|
public const string LogDebugText = " DEBUG ";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text that is prepended to a LogInfo() text.
|
||||||
|
/// </summary>
|
||||||
|
public const string LogInfoText = " CHECK ";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text that is prepended to a LogWarn() text.
|
||||||
|
/// </summary>
|
||||||
|
public const string LogWarningText = "WARNING ";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text that is prepended to a LogError() text.
|
||||||
|
/// </summary>
|
||||||
|
public const string LogErrorText = " ERROR ";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text that is prepended to a LogSuccess() text.
|
||||||
|
/// </summary>
|
||||||
|
public const string LogSuccessText = "SUCCESS ";
|
||||||
|
}
|
10
Matchmaker/Matchmaker.csproj
Executable file
10
Matchmaker/Matchmaker.csproj
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
159
Matchmaker/ServerPackets.cs
Executable file
159
Matchmaker/ServerPackets.cs
Executable file
|
@ -0,0 +1,159 @@
|
||||||
|
using Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
namespace Matchmaker.Server;
|
||||||
|
|
||||||
|
public static class Packets
|
||||||
|
{
|
||||||
|
public static class PacketHandlers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handle for if a client requests all IDs in the Lobby server database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">What server the Client is in</param>
|
||||||
|
/// <param name="fromClient">What Client the request is from</param>
|
||||||
|
/// <param name="packet">The data</param>
|
||||||
|
public static void RequestAllLobbyIDs(BaseServer.Server server, int fromClient, Packet packet)
|
||||||
|
{
|
||||||
|
var clientIdCheck = packet.ReadInt();
|
||||||
|
|
||||||
|
var tcp = server.Clients[fromClient].Tcp;
|
||||||
|
if (tcp is { Socket: { } })
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] Client {fromClient} has requested to cycle through all available lobbies.");
|
||||||
|
|
||||||
|
if (fromClient != clientIdCheck)
|
||||||
|
{
|
||||||
|
Terminal.LogWarn(
|
||||||
|
$"[{server.DisplayName}] [RequestAllLobbyIDs] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 1; i <= Program.LobbyServ!.Clients.Count; i++)
|
||||||
|
{
|
||||||
|
if (Program.LobbyServ.Clients[i].IsConnected)
|
||||||
|
{
|
||||||
|
ServerPackets.SendLobbyId(server, fromClient, i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerPackets.FinishedSendingLobbyIDs(server, fromClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle for if a client requests an ID from the Lobby server database matching a UUID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">What server the Client is in</param>
|
||||||
|
/// <param name="fromClient">What Client the request is from</param>
|
||||||
|
/// <param name="packet">The data</param>
|
||||||
|
public static void RequestLobbyIdsWithMatchingAttribute(BaseServer.Server server, int fromClient, Packet packet)
|
||||||
|
{
|
||||||
|
var clientIdCheck = packet.ReadInt();
|
||||||
|
var clientAttribName = packet.ReadString();
|
||||||
|
var clientAttribValue = packet.ReadString();
|
||||||
|
var tcp = server.Clients[fromClient].Tcp;
|
||||||
|
if (tcp is { Socket: { } })
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} requests all Lobby IDs with a matching Attribute ({clientAttribName}={clientAttribValue})");
|
||||||
|
|
||||||
|
if (fromClient != clientIdCheck)
|
||||||
|
{
|
||||||
|
Terminal.LogWarn(
|
||||||
|
$"[{server.DisplayName}] [RequestLobbyIDMatchingUUID] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = 1;
|
||||||
|
foreach (var lobby in Program.LobbyServ!.Clients)
|
||||||
|
{
|
||||||
|
if (lobby.Value.Attributes.GetAttribute(clientAttribName) == clientAttribValue)
|
||||||
|
{
|
||||||
|
Terminal.LogSuccess(
|
||||||
|
$"[{server.DisplayName}] Found Lobby with matching Attribute value ({clientAttribName}={clientAttribValue}). LobbyId: {i}");
|
||||||
|
ServerPackets.SendLobbyId(server, fromClient, i, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerPackets.FinishedSendingLobbyIDs(server, fromClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void GetLobbyAttribute(BaseServer.Server server, int fromClient, Packet packet)
|
||||||
|
{
|
||||||
|
var clientIdCheck = packet.ReadInt();
|
||||||
|
var requestedId = packet.ReadInt();
|
||||||
|
var name = packet.ReadString();
|
||||||
|
|
||||||
|
var tcp = server.Clients[fromClient].Tcp;
|
||||||
|
if (tcp is { Socket: { } })
|
||||||
|
Terminal.LogInfo(
|
||||||
|
$"[{server.DisplayName}] Client {fromClient} requests Lobby Attribute {name}.");
|
||||||
|
if (fromClient != clientIdCheck)
|
||||||
|
{
|
||||||
|
Terminal.LogWarn(
|
||||||
|
$"[{server.DisplayName}] GetLobbyAttribute] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Program.LobbyServ!.Clients[requestedId].IsConnected)
|
||||||
|
{
|
||||||
|
Program.LobbyServ.Clients[requestedId].Disconnect();
|
||||||
|
Terminal.LogDebug($"[{server.DisplayName}] Attempted to get Attribute of disconnected lobby!");
|
||||||
|
ServerPackets.GetLobbyAttributesReceived(server, fromClient, requestedId, name,
|
||||||
|
"ERR_DISCONNECTED_LOBBY");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = Program.LobbyServ.Clients[requestedId].Attributes.GetAttribute(name);
|
||||||
|
ServerPackets.GetLobbyAttributesReceived(server, fromClient, requestedId, name, value ?? "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ServerPackets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Server's ID through a Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">What server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="serverId">What ID is being passed from the Server database</param>
|
||||||
|
public static void SendLobbyId(BaseServer.Server server, int toClient, int serverId, int mod)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug($"[{server.DisplayName}] Sending lobby ID {serverId} to Client {toClient}...");
|
||||||
|
using var packet = new Packet(10);
|
||||||
|
packet.Write(serverId);
|
||||||
|
packet.Write(mod);
|
||||||
|
|
||||||
|
ServerSend.SendTcpData(server, toClient, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Server's Attribute to the Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">What server the Client is in</param>
|
||||||
|
/// <param name="toClient">ID for the recipient</param>
|
||||||
|
/// <param name="requestedId">What Server the Attribute is from</param>
|
||||||
|
/// <param name="name">The Attribute name</param>
|
||||||
|
/// <param name="value">The Attribute value</param>
|
||||||
|
public static void GetLobbyAttributesReceived(BaseServer.Server server, int toClient, int requestedId,
|
||||||
|
string name, string value)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug(
|
||||||
|
$"[{server.DisplayName}] Sending Lobby Attributes. toClient={toClient} requestedId={requestedId} name={name} value={value}");
|
||||||
|
using var packet = new Packet(11);
|
||||||
|
packet.Write(requestedId);
|
||||||
|
packet.Write(name);
|
||||||
|
packet.Write(value);
|
||||||
|
|
||||||
|
ServerSend.SendUdpData(server, toClient, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FinishedSendingLobbyIDs(BaseServer.Server server, int toClient)
|
||||||
|
{
|
||||||
|
Terminal.LogDebug(
|
||||||
|
$"[{server.DisplayName}] Informing client that we have finished sending Lobby IDs...");
|
||||||
|
using var packet = new Packet(12);
|
||||||
|
|
||||||
|
ServerSend.SendTcpData(server, toClient, packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
Matchmaker/ServerProgram.cs
Executable file
67
Matchmaker/ServerProgram.cs
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
using Matchmaker.Server.BaseServer;
|
||||||
|
|
||||||
|
namespace Matchmaker.Server;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||||
|
// ReSharper disable once MemberCanBePrivate.Global
|
||||||
|
// ReSharper disable once ConvertToConstant.Global
|
||||||
|
public static bool RunLoop = true;
|
||||||
|
|
||||||
|
|
||||||
|
private static BaseServer.Server? _mainServ = new();
|
||||||
|
public static BaseServer.Server? LobbyServ = new();
|
||||||
|
private static bool _serverStarted;
|
||||||
|
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"Techiesplash's Matchmaking API - Server (Ver. {Config.MatchmakerAPIVersion})");
|
||||||
|
StartServer();
|
||||||
|
|
||||||
|
while (RunLoop)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StartServer()
|
||||||
|
{
|
||||||
|
if (!_serverStarted)
|
||||||
|
{
|
||||||
|
Console.Title = "Matchmaking Server";
|
||||||
|
|
||||||
|
ThreadManager.Start();
|
||||||
|
|
||||||
|
var ClientPackets = new BaseServer.Server.ServerPackets(
|
||||||
|
new Dictionary<int, BaseServer.Server.ServerPackets.PacketHandler>
|
||||||
|
{
|
||||||
|
{ 10, Packets.PacketHandlers.RequestAllLobbyIDs },
|
||||||
|
{ 11, Packets.PacketHandlers.RequestLobbyIdsWithMatchingAttribute },
|
||||||
|
{ 12, Packets.PacketHandlers.GetLobbyAttribute }
|
||||||
|
});
|
||||||
|
|
||||||
|
var LobbyPackets = new BaseServer.Server.ServerPackets(
|
||||||
|
new Dictionary<int, BaseServer.Server.ServerPackets.PacketHandler>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
_mainServ = new BaseServer.Server();
|
||||||
|
LobbyServ = new BaseServer.Server();
|
||||||
|
_mainServ.Start(ClientPackets, Config.ListMaxClients, 26950, "List Server");
|
||||||
|
LobbyServ.Start(LobbyPackets, Config.MaxLobbies, 26952, "Lobby Server");
|
||||||
|
_serverStarted = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Terminal.LogWarn("Server already started.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StopServer()
|
||||||
|
{
|
||||||
|
_serverStarted = false;
|
||||||
|
_mainServ = null;
|
||||||
|
LobbyServ = null;
|
||||||
|
ThreadManager.Stop();
|
||||||
|
}
|
||||||
|
}
|
35
README.md
Executable file
35
README.md
Executable file
|
@ -0,0 +1,35 @@
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
# Matchmaker-API - Server
|
||||||
|
|
||||||
|
![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)
|
||||||
|
|
||||||
|
![GitHub forks](https://img.shields.io/github/forks/Techiesplash/Matchmaker-API-Server)
|
||||||
|
![GitHub repo size](https://img.shields.io/github/repo-size/Techiesplash/Matchmaker-API-Server)
|
||||||
|
![GitHub all releases](https://img.shields.io/github/downloads/Techiesplash/Matchmaker-API-Server/total)
|
||||||
|
![GitHub issues](https://img.shields.io/github/issues/Techiesplash/Matchmaker-API-Server)
|
||||||
|
|
||||||
|
![GitHub](https://img.shields.io/github/license/Techiesplash/Matchmaker-API-Server)
|
||||||
|
![GitHub release (latest by date)](https://img.shields.io/github/v/release/Techiesplash/Matchmaker-API-Server)
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
This is a project for implementing a Matchmaker API into Unity3D.
|
||||||
|
<br />
|
||||||
|
It can be expanded with custom packets as needed.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
It depends on another project to be used in Unity. https://github.com/Techiesplash/Matchmaker-API-Client-Unity3d
|
||||||
|
<br /><br />
|
||||||
|
This project is built upon MIT-Licensed code by Tom Weiland meant for a tutorial series.
|
||||||
|
Please check out his work: https://github.com/tom-weiland/tcp-udp-networking
|
||||||
|
<br />
|
||||||
|
|
||||||
|
![UVS Preview](./Images/preview.png)
|
||||||
|
|
||||||
|
<h2>Building</h2>
|
||||||
|
You can open this in Visual Studio or Visual Studio Code and it should be ready to run immediately.<br />
|
||||||
|
To compile from the CLI, use 'dotnet build' or 'dotnet build --configuration Release'.
|
||||||
|
<br /><br />
|
||||||
|
<h3>Anyone is free to use, copy, modify, merge, publish, distribute, sublicense, or and/or sell copies of the software.</h3>
|
Loading…
Add table
Reference in a new issue