Files
GameServer/MistoxServer/Client/MistoxClient.cs
T

237 lines
9.1 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 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;
}
}
// Remove users that left
foreach (IPEndPoint key in clientConnections.Keys) {
if (!activeIps.Contains(key.Address)) {
clientConnections.TryRemove(key, out _);
}
}
// 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<string> onUserJoined;
public void InvokeOnUserJoined(string UserName){
onUserJoined?.Invoke(UserName);
}
// When user is within range of (self) and network rendering starts
public event Action<mUDPClient> onUserConnected;
public void InvokeOnUserConnected(mUDPClient ConnectedUser){
onUserConnected?.Invoke(ConnectedUser);
}
// 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 DisconnectedUser){
onUserDisconnected?.Invoke(DisconnectedUser);
}
// When any user disconnects from the server that isn't (self)
public event Action<string> onUserLeft;
public void InvokeOnUserLeft(string UserName){
onUserLeft?.Invoke(UserName);
}
// 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 );
}
}
}