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 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 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(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 onUserJoined; public void InvokeOnUserJoined(string UserName){ onUserJoined?.Invoke(UserName); } // When user is within range of (self) and network rendering starts public event Action onUserConnected; public void InvokeOnUserConnected(mUDPClient ConnectedUser){ onUserConnected?.Invoke(ConnectedUser); } // When generic data is received public event Action 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 onUserDisconnected; public void InvokeOnUserDisconnected(mUDPClient DisconnectedUser){ onUserDisconnected?.Invoke(DisconnectedUser); } // When any user disconnects from the server that isn't (self) public event Action 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 data) { foreach( mUDPClient cur in clientConnections.Values ){ if (cur.WithinRange) { await cur.Send( data ); } } } public async Task SendClient(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 data) { if (ServerConnection == null) return; await ServerConnection.Send( data ); } } }