Multiple server instances in one process is now possible

This commit is contained in:
Sardelka
2022-06-23 14:10:16 +08:00
parent 41385abc7d
commit 82e7cdd785
11 changed files with 302 additions and 294 deletions

View File

@ -128,7 +128,7 @@ namespace RageCoop.Client.Scripting
}
/// <summary>
/// Get a <see cref="Core.Logging.Logger"/> that RAGECOOP is currently using.
/// Get a <see cref="Core.Logger"/> that RAGECOOP is currently using.
/// </summary>
/// <returns></returns>
public static Logger GetLogger()

View File

@ -21,7 +21,7 @@ namespace RageCoop.Client.Scripting
public override void OnStop()
{
}
void SetAutoRespawn(CustomEventReceivedArgs args)
private void SetAutoRespawn(CustomEventReceivedArgs args)
{
API.Config.EnableAutoRespawn=(bool)args.Args[0];
}

View File

@ -14,6 +14,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
Args= Args ?? new List<object>(0);
message.Write((byte)PacketTypes.CustomEvent);
List<byte> result = new List<byte>();

View File

@ -48,7 +48,7 @@ namespace RageCoop.Core.Scripting
var r = new Resource()
{
Scripts = new List<IScriptable>(),
Name=Path.GetDirectoryName(path),
Name=Path.GetFileName(path),
Directory=path,
};
foreach (var f in Directory.GetFiles(path, "*.dll"))

View File

@ -42,6 +42,11 @@ namespace RageCoop.Server
}
public class Client
{
private readonly Server Server;
internal Client(Server server)
{
Server=server;
}
internal long NetID = 0;
public NetConnection Connection { get;internal set; }
public ServerPed Player { get; internal set; }
@ -111,7 +116,7 @@ namespace RageCoop.Server
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/*
@ -127,13 +132,13 @@ namespace RageCoop.Server
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
if (userConnection == null)
{
Program.Logger.Error($"[Client->SendNativeCall(ulong hash, params object[] args)]: Connection \"{NetID}\" not found!");
Server.Logger?.Error($"[Client->SendNativeCall(ulong hash, params object[] args)]: Connection \"{NetID}\" not found!");
return;
}
if (args != null && args.Length == 0)
{
Program.Logger.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing arguments!");
Server.Logger?.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing arguments!");
return;
}
@ -149,7 +154,7 @@ namespace RageCoop.Server
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/// <summary>
@ -166,13 +171,13 @@ namespace RageCoop.Server
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
if (userConnection == null)
{
Program.Logger.Error($"[Client->SendNativeResponse(Action<object> callback, ulong hash, Type type, params object[] args)]: Connection \"{NetID}\" not found!");
Server.Logger?.Error($"[Client->SendNativeResponse(Action<object> callback, ulong hash, Type type, params object[] args)]: Connection \"{NetID}\" not found!");
return;
}
if (args != null && args.Length == 0)
{
Program.Logger.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing arguments!");
Server.Logger?.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing arguments!");
return;
}
@ -202,7 +207,7 @@ namespace RageCoop.Server
}
else
{
Program.Logger.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing return type!");
Server.Logger?.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing return type!");
return;
}
@ -218,7 +223,7 @@ namespace RageCoop.Server
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
*/
@ -246,7 +251,7 @@ namespace RageCoop.Server
{
var argsList = new List<object>(args);
argsList.InsertRange(0, new object[] { (byte)TypeCode.Empty,(ulong)hash});
// Program.Logger.Debug(argsList.DumpWithType());
// Server.Logger?.Debug(argsList.DumpWithType());
SendCustomEvent(CustomEvents.NativeCall, argsList);
}
private int RequestNativeCallID<T>(Action<object> callback)
@ -273,7 +278,7 @@ namespace RageCoop.Server
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
if (userConnection == null)
{
Program.Logger.Error($"[Client->SendCleanUpWorld()]: Connection \"{NetID}\" not found!");
Server.Logger?.Error($"[Client->SendCleanUpWorld()]: Connection \"{NetID}\" not found!");
return;
}
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
@ -285,7 +290,7 @@ namespace RageCoop.Server
{
if (!IsReady)
{
Program.Logger.Warning($"Player \"{Username}\" is not ready!");
Server.Logger?.Warning($"Player \"{Username}\" is not ready!");
}
try
@ -302,7 +307,7 @@ namespace RageCoop.Server
}
catch (Exception ex)
{
Program.Logger.Error(ex);
Server.Logger?.Error(ex);
}
}
#endregion

View File

@ -9,10 +9,9 @@ namespace RageCoop.Server
class Program
{
public static bool ReadyToStop = false;
public static Core.Logger Logger;
static void Main(string[] args)
{
Logger=new Core.Logger()
var mainLogger= new Core.Logger()
{
LogPath="RageCoop.Server.log",
UseConsole=true,
@ -49,13 +48,15 @@ namespace RageCoop.Server
}
};
_ = new Server();
_ = new Server(mainLogger);
}
catch (Exception e)
{
Logger.Error(e);
Logger.Error($"Fatal error occurred, server shutting down.");
mainLogger.Error(e);
mainLogger.Error($"Fatal error occurred, server shutting down.");
Thread.Sleep(3000);
}
mainLogger.Dispose();
}
#if DEBUG

