using Lidgren.Network; using RageCoop.Core; using RageCoop.Core.Scripting; using RageCoop.Server.Scripting; using System; using System.Diagnostics; using System.Net; using System.Security.Cryptography; namespace RageCoop.Server { /// /// Represent a player connected to this server. /// public class Client { private readonly Server Server; internal Client(Server server) { Server = server; } /// /// Gets the total number of entities owned by this client /// public int EntitiesCount { get; internal set; } /// /// Th client's IP address and port. /// public IPEndPoint EndPoint => 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 => _autoRespawn; set { BaseScript.SetAutoRespawn(this, value); _autoRespawn = value; } } private bool _displayNameTag = true; private readonly Stopwatch _latencyWatch = new Stopwatch(); /// /// Gets or sets whether to enable automatic respawn for this client's main ped. /// public bool DisplayNameTag { get => _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 /// Arguments public void SendCustomEvent(CustomEventFlags flags, CustomEventHash hash, params object[] args) { if (!IsReady) { Server.Logger?.Warning($"Player \"{Username}\" is not ready!"); } try { NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); new Packets.CustomEvent(flags) { Hash = hash, Args = args }.Pack(outgoingMessage); Server.MainNetServer.SendMessage(outgoingMessage, Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Event); } catch (Exception ex) { Server.Logger?.Error(ex); } } public void SendCustomEventQueued(CustomEventHash hash, params object[] args) { SendCustomEvent(CustomEventFlags.Queued, hash, args); } public void SendCustomEvent(CustomEventHash hash, params object[] args) { SendCustomEvent(CustomEventFlags.None, hash, args); } #endregion } }