using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Lidgren.Network; using RageCoop.Core; using RageCoop.Core.Scripting; using System.Reflection; using System.Net; namespace RageCoop.Server.Scripting { /// /// /// public class ServerEvents { private readonly Server Server; internal ServerEvents(Server server) { Server = server; } #region INTERNAL internal Dictionary>> CustomEventHandlers = new(); #endregion /// /// 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, Sender=sender }; OnCommandReceived?.Invoke(this, args); if (args.Cancel) { return; } if (Server.Commands.Any(x => x.Key.Name == cmdName)) { string[] argsWithoutCmd = cmdArgs.Skip(1).ToArray(); CommandContext ctx = new() { Client = sender, Args = argsWithoutCmd }; KeyValuePair> command = Server.Commands.First(x => x.Key.Name == cmdName); command.Value.Invoke(ctx); } else { Server.SendChatMessage("Server", "Command not found!", sender.Connection); } } internal void InvokeOnChatMessage(Packets.ChatMessage p, Client sender) { OnChatMessage?.Invoke(this, new ChatEventArgs() { Sender=sender, Message=p.Message }); } 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, Sender=sender }; List> handlers; if (CustomEventHandlers.TryGetValue(p.Hash, out 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 { internal readonly Server Server; internal API(Server server) { Server=server; Events=new(server); } /// /// Server side events /// public readonly ServerEvents Events; /// /// All synchronized entities on this server. /// public ServerEntities Entities { get { return Server.Entities; } } #region FUNCTIONS /// /// Get a list of all Clients /// /// All clients as a dictionary indexed by NetID public Dictionary GetAllClients() { return new(Server.ClientsByName); } /// /// 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) { return Server.Clients.Values.FirstOrDefault(x => x.Username.ToLower() == username.ToLower()); } /// /// 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") public void SendChatMessage(string message, List targets = null, string username = "Server") { try { if (Server.MainNetServer.ConnectionsCount == 0) { return; } targets ??= new(Server.Clients.Values); foreach(Client client in targets) { Server.SendChatMessage(username, message, client.Connection); } } catch (Exception e) { Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); } } /// /// Send CleanUpWorld to all players to delete all objects created by the server /// /// /// 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) { IEnumerable commands = obj.GetType().GetMethods().Where(method => method.GetCustomAttributes(typeof(Command), false).Any()); foreach (MethodInfo method in commands) { Command 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 , GTA.Native.Hash hash, params object[] args) { var argsList = new List(args); argsList.InsertRange(0, new object[] { (byte)TypeCode.Empty, (ulong)hash }); SendCustomEventQueued(clients, CustomEvents.NativeCall, argsList.ToArray()); } /// /// Send an event and data to the specified clients. Use if you want to send event to individual client. /// /// An unique identifier of the event, you can use to get it from a string /// The objects conataing your data, see for supported types. /// The target clients to send. Leave it null to send to all clients public void SendCustomEvent(List targets, int eventHash, params object[] args) { targets ??= new(Server.Clients.Values); var p = new Packets.CustomEvent() { Args=args, Hash=eventHash }; foreach (var c in targets) { Server.Send(p, c, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered); } } /// /// Send a CustomEvent that'll be queued at client side and invoked from script thread /// /// /// /// public void SendCustomEventQueued(List targets, int eventHash, params object[] args) { targets ??= new(Server.Clients.Values); var p = new Packets.CustomEvent(null,true) { Args=args, Hash=eventHash }; foreach (var c in targets) { Server.Send(p, c, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered); } } /// /// Register an handler to the specifed event hash, one event can have multiple handlers. /// /// An unique identifier of the event, you can hash your event name with /// An handler to be invoked when the event is received from the server. public void RegisterCustomEventHandler(int hash,Action handler) { List> handlers; lock (Events.CustomEventHandlers) { if (!Events.CustomEventHandlers.TryGetValue(hash,out handlers)) { Events.CustomEventHandlers.Add(hash, handlers = new List>()); } handlers.Add(handler); } } /// /// Register an event handler for specified event name. /// /// This value will be hashed to an int to reduce overhead /// The handler to be invoked when the event is received public void RegisterCustomEventHandler(string name, Action handler) { RegisterCustomEventHandler(CustomEvents.Hash(name), handler); } /// /// Get a that the server is currently using, you should use to display resource-specific information. /// public Logger Logger { get { return Server.Logger; } } /// /// Gets or sets the client that is resposible for synchronizing time and weather /// public Client Host { get { return Server._hostClient; } set { if (Server._hostClient != value) { Server._hostClient?.SendCustomEvent(CustomEvents.IsHost, false); value.SendCustomEvent(CustomEvents.IsHost, true); Server._hostClient = value; } } } #endregion } }