using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting;
namespace RageCoop.Server.Scripting;
///
///
public class ServerEvents
{
private readonly Server Server;
#region INTERNAL
internal Dictionary>> CustomEventHandlers = new();
#endregion
internal ServerEvents(Server server)
{
Server = server;
}
///
/// Invoked when a chat message is received.
///
public event EventHandler OnChatMessage;
///
/// Will be invoked from main thread before registered handlers
///
public event EventHandler OnCommandReceived;
///
/// Will be invoked from main thread when a client is attempting to connect, use
/// to deny the connection request.
///
public event EventHandler OnPlayerHandshake;
///
/// Will be invoked when a player is connected, but this player might not be ready yet(client resources not loaded),
/// using is recommended.
///
public event EventHandler OnPlayerConnected;
///
/// Will be invoked after the client connected and all resources(if any) have been loaded.
///
public event EventHandler OnPlayerReady;
///
/// Invoked when a player disconnected, all method won't be effective in this scope.
///
public event EventHandler OnPlayerDisconnected;
///
/// Invoked everytime a player's main ped has been updated
///
public event EventHandler OnPlayerUpdate;
internal void ClearHandlers()
{
OnChatMessage = null;
OnPlayerHandshake = null;
OnPlayerConnected = null;
OnPlayerReady = null;
OnPlayerDisconnected = null;
// OnCustomEventReceived=null;
OnCommandReceived = null;
OnPlayerUpdate = null;
}
#region INVOKE
internal void InvokePlayerHandshake(HandshakeEventArgs args)
{
OnPlayerHandshake?.Invoke(this, args);
}
internal void InvokeOnCommandReceived(string cmdName, string[] cmdArgs, Client sender)
{
var args = new OnCommandEventArgs
{
Name = cmdName,
Args = cmdArgs,
Client = sender
};
OnCommandReceived?.Invoke(this, args);
if (args.Cancel) return;
if (Server.Commands.Any(x => x.Key.Name == cmdName))
{
var argsWithoutCmd = cmdArgs.Skip(1).ToArray();
CommandContext ctx = new()
{
Client = sender,
Args = argsWithoutCmd
};
var command = Server.Commands.First(x => x.Key.Name == cmdName);
command.Value.Invoke(ctx);
}
else
{
Server.SendChatMessage("Server", "Command not found!", sender);
}
}
internal void InvokeOnChatMessage(string msg, Client sender, string clamiedSender = null)
{
OnChatMessage?.Invoke(this, new ChatEventArgs
{
Client = sender,
Message = msg,
ClaimedSender = clamiedSender
});
}
internal void InvokePlayerConnected(Client client)
{
OnPlayerConnected?.Invoke(this, client);
}
internal void InvokePlayerReady(Client client)
{
OnPlayerReady?.Invoke(this, client);
}
internal void InvokePlayerDisconnected(Client client)
{
OnPlayerDisconnected?.Invoke(this, client);
}
internal void InvokeCustomEventReceived(Packets.CustomEvent p, Client sender)
{
var args = new CustomEventReceivedArgs { Hash = p.Hash, Args = p.Args, Client = sender };
if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers)) handlers.ForEach(x => { x.Invoke(args); });
}
internal void InvokePlayerUpdate(Client client)
{
OnPlayerUpdate?.Invoke(this, client);
}
#endregion
}
///
/// An class that can be used to interact with RageCoop server.
///
public class API
{
///
/// Server side events
///
public readonly ServerEvents Events;
internal readonly Dictionary> RegisteredFiles = new();
internal readonly Server Server;
internal API(Server server)
{
Server = server;
Events = new ServerEvents(server);
Server.RequestHandlers.Add(PacketType.FileTransferRequest, (data, client) =>
{
var p = new Packets.FileTransferRequest();
p.Deserialize(data);
var id = Server.NewFileID();
if (RegisteredFiles.TryGetValue(p.Name, out var s))
{
Task.Run(() => { Server.SendFile(s(), p.Name, client, id); });
return new Packets.FileTransferResponse
{
ID = id,
Response = FileResponse.Loaded
};
}
return new Packets.FileTransferResponse
{
ID = id,
Response = FileResponse.LoadFailed
};
});
}
///
/// All synchronized entities on this server.
///
public ServerEntities Entities => Server.Entities;
#region FUNCTIONS
///
/// Get a list of all Clients
///
/// All clients as a dictionary indexed by their main character's id
public Dictionary GetAllClients()
{
return new Dictionary(Server.ClientsByID);
}
///
/// Get the client by its username
///
/// The username to search for (non case-sensitive)
/// The Client from this user or null
public Client GetClientByUsername(string username)
{
Server.ClientsByName.TryGetValue(username, out var c);
return c;
}
///
/// Send a chat message to all players, use to send to an
/// individual client.
///
/// The clients to send message, leave it null to send to all clients
/// The chat message
/// The username which send this message (default = "Server")
///
/// Weather to raise the event defined in
///
///
///
/// When is unspecified and is null or unspecified,
/// will be set to true
///
public void SendChatMessage(string message, List targets = null, string username = "Server",
bool? raiseEvent = null)
{
raiseEvent ??= targets == null;
try
{
if (Server.MainNetServer.ConnectionsCount != 0)
{
targets ??= new List(Server.ClientsByNetHandle.Values);
foreach (var client in targets) Server.SendChatMessage(username, message, client);
}
}
catch (Exception e)
{
Server.Logger?.Error(
$">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
if (raiseEvent.Value) Events.InvokeOnChatMessage(message, null, username);
}
///
/// Register a file to be shared with clients
///
/// name of this file
/// path to this file
public void RegisterSharedFile(string name, string path)
{
RegisteredFiles.Add(name, () => { return File.OpenRead(path); });
}
///
/// Register a file to be shared with clients
///
/// name of this file
///
public void RegisterSharedFile(string name, ResourceFile file)
{
RegisteredFiles.Add(name, file.GetStream);
}
///
/// Register a new command chat command (Example: "/test")
///
/// The name of the command (Example: "test" for "/test")
/// How to use this message (argsLength required!)
/// The length of args (Example: "/message USERNAME MESSAGE" = 2) (usage required!)
/// A callback to invoke when the command received.
public void RegisterCommand(string name, string usage, short argsLength, Action callback)
{
Server.RegisterCommand(name, usage, argsLength, callback);
}
///
/// Register a new command chat command (Example: "/test")
///
/// The name of the command (Example: "test" for "/test")
/// A callback to invoke when the command received.
public void RegisterCommand(string name, Action callback)
{
Server.RegisterCommand(name, callback);
}
///
/// Register all commands in a static class
///
/// Your static class with commands
public void RegisterCommands()
{
Server.RegisterCommands();
}
///
/// Register all commands inside an class instance
///
/// The instance of type containing the commands
public void RegisterCommands(object obj)
{
var commands = obj.GetType().GetMethods()
.Where(method => method.GetCustomAttributes(typeof(Command), false).Any());
foreach (var method in commands)
{
var attribute = method.GetCustomAttribute(true);
RegisterCommand(attribute.Name, attribute.Usage, attribute.ArgsLength,
ctx => { method.Invoke(obj, new object[] { ctx }); });
}
}
///
/// Send native call specified clients.
///
///
///
/// ///
/// Clients to send, null for all clients
public void SendNativeCall(List clients, Hash hash, params object[] args)
{
var argsList = new List