using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using Lidgren.Network; namespace CoopServer { public abstract class ServerScript { public API API { get; } = new(); } internal class Resource { public bool ReadyToStop = false; private static Thread _mainThread; private static Queue _actionQueue; private static ServerScript _script; public Resource(ServerScript script) { _actionQueue = Queue.Synchronized(new Queue()); _mainThread = new(ThreadLoop) { IsBackground = true }; _mainThread.Start(); lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(() => { _script = script; _script.API.InvokeStart(); }); } } private void ThreadLoop() { while (!Program.ReadyToStop) { Queue localQueue; lock (_actionQueue.SyncRoot) { localQueue = new(_actionQueue); _actionQueue.Clear(); } while (localQueue.Count > 0) { (localQueue.Dequeue() as Action)?.Invoke(); } // 16 milliseconds to sleep to reduce CPU usage Thread.Sleep(1000 / 60); } _script.API.InvokeStop(); ReadyToStop = true; } public bool InvokeModPacketReceived(long from, long target, string modName, byte customID, byte[] bytes) { Task task = new(() => _script.API.InvokeModPacketReceived(from, target, modName, customID, bytes)); task.Start(); task.Wait(5000); return task.Result; } public void InvokePlayerHandshake(Client client) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerHandshake(client))); } } public void InvokePlayerConnected(Client client) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerConnected(client))); } } public void InvokePlayerDisconnected(Client client) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerDisconnected(client))); } } public bool InvokeChatMessage(string username, string message) { Task task = new(() => _script.API.InvokeChatMessage(username, message)); task.Start(); task.Wait(5000); return task.Result; } public void InvokePlayerPositionUpdate(string username) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerPositionUpdate(username))); } } public void InvokePlayerUpdate(Client client) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerUpdate(client))); } } public void InvokePlayerHealthUpdate(string username) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerHealthUpdate(username))); } } public void InvokePlayerPedHandleUpdate(string username) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerPedHandleUpdate(username))); } } public void InvokePlayerVehicleHandleUpdate(string username) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerVehicleHandleUpdate(username))); } } public void InvokeTick(long tick) { lock (_actionQueue.SyncRoot) { _actionQueue.Enqueue(new Action(() => _script.API.InvokeTick(tick))); } } } public class API { #region DELEGATES public delegate void EmptyEvent(); public delegate void OnTickEvent(long tick); public delegate void ChatEvent(string username, string message, CancelEventArgs cancel); public delegate void PlayerEvent(Client client); public delegate void ModEvent(long from, long target, string modName, byte customID, byte[] bytes, CancelEventArgs args); #endregion #region EVENTS /// /// Called every tick /// public event OnTickEvent OnTick; /// /// Called when the server has started /// public event EmptyEvent OnStart; /// /// Called when the server has stopped /// public event EmptyEvent OnStop; /// /// Called when the server receives a new chat message for players /// public event ChatEvent OnChatMessage; /// /// Called when the server receives a new incoming connection /// public event PlayerEvent OnPlayerHandshake; /// /// Called when a new player has successfully connected /// public event PlayerEvent OnPlayerConnected; /// /// Called when a new player has successfully disconnected /// public event PlayerEvent OnPlayerDisconnected; /// /// Called when a new player sends data like health /// public event PlayerEvent OnPlayerUpdate; /// /// Called when a player has a new health value /// public event PlayerEvent OnPlayerHealthUpdate; /// /// Called when a player has a new position /// public event PlayerEvent OnPlayerPositionUpdate; /// /// Called when a player has a new position /// public event PlayerEvent OnPlayerPedHandleUpdate; /// /// Called when a player has a new position /// public event PlayerEvent OnPlayerVehicleHandleUpdate; /// /// Called when a player sends a packet from another modification /// public event ModEvent OnModPacketReceived; internal void InvokeTick(long tick) { OnTick?.Invoke(tick); } internal void InvokeStart() { OnStart?.Invoke(); } internal void InvokeStop() { OnStop?.Invoke(); } internal void InvokePlayerHandshake(Client client) { OnPlayerHandshake?.Invoke(client); } internal void InvokePlayerConnected(Client client) { OnPlayerConnected?.Invoke(client); } internal void InvokePlayerDisconnected(Client client) { OnPlayerDisconnected?.Invoke(client); } internal void InvokePlayerUpdate(Client client) { OnPlayerUpdate?.Invoke(client); } internal void InvokePlayerHealthUpdate(string username) { OnPlayerHealthUpdate?.Invoke(Server.Clients.FirstOrDefault(x => x.Player.Username == username)); } internal bool InvokeChatMessage(string username, string message) { CancelEventArgs args = new(false); OnChatMessage?.Invoke(username, message, args); return args.Cancel; } internal void InvokePlayerPositionUpdate(string username) { OnPlayerPositionUpdate?.Invoke(Server.Clients.FirstOrDefault(x => x.Player.Username == username)); } internal void InvokePlayerPedHandleUpdate(string username) { OnPlayerPedHandleUpdate?.Invoke(Server.Clients.FirstOrDefault(x => x.Player.Username == username)); } internal void InvokePlayerVehicleHandleUpdate(string username) { OnPlayerVehicleHandleUpdate?.Invoke(Server.Clients.FirstOrDefault(x => x.Player.Username == username)); } internal bool InvokeModPacketReceived(long from, long target, string modName, byte customID, byte[] bytes) { CancelEventArgs args = new(false); OnModPacketReceived?.Invoke(from, target, modName, customID, bytes, args); return args.Cancel; } #endregion #region FUNCTIONS /// /// Send a mod packet to all players /// /// The name of the modification that will receive the data /// The ID to check what this data is /// The serialized data /// The list of connections (players) that will receive the data public static void SendModPacketToAll(string modName, byte customID, byte[] bytes, List netHandleList = null) { try { List connections = netHandleList == null ? Server.MainNetServer.Connections : Server.MainNetServer.Connections.FindAll(c => netHandleList.Contains(c.RemoteUniqueIdentifier)); NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); new Packets.Mod() { NetHandle = 0, Target = 0, Name = modName, CustomPacketID = customID, Bytes = bytes }.PacketToNetOutGoingMessage(outgoingMessage); Server.MainNetServer.SendMessage(outgoingMessage, connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Mod); Server.MainNetServer.FlushSendQueue(); } catch (Exception e) { Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); } } /// /// Send a native call (Function.Call) to all players. /// Keys = int, float, bool, string and lvector3 /// /// The hash (Example: 0x25223CA6B4D20B7F = GET_CLOCK_HOURS) /// The arguments (Example: string = int, object = 5) public static void SendNativeCallToAll(ulong hash, params object[] args) { try { if (Server.MainNetServer.ConnectionsCount == 0) { return; } if (args != null && args.Length == 0) { Logging.Error($"[ServerScript->SendNativeCallToAll(ulong hash, params object[] args)]: args is not null!"); return; } Packets.NativeCall packet = new() { Hash = hash, Args = new List(args) ?? new List() }; NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native); } catch (Exception e) { Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); } } /// /// Get all connections as a list of NetHandle(long) /// /// public static List GetAllConnections() { List result = new(); Server.MainNetServer.Connections.ForEach(x => result.Add(x.RemoteUniqueIdentifier)); return result; } public static int GetAllClientsCount() { return Server.Clients.Count; } public static List GetAllClients() { return Server.Clients; } public static Client GetClientByUsername(string username) { return Server.Clients.FirstOrDefault(x => x.Player.Username.ToLower() == username.ToLower()); } /// /// Send a chat message to all players /// /// The chat message /// The username which send this message (default = "Server") /// The list of connections (players) who received this chat message public static void SendChatMessageToAll(string message, string username = "Server", List netHandleList = null) { try { if (Server.MainNetServer.ConnectionsCount == 0) { return; } List connections = netHandleList == null ? Server.MainNetServer.Connections : Server.MainNetServer.Connections.FindAll(c => netHandleList.Contains(c.RemoteUniqueIdentifier)); Server.SendChatMessage(username, message, connections); } catch (Exception e) { Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); } } /// /// /// public static void SendCleanUpWorldToAll(List netHandleList = null) { if (Server.MainNetServer.ConnectionsCount == 0) { return; } List connections = netHandleList == null ? Server.MainNetServer.Connections : Server.MainNetServer.Connections.FindAll(c => netHandleList.Contains(c.RemoteUniqueIdentifier)); NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); outgoingMessage.Write((byte)PacketTypes.CleanUpWorld); Server.MainNetServer.SendMessage(outgoingMessage, connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Default); } /// /// 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!) /// Create a new function! public static 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") /// Create a new function! public static void RegisterCommand(string name, Action callback) { Server.RegisterCommand(name, callback); } /// /// Register a class of commands /// /// The name of your class with functions public static void RegisterCommands() { Server.RegisterCommands(); } #endregion } [AttributeUsage(AttributeTargets.Method)] public class Command : Attribute { /// /// Sets name of the command /// public string Name { get; set; } /// /// Set the Usage (Example: "Please use "/help"". ArgsLength required!) /// public string Usage { get; set; } /// /// Set the length of arguments (Example: 2 for "/message USERNAME MESSAGE". Usage required!) /// public short ArgsLength { get; set; } public Command(string name) { Name = name; } } public class CommandContext { /// /// Gets the client which executed the command /// public Client Client { get; internal set; } /// /// Gets the arguments (Example: "/message USERNAME MESSAGE", Args[0] for USERNAME) /// public string[] Args { get; internal set; } } }