Compare commits
13
Commits
974b5c4f0a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7581fbdeb | ||
|
|
c8a0aef289 | ||
|
|
068c88de4b | ||
|
|
30feb15821 | ||
|
|
9d97ad0975 | ||
|
|
e146eed3b2 | ||
|
|
3464e93aee | ||
|
|
f946ebef1e | ||
|
|
909718d8d5 | ||
|
|
ac08c7d184 | ||
|
|
258b019f1a | ||
|
|
41805d7de9 | ||
|
|
486b53b402 |
No files matched your search
@@ -0,0 +1,48 @@
|
|||||||
|
name: Build and publish mistserver
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: alpine-linux
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Restore Dependencies
|
||||||
|
run: dotnet restore ./MistoxServer
|
||||||
|
|
||||||
|
- name: Dotnet Publish
|
||||||
|
run: dotnet publish ./MistoxServer --configuration Release --output ./publish --no-restore
|
||||||
|
|
||||||
|
- name: Archive Production Artifacts
|
||||||
|
run: |
|
||||||
|
cd ./publish
|
||||||
|
zip -r ../mistserver-${{ github.ref_name }}.zip .
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# 2. Publish directly to Gitea Releases
|
||||||
|
- name: Create Gitea Release
|
||||||
|
uses: https://gitea.com/actions/release-action@main
|
||||||
|
with:
|
||||||
|
# Automatically uses the tag string pushed (e.g., v1.2.3)
|
||||||
|
name: "Release ${{ github.ref_name }}"
|
||||||
|
|
||||||
|
# Change this text to your desired version description (Supports Markdown)
|
||||||
|
body: |
|
||||||
|
## Mistserver Version ${{ github.ref_name }}
|
||||||
|
Automated deployment build compiled on Alpine Linux.
|
||||||
|
|
||||||
|
### Changelog
|
||||||
|
- Compiled via Gitea Actions
|
||||||
|
- Run ID: ${{ github.run_id }}
|
||||||
|
|
||||||
|
# Target the zipped artifact we generated in step 1
|
||||||
|
files: |
|
||||||
|
mistserver-*.zip
|
||||||
|
|
||||||
|
# Uses Gitea's automatically injected repository token for authorization
|
||||||
|
api_key: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Mistox;
|
||||||
|
using Mistox.Client;
|
||||||
|
|
||||||
|
class clientExample {
|
||||||
|
|
||||||
|
public MistoxClient? clientObject;
|
||||||
|
|
||||||
|
void onServerConnected() {
|
||||||
|
Console.WriteLine("Connected to server sucessfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClientConnected(mUDPClient user) {
|
||||||
|
Console.WriteLine("User connected to server with username: " + user.UserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClientJoined(mUDPClient user) {
|
||||||
|
Console.WriteLine("User is now in range of you and UDP connections started with user : " + user.UserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onReceive(mUDPClient? user, dynamic data) {
|
||||||
|
switch (data){
|
||||||
|
case MessagePacket packet:
|
||||||
|
if (user == null) {
|
||||||
|
Console.WriteLine($"MSG From [Serverx]: {packet.Data}");
|
||||||
|
} else {
|
||||||
|
Console.WriteLine($"MSG From [{user.UserName}]: {packet.Data}");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClientLeft(mUDPClient user) {
|
||||||
|
Console.WriteLine("User is no longer in range of you and UDP connections ended with user : " + user.UserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClientDisconnected(mUDPClient user) {
|
||||||
|
Console.WriteLine("User disconnected from server with username: " + user.UserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onServerDisconnected() {
|
||||||
|
Console.WriteLine("Disconnected from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartClient(string userName, string host, int port, string password) {
|
||||||
|
clientObject = GameServer.newClient(userName, host, port, password);
|
||||||
|
if (clientObject == null) return;
|
||||||
|
clientObject.onServerConnected += onServerConnected;
|
||||||
|
clientObject.onUserConnected += onClientConnected;
|
||||||
|
clientObject.onUserJoined += onClientJoined;
|
||||||
|
clientObject.onDataReceived += onReceive;
|
||||||
|
clientObject.onUserLeft += onClientLeft;
|
||||||
|
clientObject.onUserDisconnected += onClientDisconnected;
|
||||||
|
clientObject.onServerDisconnected += onServerDisconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
The servers default port is 25566
|
|
||||||
|
|
||||||
|
|
||||||
TCPServer.cs
|
|
||||||
Void TCPServer() Initilizes the server object on the default game port of 25566
|
|
||||||
Void SendAll(string Data) Send data to all the clients
|
|
||||||
Event onReceived Fires when data is received from a client
|
|
||||||
|
|
||||||
TCPClient.cs
|
|
||||||
Void TCPClient(String ServerIP) Initilizes the client object
|
|
||||||
Void Send(String Data) Sends data to the server
|
|
||||||
Event onReceived Fires when data is received from the server
|
|
||||||
|
|
||||||
UDPClient.cs
|
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<Company>Mistox.net</Company>
|
<Company>Mistox.net</Company>
|
||||||
<Product>Mistox Game Server</Product>
|
<Product>Mistox Game Server</Product>
|
||||||
<Description>Game Server for Mistox Games and partners</Description>
|
<Description>Game Server for Mistox Games and partners</Description>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
<SignAssembly>false</SignAssembly>
|
<SignAssembly>false</SignAssembly>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MistoxServer\MistServerModule.csproj" />
|
<ProjectReference Include="..\MistoxServer\MistServer.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
+42
-83
@@ -1,85 +1,48 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MistoxServer;
|
|
||||||
|
|
||||||
namespace MistoxHolePunch {
|
namespace MistoxHolePunch {
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
Server,
|
||||||
|
Client
|
||||||
|
}
|
||||||
|
|
||||||
class Program {
|
class Program {
|
||||||
|
|
||||||
static IMistoxServer serverObj;
|
static serverExample? server;
|
||||||
static bool running = true;
|
static clientExample? client;
|
||||||
|
static Mode mode;
|
||||||
|
|
||||||
void onConnected( object sender, EventArgs e ) {
|
async Task StartServer(string name, int port) {
|
||||||
// sender and e are always null
|
server = new serverExample();
|
||||||
// put connected functions in here
|
await server.StartServer(name, port);
|
||||||
}
|
// On a game this sleep loop isnt needed
|
||||||
|
if (server.serverObject == null) return;
|
||||||
void slowReceive( object obj, EventArgs e ) {
|
while (server.serverObject.Alive) {
|
||||||
if ( serverObj is ServerInterface ) {
|
// becuase this is a console app if we dont keep the main thread alive the app will close
|
||||||
// If ServerMode is passive obj is byte[] and Send takes in byte[]
|
await Task.Delay(5000);
|
||||||
// If Servermode is Athoritiative obj is the Class type sent and Send takes in any Class type except generic object
|
|
||||||
|
|
||||||
// Check to make sure data is correct before relaying
|
|
||||||
// Also perform server specific checks in here
|
|
||||||
// IE player didnt teleport or is shooting from 20 feet from his body
|
|
||||||
serverObj.Send( obj, SendType.SlowUpdate );
|
|
||||||
}
|
|
||||||
Console.Write( "Received TCP : " );
|
|
||||||
Console.WriteLine( obj );
|
|
||||||
}
|
|
||||||
|
|
||||||
void fastReceive( object obj, EventArgs e ) {
|
|
||||||
if( serverObj is ServerInterface ) {
|
|
||||||
// If ServerMode is passive obj is byte[] and Send takes in byte[]
|
|
||||||
// If Servermode is Athoritiative obj is the Class type sent and Send takes in any Class type except generic object
|
|
||||||
|
|
||||||
// Check to make sure data is correct before relaying
|
|
||||||
// Also perform server specific checks in here
|
|
||||||
// IE player didnt teleport or is shooting from 20 feet from his body
|
|
||||||
serverObj.Send( obj, SendType.FastUpdate );
|
|
||||||
}
|
|
||||||
Console.Write( "Received UDP : " );
|
|
||||||
Console.WriteLine( obj );
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDisconnected( object sender, EventArgs e ) {
|
|
||||||
// sender and e are always null
|
|
||||||
// put disconnected functions in here
|
|
||||||
}
|
|
||||||
|
|
||||||
static string host = "example.com";
|
|
||||||
static int port = 7300;
|
|
||||||
static ServerMode mode = ServerMode.Passive;
|
|
||||||
|
|
||||||
async Task RunServer() {
|
|
||||||
serverObj = mServer.newServer( Convert.ToInt32( port ), mode );
|
|
||||||
serverObj.onConnected += onConnected;
|
|
||||||
serverObj.onSlowReceive += slowReceive;
|
|
||||||
serverObj.onFastReceive += fastReceive;
|
|
||||||
serverObj.onDisconnected += onDisconnected;
|
|
||||||
while ( running ) {
|
|
||||||
// Stop this thread for 1 second so the CPU isnt 100%
|
|
||||||
// All the real work is on different threads
|
|
||||||
// While loop needs to exist so our main thread doesnt close
|
|
||||||
await Task.Delay( 1000 );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task RunClient() {
|
async Task StartClient(string username, string host, int port, string password) {
|
||||||
serverObj = mServer.newClient( host, Convert.ToInt32( port ) );
|
client = new clientExample();
|
||||||
serverObj.onConnected += onConnected;
|
await client.StartClient(username, host, port, password);
|
||||||
serverObj.onSlowReceive += slowReceive;
|
// On a game this sleep loop isnt needed
|
||||||
serverObj.onFastReceive += fastReceive;
|
if (client.clientObject == null) return;
|
||||||
serverObj.onDisconnected += onDisconnected;
|
while (client.clientObject.Alive) {
|
||||||
while( running ) {
|
// becuase this is a console app if we dont keep the main thread alive the app will close
|
||||||
// Stop this thread for 1 second so the CPU isnt 100%
|
await Task.Delay(5000);
|
||||||
// All the real work is on different threads
|
|
||||||
// While loop needs to exist so our main thread doesnt close
|
|
||||||
await Task.Delay( 1000 );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task Main(string[] args) {
|
async Task Main(string[] args) {
|
||||||
string Task = args.Length > 0 ? args[0].ToLower() : null;
|
string Job = args.Length > 0 ? args[0].ToLower() : "";
|
||||||
|
|
||||||
|
string host = "";
|
||||||
|
string name = "";
|
||||||
|
string pass = "";
|
||||||
|
int port = 0;
|
||||||
|
|
||||||
for( int i = 0; i < args.Length; i++ ) {
|
for( int i = 0; i < args.Length; i++ ) {
|
||||||
string cur = args[i].ToLower();
|
string cur = args[i].ToLower();
|
||||||
@@ -87,25 +50,21 @@ namespace MistoxHolePunch {
|
|||||||
host = args [i + 1];
|
host = args [i + 1];
|
||||||
} else if( cur == "/p" || cur == "-p" ) {
|
} else if( cur == "/p" || cur == "-p" ) {
|
||||||
port = Convert.ToInt32( args [i + 1] );
|
port = Convert.ToInt32( args [i + 1] );
|
||||||
} else if( cur == "/a" || cur == "-a" ) {
|
} else if( cur == "/n" || cur == "-n" ) {
|
||||||
mode = ServerMode.Authoritative;
|
name = args [i + 1];
|
||||||
|
} else if( cur == "/u" || cur == "-u" ) {
|
||||||
|
pass = args [i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Task == "/?" || Task == "--help") {
|
if (Job == "/?" || Job == "--help") {
|
||||||
Console.WriteLine(HelpDocumentation.HelpText);
|
Console.WriteLine(HelpDocumentation.HelpText);
|
||||||
} else if (Task == "/s" || Task == "-s") {
|
} else if (Job == "/s" || Job == "-s") {
|
||||||
Program prog = new Program();
|
await StartServer(name, port);
|
||||||
await prog.RunServer();
|
} else if (Job == "/c" || Job == "-c") {
|
||||||
} else if (Task == "/c" || Task == "-c") {
|
await StartClient(name, host, port, pass);
|
||||||
Program prog = new Program();
|
|
||||||
await prog.RunClient();
|
|
||||||
} else {
|
} else {
|
||||||
host = "example.com";
|
await StartServer("example.com", 4500);
|
||||||
port = 1500;
|
|
||||||
Program prog = new Program();
|
|
||||||
//prog.RunClient();
|
|
||||||
await prog.RunServer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Mistox;
|
||||||
|
using Mistox.Server;
|
||||||
|
|
||||||
|
class serverExample {
|
||||||
|
|
||||||
|
public MistoxServer? serverObject;
|
||||||
|
|
||||||
|
void onClientConnected(ServerConnection user) {
|
||||||
|
Console.WriteLine("User connected with username: " + user.UserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onReceive(ServerConnection user, dynamic data) {
|
||||||
|
switch (data){
|
||||||
|
case MessagePacket packet when user.IsAuthenticated:
|
||||||
|
Console.WriteLine($"MSG From [{user.UserName}]: {packet.Data}");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClientDisconnected(ServerConnection user) {
|
||||||
|
Console.WriteLine("User disconnected with username: " + user.UserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartServer(string serverName, int port) {
|
||||||
|
serverObject = GameServer.newServer(serverName, port);
|
||||||
|
serverObject.onUserConnected += onClientConnected;
|
||||||
|
serverObject.onDataReceived += onReceive;
|
||||||
|
serverObject.onUserDisconnected += onClientDisconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using Mistox.Attributes;
|
||||||
|
|
||||||
|
[mPacket]
|
||||||
|
class MessagePacket {
|
||||||
|
public string Data { get; set; } = "";
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Mistox.ResevedPackets;
|
using Mistox.ResevedPackets;
|
||||||
|
|
||||||
@@ -13,15 +16,21 @@ namespace Mistox.Client {
|
|||||||
public string UserName { get; private set; } = ""; // My UserName
|
public string UserName { get; private set; } = ""; // My UserName
|
||||||
public bool Alive { get; set; } = true; // Is the server running
|
public bool Alive { get; set; } = true; // Is the server running
|
||||||
public bool isAuthenticated { get; private set; } = false; // Is the server authenticated
|
public bool isAuthenticated { get; private set; } = false; // Is the server authenticated
|
||||||
public ConcurrentDictionary<IPEndPoint, mUDPClient> clientConnections { get; private set; } // UserName, Connection
|
public ConcurrentDictionary<IPEndPoint, mUDPClient> clientConnections { get; private set; } = new(); // UserName, Connection
|
||||||
|
public Vector3 Position { get; set; } = new Vector3(0, 0, 0); // My world position
|
||||||
|
public float MaxNetworkRenderDistance { get; private set; } = 0; // The distance for connections
|
||||||
|
|
||||||
public mTCPClient ServerConnection {get; private set; } // Global TCP Server Connection
|
public mTCPClient? ServerConnection {get; private set; } // Global TCP Server Connection
|
||||||
public UdpClient ClientSocket { get; private set; } // Global UDP Sender
|
public UdpClient ClientSocket { get; private set; } // Global UDP Sender
|
||||||
public UDPReceiver UdpReceiver { get; private set; } // Global UDP Receiver
|
public UDPReceiver UdpReceiver { get; private set; } // Global UDP Receiver
|
||||||
|
|
||||||
private readonly string ConnectionKey = ""; // Server Connection Key
|
private readonly string ConnectionKey = ""; // Server Connection Key
|
||||||
|
private readonly int Port = 0; // The port of the connected socket
|
||||||
|
private readonly int HeartbeatInterval = 5000; // How often should we heartbeat
|
||||||
|
|
||||||
public MistoxClient(string userName, string connectionKey, IPEndPoint Server) {
|
public MistoxClient(string userName, string connectionKey, IPEndPoint Server) {
|
||||||
// Create and listen on UDP
|
// Create and listen on UDP
|
||||||
|
Port = Server.Port;
|
||||||
ClientSocket = new UdpClient( Server.Port );
|
ClientSocket = new UdpClient( Server.Port );
|
||||||
UdpReceiver = new UDPReceiver( this );
|
UdpReceiver = new UDPReceiver( this );
|
||||||
UserName = userName;
|
UserName = userName;
|
||||||
@@ -33,88 +42,104 @@ namespace Mistox.Client {
|
|||||||
ServerConnection = new mTCPClient( this, Server );
|
ServerConnection = new mTCPClient( this, Server );
|
||||||
Console.WriteLine( "Connected to the server sucessfully" );
|
Console.WriteLine( "Connected to the server sucessfully" );
|
||||||
Alive = true;
|
Alive = true;
|
||||||
} catch( Exception e ) {
|
} catch ( Exception e ) {
|
||||||
Console.WriteLine( "An error has occured with the connection to the server. Error { " );
|
Console.WriteLine( "An error has occured with the connection to the server. Error { " );
|
||||||
Console.WriteLine( e.ToString() );
|
Console.WriteLine( e.ToString() );
|
||||||
Console.WriteLine( "}" );
|
Console.WriteLine( "}" );
|
||||||
Alive = false;
|
Alive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start the heartbeat
|
||||||
|
_ = RunAsync(Heartbeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TryAuth() {
|
async Task RunAsync(Func<Task> work) {
|
||||||
|
try{
|
||||||
|
await work();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Console.WriteLine($"Error during background packet processing: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// Heartbeat
|
||||||
|
/////////////////////////////
|
||||||
|
|
||||||
|
async Task Heartbeat() {
|
||||||
|
while (Alive) {
|
||||||
|
await SendServer(new HeartbeatPacket {
|
||||||
|
Position = Position
|
||||||
|
});
|
||||||
|
Thread.Sleep(HeartbeatInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeartbeatReceived(HeartbeatPacket packet) {
|
||||||
|
// Build a HashSet of server IP addresses for O(1) removal checks
|
||||||
|
var activeIps = new HashSet<IPAddress>(packet.ServerList.Count);
|
||||||
|
for (int i = 0; i < packet.ServerList.Count; i++) {
|
||||||
|
activeIps.Add(packet.ServerList[i].IpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add users that joined
|
||||||
|
foreach (UserTableObject cur in packet.ServerList) {
|
||||||
|
IPEndPoint remoteHost = new IPEndPoint(cur.IpAddress, Port);
|
||||||
|
if (!clientConnections.ContainsKey(remoteHost)) {
|
||||||
|
mUDPClient newConnection = new mUDPClient(this, remoteHost);
|
||||||
|
clientConnections[remoteHost] = newConnection;
|
||||||
|
InvokeOnUserJoined(newConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove users that left
|
||||||
|
foreach (IPEndPoint key in clientConnections.Keys) {
|
||||||
|
if (!activeIps.Contains(key.Address)) {
|
||||||
|
mUDPClient? user;
|
||||||
|
clientConnections.TryRemove(key, out user);
|
||||||
|
if (user == null) return;
|
||||||
|
InvokeOnUserLeft(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update distance status
|
||||||
|
float maxDistanceSq = MaxNetworkRenderDistance * MaxNetworkRenderDistance;
|
||||||
|
foreach (mUDPClient cur in clientConnections.Values) {
|
||||||
|
float dx = Position.X - cur.Position.X;
|
||||||
|
float dy = Position.Y - cur.Position.Y;
|
||||||
|
float dz = Position.Z - cur.Position.Z;
|
||||||
|
|
||||||
|
// Fixed bug (dz * dz instead of dz + dz) & avoided Sqrt
|
||||||
|
float distanceSq = (dx * dx) + (dy * dy) + (dz * dz);
|
||||||
|
cur.WithinRange = distanceSq < maxDistanceSq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// Auth
|
||||||
|
/////////////////////////////
|
||||||
|
|
||||||
|
async Task TryAuth() {
|
||||||
Console.WriteLine("Attempting to authenticate against the server...");
|
Console.WriteLine("Attempting to authenticate against the server...");
|
||||||
|
if (ServerConnection == null) return;
|
||||||
await ServerConnection.Send(new UserAuthPacket {
|
await ServerConnection.Send(new UserAuthPacket {
|
||||||
UserName = UserName,
|
UserName = UserName,
|
||||||
ConnectionKey = ConnectionKey
|
ConnectionKey = ConnectionKey
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CheckAuthStatus(UserAuthPacket packet) {
|
async Task CheckAuthStatus(UserAuthPacket packet) {
|
||||||
if (packet.AuthSuccessful == 1) {
|
if (packet.AuthSuccessful == 1) {
|
||||||
isAuthenticated = true;
|
isAuthenticated = true;
|
||||||
}
|
}
|
||||||
ServerName = packet.ServerName;
|
ServerName = packet.ServerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
|
||||||
// Receive Methods
|
|
||||||
/////////////////////////////
|
|
||||||
|
|
||||||
public void UserConnected(UserConnectedPacket packet) {
|
|
||||||
IPEndPoint remoteUser = new IPEndPoint(packet.ClientIPAddress, packet.ClientPort);
|
|
||||||
mUDPClient user = new mUDPClient(this, remoteUser) {
|
|
||||||
UserName = packet.UserName
|
|
||||||
};
|
|
||||||
clientConnections[remoteUser] = user;
|
|
||||||
InvokeOnUserConnected(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UserDisconnected(UserDisconnectedPacket packet) {
|
|
||||||
mUDPClient client = null;
|
|
||||||
foreach(mUDPClient cur in clientConnections.Values) {
|
|
||||||
if (cur.UserName.ToLower() == packet.UserName.ToLower()) {
|
|
||||||
client = cur;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (client != null) {
|
|
||||||
InvokeOnUserDisconnected( client );
|
|
||||||
client.Alive = false;
|
|
||||||
client.Dispose();
|
|
||||||
clientConnections[client._remoteHost] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// Events
|
// Events
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
|
||||||
public event Action<dynamic> onDataReceived;
|
// When (self) connects to the server
|
||||||
public void InvokeOnReceived(dynamic DataPacket, mUDPClient remote = null){
|
public event Action? onServerConnected;
|
||||||
switch (DataPacket as object) {
|
|
||||||
case UserConnectedPacket packet:
|
|
||||||
UserConnected(packet);
|
|
||||||
break;
|
|
||||||
case UserDisconnectedPacket packet:
|
|
||||||
UserDisconnected(packet);
|
|
||||||
break;
|
|
||||||
case UserAuthPacket packet:
|
|
||||||
Task.Run(async () => {
|
|
||||||
try {
|
|
||||||
await CheckAuthStatus(packet);
|
|
||||||
} catch {}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case null:
|
|
||||||
Console.WriteLine("Received a null packet.");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
onDataReceived?.Invoke(DataPacket);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action onServerConnected;
|
|
||||||
public void InvokeOnServerConnected(){
|
public void InvokeOnServerConnected(){
|
||||||
Alive = true;
|
Alive = true;
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
@@ -125,26 +150,67 @@ namespace Mistox.Client {
|
|||||||
onServerConnected?.Invoke();
|
onServerConnected?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action<mUDPClient> onUserConnected;
|
// When any user connects to the server (self) hasn't seen yet
|
||||||
public void InvokeOnUserConnected(mUDPClient ConnectedUser){
|
public event Action<mUDPClient>? onUserJoined;
|
||||||
onUserConnected?.Invoke(ConnectedUser);
|
public void InvokeOnUserJoined(mUDPClient User){
|
||||||
|
onUserJoined?.Invoke(User);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action<mUDPClient> onUserDisconnected;
|
// When user is within range of (self) and network rendering starts
|
||||||
public void InvokeOnUserDisconnected(mUDPClient DisconnectedUser){
|
public event Action<mUDPClient>? onUserConnected;
|
||||||
onUserDisconnected?.Invoke(DisconnectedUser);
|
public void InvokeOnUserConnected(mUDPClient User){
|
||||||
|
onUserConnected?.Invoke(User);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action onServerDisconnected;
|
// When generic data is received
|
||||||
|
public event Action<mUDPClient?, dynamic>? onDataReceived;
|
||||||
|
public void InvokeOnReceived(dynamic DataPacket, mUDPClient? remote = null){
|
||||||
|
switch (DataPacket as object) {
|
||||||
|
case HeartbeatPacket packet:
|
||||||
|
HeartbeatReceived(packet);
|
||||||
|
break;
|
||||||
|
case UserAuthPacket packet:
|
||||||
|
_ = RunAsync(async () => {
|
||||||
|
await CheckAuthStatus(packet);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case null:
|
||||||
|
Console.WriteLine("Received a null packet.");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
onDataReceived?.Invoke(remote, DataPacket);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When user is no longer in range of (self) and network rendering stops
|
||||||
|
public event Action<mUDPClient>? onUserDisconnected;
|
||||||
|
public void InvokeOnUserDisconnected(mUDPClient User){
|
||||||
|
onUserDisconnected?.Invoke(User);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When any user disconnects from the server that isn't (self)
|
||||||
|
public event Action<mUDPClient>? onUserLeft;
|
||||||
|
public void InvokeOnUserLeft(mUDPClient User){
|
||||||
|
onUserLeft?.Invoke(User);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When (self) disconnects to the server
|
||||||
|
public event Action? onServerDisconnected;
|
||||||
public void InvokeOnServerDisconnected(){
|
public void InvokeOnServerDisconnected(){
|
||||||
Alive = false;
|
Alive = false;
|
||||||
onServerDisconnected?.Invoke();
|
onServerDisconnected?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// Send Methods
|
||||||
|
/////////////////////////////
|
||||||
|
|
||||||
public async Task SendAllClients<Packet>(Packet data) {
|
public async Task SendAllClientsInRange<Packet>(Packet data) {
|
||||||
foreach( mUDPClient cur in clientConnections.Values ){
|
foreach( mUDPClient cur in clientConnections.Values ){
|
||||||
await cur.Send( data );
|
if (cur.WithinRange) {
|
||||||
|
await cur.Send( data );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +224,7 @@ namespace Mistox.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendServer<Packet>(Packet data) {
|
public async Task SendServer<Packet>(Packet data) {
|
||||||
|
if (ServerConnection == null) return;
|
||||||
await ServerConnection.Send( data );
|
await ServerConnection.Send( data );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,10 +48,11 @@ namespace Mistox.Client {
|
|||||||
await ns.ReadExactlyAsync(payloadBuffer, 0, Header.DataLength);
|
await ns.ReadExactlyAsync(payloadBuffer, 0, Header.DataLength);
|
||||||
|
|
||||||
// Create the dataobject
|
// Create the dataobject
|
||||||
dynamic dataobject = DataPacket.Deserialize( Header, payloadBuffer.AsMemory(DataPacket.HeaderLength, Header.DataLength) );
|
object? dataobject = DataPacket.Deserialize( Header, payloadBuffer.AsMemory(DataPacket.HeaderLength, Header.DataLength) );
|
||||||
|
if (dataobject != null) {
|
||||||
// Return the object
|
// Return the object
|
||||||
_parent.InvokeOnReceived(dataobject);
|
_parent.InvokeOnReceived(dataobject);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Numerics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -7,9 +8,10 @@ namespace Mistox.Client {
|
|||||||
public class mUDPClient : IDisposable {
|
public class mUDPClient : IDisposable {
|
||||||
|
|
||||||
public string UserName { get; set; } = ""; // The remote players UserName
|
public string UserName { get; set; } = ""; // The remote players UserName
|
||||||
public bool Alive { get; set; } = true; // Is the connection alive
|
public bool WithinRange { get; set; } = true; // Is the user close enough?
|
||||||
public IPEndPoint _remoteHost { get; set; } // The remote IP Address
|
public Vector3 Position { get; set; } = new Vector3(0, 0, 0); // The remote location of the player
|
||||||
|
|
||||||
|
private IPEndPoint _remoteHost { get; set; } // The remote IP Address
|
||||||
private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
|
||||||
private readonly MistoxClient _parent;
|
private readonly MistoxClient _parent;
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ namespace Mistox.Client {
|
|||||||
UdpReceiveResult udpPacket = await _parent.ClientSocket.ReceiveAsync();
|
UdpReceiveResult udpPacket = await _parent.ClientSocket.ReceiveAsync();
|
||||||
|
|
||||||
// Try find the user
|
// Try find the user
|
||||||
mUDPClient client = null;
|
mUDPClient? client = null;
|
||||||
bool clientExists = _parent.clientConnections.TryGetValue(udpPacket.RemoteEndPoint, out client);
|
_parent.clientConnections.TryGetValue(udpPacket.RemoteEndPoint, out client);
|
||||||
if (clientExists) {
|
if (client != null) {
|
||||||
|
|
||||||
// Create the data readers
|
// Create the data readers
|
||||||
ReadOnlyMemory<byte> dataMemory = udpPacket.Buffer.AsMemory();
|
ReadOnlyMemory<byte> dataMemory = udpPacket.Buffer.AsMemory();
|
||||||
@@ -39,10 +39,12 @@ namespace Mistox.Client {
|
|||||||
ReadOnlyMemory<byte> data = dataMemory.Slice(DataPacket.HeaderLength, Header.DataLength);
|
ReadOnlyMemory<byte> data = dataMemory.Slice(DataPacket.HeaderLength, Header.DataLength);
|
||||||
|
|
||||||
// Extract the object
|
// Extract the object
|
||||||
dynamic buildObj = DataPacket.Deserialize( Header, data );
|
object? buildObj = DataPacket.Deserialize( Header, data );
|
||||||
|
if (buildObj != null) {
|
||||||
|
|
||||||
// Return the object
|
// Return the object
|
||||||
_parent.InvokeOnReceived( buildObj, client );
|
_parent.InvokeOnReceived( buildObj, client );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch( Exception e ) {
|
} catch( Exception e ) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Mistox {
|
|||||||
return new MistoxServer(listenerAddress, serverName);
|
return new MistoxServer(listenerAddress, serverName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MistoxClient newClient( string UserName, string ServerIPOrHostName, int Port, string ConnectionKey = "" ) {
|
public static MistoxClient? newClient( string UserName, string ServerIPOrHostName, int Port, string ConnectionKey = "" ) {
|
||||||
|
|
||||||
// Build the network packet list
|
// Build the network packet list
|
||||||
NetworkPacketDictionary.BuildNetworkPacketList();
|
NetworkPacketDictionary.BuildNetworkPacketList();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Mistox.ResevedPackets;
|
using Mistox.ResevedPackets;
|
||||||
@@ -8,25 +9,49 @@ namespace Mistox.Server {
|
|||||||
|
|
||||||
public class MistoxServer {
|
public class MistoxServer {
|
||||||
|
|
||||||
// Class Definitions
|
public string ServerName { get; private set; } = ""; // The name of the server
|
||||||
|
public int MaxClients { get; set; } = 10; // The max players per server
|
||||||
|
public float ConnectionRange { get; set; } = 40; // InGame distance for valid conections
|
||||||
|
public string ConnectionKey { get; set; } = ""; // The connection key for the server
|
||||||
|
public int ConnectionTimeoutSeconds { get; set; } = 10; // If no reply in time kill the connection
|
||||||
|
public bool Alive { get; set; } = true; // Is the server running
|
||||||
|
public ConcurrentDictionary<string, ServerConnection> Connections = new(); // All the active connections. key is username
|
||||||
|
|
||||||
public string ServerName { get; private set; } = ""; // The name of the server
|
private readonly ServerListener Listener; // The Connection Listener singleton
|
||||||
public int MaxClients { get; set; } = 10; // The max players per server
|
|
||||||
public float ConnectionRange { get; set; } = 40; // InGame distance for valid conections
|
|
||||||
public string ConnectionKey { get; set; } = ""; // The connection key for the server
|
|
||||||
public int ConnectionTimeoutSeconds { get; set; } = 10; // If no reply in time kill the connection
|
|
||||||
public bool Alive { get; set; } = true; // Is the server running
|
|
||||||
public ConcurrentDictionary<string, ServerConnection> Connections; // All the active connections. key is username
|
|
||||||
|
|
||||||
private readonly ServerListener Listener; // The Connection Listener singleton
|
|
||||||
|
|
||||||
public MistoxServer(IPEndPoint ListenerAddress, string serverName) {
|
public MistoxServer(IPEndPoint ListenerAddress, string serverName) {
|
||||||
Connections = new ConcurrentDictionary<string, ServerConnection>();
|
|
||||||
ServerName = serverName;
|
ServerName = serverName;
|
||||||
Listener = new ServerListener(ListenerAddress, this);
|
Listener = new ServerListener(ListenerAddress, this);
|
||||||
Console.WriteLine( "The Server is initilized and waiting for clients to connect at port : " + ListenerAddress.Port );
|
Console.WriteLine( "The Server is initilized and waiting for clients to connect at port : " + ListenerAddress.Port );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async Task RunAsync(Func<Task> work) {
|
||||||
|
try{
|
||||||
|
await work();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Console.WriteLine($"Error during background packet processing: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// Heartbeat
|
||||||
|
/////////////////////////////
|
||||||
|
|
||||||
|
async Task BuildServerList(ServerConnection user, HeartbeatPacket packet) {
|
||||||
|
packet.ServerList = new List<UserTableObject>();
|
||||||
|
foreach( ServerConnection cur in Connections.Values) {
|
||||||
|
packet.ServerList.Add( new UserTableObject() {
|
||||||
|
UserName = cur.UserName,
|
||||||
|
Position = cur.Position,
|
||||||
|
IpAddress = cur.getRemote()
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
await user.Send( packet );
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// Auth
|
||||||
|
/////////////////////////////
|
||||||
|
|
||||||
async Task ProcessAuthRequest(ServerConnection user, UserAuthPacket packet) {
|
async Task ProcessAuthRequest(ServerConnection user, UserAuthPacket packet) {
|
||||||
packet.ServerName = ServerName;
|
packet.ServerName = ServerName;
|
||||||
@@ -36,30 +61,36 @@ namespace Mistox.Server {
|
|||||||
user.UserName = packet.UserName;
|
user.UserName = packet.UserName;
|
||||||
Connections[packet.UserName] = user;
|
Connections[packet.UserName] = user;
|
||||||
user.IsAuthenticated = true;
|
user.IsAuthenticated = true;
|
||||||
|
InvokeUserConnected(user);
|
||||||
} else {
|
} else {
|
||||||
packet.AuthSuccessful = -1;
|
packet.AuthSuccessful = -1;
|
||||||
}
|
}
|
||||||
await SendClient(packet.UserName, packet);
|
await user.Send(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions
|
/////////////////////////////
|
||||||
|
// Events
|
||||||
|
/////////////////////////////
|
||||||
|
|
||||||
public event Action<ServerConnection> onUserConnected;
|
public event Action<ServerConnection>? onUserConnected;
|
||||||
public void InvokeUserConnected( ServerConnection user ){
|
public void InvokeUserConnected( ServerConnection user ){
|
||||||
|
Connections[user.UserName] = user;
|
||||||
onUserConnected?.Invoke( user );
|
onUserConnected?.Invoke( user );
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action<ServerConnection, dynamic> onDataReceived;
|
public event Action<ServerConnection, object>? onDataReceived;
|
||||||
public void InvokeDataReceived( ServerConnection user, dynamic data ){
|
public void InvokeDataReceived( ServerConnection user, dynamic data ){
|
||||||
switch (data as object) {
|
switch (data) {
|
||||||
case UserAuthPacket packet:
|
case UserAuthPacket packet when !user.IsAuthenticated:
|
||||||
if (!user.IsAuthenticated) {
|
_ = RunAsync(async () => {
|
||||||
Task.Run(async () => {
|
await ProcessAuthRequest(user, packet);
|
||||||
try {
|
});
|
||||||
await ProcessAuthRequest(user, packet);
|
break;
|
||||||
} catch {}
|
case HeartbeatPacket packet when user.IsAuthenticated:
|
||||||
});
|
user.Position = packet.Position;
|
||||||
}
|
_ = RunAsync(async () => {
|
||||||
|
await BuildServerList(user, packet);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case null:
|
case null:
|
||||||
Console.WriteLine("Received a null packet.");
|
Console.WriteLine("Received a null packet.");
|
||||||
@@ -70,11 +101,16 @@ namespace Mistox.Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action<ServerConnection> onUserDisconnected;
|
public event Action<ServerConnection>? onUserDisconnected;
|
||||||
public void InvokeUserDisconnected( ServerConnection user ){
|
public void InvokeUserDisconnected( ServerConnection user ){
|
||||||
|
Connections.Remove(user.UserName, out _);
|
||||||
onUserDisconnected?.Invoke( user );
|
onUserDisconnected?.Invoke( user );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// Send Methods
|
||||||
|
/////////////////////////////
|
||||||
|
|
||||||
public async Task SendAllClients<Packet>(Packet data) {
|
public async Task SendAllClients<Packet>(Packet data) {
|
||||||
foreach( ServerConnection cur in Connections.Values ) {
|
foreach( ServerConnection cur in Connections.Values ) {
|
||||||
await cur.Send(data);
|
await cur.Send(data);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Mistox.Server {
|
|||||||
|
|
||||||
public string UserName { get; set; } = ""; // The Players UserName
|
public string UserName { get; set; } = ""; // The Players UserName
|
||||||
public bool IsAuthenticated { get; set; } = false; // Has the player authenticated
|
public bool IsAuthenticated { get; set; } = false; // Has the player authenticated
|
||||||
public Vector3 Position { get; set; } // The ingame position of the player
|
public Vector3 Position { get; set; } = new Vector3(0, 0, 0); // The ingame position of the player
|
||||||
public bool Connected { get; set; } = true; // Is the connection valid
|
public bool Connected { get; set; } = true; // Is the connection valid
|
||||||
|
|
||||||
private readonly MistoxServer _parent;
|
private readonly MistoxServer _parent;
|
||||||
@@ -26,7 +26,6 @@ namespace Mistox.Server {
|
|||||||
_client.NoDelay = true;
|
_client.NoDelay = true;
|
||||||
_client.ReceiveTimeout = _parent.ConnectionTimeoutSeconds * 1000;
|
_client.ReceiveTimeout = _parent.ConnectionTimeoutSeconds * 1000;
|
||||||
|
|
||||||
_parent.InvokeUserConnected( this );
|
|
||||||
// Start the receive thread
|
// Start the receive thread
|
||||||
Task.Run(ReceiveThread);
|
Task.Run(ReceiveThread);
|
||||||
}
|
}
|
||||||
@@ -56,10 +55,11 @@ namespace Mistox.Server {
|
|||||||
await ns.ReadExactlyAsync(payloadBuffer, 0, Header.DataLength, _cts.Token);
|
await ns.ReadExactlyAsync(payloadBuffer, 0, Header.DataLength, _cts.Token);
|
||||||
|
|
||||||
// Create the dataobject
|
// Create the dataobject
|
||||||
dynamic dataobject = DataPacket.Deserialize( Header, payloadBuffer.AsMemory(DataPacket.HeaderLength) );
|
object? dataobject = DataPacket.Deserialize( Header, payloadBuffer.AsMemory(DataPacket.HeaderLength) );
|
||||||
|
if (dataobject != null) {
|
||||||
// Return the object
|
// Return the object
|
||||||
_parent.InvokeDataReceived(this, dataobject);
|
_parent.InvokeDataReceived(this, dataobject);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -82,18 +82,18 @@ namespace Mistox.Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IPAddress getRemote() {
|
||||||
|
return _remoteAddress.Address;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
if (!Connected) return;
|
|
||||||
|
|
||||||
Connected = false;
|
Connected = false;
|
||||||
_cts.Cancel();
|
_cts.Cancel();
|
||||||
_client.Close();
|
_client.Close();
|
||||||
_sendLock.Dispose();
|
_sendLock.Dispose();
|
||||||
_cts.Dispose();
|
_cts.Dispose();
|
||||||
|
|
||||||
_parent.Connections.TryRemove(UserName, out _);
|
|
||||||
_parent.InvokeUserDisconnected(this);
|
_parent.InvokeUserDisconnected(this);
|
||||||
_parent.Alive = false;
|
|
||||||
// Remove this item from the Dictionary on the Server for garbage collection
|
// Remove this item from the Dictionary on the Server for garbage collection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,17 +28,19 @@ namespace Mistox.Server {
|
|||||||
TcpClient client = await _Listener.AcceptTcpClientAsync();
|
TcpClient client = await _Listener.AcceptTcpClientAsync();
|
||||||
|
|
||||||
if (_parent.Connections.Count + 1 > _parent.MaxClients) {
|
if (_parent.Connections.Count + 1 > _parent.MaxClients) {
|
||||||
|
//client.Client.SendAsync(); // Send the client a packet saying the servername and reject reason 'server full'
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
IPEndPoint remote = client.Client.RemoteEndPoint as IPEndPoint;
|
IPEndPoint? remote = client.Client.RemoteEndPoint as IPEndPoint;
|
||||||
|
if (remote != null) {
|
||||||
|
string connectionID = Guid.NewGuid().ToString();
|
||||||
|
_parent.Connections[connectionID] = new ServerConnection( client, _parent, remote) {
|
||||||
|
UserName = connectionID
|
||||||
|
};
|
||||||
|
|
||||||
string connectionID = Guid.NewGuid().ToString();
|
Console.WriteLine( "New User Connected" );
|
||||||
_parent.Connections[connectionID] = new ServerConnection( client, _parent, remote) {
|
}
|
||||||
UserName = connectionID
|
|
||||||
};
|
|
||||||
|
|
||||||
Console.WriteLine( "New User Connected" );
|
|
||||||
}
|
}
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ namespace Mistox {
|
|||||||
return writer.WrittenSpan.ToArray();
|
return writer.WrittenSpan.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static object Deserialize( DataPacketHeader Header, ReadOnlyMemory<byte> Data ) {
|
public static object? Deserialize( DataPacketHeader Header, ReadOnlyMemory<byte> Data ) {
|
||||||
|
|
||||||
// Get the type from the tag
|
// Get the type from the tag
|
||||||
if (!NetworkPacketDictionary.PacketLookupTable.TryGetValue(Header.Type, out Type resolvedType)) {
|
if (!NetworkPacketDictionary.PacketLookupTable.TryGetValue(Header.Type, out Type? resolvedType)) {
|
||||||
throw new Exception($"Unknown packet type ID: {Header.Type}");
|
throw new Exception($"Unknown packet type ID: {Header.Type}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Mistox.Attributes {
|
|||||||
foreach (Assembly assembly in assemblies) {
|
foreach (Assembly assembly in assemblies) {
|
||||||
Type[] allClasses = assembly.GetTypes();
|
Type[] allClasses = assembly.GetTypes();
|
||||||
foreach( Type cur in allClasses) {
|
foreach( Type cur in allClasses) {
|
||||||
mPacket attribute = cur.GetCustomAttribute<mPacket>(false);
|
mPacket? attribute = cur.GetCustomAttribute<mPacket>(false);
|
||||||
if (attribute != null) {
|
if (attribute != null) {
|
||||||
|
|
||||||
// Generate a deterministic 16-bit ID based on the Type's FullName
|
// Generate a deterministic 16-bit ID based on the Type's FullName
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Mistox.Attributes;
|
using Mistox.Attributes;
|
||||||
@@ -24,33 +25,31 @@ namespace Mistox.ResevedPackets {
|
|||||||
|
|
||||||
// Tag to make sure the object on the other side is the same object i want to update here
|
// Tag to make sure the object on the other side is the same object i want to update here
|
||||||
// Need to map these Object ID's to a dictionary to make sure I only send network updates when there is a change
|
// Need to map these Object ID's to a dictionary to make sure I only send network updates when there is a change
|
||||||
public ushort ObjectId { get; set; }
|
public ushort ObjectId { get; set; } = 0;
|
||||||
|
|
||||||
// Packet payload data that im matching for in the dictionary
|
// Packet payload data that im matching for in the dictionary
|
||||||
public byte[] InnerPayload { get; set; }
|
public byte[] InnerPayload { get; set; } = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not implemented
|
public class UserTableObject {
|
||||||
// Packet sent to all clients when a users TCP stream connects to the server
|
public string UserName { get; set; } = string.Empty;
|
||||||
[mPacket]
|
public IPAddress IpAddress { get; set; } = new([]);
|
||||||
public class UserConnectedPacket {
|
public Vector3 Position { get; set; } = new();
|
||||||
public string UserName { get; set; }
|
|
||||||
public IPAddress ClientIPAddress { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not implemented
|
// Not implemented
|
||||||
// Packet sent to all clients when a global position update occurrs
|
// Packet sent to all clients when a global position update occurrs
|
||||||
[mPacket]
|
[mPacket]
|
||||||
public class UserPositionUpdatePacket {
|
public class HeartbeatPacket {
|
||||||
public string UserName { get; set; }
|
public Vector3 Position { get; set; } = new();
|
||||||
public Vector3 Position { get; set; }
|
public List<UserTableObject> ServerList { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not implimented
|
// Not implimented
|
||||||
// Packet sent to all clients when a users TCP stream disconnects from the server
|
// Packet sent to all clients when a users TCP stream disconnects from the server
|
||||||
[mPacket]
|
[mPacket]
|
||||||
public class UserDisconnectedPacket {
|
public class UserDisconnectedPacket {
|
||||||
public string UserName { get; set; }
|
public string UserName { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,46 +1,22 @@
|
|||||||
The server receive thread needs to push the user it was received from up the chain
|
The server receive thread needs to push the user it was received from up the chain
|
||||||
|
|
||||||
1. Allow the server to setup a server name Done
|
1. Allow the server to setup a server name Done
|
||||||
2. Allow the client to setup a user name Done
|
2. Allow the client to setup a user name Done
|
||||||
3. When a user connects add them to the Connections dictionary with a GUID. Done
|
3. When a user connects add them to the Connections dictionary with a GUID. Done
|
||||||
3. Have the client send the server a welcome packet Done
|
4. Have the client send the server a welcome packet Done
|
||||||
4. Have the server use the welcome packet to fillout the user information on the server Done
|
5. Have the server use the welcome packet to fillout the user information on the server Done
|
||||||
5. Respond to the client from the server with a server list of UDPEndpoints
|
6. Send the server the heartbeat location update every 5 seconds Done
|
||||||
6. Instead of looping through a list of avalible users have a local user dictionary that can be used to lookup clients IPEndPoints
|
7. Respond to the client with a server list of UDPEndpoints and heartbeat locations Done
|
||||||
|
8. Each client can figure out what connections it needs to make or drop via the list Done
|
||||||
|
10. When the TCP connection drops update the heartbeat list so all the clients remove the connection Done
|
||||||
|
11. Build a network cache table on the clients so obj's only send updates and not repeat packets
|
||||||
|
12. User joined and left events are not implimented
|
||||||
|
|
||||||
|
|
||||||
|
Set the network bubble distance [ render distance ] on the server and pass to the clients
|
||||||
|
Send a packet back to the client for if the lobby is full, notify them of why the connection failed
|
||||||
|
|
||||||
Test that the datapacket is formatted correctly. The packet size might be off from the actual packet size
|
Test that the datapacket is formatted correctly. The packet size might be off from the actual packet size
|
||||||
|
|
||||||
Have each client update the ingame location to the server every 5 seconds [ Heartbeat ]
|
1. If the TCP connection closes not started
|
||||||
For each user in the server calculate which other players are close enough for a connection
|
|
||||||
So the server needs to hold a table of what clients a client is already connected to
|
|
||||||
if a new player is now close enough update the client and the servers client table with that information
|
|
||||||
have the clients udp together
|
|
||||||
if a player goes out of the range update the clients that they no longer need the connection
|
|
||||||
drop the player from the table and notify the end user they can close the connection
|
|
||||||
|
|
||||||
When user a joins a server
|
|
||||||
The IP gets added to the servers master list
|
|
||||||
The user needs to authenticate for username
|
|
||||||
|
|
||||||
when the server connection closes
|
|
||||||
Notify all clients to drop the connections
|
|
||||||
Remove the user from the server table
|
|
||||||
|
|
||||||
Use the Server TCP connection to notify all clients when a user disconnects
|
|
||||||
|
|
||||||
Only send updates across the network. Dont send same state events
|
|
||||||
Need to setup a network buffer for Tagged Objects. ID
|
|
||||||
|
|
||||||
|
|
||||||
Connection Flow:
|
|
||||||
1. Connection to server is made working
|
|
||||||
Server object is created working
|
|
||||||
ServerConnection gets added to the Connections table working
|
|
||||||
Server fires user connected event working
|
|
||||||
|
|
||||||
2. User sends an Auth Packet to server working
|
|
||||||
Server responds with Auth sucess or fail and marks the users auth status in the server table. needs work
|
|
||||||
After sucessfull auth share the users IPEndPoint with close connections not started
|
|
||||||
|
|
||||||
3. If the TCP connection closes not started
|
|
||||||
Disconnect the user and tell all clients that the user is gone not started
|
Disconnect the user and tell all clients that the user is gone not started
|
||||||
Reference in new issue
Block a user