diff --git a/COTLMP.sln b/COTLMP.sln index f545747..29cfdec 100644 --- a/COTLMP.sln +++ b/COTLMP.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.14.36310.24 +# Visual Studio Version 18 +VisualStudioVersion = 18.5.11723.231 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "COTLMP", "COTLMP\COTLMP.csproj", "{75CAB2D0-F823-DEFE-5EF0-A25BC117280F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "COTLMPServer", "COTLMPServer\COTLMPServer.csproj", "{9476AA40-C94F-43C7-BF81-9427F4329E8F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DedicatedServer", "DedicatedServer\DedicatedServer.csproj", "{1D6C6C2A-38F4-4574-918D-24A33F1FB8C6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {9476AA40-C94F-43C7-BF81-9427F4329E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU {9476AA40-C94F-43C7-BF81-9427F4329E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9476AA40-C94F-43C7-BF81-9427F4329E8F}.Release|Any CPU.Build.0 = Release|Any CPU + {1D6C6C2A-38F4-4574-918D-24A33F1FB8C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D6C6C2A-38F4-4574-918D-24A33F1FB8C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D6C6C2A-38F4-4574-918D-24A33F1FB8C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D6C6C2A-38F4-4574-918D-24A33F1FB8C6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/COTLMP/COTLMP.csproj b/COTLMP/COTLMP.csproj index abfb6fa..00208d6 100644 --- a/COTLMP/COTLMP.csproj +++ b/COTLMP/COTLMP.csproj @@ -5,6 +5,7 @@ COTLMP Cult of the Lamb Multiplayer Mod Plug-In 0.0.0.5 + Copyright (c) 2026 GeoB99 & Neco-Arc true latest diff --git a/COTLMPServer/COTLMPServer.csproj b/COTLMPServer/COTLMPServer.csproj index 939a2ed..220779e 100644 --- a/COTLMPServer/COTLMPServer.csproj +++ b/COTLMPServer/COTLMPServer.csproj @@ -2,6 +2,10 @@ netstandard2.0 + Cult of the Lamb Core Server Library + 0.0.0.5 + Copyright (c) 2026 GeoB99 & Neco-Arc + COTLMPServer diff --git a/DedicatedServer/Cli.cs b/DedicatedServer/Cli.cs new file mode 100644 index 0000000..08d8443 --- /dev/null +++ b/DedicatedServer/Cli.cs @@ -0,0 +1,329 @@ +/* + * PROJECT: Cult of the Lamb Multiplayer Mod + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Core dedicated server CLI parser + * COPYRIGHT: Copyright 2026 GeoB99 + */ + +/* IMPORTS ********************************************************************/ + +using System; +using System.IO; +using System.Reflection; +using CommandLine; +using Newtonsoft.Json; + +/* CLASSES & CODE *************************************************************/ + +namespace DedicatedServer +{ + internal static class Cli + { + internal static ConsoleLogger Logger; + internal static bool HasOptions = true; + internal static bool HasConfigFile = true; + internal const string ConfigFile = "ServerSettings.json"; + internal static string PathLocation = Directory.GetCurrentDirectory(); + internal static Version ServerVersion = Assembly.GetExecutingAssembly().GetName().Version; + + /// + /// Enumeration class for the supported game mode values. + /// + private enum GameModes + { + Standard = 0, + Deathmatch, + BossFight, + Zombies + } + + /// + /// Translates the game mode enum value to a readable string. + /// + /// The game mode enum value to be passed. + /// Returns the name string of the game mode. + private static string TranslateGameModeToString(GameModes Modes) + { + string Mode; + + switch (Modes) + { + case GameModes.Standard: + { + Mode = "Standard"; + break; + } + + case GameModes.Deathmatch: + { + Mode = "Deathmatch"; + break; + } + + case GameModes.BossFight: + { + Mode = "Boss Fight"; + break; + } + + case GameModes.Zombies: + { + Mode = "Zombies"; + break; + } + + default: + { + Mode = null; + break; + } + } + + return Mode; + } + + /// + /// Initializes the server configuration options to defaults. + /// The dedicated server writes the defaults only if the user hasn't provided any other server option. + /// + /// Returns the default server config data to the caller. + private static ServerConfig InitializeConfigDefaults() + { + ServerConfig Config; + + Config = new ServerConfig + { + PortNumber = 36963, + ServerName = "Cult of The Lamb Server", + MaxPlayers = 12, + Password = null, + GameMode = 0 + }; + + return Config; + } + + /// + /// Validates the server argument options passed by the parser. + /// + /// The dedicated server options class of which option parameters are to be validated. + /// Returns true if the options are valid, false otherwise. + private static bool ValidateParams(DedicatedServerOptions Options) + { + int CharIndex; + + /* There's no options passed to validate */ + if (!HasOptions) + { + return true; + } + + /* Validate the required options */ + if ((Options.PortNumber == 0 || Options.PortNumber > 65535) || + string.IsNullOrEmpty(Options.ServerName) || + (Options.MaxPlayers == 0 || Options.MaxPlayers > 12)) + { + return false; + } + + /* + * Validate the optional options (game mode must be one of the + * valid mode values and the password mustn't have white spaces). + */ + if (Options.GameMode > (uint)GameModes.Zombies) + { + return false; + } + + if (!string.IsNullOrWhiteSpace(Options.Password)) + { + for (CharIndex = 0; CharIndex < Options.Password.Length; CharIndex++) + { + if (Char.IsWhiteSpace(Options.Password[CharIndex])) + { + return false; + } + } + } + + return true; + } + + /// + /// Starts the standalone server. + /// + /// An object to a server configuration class that contains server data used to start the server. + private static void StartServer(ServerConfig Config) + { + // TODO: Implement this when the Server class interface is implemented + // FIXME: Log to the console the IP address (from COTLMPServer) of the server being started + Logger.LogInfo("The server has been started! Type \u001b[94mquit\x1b[97m into the console to gracefully shutdown the server."); + } + + /// + /// Creates (or loads) the server configuration file upon server startup. + /// + /// The dedicated server options needed to start the create the server config file. + private static void SetupConfigServer(DedicatedServerOptions Options) + { + ServerConfig Config; + string AbsolutePath, JsonData; + + /* Bail out if the server options are not valid */ + if (!ValidateParams(Options)) + { + Logger.LogFatal("Failed to create the server, at least one of the option parameters aren't valid:\n" + + $"\n---> Port number expected between 1 and 65535 range, got {Options.PortNumber}" + + "\n---> Server name expected to be non-null" + + $"\n---> Maximum players expected to be up to 12, got {Options.MaxPlayers}" + + $"\n---> Game mode expected to be within supported game modes, got {Options.GameMode}" + + "\n---> Password expected to not have white spaces\n"); + return; + } + + /* + * The server config file never existed, create one based on the provided + * server options. Or write down our defaults in case no options were provided. + */ + AbsolutePath = Path.Combine(PathLocation, ConfigFile); + if (!HasConfigFile) + { + if (!HasOptions) + { + Config = InitializeConfigDefaults(); + } + else + { + Logger.LogInfo($"No \x1b[94m{ConfigFile}\x1b[97m file could be found, creating server configuration file with provided options on first run..."); + + /* + * HACK: Always hardcode the game mode to Standard if other mode was submitted. + * Because we don't support any other game modes other than the standard one.... + */ + if (Options.GameMode != (uint)GameModes.Standard) + { + Logger.LogWarning($"{TranslateGameModeToString((GameModes)Options.GameMode)} is currently not supported as a game mode, defaulting to Standard..."); + Options.GameMode = (uint)GameModes.Standard; + } + + Config = new ServerConfig + { + PortNumber = Options.PortNumber, + ServerName = Options.ServerName, + MaxPlayers = Options.MaxPlayers, + Password = Options.Password, + GameMode = Options.GameMode + }; + } + + JsonData = JsonConvert.SerializeObject(Config, Formatting.Indented); + File.WriteAllText(AbsolutePath, JsonData); + + Logger.LogInfo($"Starting server with name \x1b[92m{Config.ServerName}\x1b[97m (Version: \x1b[92m{ServerVersion}\x1b[97m)"); + StartServer(Config); + return; + } + + /* The server config file exists, deserealize the data from it */ + Config = JsonConvert.DeserializeObject(File.ReadAllText(AbsolutePath)); + if (Config == null) + { + Logger.LogFatal("Failed to create the server, couldn't read the server configuration file (might be corrupt)." + + $"Please delete the {ConfigFile} file and start the server with new server options!"); + return; + } + + /* + * The general rule is to always start the server using the data from the loaded + * config file but the parsed options (if the user ever passed them) might diverge + * from that of the ones from the file. So overwrite the config file with whatever + * has been parsed and use the newly overwritten data. + */ + if (HasOptions) + { + if (Config.PortNumber != Options.PortNumber) + { + Config.PortNumber = Options.PortNumber; + } + + if (Config.ServerName != Options.ServerName) + { + Config.ServerName = Options.ServerName; + } + + if (Config.MaxPlayers != Options.MaxPlayers) + { + Config.MaxPlayers = Options.MaxPlayers; + } + + if (Config.Password != Options.Password) + { + Config.Password = Options.Password; + } + + if (Config.GameMode != Options.GameMode) + { + /* + * HACK: Always hardcode the game mode to Standard if other mode was submitted. + * Because we don't support any other game modes other than the standard one.... + */ + if (Options.GameMode != (uint)GameModes.Standard) + { + Logger.LogWarning($"{TranslateGameModeToString((GameModes)Options.GameMode)} is currently not supported as a game mode, defaulting to Standard..."); + Options.GameMode = (uint)GameModes.Standard; + } + + Config.GameMode = Options.GameMode; + } + + JsonData = JsonConvert.SerializeObject(Config, Formatting.Indented); + File.WriteAllText(AbsolutePath, JsonData); + } + + Logger.LogInfo($"Starting server with name \x1b[92m{Config.ServerName}\x1b[97m (Version: \x1b[92m{ServerVersion}\x1b[97m)"); + StartServer(Config); + } + + /// + /// Parses the server argument options and initializes the server based on the passed options. + /// + /// The command line arguments passed from the main entry point. + public static void Initialize(string[] Arguments) + { + Logger = new ConsoleLogger(); + + /* + * The server config file doesn't exist and the user did not pass server options. + * In this case create the config file using the defaults we provide. + * Otherwise use the server options passed by the user to write down the config file. + */ + if (!File.Exists(Path.Combine(PathLocation, ConfigFile))) + { + HasConfigFile = false; + + if (Arguments.Length == 0) + { + Logger.LogInfo($"No \x1b[94m{ConfigFile}\x1b[97m file could be found, creating server configuration file with defaults on first run..."); + HasOptions = false; + } + } + else + { + /* + * The user has passed no arguments but the server config file is present. + * Load the said file and start the server based on that. + */ + if (Arguments.Length == 0) + { + HasOptions = false; + } + } + + /* Pass down the option arguments to the parser and call the parser callback */ + CommandLine.Parser.Default.ParseArguments(Arguments) + .WithParsed(SetupConfigServer); + } + } +} + +/* EOF */ diff --git a/DedicatedServer/ConsoleLogger.cs b/DedicatedServer/ConsoleLogger.cs new file mode 100644 index 0000000..16d89f6 --- /dev/null +++ b/DedicatedServer/ConsoleLogger.cs @@ -0,0 +1,64 @@ +/* + * PROJECT: Cult of the Lamb Multiplayer Mod + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Console logger methods + * COPYRIGHT: Copyright 2026 GeoB99 + */ + +/* IMPORTS ********************************************************************/ + +using System; + +/* CLASSES & CODE *************************************************************/ + +namespace DedicatedServer +{ + public class ConsoleLogger : COTLMPServer.ILogger + { + /// + /// Displays a fatal message to the console. + /// + /// The message string to be passed to this method. + public void LogFatal(string Message) + { + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine("Fatal: " + Message); + Console.ResetColor(); + } + + /// + /// Displays a normal message to the console. + /// + /// The message string to be passed to this method. + public void LogError(string Message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Error: " + Message); + Console.ResetColor(); + } + + /// + /// Displays a warning message to the console. + /// + /// The message string to be passed to this method. + public void LogWarning(string Message) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("Warning: " + Message); + Console.ResetColor(); + } + + /// + /// Displays an informational message to the console. + /// + /// The message string to be passed to this method. + public void LogInfo(string Message) + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(Message); + Console.ResetColor(); + } + } +} + +/* EOF */ diff --git a/DedicatedServer/DedicatedServer.csproj b/DedicatedServer/DedicatedServer.csproj new file mode 100644 index 0000000..df4d032 --- /dev/null +++ b/DedicatedServer/DedicatedServer.csproj @@ -0,0 +1,22 @@ + + + + Exe + net10.0 + CultOfTheLambServer + CultOfTheLambServer + Cult of the Lamb Standalone Dedicated Server + 0.0.0.5 + Copyright (c) 2026 GeoB99 & Neco-Arc + DedicatedServer + + + + + + + + + + + diff --git a/DedicatedServer/DedicatedServerOptions.cs b/DedicatedServer/DedicatedServerOptions.cs new file mode 100644 index 0000000..9dcd444 --- /dev/null +++ b/DedicatedServer/DedicatedServerOptions.cs @@ -0,0 +1,40 @@ +/* + * PROJECT: Cult of the Lamb Multiplayer Mod + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Dedicated server command line options + * COPYRIGHT: Copyright 2026 GeoB99 + */ + +/* IMPORTS ********************************************************************/ + +using System; +using CommandLine; + +/* CLASSES & CODE *************************************************************/ + +namespace DedicatedServer +{ + /// + /// Options class that contains the dedicated server command line options to be + /// passed to the dedicated server command line interface. + /// + internal class DedicatedServerOptions + { + [Value(0, Required = false, HelpText = "Port number used to create and estabilish server connection.")] + public ushort PortNumber { get; set; } + + [Option('n', "Name", Required = false, HelpText = "The name of the server used to setup the dedicated server.")] + public string ServerName { get; set; } + + [Option('c', "Count", Required = false, HelpText = "Number of allowed players to join the server.")] + public uint MaxPlayers { get; set; } + + [Option('p', "Password", Required = false, HelpText = "(Optional) Setup a password to protect the server from players joining your server.")] + public string Password { get; set; } + + [Option('g', "Game", Required = false, HelpText = "(Optional) The game-mode to use when creating the server. This option accepts values. The following supported game modes are: Standard (0), Deathmatch (1), Boss Fight (2) and Zombies (3) (only Standard is the supported game mode at the moment). Default = Standard.")] + public uint GameMode { get; set; } + } +} + +/* EOF */ diff --git a/DedicatedServer/Launcher.cs b/DedicatedServer/Launcher.cs new file mode 100644 index 0000000..31b3a2c --- /dev/null +++ b/DedicatedServer/Launcher.cs @@ -0,0 +1,29 @@ +/* + * PROJECT: Cult of the Lamb Multiplayer Mod + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Main entry point dedicated server launcher + * COPYRIGHT: Copyright 2026 GeoB99 + */ + +/* IMPORTS ********************************************************************/ + +using System; + +/* CLASSES & CODE *************************************************************/ + +namespace DedicatedServer +{ + internal static class Launcher + { + /// + /// Main entry point for COTLMP Standalone Dedicated Server. + /// + /// Command line arguments for the server. + public static void Main(string[] args) + { + DedicatedServer.Cli.Initialize(args); + } + } +} + +/* EOF */ diff --git a/DedicatedServer/ServerConfig.cs b/DedicatedServer/ServerConfig.cs new file mode 100644 index 0000000..913fe67 --- /dev/null +++ b/DedicatedServer/ServerConfig.cs @@ -0,0 +1,39 @@ +/* + * PROJECT: Cult of the Lamb Multiplayer Mod + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Main entry point dedicated server launcher + * COPYRIGHT: Copyright 2026 GeoB99 + */ + +/* IMPORTS ********************************************************************/ + +using System; +using Newtonsoft.Json; + +/* CLASSES & CODE *************************************************************/ + +namespace DedicatedServer +{ + /// + /// Server configuration class used to denote a JSON object containing server data. + /// + internal class ServerConfig + { + [JsonProperty] + internal ushort PortNumber { get; set; } + + [JsonProperty] + internal string ServerName { get; set; } + + [JsonProperty] + internal uint MaxPlayers { get; set; } + + [JsonProperty] + internal string Password { get; set; } + + [JsonProperty] + internal uint GameMode { get; set; } + } +} + +/* EOF */