using BOTWM.Server; using System.Reflection; using Newtonsoft.Json; using BOTWM.Server.ServerClasses; using BOTWM.Server.DTO; using BOTWM.Server.HelperTypes; using BOTWM.Server.DataTypes; using BOTW.Logging; using Newtonsoft.Json.Linq; namespace BOTW.DedicatedServer { public class ServerCommand : Attribute { public bool Debug; public ServerCommand(bool debug = false) { Debug = debug; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class AlternateName : Attribute { public string name; public AlternateName(string alternateName) { this.name = alternateName; } } public class Description : Attribute { public string description; public Description(string description) { this.description = description; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class ExtraHelp : Attribute { public string Help; public ExtraHelp(string help) { Help = help; } } public class Command { public MethodInfo Method; public string Name; public string Description; public List AlternateNames = new List(); public List LowerAlternateNames = new List(); public Command(MethodInfo method, string name, string description, List alternateNames) { Method = method; Name = name; AlternateNames = alternateNames; Description = description; foreach(string altName in AlternateNames) { LowerAlternateNames.Add(altName.ToLower()); } } } public class DedicatedServer { Server server = new Server(); List CommandList = new List(); ConsoleColor commandColors = ConsoleColor.Cyan; Dictionary serverVariables = new Dictionary(); Dictionary> QuestData = new Dictionary>(); //Dictionary Gamemodes = new Dictionary(); List Gamemodes = new List(); enum Weathers : int { bluesky, cloudy, rain, heavyrain, snow, heavysnow, thunderstorm, thunderrain, blueskyrain } Dictionary LandmarkPositions = JsonConvert.DeserializeObject>(File.ReadAllText(Directory.GetCurrentDirectory() + "/Landmarks.json")); List ServerProphuntLocations = JsonConvert.DeserializeObject>(File.ReadAllText(Directory.GetCurrentDirectory() + "/PropHuntLocations.json")); public void setup() { ServerConfig svConfig = new ServerConfig(); server.serverStart(svConfig.Connection.IP, svConfig.Connection.Port, svConfig.Connection.Password, svConfig.ServerInformation.Description, GetServerSettings(svConfig)); server.startListen(); Logger.LogInformation("Type help to see available commands"); } private ServerSettings GetServerSettings(ServerConfig svConfig) { if (svConfig.Gamemode.DefaultGamemode) return new ServerSettings(svConfig.DefaultGamemode.Name, svConfig.DefaultGamemode.EnemySync, svConfig.DefaultGamemode.QuestSync, svConfig.DefaultGamemode.KorokSync, svConfig.DefaultGamemode.TowerSync, svConfig.DefaultGamemode.ShrineSync, svConfig.DefaultGamemode.LocationSync, svConfig.DefaultGamemode.DungeonSync, (Gamemode)svConfig.DefaultGamemode.Special); bool isGamemode = Logger.LogInput("Are you playing a gamemode? (1 for true, 0 for false): ") == "1" ? true : false; if (isGamemode) { Logger.LogInformation("---Available gamemodes---", color: commandColors); int counter = 0; foreach (ServerSettings Gamemode in Gamemodes) { Logger.LogInformation($"({counter}) {Gamemode.SettingsName}"); counter++; } int optionSelected = -1; while (optionSelected == -1) { if (!Int32.TryParse(Logger.LogInput("Type the number corresponding to the gamemode you want to play: "), out optionSelected)) { Logger.LogError($"Invalid gamemode. Correct values go from 0 to {Gamemodes.Count() - 1}"); continue; } else { if (optionSelected > Gamemodes.Count() - 1 || optionSelected < 0) { Logger.LogError($"Invalid gamemode. Correct values go from 0 to {Gamemodes.Count() - 1}"); optionSelected = -1; continue; } Logger.LogInformation($"Selected gamemode {Gamemodes[optionSelected].SettingsName}", color: commandColors); return Gamemodes[optionSelected]; } } } //V | K | T | O | C | L | D bool enemySync = InputToBoolean("Enemy sync (1 for true, 0 for false): "); bool questSync = InputToBoolean("Quest sync (1 for true, 0 for false): "); bool korokSync = InputToBoolean("Korok sync (1 for true, 0 for false): "); bool towerSync = InputToBoolean("Tower sync (1 for true, 0 for false): "); bool shrineSync = InputToBoolean("Shrine sync (1 for true, 0 for false): "); bool locationSync = InputToBoolean("Location sync (1 for true, 0 for false): "); bool dungeonSync = InputToBoolean("Dungeon sync (1 for true, 0 for false): "); string GMInput = Logger.LogInput("Gamemode selection (0 for no gamemode, 1 for Hunter vs Speedrunner, 2 for DeathSwap): "); Gamemode GM = Gamemode.NoGamemode; if (Int32.TryParse(GMInput, out int value)) { if (value == 1) GM = Gamemode.HunterVsSpeedrunner; if (value == 2) GM = Gamemode.DeathSwap; } ServerSettings selectedServerSettings = new ServerSettings("Custom", enemySync, questSync, korokSync, towerSync, shrineSync, locationSync, dungeonSync, GM); bool Match = false; foreach (ServerSettings gamemode in Gamemodes) { if (selectedServerSettings.CompareSettings(gamemode)) { Logger.LogWarning($"Your selected server settings match \"{gamemode.SettingsName}\" gamemode. Next time you want to play with these settings, you can select that gamemode."); Match = true; selectedServerSettings.SettingsName = gamemode.SettingsName; break; } } if (!Match) { if (Logger.LogInput("Do you wish to save your selected server settings? (1 for yes, 0 for no): ") == "1") { selectedServerSettings.SettingsName = Logger.LogInput("Select a name for your settings: "); Gamemodes.Add(selectedServerSettings); string GamemodeJson = JsonConvert.SerializeObject(Gamemodes); File.WriteAllText(Directory.GetCurrentDirectory() + "/Gamemodes.json", GamemodeJson); Logger.LogInformation($"Saved gamemode: {selectedServerSettings.SettingsName}"); } else { selectedServerSettings.SettingsName = "Custom"; } } return selectedServerSettings; } private bool InputToBoolean(string message) => Logger.LogInput(message) == "1" ? true : false; public void process_commands(string input) { try { string input_command = input.Split(" ")[0]; List unprocessed_input_parameters = input.Split(" ").ToList(); unprocessed_input_parameters.RemoveAt(0); List input_parameters = new List(); for (int i = 0; i < unprocessed_input_parameters.Count; i++) { if (unprocessed_input_parameters[i].First() != '\"' || (unprocessed_input_parameters[i].First() == '\"' && unprocessed_input_parameters[i].Last() == '\"')) { input_parameters.Add(unprocessed_input_parameters[i].Replace("\"", "")); continue; } string new_input = unprocessed_input_parameters[i].Replace("\"", ""); for (int j = i + 1; j < unprocessed_input_parameters.Count; j++) { new_input += $" {unprocessed_input_parameters[j]}".Replace("\"", ""); if (unprocessed_input_parameters[j].Last() != '\"') continue; i = j; input_parameters.Add(new_input); break; } } foreach (Command command in CommandList) { if (input_command.ToLower() == command.Name.ToLower() || command.LowerAlternateNames.Contains(input_command.ToLower())) { if (input_parameters.Count() > 0 && input_parameters[0].ToLower() == "help") { string param = " "; foreach (var parameter in command.Method.GetParameters()) { param += "<" + parameter.Name + "> "; } param = param.Substring(0, param.Length - 1); Logger.LogInformation($"{command.Name}{param}: {command.Description}", color: commandColors); foreach (ExtraHelp extraHelp in command.Method.GetCustomAttributes(typeof(ExtraHelp), false)) { Logger.LogInformation($"{extraHelp.Help}"); } return; } else { if (input_parameters.Count() >= command.Method.GetParameters().Where(x => !x.IsOptional).Count() && input_parameters.Count() <= command.Method.GetParameters().Count()) { List parameters = new List(); foreach (string param in input_parameters) { parameters.Add(param); } for (int i = 0; i < command.Method.GetParameters().Count() - input_parameters.Count(); i++) { parameters.Add(Type.Missing); } command.Method.Invoke(this, parameters.ToArray()); return; } else { string param = " "; foreach (var parameter in command.Method.GetParameters()) { param += parameter.Name + " "; } Logger.LogError($"Correct usage: {command.Name}{param}"); return; } } } } Logger.LogError($"Command {input_command} was not found. Type help to see available commands"); } catch(Exception ex) { Logger.LogError($"Command failed {ex.ToString()}"); } } public void setupCommands() { string AppdataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BOTWM"; string fileName = "\\QuestFlagsNames.txt"; string text = File.ReadAllText(AppdataFolder + fileName); QuestData = JsonConvert.DeserializeObject>>(text); serverVariables.Add("time", "t"); serverVariables.Add("day", "d"); serverVariables.Add("weather", "w"); //Gamemodes.Add("Game Completion", new bool[] { true, true, true, false, true, true, true, false, false }); //Gamemodes.Add("Hunter VS Speedrunner", new bool[] { true, true, false, false, true, true, true, false, true }); //Gamemodes.Add("Any% Speedrun", new bool[] { true, true, false, false, true, true, true, false, false }); //Gamemodes.Add("Bingo???", new bool[] { true, true, false, false, true, true, true, false, false }); //Gamemodes.Add("Hide n' Seek", new bool[] { true, true, false, false, false, false, false, false, false }); Gamemodes = JsonConvert.DeserializeObject>(File.ReadAllText(Directory.GetCurrentDirectory() + "/Gamemodes.json")); var methods = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(x => x.GetTypes()) .Where(x => x.IsClass) .SelectMany(x => x.GetMethods()) .Where(x => x.GetCustomAttributes(typeof(ServerCommand), false).FirstOrDefault() != null && x.GetCustomAttributes(typeof(Description), false).FirstOrDefault() != null); foreach (var method in methods) { List alternateNames = new List(); foreach (AlternateName attribute in method.GetCustomAttributes(typeof(AlternateName), false)) { alternateNames.Add(attribute.name); } bool shouldAdd = true; foreach (var parameter in method.GetParameters()) { if (parameter.ParameterType != typeof(string)) { shouldAdd = false; } } if (!shouldAdd) continue; CommandList.Add(new Command(method, method.Name, ((Description)method.GetCustomAttribute(typeof(Description), false)).description, alternateNames)); } } public void CopyAppdataFiles() { string AppdataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BOTWM"; List Resources = Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(resource => resource.Contains("AppdataFiles")).ToList(); if (!Directory.Exists(AppdataFolder)) Directory.CreateDirectory(AppdataFolder); foreach (string resource in Resources) { Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource); string output = $"{AppdataFolder}\\{resource.Replace("BOTW.DedicatedServer.AppdataFiles.", "")}"; using (FileStream AppdataFile = new FileStream(output, FileMode.Create)) { byte[] b = new byte[s.Length + 1]; s.Read(b, 0, Convert.ToInt32(s.Length)); AppdataFile.Write(b, 0, Convert.ToInt32(b.Length - 1)); } } } [ServerCommand] [AlternateName("Commands")] [AlternateName("H")] [Description("Shows available commands")] public void Help() { Logger.LogInformation("---Showing available commands---", color: commandColors); Console.ForegroundColor = ConsoleColor.White; foreach (var command in CommandList) { if (((ServerCommand)command.Method.GetCustomAttribute(typeof(ServerCommand), false)).Debug) continue; string param = ""; if (command.Method.GetParameters().Count() > 0) { param += " "; foreach (var parameter in command.Method.GetParameters()) { param += "<" + parameter.Name + "> "; } param = param.Substring(0, param.Length - 1); } Logger.LogInformation($"{command.Name}{param}: {command.Description}"); } } private Dictionary GetLandmarks(string filter = "") => filter == "" ? LandmarkPositions.Keys .Select((key, index) => new { key, index }) .ToDictionary(x => x.index + 1, x => x.key) : LandmarkPositions.Keys .Select((key, index) => new { key, index }) .Where(x => x.key.ToLower().Contains(filter.ToLower())) .ToDictionary(x => x.index + 1, x => x.key); [ServerCommand] [Description("Gets the available landmarks to teleport to")] [ExtraHelp("")] public void Landmarks(string filter = "") { Dictionary FilteredLandmarks = GetLandmarks(filter); if(FilteredLandmarks.Count == 0) { Logger.LogError($"No landmark found with the filter {filter}"); } foreach(KeyValuePair Landmark in FilteredLandmarks) { string spaces = Landmark.Key < 10 ? " " : Landmark.Key < 100 ? " " : ""; Logger.LogInformation($"[{Landmark.Key}]{spaces} {Landmark.Value}", color: commandColors); } } [ServerCommand] [Description("Teleport player to position or other player")] [AlternateName("Tp")] [ExtraHelp("Usage 1: Tp ")] [ExtraHelp(": Player number, player name or @a for everyone")] [ExtraHelp("Use \"p#\" to teleport a player by its number. ")] [ExtraHelp(": Player number, player name or landmark")] [ExtraHelp("Use \"p#\" to teleport to a player by its number. Otherwise, use \"l#\" to teleport to a landmark")] [ExtraHelp("Usage 2: Tp ")] [ExtraHelp(": Player number, player name or @a for everyone")] [ExtraHelp(": Destination x axis")] [ExtraHelp(": Destination y axis")] [ExtraHelp(": Destination z axis")] public void Teleport(string source, string destination, string destination_y = "", string destination_z = "") { Dictionary PlayerList = ServerData.GetPlayers().Names; List SourcePlayers = new List(); if(source.StartsWith("p") && source.Count() < 4) { int player = 0; if (Int32.TryParse(source.Replace("p", ""), out player) && PlayerList.ContainsKey((byte)(player - 1))) SourcePlayers.Add(player - 1); else SourcePlayers.AddRange(PlayerList.Where(p => p.Value == source).Select(p => (int)p.Key)); } else if(source == "@a") SourcePlayers.AddRange(PlayerList.Select(p => (int)p.Key)); else SourcePlayers.AddRange(PlayerList.Where(p => p.Value == source).Select(p => (int)p.Key)); SourcePlayers = SourcePlayers.Where(p => ServerData.GetPlayer(p).Connected).ToList(); if (SourcePlayers.Count == 0) { Logger.LogError($"Could not find player that matches {source}"); return; } Vec3f Destination = new Vec3f(); if(!string.IsNullOrEmpty(destination_y) && !string.IsNullOrEmpty(destination_z)) { float x, y, z; if(!float.TryParse(destination, out x) || !float.TryParse(destination_y, out y) || !float.TryParse(destination_z, out z)) { Logger.LogError($"Destination input was not valid. Use \"tp help\" for extra information"); return; } Destination = new Vec3f(x, y, z); } else { if (destination.StartsWith("p") && destination.Count() < 4) { int player = 0; if (Int32.TryParse(destination.Replace("p", ""), out player)) Destination = ServerData.GetPlayer(player - 1).Position; else Destination = ServerData.GetPlayer(PlayerList.Where(p => p.Value == destination).Select(p => (int)p.Key).FirstOrDefault()).Position; } else if(destination.StartsWith("l") && destination.Count() < 5) { int landmark = 0; if (Int32.TryParse(destination.Replace("l", ""), out landmark) && GetLandmarks().ContainsKey(landmark)) Destination = LandmarkPositions[GetLandmarks()[landmark]]; else Destination = ServerData.GetPlayer(PlayerList.Where(p => p.Value == destination).Select(p => (int)p.Key).FirstOrDefault()).Position; } else { if(PlayerList.Any(p => p.Value == destination)) Destination = ServerData.GetPlayer(PlayerList.Where(p => p.Value == destination).Select(p => (int)p.Key).FirstOrDefault()).Position; } } if(Destination == null || (Destination.x == 0 && Destination.y == 0 && Destination.z == 0)) { Logger.LogError($"Destination input was not valid. Use \"tp help\" for extra information"); return; } ServerData.TeleportData.AddTp(SourcePlayers, Destination); Logger.LogInformation($"Requested teleport of {SourcePlayers.Count} players to {Destination.x}, {Destination.y}, {Destination.z}"); } [ServerCommand] [Description("Stops enemy and quest sync")] public void Stop() { server.isEnemySync = false; server.isQuestSync = false; Logger.LogInformation("Deactivated quest and enemy sync", color: commandColors); } [ServerCommand] [Description("Starts enemy and quest sync")] public void Start() { server.isEnemySync = true; server.isQuestSync = true; Logger.LogInformation("Activated quest and enemy sync", color: commandColors); } [ServerCommand] [Description("Get or set limits for DeathSwap")] [ExtraHelp("Usage 1: DeathSwap ")] [ExtraHelp(": on or off")] [ExtraHelp("Usage 2: DeathSwap :")] [ExtraHelp(" and should be integers")] [ExtraHelp("Usage 3: DeathSwap ")] [ExtraHelp(" should be an integer")] [ExtraHelp("Usage 4: DeathSwap")] [ExtraHelp("Get current state and current limits")] [AlternateName("DS")] public void DeathSwap(string parameter = "") { if (parameter == "on") { ServerData.DeathSwapMutex.WaitOne(100); ServerData.DeathSwap.Enabled = true; Logger.LogInformation("Enabled death swap.", color: commandColors); ServerData.DeathSwapMutex.ReleaseMutex(); return; } else if (parameter == "off") { ServerData.DeathSwapMutex.WaitOne(100); ServerData.DeathSwap.Enabled = false; ServerData.DeathSwap.Running = false; Logger.LogInformation("Disabled death swap.", color: commandColors); ServerData.DeathSwapMutex.ReleaseMutex(); return; } else if (parameter.Contains(':')) { if (parameter.Split(":").Length != 2) { Logger.LogError("Invalid parameter. Use DeathSwap Help to get information on how to use the command."); return; } int Lower; int Upper; if (!int.TryParse(parameter.Split(":")[0], out Lower)) { Logger.LogError("Invalid Lower limit. Use DeathSwap Help to get information on how to use the command."); return; } if (!int.TryParse(parameter.Split(":")[1], out Upper)) { Logger.LogError("Invalid Upper limit. Use DeathSwap Help to get information on how to use the command."); return; } ServerData.DeathSwapMutex.WaitOne(100); ServerData.DeathSwap.ChangeLimits(Lower, Upper, -1, 1); ServerData.DeathSwap.CalculateNewLimit(); Logger.LogInformation($"Set limits to: {Lower}:{Upper}", color: commandColors); ServerData.DeathSwapMutex.ReleaseMutex(); return; } else if (parameter == "") { string ExtraMessage = ""; if (ServerData.DeathSwap.TimerLimit.random) ExtraMessage = $", Lower limit: {ServerData.DeathSwap.TimerLimit.Lower}, Upper limit: {ServerData.DeathSwap.TimerLimit.Upper}"; Double TimeLeft = ServerData.DeathSwap.TimeLeft(); Logger.LogInformation($"Time until next swap: {Math.Truncate(TimeLeft)} min {(int)Math.Round((TimeLeft - Math.Truncate(TimeLeft)) * 60, 0)} sec", color: commandColors); Logger.LogInformation($"Current DeathSwap settings => Enabled: {ServerData.DeathSwap.Enabled}, Is random: {ServerData.DeathSwap.TimerLimit.random}{ExtraMessage}", color: commandColors); return; } else { int NewValue; if (int.TryParse(parameter, out NewValue)) { ServerData.DeathSwapMutex.WaitOne(100); //server.DeathSwap.TimerLimit.random = true; ServerData.DeathSwap.ChangeLimits(-1, -1, NewValue, 0); Logger.LogInformation($"Set death swap timer to {NewValue}", color: commandColors); ServerData.DeathSwapMutex.ReleaseMutex(); return; } Logger.LogError("Invalid parameter. Use DeathSwap Help to get information on how to use the command."); return; } } [ServerCommand] [Description("Change Hunter vs Speedrunner glyph settings")] [ExtraHelp("Usage: Glyph