
In some circumstances (like proximity, missions...) we may want to target messages to only a sub set of all the users connected to the server. Adding this optional parameter to SendModPacketToAll and SendChatMessageToAll allows that while being backwards compatible with the API Fixes some problems introduced in the non interactive mode Changes the SetLocalTraffic parameter to make it more readable so now SetLocalTraffic(true) means to ENABLE local traffic while SetLocalTraffic(false) means DISABLE local traffic
376 lines
12 KiB
C#
376 lines
12 KiB
C#
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 mod, byte customID, byte[] bytes)
|
|
{
|
|
Task<bool> task = new(() => _script.API.InvokeModPacketReceived(from, target, mod, customID, bytes));
|
|
task.Start();
|
|
task.Wait(5000);
|
|
|
|
return task.Result;
|
|
}
|
|
|
|
public void InvokePlayerHandshake(Client client)
|
|
{
|
|
lock (_actionQueue.SyncRoot)
|
|
{
|
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerConnected(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<bool> task = new(() => _script.API.InvokeChatMessage(username, message));
|
|
task.Start();
|
|
task.Wait(5000);
|
|
|
|
return task.Result;
|
|
}
|
|
|
|
public void InvokePlayerPositionUpdate(PlayerData playerData)
|
|
{
|
|
lock (_actionQueue.SyncRoot)
|
|
{
|
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerPositionUpdate(playerData)));
|
|
}
|
|
}
|
|
|
|
public void InvokePlayerUpdate(Client client)
|
|
{
|
|
lock (_actionQueue.SyncRoot)
|
|
{
|
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerUpdate(client)));
|
|
}
|
|
}
|
|
|
|
public void InvokePlayerHealthUpdate(PlayerData playerData)
|
|
{
|
|
lock (_actionQueue.SyncRoot)
|
|
{
|
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerHealthUpdate(playerData)));
|
|
}
|
|
}
|
|
}
|
|
|
|
public class API
|
|
{
|
|
#region DELEGATES
|
|
public delegate void EmptyEvent();
|
|
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 mod, byte customID, byte[] bytes, CancelEventArgs args);
|
|
#endregion
|
|
|
|
#region EVENTS
|
|
public event EmptyEvent OnStart;
|
|
public event EmptyEvent OnStop;
|
|
public event ChatEvent OnChatMessage;
|
|
public event PlayerEvent OnPlayerHandshake;
|
|
public event PlayerEvent OnPlayerConnected;
|
|
public event PlayerEvent OnPlayerDisconnected;
|
|
public event PlayerEvent OnPlayerUpdate;
|
|
public event PlayerEvent OnPlayerHealthUpdate;
|
|
public event PlayerEvent OnPlayerPositionUpdate;
|
|
public event ModEvent OnModPacketReceived;
|
|
|
|
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(PlayerData playerData)
|
|
{
|
|
OnPlayerHealthUpdate?.Invoke(Server.Clients.First(x => x.Player.Username == playerData.Username));
|
|
}
|
|
|
|
internal bool InvokeChatMessage(string username, string message)
|
|
{
|
|
CancelEventArgs args = new(false);
|
|
OnChatMessage?.Invoke(username, message, args);
|
|
return args.Cancel;
|
|
}
|
|
|
|
internal void InvokePlayerPositionUpdate(PlayerData playerData)
|
|
{
|
|
OnPlayerPositionUpdate?.Invoke(Server.Clients.First(x => x.Player.Username == playerData.Username));
|
|
}
|
|
|
|
internal bool InvokeModPacketReceived(long from, long target, string mod, byte customID, byte[] bytes)
|
|
{
|
|
CancelEventArgs args = new(false);
|
|
OnModPacketReceived?.Invoke(from, target, mod, customID, bytes, args);
|
|
return args.Cancel;
|
|
}
|
|
#endregion
|
|
|
|
#region FUNCTIONS
|
|
public static void SendModPacketToAll(string mod, byte customID, byte[] bytes, List<long> playerIdList = null)
|
|
{
|
|
try
|
|
{
|
|
List<NetConnection> connections;
|
|
if (playerIdList == null)
|
|
{
|
|
connections = Server.MainNetServer.Connections;
|
|
}
|
|
else
|
|
{
|
|
connections = Server.MainNetServer.Connections.FindAll(c => playerIdList.Contains(c.RemoteUniqueIdentifier));
|
|
}
|
|
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
|
new ModPacket()
|
|
{
|
|
NetHandle = 0,
|
|
Target = 0,
|
|
Mod = mod,
|
|
CustomPacketID = customID,
|
|
Bytes = bytes
|
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
|
Server.MainNetServer.SendMessage(outgoingMessage, connections, NetDeliveryMethod.ReliableOrdered, (int)ConnectionChannel.Mod);
|
|
Server.MainNetServer.FlushSendQueue();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
|
}
|
|
}
|
|
|
|
public static void SendNativeCallToAll(ulong hash, params object[] args)
|
|
{
|
|
try
|
|
{
|
|
if (Server.MainNetServer.ConnectionsCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<NativeArgument> arguments = Util.ParseNativeArguments(args);
|
|
if (arguments == null)
|
|
{
|
|
Logging.Error($"[ServerScript->SendNativeCallToAll(ulong hash, params object[] args)]: One or more arguments do not exist!");
|
|
return;
|
|
}
|
|
|
|
NativeCallPacket packet = new()
|
|
{
|
|
Hash = hash,
|
|
Args = arguments
|
|
};
|
|
|
|
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
|
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
|
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (int)ConnectionChannel.Native);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
|
}
|
|
}
|
|
|
|
public static List<long> GetAllConnections()
|
|
{
|
|
List<long> result = new();
|
|
|
|
Server.MainNetServer.Connections.ForEach(x => result.Add(x.RemoteUniqueIdentifier));
|
|
|
|
return result;
|
|
}
|
|
|
|
public static int GetAllClientsCount()
|
|
{
|
|
return Server.Clients.Count;
|
|
}
|
|
|
|
public static List<Client> GetAllClients()
|
|
{
|
|
return Server.Clients;
|
|
}
|
|
|
|
public static Client GetClientByUsername(string username)
|
|
{
|
|
return Server.Clients.Find(x => x.Player.Username.ToLower() == username.ToLower());
|
|
}
|
|
|
|
public static void SendChatMessageToAll(string message, string username = "Server", List<long> playerIdList = null)
|
|
{
|
|
try
|
|
{
|
|
if (Server.MainNetServer.ConnectionsCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<NetConnection> connections;
|
|
if (playerIdList == null)
|
|
{
|
|
connections = Server.MainNetServer.Connections;
|
|
}
|
|
else
|
|
{
|
|
connections = Server.MainNetServer.Connections.FindAll(c => playerIdList.Contains(c.RemoteUniqueIdentifier));
|
|
}
|
|
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
|
new ChatMessagePacket()
|
|
{
|
|
Username = username,
|
|
Message = message
|
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
|
Server.MainNetServer.SendMessage(outgoingMessage, connections, NetDeliveryMethod.ReliableOrdered, (int)ConnectionChannel.Chat);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
|
}
|
|
}
|
|
|
|
public static void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
|
|
{
|
|
Server.RegisterCommand(name, usage, argsLength, callback);
|
|
}
|
|
public static void RegisterCommand(string name, Action<CommandContext> callback)
|
|
{
|
|
Server.RegisterCommand(name, callback);
|
|
}
|
|
|
|
public static void RegisterCommands<T>()
|
|
{
|
|
Server.RegisterCommands<T>();
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
[AttributeUsage(AttributeTargets.Method)]
|
|
public class Command : Attribute
|
|
{
|
|
/// <summary>
|
|
/// Sets name of the command
|
|
/// </summary>
|
|
public string Name { get; set; }
|
|
|
|
public string Usage { get; set; }
|
|
|
|
public short ArgsLength { get; set; }
|
|
|
|
public Command(string name)
|
|
{
|
|
Name = name;
|
|
}
|
|
}
|
|
|
|
public class CommandContext
|
|
{
|
|
/// <summary>
|
|
/// Gets the client which executed the command
|
|
/// </summary>
|
|
public Client Client { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Gets the chatdata associated with the command
|
|
/// </summary>
|
|
public string[] Args { get; internal set; }
|
|
}
|
|
}
|