View File

@ -10,72 +10,69 @@ using System.Net;
namespace RageCoop.Server.Scripting
{
public static class API
public class APIEvents
{
#region INTERNAL
internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new();
internal Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new();
#endregion
public static class Events
{
#region DELEGATES
public delegate void EmptyEvent();
public delegate void PlayerConnect(Client client);
public delegate void PlayerDisconnect(Client client);
#endregion
public static event EventHandler<ChatEventArgs> OnChatMessage;
public static event EventHandler<HandshakeEventArgs> OnPlayerHandshake;
public static event PlayerConnect OnPlayerConnected;
public static event PlayerDisconnect OnPlayerDisconnected;
public event EventHandler<ChatEventArgs> OnChatMessage;
public event EventHandler<HandshakeEventArgs> OnPlayerHandshake;
/// <summary>
/// Will be invoked when a player is connected, but this player might not be ready yet(client resources not loaded), using <see cref="OnPlayerReady"/> is recommended.
/// </summary>
public event EventHandler<Client> OnPlayerConnected;
/// <summary>
/// Will be invoked after the client connected and all resources(if any) have been loaded.
/// </summary>
public event EventHandler<Client> OnPlayerReady;
public event EventHandler<Client> OnPlayerDisconnected;
/// <summary>
/// Will be invoked before registered handlers
/// </summary>
public static event EventHandler<OnCommandEventArgs> OnCommandReceived;
public event EventHandler<OnCommandEventArgs> OnCommandReceived;
/// <summary>
/// Invoked everytime a player's main ped has been updated
/// </summary>
public static event EventHandler<Client> OnPlayerUpdate;
/*
/// <summary>
/// This will be invoked when a CustomEvent is received from one client.
/// </summary>
public static event EventHandler<CustomEventReceivedArgs> OnCustomEventReceived;
*/
internal static void ClearHandlers()
public event EventHandler<Client> OnPlayerUpdate;
internal void ClearHandlers()
{
OnChatMessage=null;
OnPlayerHandshake=null;
OnPlayerConnected=null;
OnPlayerReady=null;
OnPlayerDisconnected=null;
// OnCustomEventReceived=null;
OnCommandReceived=null;
OnPlayerUpdate=null;
}
#region INVOKE
internal static void InvokeOnChatMessage(Packets.ChatMessage p,Client sender)
internal void InvokeOnChatMessage(Packets.ChatMessage p, Client sender)
{
OnChatMessage?.Invoke(this, new ChatEventArgs()
{
OnChatMessage?.Invoke(null,new ChatEventArgs() {
Sender=sender,
Message=p.Message
});
}
internal static void InvokePlayerConnected(Client client)
{ OnPlayerConnected?.Invoke(client); }
internal static void InvokePlayerDisconnected(Client client)
{ OnPlayerDisconnected?.Invoke(client); }
internal static void InvokePlayerHandshake(HandshakeEventArgs args)
{ OnPlayerHandshake?.Invoke(null, args); }
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 InvokePlayerHandshake(HandshakeEventArgs args)
{ OnPlayerHandshake?.Invoke(this, args); }
internal static void InvokeCustomEventReceived(Packets.CustomEvent p,Client sender)
internal void InvokeCustomEventReceived(Packets.CustomEvent p, Client sender)
{
var args = new CustomEventReceivedArgs() { Hash=p.Hash, Args=p.Args, Sender=sender };
List<Action<CustomEventReceivedArgs>> handlers;
if (CustomEventHandlers.TryGetValue(p.Hash,out handlers))
if (CustomEventHandlers.TryGetValue(p.Hash, out handlers))
{
handlers.ForEach((x) => { x.Invoke(args); });
}
}
internal static bool InvokeOnCommandReceived(string cname,string[] cargs,Client sender)
internal bool InvokeOnCommandReceived(string cname, string[] cargs, Client sender)
{
var args = new OnCommandEventArgs()
{
@ -83,16 +80,23 @@ namespace RageCoop.Server.Scripting
Args=cargs,
Sender=sender
};
OnCommandReceived?.Invoke(null,args);
OnCommandReceived?.Invoke(this, args);
return args.Cancel;
}
internal static void InvokePlayerUpdate(Client client)
internal void InvokePlayerUpdate(Client client)
{
OnPlayerUpdate?.Invoke(null,client);
OnPlayerUpdate?.Invoke(this, client);
}
#endregion
}
public class API
{
private readonly Server Server;
internal API(Server server)
{
Server=server;
}
public APIEvents Events { get; set; }=new APIEvents();
#region FUNCTIONS
/*
/// <summary>
@ -101,7 +105,7 @@ namespace RageCoop.Server.Scripting
/// </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)
public void SendNativeCallToAll(GTA.Native.Hash hash, params object[] args)
{
try
{
@ -112,7 +116,7 @@ namespace RageCoop.Server.Scripting
if (args != null && args.Length == 0)
{
Program.Logger.Error($"[ServerScript->SendNativeCallToAll(ulong hash, params object[] args)]: args is not null!");
Server.Logger?.Error($"[ServerScript->SendNativeCallToAll(ulong hash, params object[] args)]: args is not null!");
return;
}
@ -128,7 +132,7 @@ namespace RageCoop.Server.Scripting
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
*/
@ -136,7 +140,7 @@ namespace RageCoop.Server.Scripting
/// Get a list of all Clients
/// </summary>
/// <returns>All clients as a dictionary indexed by NetID</returns>
public static Dictionary<long, Client> GetAllClients()
public Dictionary<long, Client> GetAllClients()
{
return new(Server.Clients);
}
@ -146,7 +150,7 @@ namespace RageCoop.Server.Scripting
/// </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)
public Client GetClientByUsername(string username)
{
return Server.Clients.Values.FirstOrDefault(x => x.Username.ToLower() == username.ToLower());
}
@ -156,7 +160,7 @@ namespace RageCoop.Server.Scripting
/// </summary>
/// <param name="message">The chat message</param>
/// <param name="username">The username which send this message (default = "Server")</param>
public static void SendChatMessage(string message, List<Client> targets = null, string username = "Server")
public void SendChatMessage(string message, List<Client> targets = null, string username = "Server")
{
try
{
@ -172,10 +176,10 @@ namespace RageCoop.Server.Scripting
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
public static void SendChatMessage(string message, Client target, string username = "Server")
public void SendChatMessage(string message, Client target, string username = "Server")
{
try
{
@ -183,27 +187,29 @@ namespace RageCoop.Server.Scripting
}
catch (Exception e)
{
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
Server.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)
public void SendCleanUpWorldToAll(List<Client> clients = 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);
if (clients == null)
{
Server.MainNetServer.SendToAll(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Default);
}
else
{
clients.ForEach(client => { Server.MainNetServer.SendMessage(outgoingMessage,client.Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Default); });
}
}
/// <summary>
@ -213,7 +219,7 @@ namespace RageCoop.Server.Scripting
/// <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)
public void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
{
Server.RegisterCommand(name, usage, argsLength, callback);
}
@ -222,7 +228,7 @@ namespace RageCoop.Server.Scripting
/// </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)
public void RegisterCommand(string name, Action<CommandContext> callback)
{
Server.RegisterCommand(name, callback);
}
@ -231,18 +237,18 @@ namespace RageCoop.Server.Scripting
/// Register a class of commands
/// </summary>
/// <typeparam name="T">The name of your class with functions</typeparam>
public static void RegisterCommands<T>()
public void RegisterCommands<T>()
{
Server.RegisterCommands<T>();
}
/// <summary>
/// Send an event and data to the specified clients.
/// Send an event and data to the specified clients. Use <see cref="Client.SendCustomEvent(int, List{object})"/> if you want to send event to individual client.
/// </summary>
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">The objects conataing your data, supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string.</param>
/// <param name="targets">The target clients to send.</param>
public static void SendCustomEvent(int eventHash,List<object> args,List<Client> targets=null)
/// <param name="targets">The target clients to send. Leave it null to send to all clients</param>
public void SendCustomEvent(int eventHash,List<object> args,List<Client> targets=null)
{
targets ??= new(Server.Clients.Values);
var p = new Packets.CustomEvent()
@ -260,25 +266,25 @@ namespace RageCoop.Server.Scripting
/// </summary>
/// <param name="hash">An unique identifier of the event, you can hash your event name with <see cref="Core.Scripting.CustomEvents.Hash(string)"/></param>
/// <param name="handler">An handler to be invoked when the event is received from the server. This will be invoked from main thread.
public static void RegisterCustomEventHandler(int hash,Action<CustomEventReceivedArgs> handler)
public void RegisterCustomEventHandler(int hash,Action<CustomEventReceivedArgs> handler)
{
List<Action<CustomEventReceivedArgs>> handlers;
lock (CustomEventHandlers)
lock (Events.CustomEventHandlers)
{
if (!CustomEventHandlers.TryGetValue(hash,out handlers))
if (!Events.CustomEventHandlers.TryGetValue(hash,out handlers))
{
CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
Events.CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
}
handlers.Add(handler);
}
}
public static void RegisterCustomEventHandler(string name, Action<CustomEventReceivedArgs> handler)
public void RegisterCustomEventHandler(string name, Action<CustomEventReceivedArgs> handler)
{
RegisterCustomEventHandler(CustomEvents.Hash(name), handler);
}
public static Logger GetLogger()
public Logger GetLogger()
{
return Program.Logger;
return Server.Logger;
}
#endregion
}

View File

@ -11,9 +11,13 @@ namespace RageCoop.Server.Scripting
{
internal class Resources : ResourceLoader
{
public Resources() : base("RageCoop.Server.Scripting.ServerScript", Program.Logger) { }
private readonly Server Server;
public Resources(Server server) : base("RageCoop.Server.Scripting.ServerScript", server.Logger)
{
Server = server;
}
public static bool HasClientResources = false;
public bool HasClientResources = false;
public void LoadAll()
{
#region CLIENT
@ -23,7 +27,7 @@ namespace RageCoop.Server.Scripting
if (clientResources.Length!=0)
{
// Pack client side resources as a zip file
Logger.Info("Packing client-side resources");
Logger?.Info("Packing client-side resources");
try
{
@ -41,8 +45,8 @@ namespace RageCoop.Server.Scripting
}
catch (Exception ex)
{
Logger.Error("Failed to pack client resources");
Logger.Error(ex);
Logger?.Error("Failed to pack client resources");
Logger?.Error(ex);
}
}
#endregion
@ -52,7 +56,7 @@ namespace RageCoop.Server.Scripting
Directory.CreateDirectory(path);
foreach (var resource in Directory.GetDirectories(path))
{
Logger.Info($"Loading resource: {Path.GetFileName(resource)}");
Logger?.Info($"Loading resource: {Path.GetFileName(resource)}");
LoadResource(resource);
}
@ -61,14 +65,15 @@ namespace RageCoop.Server.Scripting
{
foreach (var d in LoadedResources)
{
foreach (var s in d.Scripts)
foreach (ServerScript s in d.Scripts)
{
(s as ServerScript).CurrentResource = d;
s.CurrentResource = d;
s.API=Server.API;
try
{
s.OnStart();
}
catch(Exception ex) {Logger.Error($"Failed to start resource: {d.Name}"); Logger.Error(ex); }
catch(Exception ex) {Logger?.Error($"Failed to start resource: {d.Name}"); Logger?.Error(ex); }
}
}
}
@ -104,9 +109,9 @@ namespace RageCoop.Server.Scripting
{
Task.Run(() =>
{
Logger.Info($"Sending resources to client:{client.Username}");
Logger?.Info($"Sending resources to client:{client.Username}");
Server.SendFile(path, "Resources.zip", client);
Logger.Info($"Resources sent to:{client.Username}");
Logger?.Info($"Resources sent to:{client.Username}");
});
}

View File

@ -3,7 +3,7 @@
namespace RageCoop.Server.Scripting
{
/// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded and <see cref="API"/> will be null, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary>
public abstract class ServerScript :Core.Scripting.IScriptable
{
@ -20,6 +20,8 @@ namespace RageCoop.Server.Scripting
/// Get the <see cref="Core.Scripting.Resource"/> object this script belongs to, this property will be initiated before <see cref="OnStart"/> (will be null if you access it in the constructor).
/// </summary>
public Core.Scripting.Resource CurrentResource { get;internal set; }
public API API { get; set; }
}
[AttributeUsage(AttributeTargets.Method, Inherited = false)]

View File

@ -26,24 +26,29 @@ namespace RageCoop.Server
internal class Server
{
private static readonly string _compatibleVersion = "V0_5";
public static BaseScript BaseScript { get; set; }=new BaseScript();
public static readonly Settings MainSettings = Util.Read<Settings>("Settings.xml");
public static NetServer MainNetServer;
private readonly string _compatibleVersion = "V0_5";
public BaseScript BaseScript { get; set; }=new BaseScript();
public readonly Settings MainSettings = Util.Read<Settings>("Settings.xml");
public NetServer MainNetServer;
public static readonly Dictionary<Command, Action<CommandContext>> Commands = new();
public static readonly Dictionary<long,Client> Clients = new();
private static System.Timers.Timer SendPlayerTimer = new System.Timers.Timer(5000);
public readonly Dictionary<Command, Action<CommandContext>> Commands = new();
public readonly Dictionary<long,Client> Clients = new();
private System.Timers.Timer SendPlayerTimer = new System.Timers.Timer(5000);
private static Dictionary<int,FileTransfer> InProgressFileTransfers=new();
private static Resources Resources = new Resources();
public Server()
private Dictionary<int,FileTransfer> InProgressFileTransfers=new();
private Resources Resources;
public API API;
public Logger Logger;
public Server(Logger logger=null)
{
Program.Logger.Info("================");
Program.Logger.Info($"Server bound to: 0.0.0.0:{MainSettings.Port}");
Program.Logger.Info($"Server version: {Assembly.GetCallingAssembly().GetName().Version}");
Program.Logger.Info($"Compatible RAGECOOP versions: {_compatibleVersion.Replace('_', '.')}.x");
Program.Logger.Info("================");
Logger=logger;
API=new API(this);
Resources=new Resources(this);
Logger?.Info("================");
Logger?.Info($"Server bound to: 0.0.0.0:{MainSettings.Port}");
Logger?.Info($"Server version: {Assembly.GetCallingAssembly().GetName().Version}");
Logger?.Info($"Compatible RAGECOOP versions: {_compatibleVersion.Replace('_', '.')}.x");
Logger?.Info("================");
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
NetPeerConfiguration config = new("623c92c287cc392406e7aaaac1c0f3b0")
@ -62,7 +67,7 @@ namespace RageCoop.Server
SendPlayerTimer.Elapsed+=(s,e) => { SendPlayerInfos(); };
SendPlayerTimer.AutoReset=true;
SendPlayerTimer.Enabled=true;
Program.Logger.Info(string.Format("Server listening on {0}:{1}", config.LocalAddress.ToString(), config.Port));
Logger?.Info(string.Format("Server listening on {0}:{1}", config.LocalAddress.ToString(), config.Port));
if (MainSettings.AnnounceSelf)
{
@ -89,11 +94,11 @@ namespace RageCoop.Server
string content = await response.Content.ReadAsStringAsync();
info = JsonConvert.DeserializeObject<IpInfo>(content);
Program.Logger.Info($"Your public IP is {info.Address}, announcing to master server...");
Logger?.Info($"Your public IP is {info.Address}, announcing to master server...");
}
catch (Exception ex)
{
Program.Logger.Error(ex.InnerException?.Message ?? ex.Message);
Logger?.Error(ex.InnerException?.Message ?? ex.Message);
return;
}
var realMaster = MainSettings.MasterServer=="[AUTO]" ? Util.DownloadString("https://ragecoop.online/stuff/masterserver") : MainSettings.MasterServer;
@ -115,7 +120,7 @@ namespace RageCoop.Server
}
catch (Exception ex)
{
Program.Logger.Error($"MasterServer: {ex.Message}");
Logger?.Error($"MasterServer: {ex.Message}");
// Sleep for 5s
Thread.Sleep(5000);
@ -124,19 +129,19 @@ namespace RageCoop.Server
if (response == null)
{
Program.Logger.Error("MasterServer: Something went wrong!");
Logger?.Error("MasterServer: Something went wrong!");
}
else if (response.StatusCode != HttpStatusCode.OK)
{
if (response.StatusCode == HttpStatusCode.BadRequest)
{
string requestContent = await response.Content.ReadAsStringAsync();
Program.Logger.Error($"MasterServer: [{(int)response.StatusCode}], {requestContent}");
Logger?.Error($"MasterServer: [{(int)response.StatusCode}], {requestContent}");
}
else
{
Program.Logger.Error($"MasterServer: [{(int)response.StatusCode}]");
Program.Logger.Error($"MasterServer: [{await response.Content.ReadAsStringAsync()}]");
Logger?.Error($"MasterServer: [{(int)response.StatusCode}]");
Logger?.Error($"MasterServer: [{await response.Content.ReadAsStringAsync()}]");
}
}
@ -146,15 +151,16 @@ namespace RageCoop.Server
}
catch (HttpRequestException ex)
{
Program.Logger.Error($"MasterServer: {ex.InnerException.Message}");
Logger?.Error($"MasterServer: {ex.InnerException.Message}");
}
catch (Exception ex)
{
Program.Logger.Error($"MasterServer: {ex.Message}");
Logger?.Error($"MasterServer: {ex.Message}");
}
}).Start();
#endregion
}
BaseScript.API=API;
BaseScript.OnStart();
Resources.LoadAll();
Listen();
@ -162,20 +168,27 @@ namespace RageCoop.Server
private void Listen()
{
Program.Logger.Info("Listening for clients");
Program.Logger.Info("Please use CTRL + C if you want to stop the server!");
Logger?.Info("Listening for clients");
Logger?.Info("Please use CTRL + C if you want to stop the server!");
while (!Program.ReadyToStop)
{
ProcessMessage(MainNetServer.WaitMessage(10));
try
{
ProcessMessage(MainNetServer.WaitMessage(200));
}
Program.Logger.Warning("Server is shutting down!");
catch(Exception ex)
{
Logger?.Error("Error processing message");
Logger?.Error(ex);
}
}
Logger?.Warning("Server is shutting down!");
MainNetServer.Shutdown("Server is shutting down!");
Program.Logger.Info("Waiting for resources to stop...Press Ctrl+C again to forcibly terminate the program.");
Logger?.Info("Waiting for resources to stop...Press Ctrl+C again to forcibly terminate the program.");
BaseScript.OnStop();
Resources.StopAll();
Program.Logger.Dispose();
}
Client sender;
@ -186,10 +199,10 @@ namespace RageCoop.Server
{
case NetIncomingMessageType.ConnectionApproval:
{
Program.Logger.Info($"New incoming connection from: [{message.SenderConnection.RemoteEndPoint}]");
Logger?.Info($"New incoming connection from: [{message.SenderConnection.RemoteEndPoint}]");
if (message.ReadByte() != (byte)PacketTypes.Handshake)
{
Program.Logger.Info($"IP [{message.SenderConnection.RemoteEndPoint.Address}] was blocked, reason: Wrong packet!");
Logger?.Info($"IP [{message.SenderConnection.RemoteEndPoint.Address}] was blocked, reason: Wrong packet!");
message.SenderConnection.Deny("Wrong packet!");
}
else
@ -206,7 +219,7 @@ namespace RageCoop.Server
}
catch (Exception e)
{
Program.Logger.Info($"IP [{message.SenderConnection.RemoteEndPoint.Address}] was blocked, reason: {e.Message}");
Logger?.Info($"IP [{message.SenderConnection.RemoteEndPoint.Address}] was blocked, reason: {e.Message}");
message.SenderConnection.Deny(e.Message);
}
}
@ -214,19 +227,27 @@ namespace RageCoop.Server
}
case NetIncomingMessageType.StatusChanged:
{
// Get sender client
if (!Clients.TryGetValue(message.SenderConnection.RemoteUniqueIdentifier, out sender))
{
break;
}
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
if (status == NetConnectionStatus.Disconnected)
{
long nethandle = message.SenderConnection.RemoteUniqueIdentifier;
SendPlayerDisconnectPacket(nethandle);
SendPlayerDisconnectPacket(sender);
}
else if (status == NetConnectionStatus.Connected)
{
SendPlayerConnectPacket(message.SenderConnection);
Resources.SendTo(Util.GetClientByNetID(message.SenderConnection.RemoteUniqueIdentifier));
SendPlayerConnectPacket(sender);
Resources.SendTo(sender);
API.Events.InvokePlayerConnected(sender);
if (sender.IsReady)
{
API.Events.InvokePlayerReady(sender);
}
}
break;
}
@ -291,7 +312,7 @@ namespace RageCoop.Server
Packets.ProjectileSync packet = new();
packet.Unpack(data);
ProjectileSync(packet, message.SenderConnection.RemoteUniqueIdentifier);
ProjectileSync(packet, sender);
}
break;
@ -329,7 +350,8 @@ namespace RageCoop.Server
toRemove.Cancel=true;
if (toRemove.Name=="Resources.zip")
{
Clients[message.SenderConnection.RemoteUniqueIdentifier].IsReady=true;
sender.IsReady=true;
API.Events.InvokePlayerReady(sender);
}
}
}
@ -339,14 +361,14 @@ namespace RageCoop.Server
{
// Sync Events
try
{
var toSend = MainNetServer.Connections.Exclude(message.SenderConnection);
if (toSend.Count!=0)
{
var outgoingMessage = MainNetServer.CreateMessage();
outgoingMessage.Write(btype);
outgoingMessage.Write(len);
outgoingMessage.Write(data);
var toSend = MainNetServer.Connections.Exclude(message.SenderConnection);
if (toSend.Count!=0)
{
MainNetServer.SendMessage(outgoingMessage, toSend, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.SyncEvents);
}
}
@ -357,7 +379,7 @@ namespace RageCoop.Server
}
else
{
Program.Logger.Error("Unhandled Data / Packet type");
Logger?.Error("Unhandled Data / Packet type");
}
break;
}
@ -370,32 +392,36 @@ namespace RageCoop.Server
}
case NetIncomingMessageType.ConnectionLatencyUpdated:
{
Client client = Util.GetClientByNetID(message.SenderConnection.RemoteUniqueIdentifier);
if (client != null)
// Get sender client
if (!Clients.TryGetValue(message.SenderConnection.RemoteUniqueIdentifier, out sender))
{
client.Latency = message.ReadFloat();
break;
}
if (sender != null)
{
sender.Latency = message.ReadFloat();
}
}
break;
case NetIncomingMessageType.ErrorMessage:
Program.Logger.Error(message.ReadString());
Logger?.Error(message.ReadString());
break;
case NetIncomingMessageType.WarningMessage:
Program.Logger.Warning(message.ReadString());
Logger?.Warning(message.ReadString());
break;
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.VerboseDebugMessage:
Program.Logger.Debug(message.ReadString());
Logger?.Debug(message.ReadString());
break;
default:
Program.Logger.Error(string.Format("Unhandled type: {0} {1} bytes {2} | {3}", message.MessageType, message.LengthBytes, message.DeliveryMethod, message.SequenceChannel));
Logger?.Error(string.Format("Unhandled type: {0} {1} bytes {2} | {3}", message.MessageType, message.LengthBytes, message.DeliveryMethod, message.SequenceChannel));
break;
}
MainNetServer.Recycle(message);
}
static object _sendPlayersLock=new object();
public static void SendPlayerInfos()
object _sendPlayersLock=new object();
public void SendPlayerInfos()
{
lock (_sendPlayersLock)
{
@ -422,9 +448,9 @@ namespace RageCoop.Server
private void DisconnectAndLog(NetConnection senderConnection,PacketTypes type, Exception e)
{
Program.Logger.Error($"Error receiving a packet of type {type}");
Program.Logger.Error(e.Message);
Program.Logger.Error(e.StackTrace);
Logger?.Error($"Error receiving a packet of type {type}");
Logger?.Error(e.Message);
Logger?.Error(e.StackTrace);
senderConnection.Disconnect(e.Message);
}
@ -432,7 +458,7 @@ namespace RageCoop.Server
// Before we approve the connection, we must shake hands
private void GetHandshake(NetConnection connection, Packets.Handshake packet)
{
Program.Logger.Debug("New handshake from: [Name: " + packet.Username + " | Address: " + connection.RemoteEndPoint.Address.ToString() + "]");
Logger?.Debug("New handshake from: [Name: " + packet.Username + " | Address: " + connection.RemoteEndPoint.Address.ToString() + "]");
if (!packet.ModVersion.StartsWith(_compatibleVersion))
{
@ -476,7 +502,7 @@ namespace RageCoop.Server
lock (Clients)
{
Clients.Add(connection.RemoteUniqueIdentifier,
tmpClient = new Client()
tmpClient = new Client(this)
{
NetID = connection.RemoteUniqueIdentifier,
Connection=connection,
@ -489,7 +515,7 @@ namespace RageCoop.Server
);;
}
Program.Logger.Debug($"Handshake sucess, Player:{packet.Username} PedID:{packet.PedID}");
Logger?.Debug($"Handshake sucess, Player:{packet.Username} PedID:{packet.PedID}");
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
// Create a new handshake packet
@ -504,93 +530,68 @@ namespace RageCoop.Server
}
// 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 void SendPlayerConnectPacket(Client newClient)
{
Client localClient = Util.GetClientByNetID(local.RemoteUniqueIdentifier);
if (localClient == null)
{
local.Disconnect("No data found!");
return;
}
List<NetConnection> clients=MainNetServer.Connections.Exclude(local);
// Send all players to local
if (clients.Count > 0)
// Send other players to this client
Clients.Values.ForEach(target =>
{
clients.ForEach(targetPlayer =>
{
long targetNetHandle = targetPlayer.RemoteUniqueIdentifier;
if (target==newClient) { return; }
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
Client targetClient = Util.GetClientByNetID(targetNetHandle);
if (targetClient != null)
{
new Packets.PlayerConnect()
{
// NetHandle = targetNetHandle,
Username = targetClient.Username,
PedID=targetClient.ID,
Username = target.Username,
PedID=target.ID,
}.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, local, NetDeliveryMethod.ReliableOrdered, 0);
}
MainNetServer.SendMessage(outgoingMessage, newClient.Connection, NetDeliveryMethod.ReliableOrdered, 0);
});
// Send local to all players
// Send new client to all players
var cons = MainNetServer.Connections.Exclude(newClient.Connection);
if (cons.Count!=0)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
new Packets.PlayerConnect()
{
PedID=localClient.ID,
Username = localClient.Username
PedID=newClient.ID,
Username = newClient.Username
}.Pack(outgoingMessage);
if(clients.Count > 0)
{
MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0);
}
}
API.Events.InvokePlayerConnected(localClient);
Program.Logger.Info($"Player {localClient.Username} connected!");
MainNetServer.SendMessage(outgoingMessage,cons , NetDeliveryMethod.ReliableOrdered, 0);
}
Logger?.Info($"Player {newClient.Username} connected!");
if (!string.IsNullOrEmpty(MainSettings.WelcomeMessage))
{
SendChatMessage(new Packets.ChatMessage() { Username = "Server", Message = MainSettings.WelcomeMessage }, null,new List<NetConnection>() { local });
SendChatMessage(new Packets.ChatMessage() { Username = "Server", Message = MainSettings.WelcomeMessage }, null,new List<NetConnection>() { newClient.Connection });
}
}
// Send all players a message that someone has left the server
private static void SendPlayerDisconnectPacket(long nethandle)
private void SendPlayerDisconnectPacket(Client localClient)
{
List<NetConnection> clients = MainNetServer.Connections.FindAll(x => x.RemoteUniqueIdentifier != nethandle);
int playerPedID = Clients[nethandle].ID;
if (clients.Count > 0)
var cons = MainNetServer.Connections.Exclude(localClient.Connection);
if (cons.Count!=0)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
new Packets.PlayerDisconnect()
{
PedID=playerPedID,
PedID=localClient.ID,
}.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0);
MainNetServer.SendMessage(outgoingMessage,cons , NetDeliveryMethod.ReliableOrdered, 0);
}
Client localClient = Util.GetClientByNetID( nethandle);
if (localClient == null)
{
return;
}
Clients.Remove(localClient.NetID);
API.Events.InvokePlayerDisconnected(localClient);
Program.Logger.Info($"Player {localClient.Username} disconnected! ID:{playerPedID}");
Logger?.Info($"Player {localClient.Username} disconnected! ID:{localClient.ID}");
Clients.Remove(localClient.NetID);
}
#region SyncEntities
private static void PedStateSync(Packets.PedStateSync packet, Client client)
private void PedStateSync(Packets.PedStateSync packet, Client client)
{
@ -604,7 +605,7 @@ namespace RageCoop.Server
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
private static void VehicleStateSync(Packets.VehicleStateSync packet, Client client)
private void VehicleStateSync(Packets.VehicleStateSync packet, Client client)
{
// Save the new data
if (packet.Passengers.ContainsValue(client.ID))
@ -620,7 +621,7 @@ namespace RageCoop.Server
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
private static void PedSync(Packets.PedSync packet, Client client)
private void PedSync(Packets.PedSync packet, Client client)
{
bool isPlayer = packet.ID==client.ID;
if (isPlayer)
@ -654,7 +655,7 @@ namespace RageCoop.Server
MainNetServer.SendMessage(outgoingMessage,c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
private static void VehicleSync(Packets.VehicleSync packet, Client client)
private void VehicleSync(Packets.VehicleSync packet, Client client)
{
bool isPlayer = packet.ID==client.Player.VehicleID;
foreach (var c in Clients.Values)
@ -678,13 +679,8 @@ namespace RageCoop.Server
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
private static void ProjectileSync(Packets.ProjectileSync packet, long ClientID)
private void ProjectileSync(Packets.ProjectileSync packet, Client client)
{
Client client = Util.GetClientByNetID(ClientID);
if (client == null)
{
return;
}
foreach (var c in Clients.Values)
{
@ -697,7 +693,7 @@ namespace RageCoop.Server
#endregion
// Send a message to targets or all players
private static void SendChatMessage(Packets.ChatMessage packet,Client sender=null, List<NetConnection> targets = null)
private void SendChatMessage(Packets.ChatMessage packet,Client sender=null, List<NetConnection> targets = null)
{
if (packet.Message.StartsWith('/'))
{
@ -722,13 +718,8 @@ namespace RageCoop.Server
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);
SendChatMessage("Server", command.Key.Usage,sender.Connection);
return;
}
@ -736,13 +727,8 @@ namespace RageCoop.Server
}
else
{
NetConnection userConnection = Util.GetConnectionByUsername(packet.Username);
if (userConnection == default)
{
return;
}
SendChatMessage("Server", "Command not found!", userConnection);
SendChatMessage("Server", "Command not found!", sender.Connection);
}
return;
@ -750,25 +736,26 @@ namespace RageCoop.Server
packet.Message = packet.Message.Replace("~", "");
SendChatMessage(packet.Username, packet.Message, targets);
Program.Logger.Info(packet.Username + ": " + packet.Message);
Logger?.Info(packet.Username + ": " + packet.Message);
}
public static void SendChatMessage(string username, string message, List<NetConnection> targets = null)
public void SendChatMessage(string username, string message, List<NetConnection> targets = null)
{
if (MainNetServer.Connections.Count==0) { return; }
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
new Packets.ChatMessage() { Username = username, Message = message }.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, targets ?? MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Chat);
}
public static void SendChatMessage(string username, string message, NetConnection target)
public void SendChatMessage(string username, string message, NetConnection target)
{
SendChatMessage(username, message, new List<NetConnection>() { target });
}
#endregion
public static void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
public void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
{
Command command = new(name) { Usage = usage, ArgsLength = argsLength };
@ -779,7 +766,7 @@ namespace RageCoop.Server
Commands.Add(command, callback);
}
public static void RegisterCommand(string name, Action<CommandContext> callback)
public void RegisterCommand(string name, Action<CommandContext> callback)
{
Command command = new(name);
@ -791,7 +778,7 @@ namespace RageCoop.Server
Commands.Add(command, callback);
}
public static void RegisterCommands<T>()
public void RegisterCommands<T>()
{
IEnumerable<MethodInfo> commands = typeof(T).GetMethods().Where(method => method.GetCustomAttributes(typeof(Command), false).Any());
@ -803,13 +790,13 @@ namespace RageCoop.Server
}
}
public static void SendFile(string path,string name,Client client,Action<float> updateCallback=null)
public void SendFile(string path,string name,Client client,Action<float> updateCallback=null)
{
int id = RequestFileID();
var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
fs.Seek(0, SeekOrigin.Begin);
var total = fs.Length;
Program.Logger.Debug($"Initiating file transfer:{name}, {total}");
Logger?.Debug($"Initiating file transfer:{name}, {total}");
FileTransfer transfer = new()
{
ID=id,
@ -835,7 +822,7 @@ namespace RageCoop.Server
if (thisRead!=chunk.Length)
{
if (thisRead==0) { break; }
Program.Logger.Trace($"Purging chunk:{thisRead}");
Logger?.Trace($"Purging chunk:{thisRead}");
Array.Resize(ref chunk, thisRead);
}
Send(
@ -858,10 +845,10 @@ namespace RageCoop.Server
);
fs.Close();
fs.Dispose();
Program.Logger.Debug($"All file chunks sent:{name}");
Logger?.Debug($"All file chunks sent:{name}");
InProgressFileTransfers.Remove(id);
}
private static int RequestFileID()
private int RequestFileID()
{
int ID = 0;
while ((ID==0)
@ -883,7 +870,7 @@ namespace RageCoop.Server
/// <param name="p"></param>
/// <param name="channel"></param>
/// <param name="method"></param>
public static void Send(Packet p,Client client, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
public void Send(Packet p,Client client, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
p.Pack(outgoingMessage);

View File

@ -47,7 +47,7 @@ namespace RageCoop.Server
_ => (0x0, null),
};
}
/*
public static Client GetClientByNetID(long id)
{
Client result = null;
@ -75,17 +75,18 @@ namespace RageCoop.Server
return Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == client.NetID);
}
*/
// Return a list of all connections but not the local connection
public static List<NetConnection> Exclude(this IEnumerable<NetConnection> connections,NetConnection toExclude)
{
return new(connections.Where(e => e != toExclude));
}
/*
public static List<NetConnection> FilterAllLocal(long local)
{
return new(Server.MainNetServer.Connections.Where(e => e.RemoteUniqueIdentifier != local));
}
*/
public static T Read<T>(string file) where T : new()
{
XmlSerializer ser = new(typeof(T));