242 lines
9.3 KiB
C#
242 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Numerics;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MessagePack.Formatters;
|
|
using Mistox.ResevedPackets;
|
|
|
|
namespace Mistox.Client {
|
|
|
|
public class MistoxClient {
|
|
|
|
public string ServerName { get; private set; } = ""; // The Name of the server
|
|
public string UserName { get; private set; } = ""; // My UserName
|
|
public bool Alive { get; set; } = true; // Is the server running
|
|
public bool isAuthenticated { get; private set; } = false; // Is the server authenticated
|
|
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 UdpClient ClientSocket { get; private set; } // Global UDP Sender
|
|
public UDPReceiver UdpReceiver { get; private set; } // Global UDP Receiver
|
|
|
|
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) {
|
|
// Create and listen on UDP
|
|
Port = Server.Port;
|
|
ClientSocket = new UdpClient( Server.Port );
|
|
UdpReceiver = new UDPReceiver( this );
|
|
UserName = userName;
|
|
ConnectionKey = connectionKey;
|
|
|
|
onServerConnected += () => {};
|
|
onUserConnected += (_) => {};
|
|
onUserJoined += (_) => {};
|
|
onDataReceived += (_,_) => {};
|
|
onUserLeft += (_) => {};
|
|
onUserDisconnected += (_) => {};
|
|
onServerDisconnected += () => {};
|
|
|
|
// Create and connect to server on TCP
|
|
Console.WriteLine( "The client is initilized and trying to connect to the server at ip : " + Server.Address );
|
|
try {
|
|
ServerConnection = new mTCPClient( this, Server );
|
|
Console.WriteLine( "Connected to the server sucessfully" );
|
|
Alive = true;
|
|
} catch ( Exception e ) {
|
|
Console.WriteLine( "An error has occured with the connection to the server. Error { " );
|
|
Console.WriteLine( e.ToString() );
|
|
Console.WriteLine( "}" );
|
|
Alive = false;
|
|
}
|
|
|
|
// Start the heartbeat
|
|
_ = RunAsync(Heartbeat);
|
|
}
|
|
|
|
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...");
|
|
if (ServerConnection == null) return;
|
|
await ServerConnection.Send(new UserAuthPacket {
|
|
UserName = UserName,
|
|
ConnectionKey = ConnectionKey
|
|
});
|
|
}
|
|
|
|
async Task CheckAuthStatus(UserAuthPacket packet) {
|
|
if (packet.AuthSuccessful == 1) {
|
|
isAuthenticated = true;
|
|
}
|
|
ServerName = packet.ServerName;
|
|
}
|
|
|
|
/////////////////////////////
|
|
// Events
|
|
/////////////////////////////
|
|
|
|
// When (self) connects to the server
|
|
public event Action onServerConnected;
|
|
public void InvokeOnServerConnected(){
|
|
Alive = true;
|
|
Task.Run(async () => {
|
|
try {
|
|
await TryAuth();
|
|
} catch {}
|
|
});
|
|
onServerConnected?.Invoke();
|
|
}
|
|
|
|
// When any user connects to the server (self) hasn't seen yet
|
|
public event Action<mUDPClient> onUserJoined;
|
|
public void InvokeOnUserJoined(mUDPClient User){
|
|
onUserJoined?.Invoke(User);
|
|
}
|
|
|
|
// When user is within range of (self) and network rendering starts
|
|
public event Action<mUDPClient> onUserConnected;
|
|
public void InvokeOnUserConnected(mUDPClient User){
|
|
onUserConnected?.Invoke(User);
|
|
}
|
|
|
|
// 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(){
|
|
Alive = false;
|
|
onServerDisconnected?.Invoke();
|
|
}
|
|
|
|
/////////////////////////////
|
|
// Send Methods
|
|
/////////////////////////////
|
|
|
|
public async Task SendAllClientsInRange<Packet>(Packet data) {
|
|
foreach( mUDPClient cur in clientConnections.Values ){
|
|
if (cur.WithinRange) {
|
|
await cur.Send( data );
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task SendClient<Packet>(string UserName, Packet data) {
|
|
foreach( mUDPClient cur in clientConnections.Values ) {
|
|
if (cur.UserName.ToLower() == UserName.ToLower()) {
|
|
await cur.Send( data );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task SendServer<Packet>(Packet data) {
|
|
if (ServerConnection == null) return;
|
|
await ServerConnection.Send( data );
|
|
}
|
|
}
|
|
|
|
}
|