Scripting prototype?

This commit is contained in:
Sardelka
2022-06-04 18:09:42 +08:00
parent 1607b7025c
commit 9f2cf2eb03
14 changed files with 391 additions and 603 deletions

View File

@ -11,7 +11,7 @@ namespace RageCoop.Client.Scripting
{ {
internal class Engine : Core.Scripting.ScriptingEngine internal class Engine : Core.Scripting.ScriptingEngine
{ {
public Engine() : base(typeof(ClientScript), Main.Logger) { } public Engine() : base("RageCoop.Client.Scripting.ClientScript", Main.Logger) { }
} }
} }

View File

@ -12,9 +12,16 @@ namespace RageCoop.Core.Scripting
{ {
internal class ScriptingEngine internal class ScriptingEngine
{ {
private Type BaseScriptType; protected List<string> ToIgnore = new List<string>
{
"RageCoop.Client.dll",
"RageCoop.Core.dll",
"RageCoop.Server.dll",
"ScriptHookVDotNet3.dll"
};
private string BaseScriptType;
public Logging.Logger Logger { get; set; } public Logging.Logger Logger { get; set; }
public ScriptingEngine(Type baseScriptType, Logging.Logger logger) public ScriptingEngine(string baseScriptType, Logging.Logger logger)
{ {
BaseScriptType = baseScriptType; BaseScriptType = baseScriptType;
Logger = logger; Logger = logger;
@ -22,29 +29,29 @@ namespace RageCoop.Core.Scripting
/// <summary> /// <summary>
/// Loads scripts from the specified assembly file. /// Loads scripts from the specified assembly file.
/// </summary> /// </summary>
/// <param name="filename">The path to the assembly file to load.</param> /// <param name="path">The path to the assembly file to load.</param>
/// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns> /// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns>
private bool LoadScriptsFromAssembly(string filename) protected bool LoadScriptsFromAssembly(string path)
{ {
if (!IsManagedAssembly(filename)) if (!IsManagedAssembly(path)) { return false; }
return false; if (ToIgnore.Contains(Path.GetFileName(path))) { return false; }
Logger?.Debug($"Loading assembly {Path.GetFileName(filename)} ..."); Logger?.Debug($"Loading assembly {Path.GetFileName(path)} ...");
Assembly assembly; Assembly assembly;
try try
{ {
assembly = Assembly.LoadFrom(filename); assembly = Assembly.LoadFrom(path);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger?.Error( "Unable to load "+Path.GetFileName(filename)); Logger?.Error( "Unable to load "+Path.GetFileName(path));
Logger?.Error(ex); Logger?.Error(ex);
return false; return false;
} }
return LoadScriptsFromAssembly(assembly, filename); return LoadScriptsFromAssembly(assembly, path);
} }
/// <summary> /// <summary>
/// Loads scripts from the specified assembly object. /// Loads scripts from the specified assembly object.
@ -59,7 +66,7 @@ namespace RageCoop.Core.Scripting
try try
{ {
// Find all script types in the assembly // Find all script types in the assembly
foreach (var type in assembly.GetTypes().Where(x => IsSubclassOf(x,nameof(BaseScriptType)))) foreach (var type in assembly.GetTypes().Where(x => IsSubclassOf(x,BaseScriptType)))
{ {
ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes); ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
if (constructor != null && constructor.IsPublic) if (constructor != null && constructor.IsPublic)
@ -90,7 +97,7 @@ namespace RageCoop.Core.Scripting
return false; return false;
} }
Logger?.Info($"Loaded {count.ToString()} script(s) in {Path.GetFileName(filename)}"); Logger?.Info($"Loaded {count} script(s) in {Path.GetFileName(filename)}");
return count != 0; return count != 0;
} }
private bool IsManagedAssembly(string filename) private bool IsManagedAssembly(string filename)

View File

@ -9,6 +9,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "Client\R
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Resources.Base", "Resources\Base\RageCoop.Resources.Base.csproj", "{9DC11623-8A8B-4D17-B18B-91852922163D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resources", "Resources", "{F49A2617-832B-44DA-9D42-29B5DD09A186}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -41,10 +45,21 @@ Global
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|Any CPU.Build.0 = Release|Any CPU {CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|Any CPU.Build.0 = Release|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|x64.ActiveCfg = Release|Any CPU {CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|x64.ActiveCfg = Release|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|x64.Build.0 = Release|Any CPU {CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|x64.Build.0 = Release|Any CPU
{9DC11623-8A8B-4D17-B18B-91852922163D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DC11623-8A8B-4D17-B18B-91852922163D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DC11623-8A8B-4D17-B18B-91852922163D}.Debug|x64.ActiveCfg = Debug|Any CPU
{9DC11623-8A8B-4D17-B18B-91852922163D}.Debug|x64.Build.0 = Debug|Any CPU
{9DC11623-8A8B-4D17-B18B-91852922163D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DC11623-8A8B-4D17-B18B-91852922163D}.Release|Any CPU.Build.0 = Release|Any CPU
{9DC11623-8A8B-4D17-B18B-91852922163D}.Release|x64.ActiveCfg = Release|Any CPU
{9DC11623-8A8B-4D17-B18B-91852922163D}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9DC11623-8A8B-4D17-B18B-91852922163D} = {F49A2617-832B-44DA-9D42-29B5DD09A186}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7} SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}
EndGlobalSection EndGlobalSection

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="RageCoop.Server">
<HintPath>..\..\Server\bin\Debug\net6.0\RageCoop.Server.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
using RageCoop.Server.Scripting;
namespace RageCoop.Resources.Base
{
public class ServerBase :ServerScript
{
public ServerBase()
{
API.RegisterCommand("kick", (ctx) =>
{
if (ctx.Args.Length<1) { return; }
var reason = "eat poop";
if(ctx.Args.Length>=2) { reason=ctx.Args[1]; }
API.GetClientByUsername(ctx.Args[0]).Kick(reason);
});
}
}
}

View File

@ -9,21 +9,7 @@ namespace RageCoop.Server
public class Client public class Client
{ {
public long NetID = 0; public long NetID = 0;
private float _currentLatency = 0f; internal NetConnection Connection { get; set; }
public NetConnection Connection { get; set; }
public float Latency
{
get => _currentLatency;
internal set
{
_currentLatency = value;
if ((value * 1000f) > Server.MainSettings.MaxLatency)
{
Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID)?.Disconnect($"Too high latency [{value * 1000f}/{(float)Server.MainSettings.MaxLatency}]");
}
}
}
public PlayerData Player; public PlayerData Player;
private readonly Dictionary<string, object> _customData = new(); private readonly Dictionary<string, object> _customData = new();
private long _callbacksCount = 0; private long _callbacksCount = 0;
@ -64,13 +50,13 @@ namespace RageCoop.Server
#endregion #endregion
#region FUNCTIONS #region FUNCTIONS
public void Kick(string reason) public void Kick(string reason="You has been kicked!")
{ {
Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID)?.Disconnect(reason); Connection?.Disconnect(reason);
} }
public void Kick(string[] reason) public void Kick(params string[] reasons)
{ {
Kick(string.Join(" ", reason)); Kick(string.Join(" ", reasons));
} }
public void SendChatMessage(string message, string from = "Server") public void SendChatMessage(string message, string from = "Server")
@ -201,32 +187,6 @@ namespace RageCoop.Server
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Default); Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Default);
} }
public void SendModPacket(string modName, byte customID, byte[] bytes)
{
try
{
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
if (userConnection == null)
{
return;
}
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
new Packets.Mod()
{
Name = modName,
CustomPacketID = customID,
Bytes = bytes
}.Pack(outgoingMessage);
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Mod);
Server.MainNetServer.FlushSendQueue();
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
public void SendTriggerEvent(string eventName, params object[] args) public void SendTriggerEvent(string eventName, params object[] args)
{ {
if (!FilesReceived) if (!FilesReceived)

View File

@ -105,7 +105,7 @@ namespace RageCoop.Server
{ {
lock (Server.Clients) lock (Server.Clients)
{ {
Client x = Util.GetClientByID(client.NetHandle); Client x = Util.GetClientByNetID(client.NetHandle);
if (x != null) if (x != null)
{ {
x.FilesReceived = true; x.FilesReceived = true;

192
Server/Scripting/API.cs Normal file
View File

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lidgren.Network;
using RageCoop.Core;
using System.Net;
namespace RageCoop.Server.Scripting
{
public static class API
{
public static class Events
{
#region DELEGATES
public delegate void EmptyEvent();
public delegate void PlayerConnect(int id,string name);
public delegate void PlayerDisconnect(int id, string name);
#endregion
public static event EmptyEvent OnStop;
public static event EventHandler<ChatEventArgs> OnChatMessage;
public static event EventHandler<HandshakeEventArgs> OnPlayerHandshake;
public static event PlayerConnect OnPlayerConnected;
public static event PlayerDisconnect OnPlayerDisconnected;
// public static event EventHandler OnPlayerUpdate;
#region INVOKE
internal static void InvokeOnStop() { OnStop?.Invoke(); }
internal static void InvokeOnChatMessage(Packets.ChatMessage p,NetConnection con)
{
OnChatMessage?.Invoke(null,new ChatEventArgs() {
Sender=Util.GetClientByNetID(con.RemoteUniqueIdentifier),
Message=p.Message
});
}
internal static void InvokePlayerConnected(Client client)
{ OnPlayerConnected?.Invoke(client.Player.PedID,client.Player.Username); }
internal static void InvokePlayerDisconnected(Client client)
{ OnPlayerDisconnected?.Invoke(client.Player.PedID, client.Player.Username); }
internal static void InvokePlayerHandshake(HandshakeEventArgs args)
{
OnPlayerHandshake?.Invoke(null, args);
}
#endregion
}
#region FUNCTIONS
/// <summary>
/// Send a native call (Function.Call) to all players.
/// Keys = int, float, bool, string and lvector3
/// </summary>
/// <param name="hash">The hash (Example: 0x25223CA6B4D20B7F = GET_CLOCK_HOURS)</param>
/// <param name="args">The arguments (Example: string = int, object = 5)</param>
public static void SendNativeCallToAll(GTA.Native.Hash hash, params object[] args)
{
try
{
if (Server.MainNetServer.ConnectionsCount == 0)
{
return;
}
if (args != null && args.Length == 0)
{
Program.Logger.Error($"[ServerScript->SendNativeCallToAll(ulong hash, params object[] args)]: args is not null!");
return;
}
Packets.NativeCall packet = new()
{
Hash = (ulong)hash,
Args = new List<object>(args) ?? new List<object>()
};
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native);
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/// <summary>
/// Get a list of all Clients
/// </summary>
/// <returns>All clients as a dictionary indexed by NetID</returns>
public static Dictionary<long, Client> GetAllClients()
{
return Server.Clients;
}
/// <summary>
/// Get the client by its username
/// </summary>
/// <param name="username">The username to search for (non case-sensitive)</param>
/// <returns>The Client from this user or null</returns>
public static Client GetClientByUsername(string username)
{
return Server.Clients.Values.FirstOrDefault(x => x.Player.Username.ToLower() == username.ToLower());
}
/// <summary>
/// Send a chat message to all players
/// </summary>
/// <param name="message">The chat message</param>
/// <param name="username">The username which send this message (default = "Server")</param>
/// <param name="netHandleList">The list of connections (players) who received this chat message</param>
public static void SendChatMessageToAll(string message, string username = "Server", List<long> netHandleList = null)
{
try
{
if (Server.MainNetServer.ConnectionsCount == 0)
{
return;
}
List<NetConnection> connections = netHandleList == null
? Server.MainNetServer.Connections
: Server.MainNetServer.Connections.FindAll(c => netHandleList.Contains(c.RemoteUniqueIdentifier));
Server.SendChatMessage(username, message, connections);
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/// <summary>
/// Send CleanUpWorld to all players to delete all objects created by the server
/// </summary>
public static void SendCleanUpWorldToAll(List<long> netHandleList = null)
{
if (Server.MainNetServer.ConnectionsCount == 0)
{
return;
}
List<NetConnection> 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);
}
/// <summary>
/// Register a new command chat command (Example: "/test")
/// </summary>
/// <param name="name">The name of the command (Example: "test" for "/test")</param>
/// <param name="usage">How to use this message (argsLength required!)</param>
/// <param name="argsLength">The length of args (Example: "/message USERNAME MESSAGE" = 2) (usage required!)</param>
/// <param name="callback">Create a new function!</param>
public static void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
{
Server.RegisterCommand(name, usage, argsLength, callback);
}
/// <summary>
/// Register a new command chat command (Example: "/test")
/// </summary>
/// <param name="name">The name of the command (Example: "test" for "/test")</param>
/// <param name="callback">Create a new function!</param>
public static void RegisterCommand(string name, Action<CommandContext> callback)
{
Server.RegisterCommand(name, callback);
}
/// <summary>
/// Register a class of commands
/// </summary>
/// <typeparam name="T">The name of your class with functions</typeparam>
public static void RegisterCommands<T>()
{
Server.RegisterCommands<T>();
}
/// <summary>
/// Register a class of events
/// </summary>
/// <typeparam name="T">The name of your class with functions</typeparam>
public static void RegisterEvents<T>()
{
Server.RegisterEvents<T>();
}
#endregion
}
}

View File

@ -4,17 +4,30 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using System.IO;
namespace RageCoop.Server.Scripting namespace RageCoop.Server.Scripting
{ {
internal class Engine:ScriptingEngine internal class Engine:ScriptingEngine
{ {
public Engine() : base(typeof(ServerScript), Program.Logger) public Engine() : base("RageCoop.Server.Scripting.ServerScript", Program.Logger)
{ {
} }
public static void Load() public void LoadAll()
{ {
var path = Path.Combine("Resources", "Server");
Directory.CreateDirectory(path);
foreach (var resource in Directory.GetDirectories(path))
{
Logger.Info($"Loading resource: {Path.GetFileName(resource)}");
LoadResource(resource);
}
}
private void LoadResource(string path)
{
foreach(var assembly in Directory.GetFiles(path,"*.dll"))
{
LoadScriptsFromAssembly(assembly);
}
} }
} }
} }

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
namespace RageCoop.Server.Scripting
{
public class ChatEventArgs : EventArgs
{
public Client Sender { get; set; }
public string Message { get; set; }
}
public class HandshakeEventArgs : EventArgs
{
public int ID { get; set; }
public string Username { get; set; }
public IPEndPoint EndPoint { get; set; }
public void Deny(string reason)
{
DenyReason=reason;
Cancel=true;
}
internal string DenyReason { get; set; }
internal bool Cancel { get; set; }=false;
}
}

View File

@ -8,401 +8,10 @@ using System.Threading.Tasks;
using RageCoop.Core; using RageCoop.Core;
using Lidgren.Network; using Lidgren.Network;
namespace RageCoop.Server namespace RageCoop.Server.Scripting
{ {
public abstract class ServerScript public abstract class ServerScript
{ {
public API API { get; } = new();
}
public 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();
}
// 15 milliseconds to sleep to reduce CPU usage
Thread.Sleep(15);
}
_script.API.InvokeStop();
ReadyToStop = true;
}
public bool InvokeModPacketReceived(long from, long target, string modName, byte customID, byte[] bytes)
{
Task<bool> 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<bool> task = new(() => _script.API.InvokeChatMessage(username, message));
task.Start();
task.Wait(5000);
return task.Result;
}
public void InvokePlayerUpdate(Client client)
{
lock (_actionQueue.SyncRoot)
{
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerUpdate(client)));
}
}
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
/// <summary>
/// Called every tick
/// </summary>
public event OnTickEvent OnTick;
/// <summary>
/// Called when the server has started
/// </summary>
public event EmptyEvent OnStart;
/// <summary>
/// Called when the server has stopped
/// </summary>
public event EmptyEvent OnStop;
/// <summary>
/// Called when the server receives a new chat message for players
/// </summary>
public event ChatEvent OnChatMessage;
/// <summary>
/// Called when the server receives a new incoming connection
/// </summary>
public event PlayerEvent OnPlayerHandshake;
/// <summary>
/// Called when a new player has successfully connected
/// </summary>
public event PlayerEvent OnPlayerConnected;
/// <summary>
/// Called when a new player has successfully disconnected
/// </summary>
public event PlayerEvent OnPlayerDisconnected;
/// <summary>
/// Called when a new player sends data like health
/// </summary>
public event PlayerEvent OnPlayerUpdate;
public event ModEvent OnModPacketReceived;
public void InvokeTick(long tick)
{
OnTick?.Invoke(tick);
}
public void InvokeStart()
{
OnStart?.Invoke();
}
public void InvokeStop()
{
OnStop?.Invoke();
}
public void InvokePlayerHandshake(Client client)
{
OnPlayerHandshake?.Invoke(client);
}
public void InvokePlayerConnected(Client client)
{
OnPlayerConnected?.Invoke(client);
}
public void InvokePlayerDisconnected(Client client)
{
OnPlayerDisconnected?.Invoke(client);
}
public void InvokePlayerUpdate(Client client)
{
OnPlayerUpdate?.Invoke(client);
}
public bool InvokeChatMessage(string username, string message)
{
CancelEventArgs args = new(false);
OnChatMessage?.Invoke(username, message, args);
return args.Cancel;
}
public 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
/// <summary>
/// Send a mod packet to all players
/// </summary>
/// <param name="modName">The name of the modification that will receive the data</param>
/// <param name="customID">The ID to check what this data is</param>
/// <param name="bytes">The serialized data</param>
/// <param name="playerList">The list of player ID (PedID) that will receive the data</param>
public static void SendModPacketToAll(string modName, byte customID, byte[] bytes, List<int> playerList = null)
{
try
{// A resource can be calling this function on disconnect of the last player in the server and we will
// get an empty connection list, make sure connections has at least one handle in it
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
new Packets.Mod()
{
Name = modName,
CustomPacketID = customID,
Bytes = bytes
}.Pack(outgoingMessage);
if (playerList==null)
{
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Mod);
}
else
{
foreach(var c in Server.Clients.Values)
{
if (playerList.Contains(c.Player.PedID))
{
Server.MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Mod);
}
}
}
Server.MainNetServer.FlushSendQueue();
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/// <summary>
/// Send a native call (Function.Call) to all players.
/// Keys = int, float, bool, string and lvector3
/// </summary>
/// <param name="hash">The hash (Example: 0x25223CA6B4D20B7F = GET_CLOCK_HOURS)</param>
/// <param name="args">The arguments (Example: string = int, object = 5)</param>
public static void SendNativeCallToAll(ulong hash, params object[] args)
{
try
{
if (Server.MainNetServer.ConnectionsCount == 0)
{
return;
}
if (args != null && args.Length == 0)
{
Program.Logger.Error($"[ServerScript->SendNativeCallToAll(ulong hash, params object[] args)]: args is not null!");
return;
}
Packets.NativeCall packet = new()
{
Hash = hash,
Args = new List<object>(args) ?? new List<object>()
};
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native);
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/// <summary>
/// Get a list of all Clients
/// </summary>
/// <returns>All clients as a dictionary indexed by NetID</returns>
public static Dictionary<long,Client> GetAllClients()
{
return Server.Clients;
}
/// <summary>
/// Get the client by its username
/// </summary>
/// <param name="username">The username to search for</param>
/// <returns>The Client from this user or null</returns>
public static Client GetClientByUsername(string username)
{
return Server.Clients.Values.FirstOrDefault(x => x.Player.Username.ToLower() == username.ToLower());
}
/// <summary>
/// Send a chat message to all players
/// </summary>
/// <param name="message">The chat message</param>
/// <param name="username">The username which send this message (default = "Server")</param>
/// <param name="netHandleList">The list of connections (players) who received this chat message</param>
public static void SendChatMessageToAll(string message, string username = "Server", List<long> netHandleList = null)
{
try
{
if (Server.MainNetServer.ConnectionsCount == 0)
{
return;
}
List<NetConnection> connections = netHandleList == null
? Server.MainNetServer.Connections
: Server.MainNetServer.Connections.FindAll(c => netHandleList.Contains(c.RemoteUniqueIdentifier));
Server.SendChatMessage(username, message, connections);
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/// <summary>
/// Send CleanUpWorld to all players to delete all objects created by the server
/// </summary>
public static void SendCleanUpWorldToAll(List<long> netHandleList = null)
{
if (Server.MainNetServer.ConnectionsCount == 0)
{
return;
}
List<NetConnection> 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);
}
/// <summary>
/// Register a new command chat command (Example: "/test")
/// </summary>
/// <param name="name">The name of the command (Example: "test" for "/test")</param>
/// <param name="usage">How to use this message (argsLength required!)</param>
/// <param name="argsLength">The length of args (Example: "/message USERNAME MESSAGE" = 2) (usage required!)</param>
/// <param name="callback">Create a new function!</param>
public static void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
{
Server.RegisterCommand(name, usage, argsLength, callback);
}
/// <summary>
/// Register a new command chat command (Example: "/test")
/// </summary>
/// <param name="name">The name of the command (Example: "test" for "/test")</param>
/// <param name="callback">Create a new function!</param>
public static void RegisterCommand(string name, Action<CommandContext> callback)
{
Server.RegisterCommand(name, callback);
}
/// <summary>
/// Register a class of commands
/// </summary>
/// <typeparam name="T">The name of your class with functions</typeparam>
public static void RegisterCommands<T>()
{
Server.RegisterCommands<T>();
}
/// <summary>
/// Register a class of events
/// </summary>
/// <typeparam name="T">The name of your class with functions</typeparam>
public static void RegisterEvents<T>()
{
Server.RegisterEvents<T>();
}
#endregion
} }
[AttributeUsage(AttributeTargets.Method, Inherited = false)] [AttributeUsage(AttributeTargets.Method, Inherited = false)]

View File

@ -12,6 +12,7 @@ using Newtonsoft.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Lidgren.Network; using Lidgren.Network;
using System.Timers; using System.Timers;
using RageCoop.Server.Scripting;
namespace RageCoop.Server namespace RageCoop.Server
{ {
@ -21,21 +22,20 @@ namespace RageCoop.Server
public string Address { get; set; } public string Address { get; set; }
} }
public class Server internal class Server
{ {
private static readonly string _compatibleVersion = "V0_4"; private static readonly string _compatibleVersion = "V0_4";
private static long _currentTick = 0;
public static readonly Settings MainSettings = Util.Read<Settings>("Settings.xml"); public static readonly Settings MainSettings = Util.Read<Settings>("Settings.xml");
private readonly Blocklist _mainBlocklist = Util.Read<Blocklist>("Blocklist.xml"); private readonly Blocklist _mainBlocklist = Util.Read<Blocklist>("Blocklist.xml");
public static NetServer MainNetServer; public static NetServer MainNetServer;
public static Resource RunningResource = null;
public static readonly Dictionary<Command, Action<CommandContext>> Commands = new(); public static readonly Dictionary<Command, Action<CommandContext>> Commands = new();
public static readonly Dictionary<TriggerEvent, Action<EventContext>> TriggerEvents = new(); public static readonly Dictionary<TriggerEvent, Action<EventContext>> TriggerEvents = new();
public static readonly Dictionary<long,Client> Clients = new(); public static readonly Dictionary<long,Client> Clients = new();
private static System.Timers.Timer SendLatencyTimer = new System.Timers.Timer(5000); private static System.Timers.Timer SendLatencyTimer = new System.Timers.Timer(5000);
public static readonly Engine ScriptingEngine = new();
public Server() public Server()
{ {
Program.Logger.Info("================"); Program.Logger.Info("================");
@ -169,40 +169,7 @@ namespace RageCoop.Server
}).Start(); }).Start();
#endregion #endregion
} }
ScriptingEngine.LoadAll();
if (!string.IsNullOrEmpty(MainSettings.Resource))
{
try
{
string resourcepath = AppDomain.CurrentDomain.BaseDirectory + "resources" + Path.DirectorySeparatorChar + MainSettings.Resource + ".dll";
Program.Logger.Info($"Loading resource \"{MainSettings.Resource}.dll\"...");
Assembly asm = Assembly.LoadFrom(resourcepath);
Type[] types = asm.GetExportedTypes();
IEnumerable<Type> validTypes = types.Where(t => !t.IsInterface && !t.IsAbstract).Where(t => typeof(ServerScript).IsAssignableFrom(t));
Type[] enumerable = validTypes as Type[] ?? validTypes.ToArray();
if (!enumerable.Any())
{
Program.Logger.Error("ERROR: No classes that inherit from ServerScript have been found in the assembly. Starting freeroam.");
}
else
{
if (Activator.CreateInstance(enumerable.ToArray()[0]) is ServerScript script)
{
RunningResource = new(script);
}
else
{
Program.Logger.Warning("Could not create resource: it is null.");
}
}
}
catch (Exception e)
{
Program.Logger.Error(e.InnerException.Message);
}
}
Program.Logger.Info("Searching for client-side files..."); Program.Logger.Info("Searching for client-side files...");
DownloadManager.CheckForDirectoryAndFiles(); DownloadManager.CheckForDirectoryAndFiles();
@ -224,10 +191,6 @@ namespace RageCoop.Server
}), new SynchronizationContext()); }), new SynchronizationContext());
while (!Program.ReadyToStop) while (!Program.ReadyToStop)
{ {
if (RunningResource != null)
{
RunningResource.InvokeTick(++_currentTick);
}
// Only new clients that did not receive files on connection will receive the current files in "clientside" // Only new clients that did not receive files on connection will receive the current files in "clientside"
if (DownloadManager.AnyFileExists) if (DownloadManager.AnyFileExists)
@ -249,23 +212,15 @@ namespace RageCoop.Server
// 3 milliseconds to sleep to reduce CPU usage // 15 milliseconds to sleep to reduce CPU usage
Thread.Sleep(3); Thread.Sleep(15);
} }
Program.Logger.Warning("Server is shutting down!"); Program.Logger.Warning("Server is shutting down!");
if (RunningResource != null)
{
// Waiting for resource...
while (!RunningResource.ReadyToStop)
{
// 16 milliseconds to sleep to reduce CPU usage
Thread.Sleep(1000 / 60);
}
}
if (MainNetServer.Connections.Count > 0) if (MainNetServer.Connections.Count > 0)
{ {
API.Events.InvokeOnStop();
MainNetServer.Connections.ForEach(x => x.Disconnect("Server is shutting down!")); MainNetServer.Connections.ForEach(x => x.Disconnect("Server is shutting down!"));
// We have to wait some time for all Disconnect() messages to be sent successfully // We have to wait some time for all Disconnect() messages to be sent successfully
// Sleep for 1 second // Sleep for 1 second
@ -437,6 +392,7 @@ namespace RageCoop.Server
Packets.ChatMessage packet = new(); Packets.ChatMessage packet = new();
packet.Unpack(data); packet.Unpack(data);
API.Events.InvokeOnChatMessage(packet,message.SenderConnection);
SendChatMessage(packet); SendChatMessage(packet);
} }
catch (Exception e) catch (Exception e)
@ -456,7 +412,7 @@ namespace RageCoop.Server
Packets.NativeResponse packet = new(); Packets.NativeResponse packet = new();
packet.Unpack(data); packet.Unpack(data);
Client client = Util.GetClientByID(message.SenderConnection.RemoteUniqueIdentifier); Client client = Util.GetClientByNetID(message.SenderConnection.RemoteUniqueIdentifier);
if (client != null) if (client != null)
{ {
if (client.Callbacks.ContainsKey(packet.ID)) if (client.Callbacks.ContainsKey(packet.ID))
@ -484,7 +440,7 @@ namespace RageCoop.Server
Packets.FileTransferComplete packet = new(); Packets.FileTransferComplete packet = new();
packet.Unpack(data); packet.Unpack(data);
Client client = Util.GetClientByID(message.SenderConnection.RemoteUniqueIdentifier); Client client = Util.GetClientByNetID(message.SenderConnection.RemoteUniqueIdentifier);
if (client != null && !client.FilesReceived) if (client != null && !client.FilesReceived)
{ {
DownloadManager.TryToRemoveClient(client.NetID, packet.ID); DownloadManager.TryToRemoveClient(client.NetID, packet.ID);
@ -511,7 +467,7 @@ namespace RageCoop.Server
Client client = null; Client client = null;
lock (Clients) lock (Clients)
{ {
client = Util.GetClientByID(senderNetHandle); client = Util.GetClientByNetID(senderNetHandle);
} }
if (client != null) if (client != null)
@ -571,10 +527,10 @@ namespace RageCoop.Server
break; break;
case NetIncomingMessageType.ConnectionLatencyUpdated: case NetIncomingMessageType.ConnectionLatencyUpdated:
{ {
Client client = Util.GetClientByID(message.SenderConnection.RemoteUniqueIdentifier); Client client = Util.GetClientByNetID(message.SenderConnection.RemoteUniqueIdentifier);
if (client != null) if (client != null)
{ {
client.Latency = message.ReadFloat(); client.Player.Latency = message.ReadFloat();
} }
} }
break; break;
@ -607,7 +563,7 @@ namespace RageCoop.Server
{ {
PedID=c.Player.PedID, PedID=c.Player.PedID,
Username=c.Player.Username, Username=c.Player.Username,
Latency=c.Player.Latency=c.Latency, Latency=c.Player.Latency,
}.Pack(outgoingMessage); }.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.ReliableSequenced, (byte)ConnectionChannel.Default); MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.ReliableSequenced, (byte)ConnectionChannel.Default);
}); });
@ -625,38 +581,38 @@ namespace RageCoop.Server
#region -- PLAYER -- #region -- PLAYER --
// Before we approve the connection, we must shake hands // Before we approve the connection, we must shake hands
private void GetHandshake(NetConnection local, Packets.Handshake packet) private void GetHandshake(NetConnection connection, Packets.Handshake packet)
{ {
Program.Logger.Debug("New handshake from: [Name: " + packet.Username + " | Address: " + local.RemoteEndPoint.Address.ToString() + "]"); Program.Logger.Debug("New handshake from: [Name: " + packet.Username + " | Address: " + connection.RemoteEndPoint.Address.ToString() + "]");
if (!packet.ModVersion.StartsWith(_compatibleVersion)) if (!packet.ModVersion.StartsWith(_compatibleVersion))
{ {
local.Deny($"RAGECOOP version {_compatibleVersion.Replace('_', '.')}.x required!"); connection.Deny($"RAGECOOP version {_compatibleVersion.Replace('_', '.')}.x required!");
return; return;
} }
if (string.IsNullOrWhiteSpace(packet.Username)) if (string.IsNullOrWhiteSpace(packet.Username))
{ {
local.Deny("Username is empty or contains spaces!"); connection.Deny("Username is empty or contains spaces!");
return; return;
} }
if (packet.Username.Any(p => !char.IsLetterOrDigit(p) && !(p == '_') && !(p=='-'))) if (packet.Username.Any(p => !char.IsLetterOrDigit(p) && !(p == '_') && !(p=='-')))
{ {
local.Deny("Username contains special chars!"); connection.Deny("Username contains special chars!");
return; return;
} }
if (_mainBlocklist.Username.Contains(packet.Username.ToLower())) if (_mainBlocklist.Username.Contains(packet.Username.ToLower()))
{ {
local.Deny("This Username has been blocked by this server!"); connection.Deny("This Username has been blocked by this server!");
return; return;
} }
if (_mainBlocklist.IP.Contains(local.RemoteEndPoint.ToString().Split(":")[0])) if (_mainBlocklist.IP.Contains(connection.RemoteEndPoint.ToString().Split(":")[0]))
{ {
local.Deny("This IP was blocked by this server!"); connection.Deny("This IP was blocked by this server!");
return; return;
} }
if (Clients.Values.Any(x => x.Player.Username.ToLower() == packet.Username.ToLower())) if (Clients.Values.Any(x => x.Player.Username.ToLower() == packet.Username.ToLower()))
{ {
local.Deny("Username is already taken!"); connection.Deny("Username is already taken!");
return; return;
} }
@ -667,11 +623,11 @@ namespace RageCoop.Server
// Add the player to Players // Add the player to Players
lock (Clients) lock (Clients)
{ {
Clients.Add(local.RemoteUniqueIdentifier, Clients.Add(connection.RemoteUniqueIdentifier,
tmpClient = new Client() tmpClient = new Client()
{ {
NetID = local.RemoteUniqueIdentifier, NetID = connection.RemoteUniqueIdentifier,
Connection=local, Connection=connection,
Player = new() Player = new()
{ {
Username = packet.Username, Username = packet.Username,
@ -680,6 +636,19 @@ namespace RageCoop.Server
} }
);; );;
} }
var args = new HandshakeEventArgs()
{
EndPoint=connection.RemoteEndPoint,
ID=packet.PedID,
Username=packet.Username
};
API.Events.InvokePlayerHandshake(args);
if (args.Cancel)
{
connection.Deny(args.DenyReason);
return;
}
Program.Logger.Info($"Handshake sucess, Player:{packet.Username} PedID:{packet.PedID}"); Program.Logger.Info($"Handshake sucess, Player:{packet.Username} PedID:{packet.PedID}");
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
@ -690,20 +659,14 @@ namespace RageCoop.Server
Username = string.Empty, Username = string.Empty,
ModVersion = string.Empty, ModVersion = string.Empty,
}.Pack(outgoingMessage); }.Pack(outgoingMessage);
// Accept the connection and send back a new handshake packet with the connection ID // Accept the connection and send back a new handshake packet with the connection ID
local.Approve(outgoingMessage); connection.Approve(outgoingMessage);
if (RunningResource != null)
{
RunningResource.InvokePlayerHandshake(tmpClient);
}
} }
// The connection has been approved, now we need to send all other players to the new player and the new player to all players // The connection has been approved, now we need to send all other players to the new player and the new player to all players
private static void SendPlayerConnectPacket(NetConnection local) private static void SendPlayerConnectPacket(NetConnection local)
{ {
Client localClient = Util.GetClientByID(local.RemoteUniqueIdentifier); Client localClient = Util.GetClientByNetID(local.RemoteUniqueIdentifier);
if (localClient == null) if (localClient == null)
{ {
local.Disconnect("No data found!"); local.Disconnect("No data found!");
@ -718,7 +681,7 @@ namespace RageCoop.Server
{ {
long targetNetHandle = targetPlayer.RemoteUniqueIdentifier; long targetNetHandle = targetPlayer.RemoteUniqueIdentifier;
Client targetClient = Util.GetClientByID(targetNetHandle); Client targetClient = Util.GetClientByNetID(targetNetHandle);
if (targetClient != null) if (targetClient != null)
{ {
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
@ -743,15 +706,9 @@ namespace RageCoop.Server
}.Pack(outgoingMessage); }.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0); MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0);
} }
API.Events.InvokePlayerConnected(localClient);
if (RunningResource != null)
{ Program.Logger.Info($"Player {localClient.Player.Username} connected!");
RunningResource.InvokePlayerConnected(localClient);
}
else
{
Program.Logger.Info($"Player {localClient.Player.Username} connected!");
}
if (!string.IsNullOrEmpty(MainSettings.WelcomeMessage)) if (!string.IsNullOrEmpty(MainSettings.WelcomeMessage))
{ {
@ -775,7 +732,7 @@ namespace RageCoop.Server
MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0); MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0);
} }
Client localClient = Util.GetClientByID( nethandle); Client localClient = Util.GetClientByNetID( nethandle);
if (localClient == null) if (localClient == null)
{ {
return; return;
@ -783,14 +740,9 @@ namespace RageCoop.Server
Clients.Remove(localClient.NetID); Clients.Remove(localClient.NetID);
if (RunningResource != null) API.Events.InvokePlayerDisconnected(localClient);
{ Program.Logger.Info($"Player {localClient.Player.Username} disconnected! ID:{playerPedID}");
RunningResource.InvokePlayerDisconnected(localClient);
}
else
{
Program.Logger.Info($"Player {localClient.Player.Username} disconnected! ID:{playerPedID}");
}
} }
#region SyncEntities #region SyncEntities
@ -798,7 +750,7 @@ namespace RageCoop.Server
{ {
Client client = Util.GetClientByID(ClientID); Client client = Util.GetClientByNetID(ClientID);
if (client == null) if (client == null)
{ {
return; return;
@ -812,14 +764,10 @@ namespace RageCoop.Server
if (c.NetID==client.NetID) { continue; } if (c.NetID==client.NetID) { continue; }
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync); MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
} }
if (RunningResource != null && packet.ID==client.Player.PedID)
{
RunningResource.InvokePlayerUpdate(client);
}
} }
private static void VehicleStateSync(Packets.VehicleStateSync packet, long ClientID) private static void VehicleStateSync(Packets.VehicleStateSync packet, long ClientID)
{ {
Client client = Util.GetClientByID(ClientID); Client client = Util.GetClientByNetID(ClientID);
if (client == null) if (client == null)
{ {
return; return;
@ -841,7 +789,7 @@ namespace RageCoop.Server
} }
private static void PedSync(Packets.PedSync packet, long ClientID) private static void PedSync(Packets.PedSync packet, long ClientID)
{ {
Client client = Util.GetClientByID(ClientID); Client client = Util.GetClientByNetID(ClientID);
if (client == null) if (client == null)
{ {
return; return;
@ -870,15 +818,10 @@ namespace RageCoop.Server
} }
MainNetServer.SendMessage(outgoingMessage,c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync); MainNetServer.SendMessage(outgoingMessage,c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
} }
if (RunningResource != null && isPlayer)
{
RunningResource.InvokePlayerUpdate(client);
}
} }
private static void VehicleSync(Packets.VehicleSync packet, long ClientID) private static void VehicleSync(Packets.VehicleSync packet, long ClientID)
{ {
Client client = Util.GetClientByID(ClientID); Client client = Util.GetClientByNetID(ClientID);
if (client == null) if (client == null)
{ {
return; return;
@ -907,7 +850,7 @@ namespace RageCoop.Server
} }
private static void ProjectileSync(Packets.ProjectileSync packet, long ClientID) private static void ProjectileSync(Packets.ProjectileSync packet, long ClientID)
{ {
Client client = Util.GetClientByID(ClientID); Client client = Util.GetClientByNetID(ClientID);
if (client == null) if (client == null)
{ {
return; return;
@ -926,39 +869,23 @@ namespace RageCoop.Server
// Send a message to targets or all players // Send a message to targets or all players
private static void SendChatMessage(Packets.ChatMessage packet, List<NetConnection> targets = null) private static void SendChatMessage(Packets.ChatMessage packet, List<NetConnection> targets = null)
{ {
if (RunningResource != null) if (packet.Message.StartsWith('/'))
{ {
if (packet.Message.StartsWith('/')) string[] cmdArgs = packet.Message.Split(" ");
string cmdName = cmdArgs[0].Remove(0, 1);
if (Commands.Any(x => x.Key.Name == cmdName))
{ {
string[] cmdArgs = packet.Message.Split(" "); string[] argsWithoutCmd = cmdArgs.Skip(1).ToArray();
string cmdName = cmdArgs[0].Remove(0, 1);
if (Commands.Any(x => x.Key.Name == cmdName)) CommandContext ctx = new()
{ {
string[] argsWithoutCmd = cmdArgs.Skip(1).ToArray(); Client = Clients.Values.Where(x => x.Player.Username == packet.Username).FirstOrDefault(),
Args = argsWithoutCmd
};
CommandContext ctx = new() KeyValuePair<Command, Action<CommandContext>> command = Commands.First(x => x.Key.Name == cmdName);
{
Client = Clients.Values.Where(x => x.Player.Username == packet.Username).FirstOrDefault(),
Args = argsWithoutCmd
};
KeyValuePair<Command, Action<CommandContext>> command = Commands.First(x => x.Key.Name == cmdName); if (command.Key.Usage != null && command.Key.ArgsLength != argsWithoutCmd.Length)
if (command.Key.Usage != null && command.Key.ArgsLength != argsWithoutCmd.Length)
{
NetConnection userConnection = Util.GetConnectionByUsername(packet.Username);
if (userConnection == default)
{
return;
}
SendChatMessage("Server", command.Key.Usage, userConnection);
return;
}
command.Value.Invoke(ctx);
}
else
{ {
NetConnection userConnection = Util.GetConnectionByUsername(packet.Username); NetConnection userConnection = Util.GetConnectionByUsername(packet.Username);
if (userConnection == default) if (userConnection == default)
@ -966,20 +893,26 @@ namespace RageCoop.Server
return; return;
} }
SendChatMessage("Server", "Command not found!", userConnection); SendChatMessage("Server", command.Key.Usage, userConnection);
return;
} }
return; command.Value.Invoke(ctx);
} }
else
if (RunningResource.InvokeChatMessage(packet.Username, packet.Message))
{ {
return; NetConnection userConnection = Util.GetConnectionByUsername(packet.Username);
if (userConnection == default)
{
return;
}
SendChatMessage("Server", "Command not found!", userConnection);
} }
return;
} }
packet.Message = packet.Message.Replace("~", ""); packet.Message = packet.Message.Replace("~", "");
SendChatMessage(packet.Username, packet.Message, targets); SendChatMessage(packet.Username, packet.Message, targets);
Program.Logger.Info(packet.Username + ": " + packet.Message); Program.Logger.Info(packet.Username + ": " + packet.Message);

View File

@ -7,7 +7,6 @@
public int MaxLatency { get; set; } = 500; public int MaxLatency { get; set; } = 500;
public string Name { get; set; } = "RAGECOOP server"; public string Name { get; set; } = "RAGECOOP server";
public string WelcomeMessage { get; set; } = "Welcome on this server :)"; public string WelcomeMessage { get; set; } = "Welcome on this server :)";
public string Resource { get; set; } = "";
public bool UPnP { get; set; } = true; public bool UPnP { get; set; } = true;
public bool AnnounceSelf { get; set; } = false; public bool AnnounceSelf { get; set; } = false;
public string MasterServer { get; set; } = "[AUTO]"; public string MasterServer { get; set; } = "[AUTO]";

View File

@ -47,7 +47,7 @@ namespace RageCoop.Server
}; };
} }
public static Client GetClientByID(long id) public static Client GetClientByNetID(long id)
{ {
Client result = null; Client result = null;
Server.Clients.TryGetValue(id,out result); Server.Clients.TryGetValue(id,out result);