using System; using System.Collections.Generic; using RageCoop.Core; using Lidgren.Network; using System.Diagnostics; using RageCoop.Core.Scripting; using System.Security.Cryptography; using RageCoop.Server.Scripting; using System.Net; namespace RageCoop.Server { /// /// Represent a player connected to this server. /// public class Client { private readonly Server Server; internal Client(Server server) { Server=server; } /// /// Th client's IP address and port. /// public IPEndPoint EndPoint { get { return Connection?.RemoteEndPoint; } } /// /// Internal(LAN) address of this client, used for NAT hole-punching /// public IPEndPoint InternalEndPoint { get; internal set; } internal long NetHandle = 0; internal NetConnection Connection { get;set; } /// /// The instance representing the client's main character. /// public ServerPed Player { get; internal set; } /// /// The client's latency in seconds. /// public float Latency => Connection.AverageRoundtripTime/2; internal readonly Dictionary> Callbacks = new(); internal byte[] PublicKey { get; set; } /// /// Indicates whether the client has succefully loaded all resources. /// public bool IsReady { get; internal set; }=false; /// /// The client's username. /// public string Username { get;internal set; } = "N/A"; private bool _autoRespawn=true; /// /// Gets or sets whether to enable automatic respawn for this client's main ped. /// public bool EnableAutoRespawn { get { return _autoRespawn; } set { BaseScript.SetAutoRespawn(this,value); _autoRespawn=value; } } private bool _displayNameTag=true; private Stopwatch _latencyWatch = new Stopwatch(); /// /// Gets or sets whether to enable automatic respawn for this client's main ped. /// public bool DisplayNameTag { get { return _displayNameTag; } set { Server.BaseScript.SetNameTag(this,value); _displayNameTag=value; } } #region FUNCTIONS /// /// Kick this client /// /// public void Kick(string reason="You have been kicked!") { Connection?.Disconnect(reason); } /// /// Kick this client /// /// Reasons to kick public void Kick(params string[] reasons) { Kick(string.Join(" ", reasons)); } /// /// Send a chat messsage to this client, not visible to others. /// /// /// public void SendChatMessage(string message, string from = "Server") { try { Server.SendChatMessage(from, message, this); } catch (Exception e) { Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); } } /// /// Send a native call to client and do a callback when the response received. /// /// Type of the response /// /// /// public void SendNativeCall(Action callBack, GTA.Native.Hash hash, params object[] args) { var argsList= new List(args); argsList.InsertRange(0, new object[] { (byte)Type.GetTypeCode(typeof(T)), RequestNativeCallID(callBack), (ulong)hash }); SendCustomEventQueued(CustomEvents.NativeCall, argsList.ToArray()); } /// /// Send a native call to client and ignore it's response. /// /// /// public void SendNativeCall(GTA.Native.Hash hash, params object[] args) { var argsList = new List(args); argsList.InsertRange(0, new object[] { (byte)TypeCode.Empty,(ulong)hash }); // Server.Logger?.Debug(argsList.DumpWithType()); SendCustomEventQueued(CustomEvents.NativeCall, argsList.ToArray()); } private int RequestNativeCallID(Action callback) { int ID = 0; lock (Callbacks) { while ((ID==0) || Callbacks.ContainsKey(ID)) { byte[] rngBytes = new byte[4]; RandomNumberGenerator.Create().GetBytes(rngBytes); // Convert the bytes into an integer ID = BitConverter.ToInt32(rngBytes, 0); } Callbacks.Add(ID, callback); } return ID; } /// /// Trigger a CustomEvent for this client /// /// An unique identifier of the event, you can use to get it from a string /// Arguments public void SendCustomEvent(int hash,params object[] args) { if (!IsReady) { Server.Logger?.Warning($"Player \"{Username}\" is not ready!"); } try { NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); new Packets.CustomEvent() { Hash=hash, Args=args }.Pack(outgoingMessage); Server.MainNetServer.SendMessage(outgoingMessage, Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Event); } catch (Exception ex) { Server.Logger?.Error(ex); } } /// /// Send a CustomEvent that'll be queued at client side and invoked from script thread /// /// /// public void SendCustomEventQueued(int hash, params object[] args) { if (!IsReady) { Server.Logger?.Warning($"Player \"{Username}\" is not ready!"); } try { NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); new Packets.CustomEvent(null,true) { Hash=hash, Args=args }.Pack(outgoingMessage); Server.MainNetServer.SendMessage(outgoingMessage, Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Event); } catch (Exception ex) { Server.Logger?.Error(ex); } } #endregion } }