2021-11-25 16:32:04 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2022-05-22 15:55:26 +08:00
|
|
|
|
using RageCoop.Core;
|
2021-08-26 17:01:32 +02:00
|
|
|
|
using Lidgren.Network;
|
2022-07-29 20:35:39 +08:00
|
|
|
|
using System.Diagnostics;
|
2022-06-19 11:12:20 +08:00
|
|
|
|
using RageCoop.Core.Scripting;
|
2022-06-23 09:46:38 +08:00
|
|
|
|
using System.Security.Cryptography;
|
2022-07-07 16:57:43 +08:00
|
|
|
|
using RageCoop.Server.Scripting;
|
2022-08-06 12:32:04 +08:00
|
|
|
|
using System.Net;
|
2021-08-26 17:01:32 +02:00
|
|
|
|
|
2022-05-22 15:55:26 +08:00
|
|
|
|
namespace RageCoop.Server
|
2021-08-26 17:01:32 +02:00
|
|
|
|
{
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represent a player connected to this server.
|
|
|
|
|
/// </summary>
|
2021-08-26 17:01:32 +02:00
|
|
|
|
public class Client
|
|
|
|
|
{
|
2022-06-23 14:10:16 +08:00
|
|
|
|
private readonly Server Server;
|
|
|
|
|
internal Client(Server server)
|
|
|
|
|
{
|
|
|
|
|
Server=server;
|
|
|
|
|
}
|
2022-07-01 17:00:42 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Th client's IP address and port.
|
|
|
|
|
/// </summary>
|
2022-08-06 12:32:04 +08:00
|
|
|
|
public IPEndPoint EndPoint { get { return Connection?.RemoteEndPoint; } }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Internal(LAN) address of this client, used for NAT hole-punching
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IPEndPoint InternalEndPoint { get; internal set; }
|
|
|
|
|
|
|
|
|
|
internal long NetHandle = 0;
|
2022-07-01 13:54:18 +08:00
|
|
|
|
internal NetConnection Connection { get;set; }
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The <see cref="ServerPed"/> instance representing the client's main character.
|
|
|
|
|
/// </summary>
|
2022-06-22 14:18:20 +08:00
|
|
|
|
public ServerPed Player { get; internal set; }
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// <summary>
|
2022-07-02 11:23:12 +08:00
|
|
|
|
/// The client's latency in seconds.
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// </summary>
|
2022-08-08 17:03:41 +08:00
|
|
|
|
public float Latency => Connection.AverageRoundtripTime/2;
|
2022-06-23 09:46:38 +08:00
|
|
|
|
internal readonly Dictionary<int, Action<object>> Callbacks = new();
|
2022-06-24 10:33:36 +08:00
|
|
|
|
internal byte[] PublicKey { get; set; }
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Indicates whether the client has succefully loaded all resources.
|
|
|
|
|
/// </summary>
|
2022-06-06 17:37:11 +08:00
|
|
|
|
public bool IsReady { get; internal set; }=false;
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// <summary>
|
2022-07-03 10:46:24 +08:00
|
|
|
|
/// The client's username.
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// </summary>
|
2022-06-21 18:13:30 +08:00
|
|
|
|
public string Username { get;internal set; } = "N/A";
|
2022-07-03 10:46:24 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private bool _autoRespawn=true;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets or sets whether to enable automatic respawn for this client's main ped.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool EnableAutoRespawn {
|
|
|
|
|
get { return _autoRespawn; }
|
|
|
|
|
set {
|
2022-07-09 19:32:11 +08:00
|
|
|
|
BaseScript.SetAutoRespawn(this,value);
|
2022-07-03 10:46:24 +08:00
|
|
|
|
_autoRespawn=value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool _displayNameTag=true;
|
2022-07-29 20:35:39 +08:00
|
|
|
|
private Stopwatch _latencyWatch = new Stopwatch();
|
2022-07-03 10:46:24 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets or sets whether to enable automatic respawn for this client's main ped.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool DisplayNameTag
|
|
|
|
|
{
|
|
|
|
|
get { return _displayNameTag; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
Server.BaseScript.SetNameTag(this,value);
|
|
|
|
|
_displayNameTag=value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-25 16:32:04 +01:00
|
|
|
|
#region FUNCTIONS
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Kick this client
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="reason"></param>
|
2022-06-22 14:18:20 +08:00
|
|
|
|
public void Kick(string reason="You have been kicked!")
|
2021-12-11 14:00:22 +01:00
|
|
|
|
{
|
2022-06-04 18:09:42 +08:00
|
|
|
|
Connection?.Disconnect(reason);
|
2021-12-11 14:00:22 +01:00
|
|
|
|
}
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Kick this client
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="reasons">Reasons to kick</param>
|
2022-06-04 18:09:42 +08:00
|
|
|
|
public void Kick(params string[] reasons)
|
2022-04-08 22:30:34 +02:00
|
|
|
|
{
|
2022-06-04 18:09:42 +08:00
|
|
|
|
Kick(string.Join(" ", reasons));
|
2022-04-08 22:30:34 +02:00
|
|
|
|
}
|
2021-08-26 17:01:32 +02:00
|
|
|
|
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Send a chat messsage to this client, not visible to others.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="message"></param>
|
|
|
|
|
/// <param name="from"></param>
|
2021-08-26 17:01:32 +02:00
|
|
|
|
public void SendChatMessage(string message, string from = "Server")
|
|
|
|
|
{
|
2021-11-25 16:32:04 +01:00
|
|
|
|
try
|
2021-08-26 17:01:32 +02:00
|
|
|
|
{
|
2022-07-22 19:43:48 +08:00
|
|
|
|
Server.SendChatMessage(from, message, this);
|
2021-11-25 16:32:04 +01:00
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
2022-06-23 14:10:16 +08:00
|
|
|
|
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
2021-11-25 16:32:04 +01:00
|
|
|
|
}
|
2021-08-26 17:01:32 +02:00
|
|
|
|
}
|
2021-12-10 13:38:03 +01:00
|
|
|
|
|
2022-06-23 09:46:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Send a native call to client and do a callback when the response received.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T">Type of the response</typeparam>
|
|
|
|
|
/// <param name="callBack"></param>
|
|
|
|
|
/// <param name="hash"></param>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
public void SendNativeCall<T>(Action<object> callBack, GTA.Native.Hash hash, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
var argsList= new List<object>(args);
|
|
|
|
|
argsList.InsertRange(0, new object[] { (byte)Type.GetTypeCode(typeof(T)), RequestNativeCallID<T>(callBack), (ulong)hash });
|
|
|
|
|
|
2022-07-05 11:18:26 +08:00
|
|
|
|
SendCustomEventQueued(CustomEvents.NativeCall, argsList.ToArray());
|
2022-06-23 09:46:38 +08:00
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Send a native call to client and ignore it's response.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="hash"></param>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
public void SendNativeCall(GTA.Native.Hash hash, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
var argsList = new List<object>(args);
|
2022-07-05 10:52:22 +08:00
|
|
|
|
argsList.InsertRange(0, new object[] { (byte)TypeCode.Empty,(ulong)hash });
|
2022-06-23 14:10:16 +08:00
|
|
|
|
// Server.Logger?.Debug(argsList.DumpWithType());
|
2022-07-05 11:18:26 +08:00
|
|
|
|
SendCustomEventQueued(CustomEvents.NativeCall, argsList.ToArray());
|
2022-06-23 09:46:38 +08:00
|
|
|
|
}
|
|
|
|
|
private int RequestNativeCallID<T>(Action<object> 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;
|
|
|
|
|
}
|
2022-07-01 13:54:18 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Trigger a CustomEvent for this client
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="hash">An unique identifier of the event, you can use <see cref="CustomEvents.Hash(string)"/> to get it from a string</param>
|
2022-07-02 12:39:50 +08:00
|
|
|
|
/// <param name="args">Arguments</param>
|
2022-07-01 19:02:38 +08:00
|
|
|
|
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,
|
2022-07-05 10:52:22 +08:00
|
|
|
|
Args=args
|
2022-07-01 19:02:38 +08:00
|
|
|
|
}.Pack(outgoingMessage);
|
|
|
|
|
Server.MainNetServer.SendMessage(outgoingMessage, Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Event);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Server.Logger?.Error(ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-05 11:18:26 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Send a CustomEvent that'll be queued at client side and invoked from script thread
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="hash"></param>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-25 16:32:04 +01:00
|
|
|
|
#endregion
|
2021-08-26 17:01:32 +02:00
|
|
|
|
}
|
|
|
|
|
}
|