diff --git a/SlipeServer.ConfigurationProviders/Configurations/XmlConfigurationProvider.cs b/SlipeServer.ConfigurationProviders/Configurations/XmlConfigurationProvider.cs index 239e55ac..3e5b28a2 100644 --- a/SlipeServer.ConfigurationProviders/Configurations/XmlConfigurationProvider.cs +++ b/SlipeServer.ConfigurationProviders/Configurations/XmlConfigurationProvider.cs @@ -1,6 +1,7 @@ using SlipeServer.Server; using SlipeServer.Server.Enums; using System; +using System.Collections.Generic; using System.Linq; using System.Xml; @@ -19,22 +20,30 @@ public XmlConfigurationProvider(string fileName) ushort result; + var startupResources = new List(); + foreach (XmlNode node in xmlConfig.FirstChild.ChildNodes) { switch (node.Name) { - case "serverName": + case "servername": this.Configuration.ServerName = node.InnerText; break; - case "host": - this.Configuration.Host = node.InnerText; + case "serverip": + if(node.InnerText == "auto" || node.InnerText == "any") + { + this.Configuration.Host = ""; + } else + { + this.Configuration.Host = node.InnerText; + } break; - case "port": + case "serverport": if (ushort.TryParse(node.InnerText, out result)) this.Configuration.Port = result; break; - case "maxPlayers": + case "maxplayers": if (ushort.TryParse(node.InnerText, out result)) this.Configuration.MaxPlayerCount = result; @@ -43,19 +52,19 @@ public XmlConfigurationProvider(string fileName) this.Configuration.Password = node.InnerText; break; - case "httpPort": + case "httpport": this.Configuration.HttpPort = ushort.Parse(node.InnerText); break; - case "httpUrl": + case "httpurl": this.Configuration.HttpUrl = node.InnerText; break; - case "httpHost": + case "httphost": this.Configuration.HttpHost = node.InnerText; break; - case "httpConnectionsPerClient": + case "httpmaxconnectionsperclient": this.Configuration.HttpConnectionsPerClient = int.Parse(node.InnerText); break; @@ -102,7 +111,29 @@ public XmlConfigurationProvider(string fileName) case "IsVoiceEnabled": this.Configuration.IsVoiceEnabled = bool.Parse(node.InnerText); break; + + case "resource": + var startupResource = new StartupResource(); + foreach (XmlAttribute item in node.Attributes) + { + switch (item.Name) + { + case "src": + startupResource.Name = item.Value; + break; + case "startup": + startupResource.Start = item.Value == "1" || item.Value == "true"; + break; + case "protected": + startupResource.Protected = item.Value == "1" || item.Value == "true"; + break; + } + } + startupResources.Add(startupResource); + break; } } + + this.Configuration.StartupResources = startupResources.ToArray(); } } diff --git a/SlipeServer.Console/test.lua b/SlipeServer.Console/test.lua index 76214453..1510884f 100644 --- a/SlipeServer.Console/test.lua +++ b/SlipeServer.Console/test.lua @@ -28,6 +28,15 @@ print("md5 test: ", md5("qwerty") == "D8578EDF8458CE06FBC5BB76A58C5CA4"); print("sha256 test: ", sha256("qwerty") == "65E84BE33532FB784C48129675F9EFF3A682B27168C0EA744B2CF58EE02337C5"); print("custom definitions:",add(2,2), substract(2,2)) + + -- Begin blips + local blip = createBlip(100, 0,0, 51, 0, 0, 0, 255); + print("blip color:", getBlipColor(blip)) + print("blip icon:", getBlipIcon(blip)) + print("blip ordering:", getBlipOrdering(blip)) + print("blip size:", getBlipSize(blip)) + print("blip visible distance:", getBlipVisibleDistance(blip)) + -- End blips end createExplosion(-10, 0,0, 4); diff --git a/SlipeServer.Hosting/SlipeServer.Hosting.csproj b/SlipeServer.Hosting/SlipeServer.Hosting.csproj index bb04be80..442e7ad9 100644 --- a/SlipeServer.Hosting/SlipeServer.Hosting.csproj +++ b/SlipeServer.Hosting/SlipeServer.Hosting.csproj @@ -8,7 +8,7 @@ - + diff --git a/SlipeServer.Legacy/CustomConsoleFormatter.cs b/SlipeServer.Legacy/CustomConsoleFormatter.cs new file mode 100644 index 00000000..9264b77f --- /dev/null +++ b/SlipeServer.Legacy/CustomConsoleFormatter.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging.Console; +using Microsoft.Extensions.Logging.Abstractions; + +namespace SlipeServer.Legacy; + +public class CustomConsoleFormatter : ConsoleFormatter +{ + private readonly SimpleConsoleFormatterOptions formatterOptions; + + public CustomConsoleFormatter(IOptionsMonitor options) + : base("customFormatter") + { + this.formatterOptions = new SimpleConsoleFormatterOptions + { + TimestampFormat = "[HH:mm:ss] " + }; + } + + public override void Write(in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter) + { + if (textWriter == null) + { + throw new ArgumentNullException(nameof(textWriter)); + } + + var timestamp = DateTime.Now.ToString(this.formatterOptions.TimestampFormat); + textWriter.Write(timestamp); + textWriter.Write(logEntry.Formatter(logEntry.State, logEntry.Exception)); + textWriter.Write(Environment.NewLine); + } +} diff --git a/SlipeServer.Legacy/InteractiveServerConsole.cs b/SlipeServer.Legacy/InteractiveServerConsole.cs new file mode 100644 index 00000000..7d803dea --- /dev/null +++ b/SlipeServer.Legacy/InteractiveServerConsole.cs @@ -0,0 +1,38 @@ +namespace SlipeServer.Legacy; + +public delegate void ConsoleCommandCallback(string arguments); + +public class InteractiveServerConsole +{ + private readonly Dictionary commands = []; + + public IEnumerable Commands => commands.Keys; + + public InteractiveServerConsole() + { + + } + + public bool AddCommand(string command, ConsoleCommandCallback callback) + { + if (this.commands.ContainsKey(command)) + return false; + + this.commands[command] = callback; + return true; + } + + public void ExecuteCommand(string command) + { + var splt = command.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); + + var commandName = splt[0]; + if (this.commands.TryGetValue(commandName, out var callback)){ + + if (splt.Length == 2) + callback(splt[1]); + else + callback(""); + } + } +} diff --git a/SlipeServer.Legacy/LegacyConsoleCommandsService.cs b/SlipeServer.Legacy/LegacyConsoleCommandsService.cs new file mode 100644 index 00000000..5619a1f3 --- /dev/null +++ b/SlipeServer.Legacy/LegacyConsoleCommandsService.cs @@ -0,0 +1,192 @@ +using Microsoft.Extensions.Logging; +using SlipeServer.Scripting; +using SlipeServer.Server.Resources; +using SlipeServer.Server.Resources.Providers; +using SlipeServer.Server.Services; + +namespace SlipeServer.Legacy; + +internal sealed class LegacyConsoleCommandsService : IHostedService +{ + private readonly ILogger logger; + private readonly InteractiveServerConsole serverConsole; + private readonly IResourceProvider resourceProvider; + private readonly ClientResourceService clientResourceService; + private readonly ServerResourceService serverResourcesService; + private readonly ChatBox chatBox; + + public LegacyConsoleCommandsService(ILogger logger, InteractiveServerConsole serverConsole, IResourceProvider resourceProvider, ClientResourceService clientResourceService, ServerResourceService serverResourcesService, ChatBox chatBox) + { + this.logger = logger; + this.serverConsole = serverConsole; + this.resourceProvider = resourceProvider; + this.clientResourceService = clientResourceService; + this.serverResourcesService = serverResourcesService; + this.chatBox = chatBox; + } + + private void HandleStartCommand(string name) + { + var serverResource = this.serverResourcesService.GetResource(name); + + if(serverResource == null) + { + this.logger.LogInformation("* Syntax: start "); + return; + } + + this.logger.LogInformation("start: Requested by Console"); + + try + { + if (this.serverResourcesService.StartResource(name)) + { + this.logger.LogInformation("start: Resource '{resourceName}' started", name); + } else + { + this.logger.LogWarning("start: Resource is already running"); + } + } + catch(Exception ex) + { + Console.WriteLine(ex); + this.logger.LogError(ex, "Failed to start resource {resourceName}", name); + } + } + + private void HandleStopCommand(string name) + { + var resource = this.serverResourcesService.GetResource(name); + + if(resource == null) + { + this.logger.LogInformation("* Syntax: stop "); + return; + } + + this.logger.LogInformation("stop: Requested by Console"); + this.logger.LogInformation("stop: Resource stopping"); + try + { + this.serverResourcesService.StopResource(name); + } + catch(Exception ex) + { + this.logger.LogError(ex, "Failed to stop resource {resourceName}", name); + } + } + + private void HandleRestartCommand(string name) + { + var resource = this.serverResourcesService.GetResource(name); + + if(resource == null) + { + this.logger.LogInformation("* Syntax: stop "); + return; + } + + this.logger.LogInformation("restart: Requested by Console"); + this.logger.LogInformation("restart: Resource restarting"); + try + { + if (this.serverResourcesService.StopResource(name)) + { + this.serverResourcesService.StartResource(name); + } else + { + this.logger.LogWarning("restart: Resource is not running"); + } + } + catch(Exception ex) + { + Console.WriteLine(ex); + this.logger.LogError(ex, "Failed to restart resource {resourceName}", name); + } + } + + private void HandleRefreshCommand(string name) + { + var resource = this.serverResourcesService.GetResource(name); + + if(resource == null) + { + this.logger.LogInformation("* Syntax: stop "); + return; + } + + try + { + this.resourceProvider.Refresh(); + } + catch(Exception ex) + { + this.logger.LogError(ex, "Failed to stop resource {resourceName}", name); + } + } + + private void HandleSayCommand(string text) + { + this.logger.LogInformation("CONSOLECHAT: {0}", text); + this.chatBox.Output(text, System.Drawing.Color.FromArgb(223, 149, 232)); + } + + private void HandleListCommand(string text) + { + int count = 0; + int failedCount = 0; + int runningCount = 0; + this.logger.LogInformation("== Resource list=="); + foreach (var resource in this.resourceProvider.GetResources()) + { + var resourceState = this.serverResourcesService.GetResourceState(resource.Name); + switch (resourceState) + { + case ResourceState.Loaded: + count++; + this.logger.LogInformation(" {resourceName} STOPPED ({resourcesFilesCount} files)", resource.Name.PadRight(20), resource.Files.Count); + break; + case ResourceState.Running: + runningCount++; + this.logger.LogInformation(" {resourceName} RUNNING", resource.Name.PadRight(20)); + break; + } + this.chatBox.Output(text, System.Drawing.Color.FromArgb(223, 149, 232)); + } + this.logger.LogInformation("== Resources: {loadedResources} loaded, {failedToStartResources} failed, {runningResources} running ==", count, failedCount, runningCount); + } + + private void HandleHelpCommand(string text) + { + this.logger.LogInformation("help [command]"); + this.logger.LogInformation("Available commands:"); + Console.WriteLine(); + + var commands = this.serverConsole.Commands.ToArray(); + for (int i = 0; i < commands.Length; i += 3) + { + string A = commands[i]; + string B = (i + 1 < commands.Length) ? commands[i + 1] : string.Empty; + string C = (i + 2 < commands.Length) ? commands[i + 2] : string.Empty; + + Console.WriteLine(string.Format("{0,-25} {1,-25} {2,-25}", A, B, C)); + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + this.serverConsole.AddCommand("start", HandleStartCommand); + this.serverConsole.AddCommand("stop", HandleStopCommand); + this.serverConsole.AddCommand("restart", HandleRestartCommand); + this.serverConsole.AddCommand("refresh", HandleRefreshCommand); + this.serverConsole.AddCommand("say", HandleSayCommand); + this.serverConsole.AddCommand("list", HandleListCommand); + this.serverConsole.AddCommand("help", HandleHelpCommand); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/SlipeServer.Legacy/LegacyMtaServer.cs b/SlipeServer.Legacy/LegacyMtaServer.cs new file mode 100644 index 00000000..9d7c1a4d --- /dev/null +++ b/SlipeServer.Legacy/LegacyMtaServer.cs @@ -0,0 +1,112 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using SlipeServer.Server.Resources.Serving; +using SlipeServer.ConfigurationProviders.Configurations; +using SlipeServer.Lua; +using SlipeServer.Server.Resources.Interpreters; +using Microsoft.Extensions.Logging.Console; +using SlipeServer.Server.Resources; + +namespace SlipeServer.Legacy; + +public class LegacyMtaServer +{ + private readonly IHostBuilder hostBuilder; + private readonly Configuration configuration; + + public LegacyMtaServer() + { + this.hostBuilder = Host.CreateDefaultBuilder(); + + var mtaServerConfiguration = new XmlConfigurationProvider("mods/deathmatch/mtaserver.conf"); + + this.configuration = mtaServerConfiguration.Configuration; + + this.hostBuilder.ConfigureServices((hostBuilderContext, services) => + { + if (hostBuilderContext.HostingEnvironment.IsDevelopment()) + { + var currentDirectory = Directory.GetCurrentDirectory(); + var projectDirectory = Directory.GetParent(currentDirectory).Parent.Parent.FullName; + + this.configuration.ResourceDirectory = Path.Join(projectDirectory, "mods/deathmatch/resources"); + } else + { + this.configuration.ResourceDirectory = "mods/deathmatch/resources"; + } + services.AddLogging(configure => + { + configure.AddConsole(options => + { + options.FormatterName = "customFormatter"; + }); + configure.AddConsoleFormatter(); + }); + + services.AddSingleton(); + services.AddSingleton(); + services.AddLua(); + services.AddHttpClient(); + services.AddDefaultMtaServerServices(); + services.AddResourceInterpreter(); + + services.AddSingleton(); + services.AddHostedService(); + services.AddHostedService(); + + services.TryAddSingleton(x => x.GetRequiredService>()); + }); + + this.hostBuilder.AddMtaServer(serverBuilder => + { + serverBuilder.UseConfiguration(configuration!); + serverBuilder.AddHostedDefaults(exceptBehaviours: ServerBuilderDefaultBehaviours.MasterServerAnnouncementBehaviour); + }); + } + + public async Task BuildAndRunAsync(CancellationToken cancellationToken = default) + { + var app = this.hostBuilder.Build(); + Console.WriteLine("=================================================================="); + Console.WriteLine("= Multi Theft Auto: San Andreas v1.6"); + Console.WriteLine("= Server name : {0}", this.configuration.ServerName); + Console.WriteLine("= Server IP address: {0}", this.configuration.Host); + Console.WriteLine("= Server port : {0}", this.configuration.Port); + Console.WriteLine("="); + Console.WriteLine("= Log file : {0}", "none"); + Console.WriteLine("= Maximum players : {0}", this.configuration.MaxPlayerCount); + Console.WriteLine("= HTTP port : {0}", this.configuration.HttpPort); + Console.WriteLine("= Voice Chat : {0}", this.configuration.IsVoiceEnabled ? "Enabled" : "Disabled"); + Console.WriteLine("= Bandwidth saving : {0}", "TODO"); + Console.WriteLine("=================================================================="); + + var serverConsole = app.Services.GetRequiredService(); + + var _ = Task.Run(() => + { + try + { + while (true) + { + try + { + var line = Console.ReadLine(); + if(line != null) + { + serverConsole.ExecuteCommand(line); + } + } + catch (Exception ex) + { + + } + } + } + catch(Exception ex) + { + + } + }); + await app.RunAsync(cancellationToken); + } +} diff --git a/SlipeServer.Legacy/LegacyServerService.cs b/SlipeServer.Legacy/LegacyServerService.cs new file mode 100644 index 00000000..204c867e --- /dev/null +++ b/SlipeServer.Legacy/LegacyServerService.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.Logging; +using SlipeServer.Lua; +using SlipeServer.Scripting; +using SlipeServer.Server; +using SlipeServer.Server.Resources; +using SlipeServer.Server.Resources.Interpreters; +using SlipeServer.Server.Resources.Providers; + +namespace SlipeServer.Legacy; + +internal sealed class LegacyServerService : IHostedService +{ + private readonly IResourceProvider resourceProvider; + private readonly IEnumerable resourceInterpreters; + private readonly ILogger logger; + private readonly MtaServer mtaServer; + private readonly ClientResourceService resourceService; + private readonly ServerResourceService serverResourcesService; + private readonly IScriptEventRuntime scriptEventRuntime; + private readonly LuaService luaService; + + public LegacyServerService(IResourceProvider resourceProvider, IEnumerable resourceInterpreters, ILogger logger, MtaServer mtaServer, ClientResourceService resourceService, ServerResourceService serverResourcesService, IScriptEventRuntime scriptEventRuntime, LuaService luaService) + { + foreach (var resourceInterpreter in resourceInterpreters) + resourceProvider.AddResourceInterpreter(resourceInterpreter); + + this.resourceProvider = resourceProvider; + this.resourceInterpreters = resourceInterpreters; + this.logger = logger; + this.mtaServer = mtaServer; + this.resourceService = resourceService; + this.serverResourcesService = serverResourcesService; + this.scriptEventRuntime = scriptEventRuntime; + this.luaService = luaService; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + this.scriptEventRuntime.LoadDefaultEvents(); + this.luaService.LoadDefaultDefinitions(); + + this.resourceProvider.Refresh(); + var resources = this.resourceProvider.GetResources().ToArray(); + + foreach (var pair in this.resourceProvider.GetServerResourcesFiles()) + { + this.serverResourcesService.Create(pair.Value, resources.First(x => x.Name == pair.Value.Name)); + } + + this.logger.LogInformation("Resources: {loadedResourcesCount} loaded, {failedResourcesCount} failed", resources.Length, 0); + if (this.mtaServer.HasPassword) + { + this.logger.LogInformation("Server password set to '{serverPassword}'", mtaServer.Password); + } + this.logger.LogInformation("Starting resources..."); + + foreach (var startupResource in this.mtaServer.Configuration.StartupResources) + { + var resource = resources.FirstOrDefault(x => x.Name == startupResource.Name); + if(resource != null) + { + this.resourceService.StartResource(startupResource.Name); + } else + { + this.logger.LogWarning("ERROR: Couldn't find resource {resourceName}. Check it exists.", startupResource.Name); + } + } + + this.logger.LogInformation("Server started and is ready to accept connections!"); + this.logger.LogInformation("To stop the server, type 'shutdown' or press Ctrl-C"); + this.logger.LogInformation("Type 'help' for a list of commands."); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this.mtaServer.Stop(); + return Task.CompletedTask; + } +} diff --git a/SlipeServer.Legacy/SlipeServer.Legacy.csproj b/SlipeServer.Legacy/SlipeServer.Legacy.csproj new file mode 100644 index 00000000..f86c4d31 --- /dev/null +++ b/SlipeServer.Legacy/SlipeServer.Legacy.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + diff --git a/SlipeServer.LegacyServer/Program.cs b/SlipeServer.LegacyServer/Program.cs new file mode 100644 index 00000000..058985ff --- /dev/null +++ b/SlipeServer.LegacyServer/Program.cs @@ -0,0 +1,4 @@ +using SlipeServer.Legacy; + +var server = new LegacyMtaServer(); +await server.BuildAndRunAsync(); diff --git a/SlipeServer.LegacyServer/Properties/launchSettings.json b/SlipeServer.LegacyServer/Properties/launchSettings.json new file mode 100644 index 00000000..e8f9c825 --- /dev/null +++ b/SlipeServer.LegacyServer/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "SlipeServer.LegacyServer": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/SlipeServer.LegacyServer/SlipeServer.LegacyServer.csproj b/SlipeServer.LegacyServer/SlipeServer.LegacyServer.csproj new file mode 100644 index 00000000..ee81dcd5 --- /dev/null +++ b/SlipeServer.LegacyServer/SlipeServer.LegacyServer.csproj @@ -0,0 +1,40 @@ + + + + Exe + net8.0 + enable + enable + + + + x86 + + + + x86 + + + + x86 + + + + x86 + + + + x64 + + + + + + + + + Always + + + + \ No newline at end of file diff --git a/SlipeServer.LegacyServer/mods/deathmatch/mtaserver.conf b/SlipeServer.LegacyServer/mods/deathmatch/mtaserver.conf new file mode 100644 index 00000000..c66b4ab2 --- /dev/null +++ b/SlipeServer.LegacyServer/mods/deathmatch/mtaserver.conf @@ -0,0 +1,308 @@ + + + + Default MTA Server + + + + + + + auto + + + + 22003 + + + 32 + + + 22005 + + + + + + 5 + + + 20 + + + + + + none + + + + + + + + + + + + + + + + 1 + + + + + + 1 + + + 0 + + + + + + medium + + + + 100 + + 1500 + + 500 + + 400 + + 2000 + + 400 + + 100 + + 100 + + + 1 + + + 0 + + + 150 + + + 0 + + + server-id.keys + + + logs/server.log + + + logs/server_auth.log + + + logs/db.log + + + + + + acl.xml + + + logs/scripts.log + + + 0 + + + 0 + + + 1 + + + 74 + + + 0 + + + 1 + + + 4 + + + + + + backups + + + 3 + + + 5 + + + 1 + + + 1 + + + Admin + + + 1 + + + 127.0.0.1 + + + 1 + + + 1000 + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SlipeServer.LegacyServer/mods/deathmatch/resources/hello/c.lua b/SlipeServer.LegacyServer/mods/deathmatch/resources/hello/c.lua new file mode 100644 index 00000000..881425c8 --- /dev/null +++ b/SlipeServer.LegacyServer/mods/deathmatch/resources/hello/c.lua @@ -0,0 +1,2 @@ +print("hello client"); +outputChatBox("hello client"); \ No newline at end of file diff --git a/SlipeServer.LegacyServer/mods/deathmatch/resources/hello/meta.xml b/SlipeServer.LegacyServer/mods/deathmatch/resources/hello/meta.xml new file mode 100644 index 00000000..47a9e465 --- /dev/null +++ b/SlipeServer.LegacyServer/mods/deathmatch/resources/hello/meta.xml @@ -0,0 +1,4 @@ + +