using Barotrauma.Items.Components; using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; namespace Barotrauma { struct ColoredText { public string Text; public Color Color; public bool IsCommand; public readonly string Time; public ColoredText(string text, Color color, bool isCommand) { this.Text = text; this.Color = color; this.IsCommand = isCommand; Time = DateTime.Now.ToString(); } } static partial class DebugConsole { public class Command { public readonly string[] names; public readonly string help; private Action onExecute; /// /// Executed when a client uses the command. If not set, the command is relayed to the server as-is. /// private Action onClientExecute; /// /// Executed server-side when a client attempts to use the command. /// private Action onClientRequestExecute; public Func GetValidArgs; public bool RelayToServer { get { return onClientExecute == null; } } /// The name of the command. Use | to give multiple names/aliases to the command. /// The text displayed when using the help command. /// The default action when executing the command. /// The action when a client attempts to execute the command. If null, the command is relayed to the server as-is. /// The server-side action when a client requests executing the command. If null, the default action is executed. public Command(string name, string help, Action onExecute, Action onClientExecute, Action onClientRequestExecute, Func getValidArgs = null) { names = name.Split('|'); this.help = help; this.onExecute = onExecute; this.onClientExecute = onClientExecute; this.onClientRequestExecute = onClientRequestExecute; this.GetValidArgs = getValidArgs; } /// /// Use this constructor to create a command that executes the same action regardless of whether it's executed by a client or the server. /// public Command(string name, string help, Action onExecute, Func getValidArgs = null) { names = name.Split('|'); this.help = help; this.onExecute = onExecute; this.onClientExecute = onExecute; this.GetValidArgs = getValidArgs; } public void Execute(string[] args) { if (onExecute == null) return; onExecute(args); } public void ClientExecute(string[] args) { onClientExecute(args); } public void ServerExecuteOnClientRequest(Client client, Vector2 cursorWorldPos, string[] args) { if (onClientRequestExecute == null) { if (onExecute == null) return; onExecute(args); } else { onClientRequestExecute(client, cursorWorldPos, args); } } } const int MaxMessages = 200; public static List Messages = new List(); public delegate void QuestionCallback(string answer); private static QuestionCallback activeQuestionCallback; private static List commands = new List(); public static List Commands { get { return commands; } } private static string currentAutoCompletedCommand; private static int currentAutoCompletedIndex; //used for keeping track of the message entered when pressing up/down static int selectedIndex; private static List unsavedMessages = new List(); private static int messagesPerFile = 800; public const string SavePath = "ConsoleLogs"; static DebugConsole() { commands.Add(new Command("help", "", (string[] args) => { if (args.Length == 0) { foreach (Command c in commands) { if (string.IsNullOrEmpty(c.help)) continue; NewMessage(c.help, Color.Cyan); } } else { var matchingCommand = commands.Find(c => c.names.Any(name => name == args[0])); if (matchingCommand == null) { NewMessage("Command " + args[0] + " not found.", Color.Red); } else { NewMessage(matchingCommand.help, Color.Cyan); } } })); commands.Add(new Command("clientlist", "clientlist: List all the clients connected to the server.", (string[] args) => { if (GameMain.Server == null) return; NewMessage("***************", Color.Cyan); foreach (Client c in GameMain.Server.ConnectedClients) { NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); } NewMessage("***************", Color.Cyan); }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { GameMain.Server.SendConsoleMessage("***************", client); foreach (Client c in GameMain.Server.ConnectedClients) { GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client); } GameMain.Server.SendConsoleMessage("***************", client); })); commands.Add(new Command("traitorlist", "traitorlist: List all the traitors and their targets.", (string[] args) => { if (GameMain.Server == null) return; TraitorManager traitorManager = GameMain.Server.TraitorManager; if (traitorManager == null) return; foreach (Traitor t in traitorManager.TraitorList) { NewMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", Color.Cyan); } NewMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", Color.Cyan); })); commands.Add(new Command("itemlist", "itemlist: List all the item prefabs available for spawning.", (string[] args) => { NewMessage("***************", Color.Cyan); foreach (MapEntityPrefab ep in MapEntityPrefab.List) { var itemPrefab = ep as ItemPrefab; if (itemPrefab == null || itemPrefab.Name == null) continue; NewMessage("- " + itemPrefab.Name, Color.Cyan); } NewMessage("***************", Color.Cyan); })); commands.Add(new Command("setpassword|setserverpassword", "setpassword [password]: Changes the password of the server that's being hosted.", (string[] args) => { if (GameMain.Server == null || args.Length == 0) return; GameMain.Server.SetPassword(args[0]); })); commands.Add(new Command("createfilelist", "", (string[] args) => { UpdaterUtil.SaveFileList("filelist.xml"); })); commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside/cursor]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) => { string errorMsg; SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg); if (!string.IsNullOrWhiteSpace(errorMsg)) { ThrowError(errorMsg); } }, null, (Client client, Vector2 cursorPos, string[] args) => { string errorMsg; SpawnCharacter(args, cursorPos, out errorMsg); if (!string.IsNullOrWhiteSpace(errorMsg)) { ThrowError(errorMsg); } }, () => { List characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); for (int i = 0; i < characterFiles.Count; i++) { characterFiles[i] = Path.GetFileNameWithoutExtension(characterFiles[i]).ToLowerInvariant(); } return new string[][] { characterFiles.ToArray(), new string[] { "near", "inside", "outside", "cursor" } }; })); commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory/cargo/random/[name]]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the last parameter is omitted or \"random\".", (string[] args) => { SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); if (!string.IsNullOrWhiteSpace(errorMsg)) { ThrowError(errorMsg); } }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { SpawnItem(args, cursorWorldPos, client.Character, out string errorMsg); if (!string.IsNullOrWhiteSpace(errorMsg)) { GameMain.Server.SendConsoleMessage(errorMsg, client); } }, () => { List itemNames = new List(); foreach (MapEntityPrefab prefab in MapEntityPrefab.List) { if (prefab is ItemPrefab itemPrefab) itemNames.Add(itemPrefab.Name); } return new string[][] { itemNames.ToArray(), new string[] { "cursor", "inventory" } }; })); commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => { HumanAIController.DisableCrewAI = true; NewMessage("Crew AI disabled", Color.White); }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { HumanAIController.DisableCrewAI = true; NewMessage("Crew AI disabled by \"" + client.Name + "\"", Color.White); GameMain.Server.SendConsoleMessage("Crew AI disabled", client); })); commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) => { HumanAIController.DisableCrewAI = false; NewMessage("Crew AI enabled", Color.White); }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { HumanAIController.DisableCrewAI = false; NewMessage("Crew AI enabled by \"" + client.Name + "\"", Color.White); GameMain.Server.SendConsoleMessage("Crew AI enabled", client); })); commands.Add(new Command("autorestart", "autorestart [true/false]: Enable or disable round auto-restart.", (string[] args) => { if (GameMain.Server == null) return; bool enabled = GameMain.Server.AutoRestart; if (args.Length > 0) { bool.TryParse(args[0], out enabled); } else { enabled = !enabled; } if (enabled != GameMain.Server.AutoRestart) { if (GameMain.Server.AutoRestartInterval <= 0) GameMain.Server.AutoRestartInterval = 10; GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; GameMain.Server.AutoRestart = enabled; #if CLIENT GameMain.NetLobbyScreen.SetAutoRestart(enabled, GameMain.Server.AutoRestartTimer); #endif GameMain.NetLobbyScreen.LastUpdateID++; } NewMessage(GameMain.Server.AutoRestart ? "Automatic restart enabled." : "Automatic restart disabled.", Color.White); }, null, null)); commands.Add(new Command("autorestartinterval", "autorestartinterval [seconds]: Set how long the server waits between rounds before automatically starting a new one. If set to 0, autorestart is disabled.", (string[] args) => { if (GameMain.Server == null) return; if (args.Length > 0) { int parsedInt = 0; if (int.TryParse(args[0], out parsedInt)) { if (parsedInt >= 0) { GameMain.Server.AutoRestart = true; GameMain.Server.AutoRestartInterval = parsedInt; if (GameMain.Server.AutoRestartTimer >= GameMain.Server.AutoRestartInterval) GameMain.Server.AutoRestartTimer = GameMain.Server.AutoRestartInterval; NewMessage("Autorestart interval set to " + GameMain.Server.AutoRestartInterval + " seconds.", Color.White); } else { GameMain.Server.AutoRestart = false; NewMessage("Autorestart disabled.", Color.White); } #if CLIENT GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); #endif GameMain.NetLobbyScreen.LastUpdateID++; } } }, null, null)); commands.Add(new Command("autorestarttimer", "autorestarttimer [seconds]: Set the current autorestart countdown to the specified value.", (string[] args) => { if (GameMain.Server == null) return; if (args.Length > 0) { int parsedInt = 0; if (int.TryParse(args[0], out parsedInt)) { if (parsedInt >= 0) { GameMain.Server.AutoRestart = true; GameMain.Server.AutoRestartTimer = parsedInt; if (GameMain.Server.AutoRestartInterval <= GameMain.Server.AutoRestartTimer) GameMain.Server.AutoRestartInterval = GameMain.Server.AutoRestartTimer; GameMain.NetLobbyScreen.LastUpdateID++; NewMessage("Autorestart timer set to " + GameMain.Server.AutoRestartTimer + " seconds.", Color.White); } else { GameMain.Server.AutoRestart = false; NewMessage("Autorestart disabled.", Color.White); } #if CLIENT GameMain.NetLobbyScreen.SetAutoRestart(GameMain.Server.AutoRestart, GameMain.Server.AutoRestartTimer); #endif GameMain.NetLobbyScreen.LastUpdateID++; } } }, null, null)); commands.Add(new Command("giveperm", "giveperm [id]: Grants administrative permissions to the player with the specified client ID.", (string[] args) => { if (GameMain.Server == null) return; if (args.Length < 1) { NewMessage("giveperm [id]: Grants administrative permissions to the player with the specified client ID.", Color.Cyan); return; } int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } NewMessage("Valid permissions are:",Color.White); NewMessage(" - all",Color.White); foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { NewMessage(" - " + permission.ToString(),Color.White); } ShowQuestionPrompt("Permission to grant to \"" + client.Name + "\"?", (perm) => { ClientPermissions permission = ClientPermissions.None; if (perm.ToLower() == "all") { permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; } else { if (!Enum.TryParse(perm, true, out permission)) { NewMessage(perm + " is not a valid permission!", Color.Red); return; } } client.GivePermission(permission); GameMain.Server.UpdateClientPermissions(client); NewMessage("Granted " + perm + " permissions to " + client.Name + ".", Color.White); }); }, (string[] args) => { #if CLIENT if (args.Length < 1) return; int id; if (!int.TryParse(args[0], out id)) { ThrowError("\"" + id + "\" is not a valid client ID."); return; } NewMessage("Valid permissions are:", Color.White); NewMessage(" - all", Color.White); foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { NewMessage(" - " + permission.ToString(), Color.White); } ShowQuestionPrompt("Permission to grant to client #" + id + "?", (perm) => { GameMain.Client.SendConsoleCommand("giveperm " +id + " " + perm); }); #endif }, (Client senderClient, Vector2 cursorWorldPos, string[] args) => { if (args.Length < 2) return; int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); return; } string perm = string.Join("", args.Skip(1)); ClientPermissions permission = ClientPermissions.None; if (perm.ToLower() == "all") { permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; } else { if (!Enum.TryParse(perm, true, out permission)) { GameMain.Server.SendConsoleMessage(perm + " is not a valid permission!", senderClient); return; } } client.GivePermission(permission); GameMain.Server.UpdateClientPermissions(client); GameMain.Server.SendConsoleMessage("Granted " + perm + " permissions to " + client.Name + ".", senderClient); NewMessage(senderClient.Name + " granted " + perm + " permissions to " + client.Name + ".", Color.White); })); commands.Add(new Command("revokeperm", "revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", (string[] args) => { if (GameMain.Server == null) return; if (args.Length < 1) { NewMessage("revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", Color.Cyan); return; } int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } NewMessage("Valid permissions are:", Color.White); NewMessage(" - all", Color.White); foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { NewMessage(" - " + permission.ToString(), Color.White); } ShowQuestionPrompt("Permission to revoke from \"" + client.Name + "\"?", (perm) => { ClientPermissions permission = ClientPermissions.None; if (perm.ToLower() == "all") { permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; } else { if (!Enum.TryParse(perm, true, out permission)) { NewMessage(perm + " is not a valid permission!", Color.Red); return; } } client.RemovePermission(permission); GameMain.Server.UpdateClientPermissions(client); NewMessage("Revoked " + perm + " permissions from " + client.Name + ".", Color.White); }); }, (string[] args) => { #if CLIENT if (args.Length < 1) return; int id; if (!int.TryParse(args[0], out id)) { ThrowError("\"" + id + "\" is not a valid client ID."); return; } NewMessage("Valid permissions are:", Color.White); NewMessage(" - all", Color.White); foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { NewMessage(" - " + permission.ToString(), Color.White); } ShowQuestionPrompt("Permission to revoke from client #" + id + "?", (perm) => { GameMain.Client.SendConsoleCommand("revokeperm " + id + " " + perm); }); #endif }, (Client senderClient, Vector2 cursorWorldPos, string[] args) => { if (args.Length < 2) return; int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); return; } string perm = string.Join("", args.Skip(1)); ClientPermissions permission = ClientPermissions.None; if (perm.ToLower() == "all") { permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands; } else { if (!Enum.TryParse(perm, true, out permission)) { GameMain.Server.SendConsoleMessage(perm + " is not a valid permission!", senderClient); return; } } client.RemovePermission(permission); GameMain.Server.UpdateClientPermissions(client); GameMain.Server.SendConsoleMessage("Revoked " + perm + " permissions from " + client.Name + ".", senderClient); NewMessage(senderClient.Name + " revoked " + perm + " permissions from " + client.Name + ".", Color.White); })); commands.Add(new Command("giverank", "giverank [id]: Assigns a specific rank (= a set of administrative permissions) to the player with the specified client ID.", (string[] args) => { if (GameMain.Server == null) return; if (args.Length < 1) { NewMessage("giverank [id]: Assigns a specific rank(= a set of administrative permissions) to the player with the specified client ID.", Color.Cyan); return; } int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } NewMessage("Valid ranks are:", Color.White); foreach (PermissionPreset permissionPreset in PermissionPreset.List) { NewMessage(" - " + permissionPreset.Name, Color.White); } ShowQuestionPrompt("Rank to grant to \"" + client.Name + "\"?", (rank) => { PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); if (preset == null) { ThrowError("Rank \"" + rank + "\" not found."); return; } client.SetPermissions(preset.Permissions, preset.PermittedCommands); GameMain.Server.UpdateClientPermissions(client); NewMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White); }); }, (string[] args) => { #if CLIENT if (args.Length < 1) return; int id; if (!int.TryParse(args[0], out id)) { ThrowError("\"" + id + "\" is not a valid client ID."); return; } NewMessage("Valid ranks are:", Color.White); foreach (PermissionPreset permissionPreset in PermissionPreset.List) { NewMessage(" - " + permissionPreset.Name, Color.White); } ShowQuestionPrompt("Rank to grant to client #" + id + "?", (rank) => { GameMain.Client.SendConsoleCommand("giverank " + id + " " + rank); }); #endif }, (Client senderClient, Vector2 cursorWorldPos, string[] args) => { if (args.Length < 2) return; int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); return; } string rank = string.Join("", args.Skip(1)); PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.ToLowerInvariant() == rank.ToLowerInvariant()); if (preset == null) { GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient); return; } client.SetPermissions(preset.Permissions, preset.PermittedCommands); GameMain.Server.UpdateClientPermissions(client); GameMain.Server.SendConsoleMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", senderClient); NewMessage(senderClient.Name + " granted the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White); })); commands.Add(new Command("givecommandperm", "givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", (string[] args) => { if (GameMain.Server == null) return; if (args.Length < 1) { NewMessage("givecommandperm [id]: Gives the player with the specified client ID the permission to use the specified console commands.", Color.Cyan); return; } int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } ShowQuestionPrompt("Console command permissions to grant to \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => { string[] splitCommands = commandsStr.Split(' '); List grantedCommands = new List(); for (int i = 0; i < splitCommands.Length; i++) { splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); if (matchingCommand == null) { ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); } else { grantedCommands.Add(matchingCommand); } } client.GivePermission(ClientPermissions.ConsoleCommands); client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); GameMain.Server.UpdateClientPermissions(client); NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White); }); }, (string[] args) => { #if CLIENT if (args.Length < 1) return; int id; if (!int.TryParse(args[0], out id)) { ThrowError("\"" + id + "\" is not a valid client ID."); return; } ShowQuestionPrompt("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => { GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); }); #endif }, (Client senderClient, Vector2 cursorWorldPos, string[] args) => { if (args.Length < 2) return; int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); return; } string[] splitCommands = args.Skip(1).ToArray(); List grantedCommands = new List(); for (int i = 0; i < splitCommands.Length; i++) { splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); if (matchingCommand == null) { GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); } else { grantedCommands.Add(matchingCommand); } } client.GivePermission(ClientPermissions.ConsoleCommands); client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); GameMain.Server.UpdateClientPermissions(client); GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", senderClient); NewMessage("Gave the client \"" + client.Name + "\" the permission to use the console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White); })); commands.Add(new Command("revokecommandperm", "revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", (string[] args) => { if (GameMain.Server == null) return; if (args.Length < 1) { NewMessage("revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", Color.Cyan); return; } int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } ShowQuestionPrompt("Console command permissions to revoke from \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => { string[] splitCommands = commandsStr.Split(' '); List revokedCommands = new List(); for (int i = 0; i < splitCommands.Length; i++) { splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); if (matchingCommand == null) { ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); } else { revokedCommands.Add(matchingCommand); } } client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); GameMain.Server.UpdateClientPermissions(client); NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White); }); }, (string[] args) => { #if CLIENT if (args.Length < 1) return; int id; if (!int.TryParse(args[0], out id)) { ThrowError("\"" + id + "\" is not a valid client ID."); return; } ShowQuestionPrompt("Console command permissions to grant to client #" + id + "? You may enter multiple commands separated with a space.", (commandNames) => { GameMain.Client.SendConsoleCommand("givecommandperm " + id + " " + commandNames); }); #endif }, (Client senderClient, Vector2 cursorWorldPos, string[] args) => { if (args.Length < 2) return; int.TryParse(args[0], out int id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); return; } string[] splitCommands = args.Skip(1).ToArray(); List revokedCommands = new List(); for (int i = 0; i < splitCommands.Length; i++) { splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); if (matchingCommand == null) { GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); } else { revokedCommands.Add(matchingCommand); } } client.GivePermission(ClientPermissions.ConsoleCommands); client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); GameMain.Server.UpdateClientPermissions(client); GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient); NewMessage(senderClient.Name + " revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White); })); commands.Add(new Command("showperm", "showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", (string[] args) => { if (GameMain.Server == null) return; if (args.Length < 1) { NewMessage("showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", Color.Cyan); return; } int.TryParse(args[0], out int id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } if (client.Permissions == ClientPermissions.None) { NewMessage(client.Name + " has no special permissions.", Color.White); return; } NewMessage(client.Name + " has the following permissions:", Color.White); foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); NewMessage(" - " + attributes[0].Description, Color.White); } if (client.HasPermission(ClientPermissions.ConsoleCommands)) { if (client.PermittedConsoleCommands.Count == 0) { NewMessage("No permitted console commands:", Color.White); } else { NewMessage("Permitted console commands:", Color.White); foreach (Command permittedCommand in client.PermittedConsoleCommands) { NewMessage(" - " + permittedCommand.names[0], Color.White); } } } }, (string[] args) => { #if CLIENT if (args.Length < 1) return; int id; if (!int.TryParse(args[0], out id)) { ThrowError("\"" + id + "\" is not a valid client ID."); return; } GameMain.Client.SendConsoleCommand("showperm " + id); #endif }, (Client senderClient, Vector2 cursorWorldPos, string[] args) => { if (args.Length < 2) return; int id; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { GameMain.Server.SendConsoleMessage("Client id \"" + id + "\" not found.", senderClient); return; } if (client.Permissions == ClientPermissions.None) { GameMain.Server.SendConsoleMessage(client.Name + " has no special permissions.", senderClient); return; } GameMain.Server.SendConsoleMessage(client.Name + " has the following permissions:", senderClient); foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); GameMain.Server.SendConsoleMessage(" - " + attributes[0].Description, senderClient); } if (client.HasPermission(ClientPermissions.ConsoleCommands)) { if (client.PermittedConsoleCommands.Count == 0) { GameMain.Server.SendConsoleMessage("No permitted console commands:", senderClient); } else { GameMain.Server.SendConsoleMessage("Permitted console commands:", senderClient); foreach (Command permittedCommand in client.PermittedConsoleCommands) { GameMain.Server.SendConsoleMessage(" - " + permittedCommand.names[0], senderClient); } } } })); commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", (string[] args) => { throw new NotImplementedException(); if (GameMain.Server == null) return; GameMain.Server.KarmaEnabled = !GameMain.Server.KarmaEnabled; })); commands.Add(new Command("kick", "kick [name]: Kick a player out of the server.", (string[] args) => { if (GameMain.NetworkMember == null || args.Length == 0) return; string playerName = string.Join(" ", args); ShowQuestionPrompt("Reason for kicking \"" + playerName + "\"?", (reason) => { GameMain.NetworkMember.KickPlayer(playerName, reason); }); }, () => { if (GameMain.NetworkMember == null) return null; return new string[][] { GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() }; })); commands.Add(new Command("kickid", "kickid [id]: Kick the player with the specified client ID out of the server.", (string[] args) => { if (GameMain.NetworkMember == null || args.Length == 0) return; int.TryParse(args[0], out int id); var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } ShowQuestionPrompt("Reason for kicking \"" + client.Name + "\"?", (reason) => { GameMain.NetworkMember.KickPlayer(client.Name, reason); }); })); commands.Add(new Command("ban", "ban [name]: Kick and ban the player from the server.", (string[] args) => { if (GameMain.NetworkMember == null || args.Length == 0) return; string clientName = string.Join(" ", args); ShowQuestionPrompt("Reason for banning \"" + clientName + "\"?", (reason) => { ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => { TimeSpan? banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { TimeSpan parsedBanDuration; if (!TryParseTimeSpan(duration, out parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; } banDuration = parsedBanDuration; } GameMain.NetworkMember.BanPlayer(clientName, reason, false, banDuration); }); }); }, () => { if (GameMain.NetworkMember == null) return null; return new string[][] { GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() }; })); commands.Add(new Command("banid", "banid [id]: Kick and ban the player with the specified client ID from the server.", (string[] args) => { if (GameMain.NetworkMember == null || args.Length == 0) return; int.TryParse(args[0], out int id); var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } ShowQuestionPrompt("Reason for banning \"" + client.Name + "\"?", (reason) => { ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => { TimeSpan? banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { if (!TryParseTimeSpan(duration, out TimeSpan parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; } banDuration = parsedBanDuration; } GameMain.NetworkMember.BanPlayer(client.Name, reason, false, banDuration); }); }); })); commands.Add(new Command("banip", "banip [ip]: Ban the IP address from the server.", (string[] args) => { if (GameMain.Server == null || args.Length == 0) return; ShowQuestionPrompt("Reason for banning the ip \"" + args[0] + "\"?", (reason) => { ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => { TimeSpan? banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { if (!TryParseTimeSpan(duration, out TimeSpan parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; } banDuration = parsedBanDuration; } var clients = GameMain.Server.ConnectedClients.FindAll(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); if (clients.Count == 0) { GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, banDuration); } else { foreach (Client cl in clients) { GameMain.Server.BanClient(cl, reason, false, banDuration); } } }); }); }, (string[] args) => { #if CLIENT if (GameMain.Client == null || args.Length == 0) return; ShowQuestionPrompt("Reason for banning the ip \"" + args[0] + "\"?", (reason) => { ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => { TimeSpan? banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { if (!TryParseTimeSpan(duration, out TimeSpan parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; } banDuration = parsedBanDuration; } GameMain.Client.SendConsoleCommand( "banip " + args[0] + " " + (banDuration.HasValue ? banDuration.Value.TotalSeconds.ToString() : "0") + " " + reason); }); }); #endif }, (Client client, Vector2 cursorPos, string[] args) => { if (args.Length < 1) return; var clients = GameMain.Server.ConnectedClients.FindAll(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); TimeSpan? duration = null; if (args.Length > 1) { if (double.TryParse(args[1], out double durationSeconds)) { if (durationSeconds > 0) duration = TimeSpan.FromSeconds(durationSeconds); } else { GameMain.Server.SendConsoleMessage("\"" + args[1] + "\" is not a valid ban duration.", client); return; } } string reason = ""; if (args.Length > 2) reason = string.Join(" ", args.Skip(2)); if (clients.Count == 0) { GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, duration); } else { foreach (Client cl in clients) { GameMain.Server.BanClient(cl, reason, false, duration); } } })); commands.Add(new Command("teleportcharacter|teleport", "teleport [character name]: Teleport the specified character to the position of the cursor. If the name parameter is omitted, the controlled character will be teleported.", (string[] args) => { Character tpCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false); if (tpCharacter == null) return; var cam = GameMain.GameScreen.Cam; tpCharacter.AnimController.CurrentHull = null; tpCharacter.Submarine = null; tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition))); tpCharacter.AnimController.FindHull(cam.ScreenToWorld(PlayerInput.MousePosition), true); }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { Character tpCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args, false); if (tpCharacter == null) return; var cam = GameMain.GameScreen.Cam; tpCharacter.AnimController.CurrentHull = null; tpCharacter.Submarine = null; tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cursorWorldPos)); tpCharacter.AnimController.FindHull(cursorWorldPos, true); }, () => { return new string[][] { Character.CharacterList.Select(c => c.Name).Distinct().ToArray() }; })); commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) => { if (Submarine.MainSub == null) return; Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode; NewMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", Color.White); }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { if (Submarine.MainSub == null) return; Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode; NewMessage((Submarine.MainSub.GodMode ? "Godmode turned on by \"" : "Godmode off by \"") + client.Name+"\"", Color.White); GameMain.Server.SendConsoleMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", client); })); commands.Add(new Command("lockx", "lockx: Lock horizontal movement of the main submarine.", (string[] args) => { Submarine.LockX = !Submarine.LockX; }, null, null)); commands.Add(new Command("locky", "locky: Lock vertical movement of the main submarine.", (string[] args) => { Submarine.LockY = !Submarine.LockY; }, null, null)); commands.Add(new Command("dumpids", "", (string[] args) => { try { int count = args.Length == 0 ? 10 : int.Parse(args[0]); Entity.DumpIds(count); } catch (Exception e) { ThrowError("Failed to dump ids", e); } })); commands.Add(new Command("findentityids", "findentityids [entityname]", (string[] args) => { if (args.Length == 0) return; args[0] = args[0].ToLowerInvariant(); foreach (MapEntity mapEntity in MapEntity.mapEntityList) { if (mapEntity.Name.ToLowerInvariant() == args[0]) { ThrowError(mapEntity.ID + ": " + mapEntity.Name.ToString()); } } foreach (Character character in Character.CharacterList) { if (character.Name.ToLowerInvariant() == args[0] || character.SpeciesName.ToLowerInvariant() == args[0]) { ThrowError(character.ID + ": " + character.Name.ToString()); } } })); commands.Add(new Command("heal", "heal [character name]: Restore the specified character to full health. If the name parameter is omitted, the controlled character will be healed.", (string[] args) => { Character healedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); if (healedCharacter != null) { healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); healedCharacter.Oxygen = 100.0f; healedCharacter.Bleeding = 0.0f; healedCharacter.SetStun(0.0f, true); } }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { Character healedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); if (healedCharacter != null) { healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); healedCharacter.Oxygen = 100.0f; healedCharacter.Bleeding = 0.0f; healedCharacter.SetStun(0.0f, true); } }, () => { return new string[][] { Character.CharacterList.Select(c => c.Name).Distinct().ToArray() }; })); commands.Add(new Command("revive", "revive [character name]: Bring the specified character back from the dead. If the name parameter is omitted, the controlled character will be revived.", (string[] args) => { Character revivedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); if (revivedCharacter == null) return; revivedCharacter.Revive(false); if (GameMain.Server != null) { foreach (Client c in GameMain.Server.ConnectedClients) { if (c.Character != revivedCharacter) continue; //clients stop controlling the character when it dies, force control back GameMain.Server.SetClientCharacter(c, revivedCharacter); break; } } }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { Character revivedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); if (revivedCharacter == null) return; revivedCharacter.Revive(false); if (GameMain.Server != null) { foreach (Client c in GameMain.Server.ConnectedClients) { if (c.Character != revivedCharacter) continue; //clients stop controlling the character when it dies, force control back GameMain.Server.SetClientCharacter(c, revivedCharacter); break; } } }, () => { return new string[][] { Character.CharacterList.Select(c => c.Name).Distinct().ToArray() }; })); commands.Add(new Command("freeze", "", (string[] args) => { if (Character.Controlled != null) Character.Controlled.AnimController.Frozen = !Character.Controlled.AnimController.Frozen; }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { if (client.Character != null) client.Character.AnimController.Frozen = !client.Character.AnimController.Frozen; })); commands.Add(new Command("ragdoll", "ragdoll [character name]: Force-ragdoll the specified character. If the name parameter is omitted, the controlled character will be ragdolled.", (string[] args) => { Character ragdolledCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); if (ragdolledCharacter != null) { ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; } }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { Character ragdolledCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); if (ragdolledCharacter != null) { ragdolledCharacter.IsForceRagdolled = !ragdolledCharacter.IsForceRagdolled; } }, () => { return new string[][] { Character.CharacterList.Select(c => c.Name).Distinct().ToArray() }; })); commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) => { Character.Controlled = null; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; })); commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) => { if (GameMain.Client == null) { Hull.EditWater = !Hull.EditWater; NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); } })); commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) => { if (GameMain.Client == null) { Hull.EditFire = !Hull.EditFire; NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White); } })); commands.Add(new Command("explosion", "explosion [range] [force] [damage] [structuredamage] [emp strength]: Creates an explosion at the position of the cursor.", (string[] args) => { Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; if (args.Length > 0) float.TryParse(args[0], out range); if (args.Length > 1) float.TryParse(args[1], out force); if (args.Length > 2) float.TryParse(args[2], out damage); if (args.Length > 3) float.TryParse(args[3], out structureDamage); if (args.Length > 4) float.TryParse(args[4], out empStrength); new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { Vector2 explosionPos = cursorWorldPos; float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; ; if (args.Length > 0) float.TryParse(args[0], out range); if (args.Length > 1) float.TryParse(args[1], out force); if (args.Length > 2) float.TryParse(args[2], out damage); if (args.Length > 3) float.TryParse(args[3], out structureDamage); if (args.Length > 4) float.TryParse(args[4], out empStrength); new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos); })); commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => { foreach (Item it in Item.ItemList) { it.Condition = it.Prefab.Health; } }, null, null)); commands.Add(new Command("fixhulls|fixwalls", "fixwalls/fixhulls: Fixes all walls.", (string[] args) => { foreach (Structure w in Structure.WallList) { for (int i = 0; i < w.SectionCount; i++) { w.AddDamage(i, -100000.0f); } } }, null, null)); commands.Add(new Command("power", "power [temperature]: Immediately sets the temperature of the nuclear reactor to the specified value.", (string[] args) => { Item reactorItem = Item.ItemList.Find(i => i.GetComponent() != null); if (reactorItem == null) return; float power = 5000.0f; if (args.Length > 0) float.TryParse(args[0], out power); var reactor = reactorItem.GetComponent(); reactor.ShutDownTemp = power == 0 ? 0 : 7000.0f; reactor.AutoTemp = true; reactor.Temperature = power; if (GameMain.Server != null) { reactorItem.CreateServerEvent(reactor); } }, null, null)); commands.Add(new Command("oxygen|air", "oxygen/air: Replenishes the oxygen levels in every room to 100%.", (string[] args) => { foreach (Hull hull in Hull.hullList) { hull.OxygenPercentage = 100.0f; } }, null, null)); commands.Add(new Command("kill", "kill [character]: Immediately kills the specified character.", (string[] args) => { Character killedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args); killedCharacter?.AddDamage(CauseOfDeath.Damage, killedCharacter.MaxHealth * 2, null); }, null, (Client client, Vector2 cursorWorldPos, string[] args) => { Character killedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); killedCharacter?.AddDamage(CauseOfDeath.Damage, killedCharacter.MaxHealth * 2, null); })); commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => { foreach (Character c in Character.CharacterList) { if (!(c.AIController is EnemyAIController)) continue; c.AddDamage(CauseOfDeath.Damage, c.MaxHealth * 2, null); } }, null, null)); commands.Add(new Command("netstats", "netstats: Toggles the visibility of the network statistics UI.", (string[] args) => { if (GameMain.Server == null) return; GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats; })); commands.Add(new Command("setclientcharacter", "setclientcharacter [client name] ; [character name]: Gives the client control of the specified character.", (string[] args) => { if (GameMain.Server == null) return; int separatorIndex = Array.IndexOf(args, ";"); if (separatorIndex == -1 || args.Length < 3) { ThrowError("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\""); return; } string[] argsLeft = args.Take(separatorIndex).ToArray(); string[] argsRight = args.Skip(separatorIndex + 1).ToArray(); string clientName = string.Join(" ", argsLeft); var client = GameMain.Server.ConnectedClients.Find(c => c.Name == clientName); if (client == null) { ThrowError("Client \"" + clientName + "\" not found."); } var character = FindMatchingCharacter(argsRight, false); GameMain.Server.SetClientCharacter(client, character); }, null, (Client senderClient, Vector2 cursorWorldPos, string[] args) => { int separatorIndex = Array.IndexOf(args, ";"); if (separatorIndex == -1 || args.Length < 3) { GameMain.Server.SendConsoleMessage("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\"", senderClient); return; } string[] argsLeft = args.Take(separatorIndex).ToArray(); string[] argsRight = args.Skip(separatorIndex + 1).ToArray(); string clientName = string.Join(" ", argsLeft); var client = GameMain.Server.ConnectedClients.Find(c => c.Name == clientName); if (client == null) { GameMain.Server.SendConsoleMessage("Client \"" + clientName + "\" not found.", senderClient); } var character = FindMatchingCharacter(argsRight, false); GameMain.Server.SetClientCharacter(client, character); }, () => { if (GameMain.NetworkMember == null) return null; return new string[][] { GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(), Character.CharacterList.Select(c => c.Name).Distinct().ToArray() }; })); commands.Add(new Command("campaigninfo|campaignstatus", "campaigninfo: Display information about the state of the currently active campaign.", (string[] args) => { var campaign = GameMain.GameSession?.GameMode as CampaignMode; if (campaign == null) { ThrowError("No campaign active!"); return; } campaign.LogState(); })); commands.Add(new Command("campaigndestination|setcampaigndestination", "campaigndestination [index]: Set the location to head towards in the currently active campaign.", (string[] args) => { var campaign = GameMain.GameSession?.GameMode as CampaignMode; if (campaign == null) { ThrowError("No campaign active!"); return; } if (args.Length == 0) { int i = 0; foreach (LocationConnection connection in campaign.Map.CurrentLocation.Connections) { NewMessage(" " + i + ". " + connection.OtherLocation(campaign.Map.CurrentLocation).Name, Color.White); i++; } ShowQuestionPrompt("Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) + "):", (string selectedDestination) => { int destinationIndex = -1; if (!int.TryParse(selectedDestination, out destinationIndex)) return; if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) { NewMessage("Index out of bounds!", Color.Red); return; } Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); campaign.Map.SelectLocation(location); NewMessage(location.Name+" selected.", Color.White); }); } else { int destinationIndex = -1; if (!int.TryParse(args[0], out destinationIndex)) return; if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) { NewMessage("Index out of bounds!", Color.Red); return; } Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); campaign.Map.SelectLocation(location); NewMessage(location.Name + " selected.", Color.White); } }, (string[] args) => { #if CLIENT var campaign = GameMain.GameSession?.GameMode as CampaignMode; if (campaign == null) { ThrowError("No campaign active!"); return; } if (args.Length == 0) { int i = 0; foreach (LocationConnection connection in campaign.Map.CurrentLocation.Connections) { NewMessage(" " + i + ". " + connection.OtherLocation(campaign.Map.CurrentLocation).Name, Color.White); i++; } ShowQuestionPrompt("Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) + "):", (string selectedDestination) => { int destinationIndex = -1; if (!int.TryParse(selectedDestination, out destinationIndex)) return; if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) { NewMessage("Index out of bounds!", Color.Red); return; } GameMain.Client.SendConsoleCommand("campaigndestination " + destinationIndex); }); } else { int destinationIndex = -1; if (!int.TryParse(args[0], out destinationIndex)) return; if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) { NewMessage("Index out of bounds!", Color.Red); return; } GameMain.Client.SendConsoleCommand("campaigndestination " + destinationIndex); } #endif }, (Client senderClient, Vector2 cursorWorldPos, string[] args) => { var campaign = GameMain.GameSession?.GameMode as CampaignMode; if (campaign == null) { GameMain.Server.SendConsoleMessage("No campaign active!", senderClient); return; } int destinationIndex = -1; if (args.Length < 1 || !int.TryParse(args[0], out destinationIndex)) return; if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) { GameMain.Server.SendConsoleMessage("Index out of bounds!", senderClient); return; } Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); campaign.Map.SelectLocation(location); GameMain.Server.SendConsoleMessage(location.Name + " selected.", senderClient); })); #if DEBUG commands.Add(new Command("spamevents", "A debug command that immediately creates entity events for all items, characters and structures.", (string[] args) => { foreach (Item item in Item.ItemList) { for (int i = 0; i < item.components.Count; i++) { if (item.components[i] is IServerSerializable) { GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, i }); } var itemContainer = item.GetComponent(); if (itemContainer != null) { GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState }); } GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); item.NeedsPositionUpdate = true; } } foreach (Character c in Character.CharacterList) { GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); } foreach (Structure wall in Structure.WallList) { GameMain.Server.CreateEntityEvent(wall); } }, null, null)); commands.Add(new Command("flipx", "flipx: mirror the main submarine horizontally", (string[] args) => { Submarine.MainSub?.FlipX(); })); #endif InitProjectSpecific(); commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); } private static string[] SplitCommand(string command) { command = command.Trim(); List commands = new List(); int escape = 0; bool inQuotes = false; string piece = ""; for (int i = 0; i < command.Length; i++) { if (command[i] == '\\') { if (escape == 0) escape = 2; else piece += '\\'; } else if (command[i] == '"') { if (escape == 0) inQuotes = !inQuotes; else piece += '"'; } else if (command[i] == ' ' && !inQuotes) { if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); piece = ""; } else if (escape == 0) piece += command[i]; if (escape > 0) escape--; } if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); //add final piece return commands.ToArray(); } public static string AutoComplete(string command) { string[] splitCommand = SplitCommand(command); string[] args = splitCommand.Skip(1).ToArray(); //if an argument is given or the last character is a space, attempt to autocomplete the argument if (args.Length > 0 || (command.Length > 0 && command.Last() == ' ')) { Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0])); if (matchingCommand == null || matchingCommand.GetValidArgs == null) return command; int autoCompletedArgIndex = args.Length > 0 && command.Last() != ' ' ? args.Length - 1 : args.Length; //get all valid arguments for the given command string[][] allArgs = matchingCommand.GetValidArgs(); if (allArgs == null || allArgs.GetLength(0) < autoCompletedArgIndex + 1) return command; if (string.IsNullOrEmpty(currentAutoCompletedCommand)) { currentAutoCompletedCommand = autoCompletedArgIndex > args.Length - 1 ? " " : args.Last(); } //find all valid autocompletions for the given argument string[] validArgs = allArgs[autoCompletedArgIndex].Where(arg => currentAutoCompletedCommand.Trim().Length <= arg.Length && arg.Substring(0, currentAutoCompletedCommand.Trim().Length).ToLower() == currentAutoCompletedCommand.Trim().ToLower()).ToArray(); if (validArgs.Length == 0) return command; currentAutoCompletedIndex = currentAutoCompletedIndex % validArgs.Length; string autoCompletedArg = validArgs[currentAutoCompletedIndex++]; //add quotation marks to args that contain spaces if (autoCompletedArg.Contains(' ')) autoCompletedArg = '"' + autoCompletedArg + '"'; for (int i = 0; i < splitCommand.Length; i++) { if (splitCommand[i].Contains(' ')) splitCommand[i] = '"' + splitCommand[i] + '"'; } return string.Join(" ", autoCompletedArgIndex >= args.Length ? splitCommand : splitCommand.Take(splitCommand.Length - 1)) + " " + autoCompletedArg; } else { if (string.IsNullOrWhiteSpace(currentAutoCompletedCommand)) { currentAutoCompletedCommand = command; } List matchingCommands = new List(); foreach (Command c in commands) { foreach (string name in c.names) { if (currentAutoCompletedCommand.Length > name.Length) continue; if (currentAutoCompletedCommand == name.Substring(0, currentAutoCompletedCommand.Length)) { matchingCommands.Add(name); } } } if (matchingCommands.Count == 0) return command; currentAutoCompletedIndex = currentAutoCompletedIndex % matchingCommands.Count; return matchingCommands[currentAutoCompletedIndex++]; } } private static string AutoCompleteStr(string str, IEnumerable validStrings) { if (string.IsNullOrEmpty(str)) return str; foreach (string validStr in validStrings) { if (validStr.Length > str.Length && validStr.Substring(0, str.Length) == str) return validStr; } return str; } public static void ResetAutoComplete() { currentAutoCompletedCommand = ""; currentAutoCompletedIndex = 0; } public static string SelectMessage(int direction) { if (Messages.Count == 0) return ""; direction = MathHelper.Clamp(direction, -1, 1); int i = 0; do { selectedIndex += direction; if (selectedIndex < 0) selectedIndex = Messages.Count - 1; selectedIndex = selectedIndex % Messages.Count; if (++i >= Messages.Count) break; } while (!Messages[selectedIndex].IsCommand); return Messages[selectedIndex].Text; } public static void ExecuteCommand(string command) { if (activeQuestionCallback != null) { #if CLIENT activeQuestionText = null; #endif NewMessage(command, Color.White, true); //reset the variable before invoking the delegate because the method may need to activate another question var temp = activeQuestionCallback; activeQuestionCallback = null; temp(command); return; } if (string.IsNullOrWhiteSpace(command) || command == "\\" || command == "\n") return; string[] splitCommand = SplitCommand(command); if (splitCommand.Length == 0) { ThrowError("Failed to execute command \"" + command + "\"!"); GameAnalyticsManager.AddErrorEventOnce( "DebugConsole.ExecuteCommand:LengthZero", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to execute command \"" + command + "\"!"); return; } if (!splitCommand[0].ToLowerInvariant().Equals("admin")) { NewMessage(command, Color.White, true); } #if CLIENT if (GameMain.Client != null) { if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant())) { Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); //if the command is not defined client-side, we'll relay it anyway because it may be a custom command at the server's side if (matchingCommand == null || matchingCommand.RelayToServer) { GameMain.Client.SendConsoleCommand(command); } else { matchingCommand.ClientExecute(splitCommand.Skip(1).ToArray()); } NewMessage("Server command: " + command, Color.White); return; } #if !DEBUG if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) { ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); return; } #endif } #endif bool commandFound = false; foreach (Command c in commands) { if (c.names.Contains(splitCommand[0].ToLowerInvariant())) { c.Execute(splitCommand.Skip(1).ToArray()); commandFound = true; break; } } if (!commandFound) { ThrowError("Command \"" + splitCommand[0] + "\" not found."); } } public static void ExecuteClientCommand(Client client, Vector2 cursorWorldPos, string command) { if (GameMain.Server == null) return; if (string.IsNullOrWhiteSpace(command)) return; if (!client.HasPermission(ClientPermissions.ConsoleCommands)) { GameMain.Server.SendConsoleMessage("You are not permitted to use console commands!", client); GameServer.Log(client.Name + " attempted to execute the console command \"" + command + "\" without a permission to use console commands.", ServerLog.MessageType.ConsoleUsage); return; } string[] splitCommand = SplitCommand(command); Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand)) { GameMain.Server.SendConsoleMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client); GameServer.Log(client.Name + " attempted to execute the console command \"" + command + "\" without a permission to use the command.", ServerLog.MessageType.ConsoleUsage); return; } else if (matchingCommand == null) { GameMain.Server.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client); return; } if (!MathUtils.IsValid(cursorWorldPos)) { GameMain.Server.SendConsoleMessage("Could not execute command \"" + command + "\" - invalid cursor position.", client); NewMessage(client.Name + " attempted to execute the console command \"" + command + "\" with invalid cursor position.", Color.White); return; } try { matchingCommand.ServerExecuteOnClientRequest(client, cursorWorldPos, splitCommand.Skip(1).ToArray()); GameServer.Log("Console command \"" + command + "\" executed by " + client.Name + ".", ServerLog.MessageType.ConsoleUsage); } catch (Exception e) { ThrowError("Executing the command \"" + matchingCommand.names[0] + "\" by request from \"" + client.Name + "\" failed.", e); } } private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false) { if (args.Length == 0) return null; int characterIndex; string characterName; if (int.TryParse(args.Last(), out characterIndex) && args.Length > 1) { characterName = string.Join(" ", args.Take(args.Length - 1)).ToLowerInvariant(); } else { characterName = string.Join(" ", args).ToLowerInvariant(); characterIndex = -1; } var matchingCharacters = Character.CharacterList.FindAll(c => (!ignoreRemotePlayers || !c.IsRemotePlayer) && c.Name.ToLowerInvariant() == characterName); if (!matchingCharacters.Any()) { NewMessage("Character \""+ characterName + "\" not found", Color.Red); return null; } if (characterIndex == -1) { if (matchingCharacters.Count > 1) { NewMessage( "Found multiple matching characters. " + "Use \"[charactername] [0-" + (matchingCharacters.Count - 1) + "]\" to choose a specific character.", Color.LightGray); } return matchingCharacters[0]; } else if (characterIndex < 0 || characterIndex >= matchingCharacters.Count) { ThrowError("Character index out of range. Select an index between 0 and " + (matchingCharacters.Count - 1)); } else { return matchingCharacters[characterIndex]; } return null; } private static void SpawnCharacter(string[] args, Vector2 cursorWorldPos, out string errorMsg) { errorMsg = ""; if (args.Length == 0) return; Character spawnedCharacter = null; Vector2 spawnPosition = Vector2.Zero; WayPoint spawnPoint = null; if (args.Length > 1) { switch (args[1].ToLowerInvariant()) { case "inside": spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); break; case "outside": spawnPoint = WayPoint.GetRandom(SpawnType.Enemy); break; case "near": case "close": float closestDist = -1.0f; foreach (WayPoint wp in WayPoint.WayPointList) { if (wp.Submarine != null) continue; //don't spawn inside hulls if (Hull.FindHull(wp.WorldPosition, null) != null) continue; float dist = Vector2.Distance(wp.WorldPosition, GameMain.GameScreen.Cam.WorldViewCenter); if (closestDist < 0.0f || dist < closestDist) { spawnPoint = wp; closestDist = dist; } } break; case "cursor": spawnPosition = cursorWorldPos; break; default: spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); break; } } else { spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); } if (string.IsNullOrWhiteSpace(args[0])) return; if (spawnPoint != null) spawnPosition = spawnPoint.WorldPosition; if (args[0].ToLowerInvariant() == "human") { spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition); #if CLIENT if (GameMain.GameSession != null) { SinglePlayerCampaign mode = GameMain.GameSession.GameMode as SinglePlayerCampaign; if (mode != null) { Character.Controlled = spawnedCharacter; GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled); GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled); } } #endif } else { List characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); foreach (string characterFile in characterFiles) { if (Path.GetFileNameWithoutExtension(characterFile).ToLowerInvariant() == args[0].ToLowerInvariant()) { Character.Create(characterFile, spawnPosition); return; } } errorMsg = "No character matching the name \"" + args[0] + "\" found in the selected content package."; //attempt to open the config from the default path (the file may still be present even if it isn't included in the content package) string configPath = "Content/Characters/" + args[0].First().ToString().ToUpper() + args[0].Substring(1) + "/" + args[0].ToLower() + ".xml"; Character.Create(configPath, spawnPosition); } } private static void SpawnItem(string[] args, Vector2 cursorPos, Character controlledCharacter, out string errorMsg) { errorMsg = ""; if (args.Length < 1) return; Vector2? spawnPos = null; Inventory spawnInventory = null; int extraParams = 0; switch (args.Last().ToLowerInvariant()) { case "cursor": extraParams = 1; spawnPos = cursorPos; break; case "inventory": extraParams = 1; spawnInventory = controlledCharacter == null ? null : controlledCharacter.Inventory; break; case "cargo": var wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; break; //Dont do a thing, random is basically Human points anyways - its in the help description. case "random": extraParams = 1; return; default: extraParams = 0; break; } string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); ItemPrefab itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (itemPrefab == null && extraParams == 0) { if (GameMain.Server != null) { var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower()); if (client != null) { extraParams += 1; itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); if (client.Character != null && client.Character.Name == args.Last().ToLower()) spawnInventory = client.Character.Inventory; itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; } } } //Check again if the item can be found again after having checked for a character if (itemPrefab == null) { errorMsg = "Item \"" + itemName + "\" not found!"; return; } if ((spawnPos == null || spawnPos == Vector2.Zero) && spawnInventory == null) { var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; } if (spawnPos != null) { Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); } else if (spawnInventory != null) { Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory); } } public static void NewMessage(string msg, Color color, bool isCommand = false) { if (string.IsNullOrEmpty((msg))) return; #if SERVER var newMsg = new ColoredText(msg, color, isCommand); Messages.Add(newMsg); //TODO: REMOVE Console.ForegroundColor = XnaToConsoleColor.Convert(color); Console.WriteLine(msg); Console.ForegroundColor = ConsoleColor.White; if (GameSettings.SaveDebugConsoleLogs) { unsavedMessages.Add(newMsg); if (unsavedMessages.Count >= messagesPerFile) { SaveLogs(); unsavedMessages.Clear(); } } if (Messages.Count > MaxMessages) { Messages.RemoveRange(0, Messages.Count - MaxMessages); } #elif CLIENT lock (queuedMessages) { queuedMessages.Enqueue(new ColoredText(msg, color, isCommand)); } #endif } public static void ShowQuestionPrompt(string question, QuestionCallback onAnswered) { #if CLIENT activeQuestionText = new GUITextBlock(new Rectangle(0, 0, listBox.Rect.Width, 30), " >>" + question, "", Alignment.TopLeft, Alignment.Left, null, true, GUI.SmallFont); activeQuestionText.CanBeFocused = false; activeQuestionText.TextColor = Color.Cyan; #else NewMessage(" >>" + question, Color.Cyan); #endif activeQuestionCallback += onAnswered; } private static bool TryParseTimeSpan(string s, out TimeSpan timeSpan) { timeSpan = new TimeSpan(); if (string.IsNullOrWhiteSpace(s)) return false; string currNum = ""; foreach (char c in s) { if (char.IsDigit(c)) { currNum += c; } else if (char.IsWhiteSpace(c)) { continue; } else { int parsedNum = 0; if (!int.TryParse(currNum, out parsedNum)) { return false; } switch (c) { case 'd': timeSpan += new TimeSpan(parsedNum, 0, 0, 0, 0); break; case 'h': timeSpan += new TimeSpan(0, parsedNum, 0, 0, 0); break; case 'm': timeSpan += new TimeSpan(0, 0, parsedNum, 0, 0); break; case 's': timeSpan += new TimeSpan(0, 0, 0, parsedNum, 0); break; default: return false; } currNum = ""; } } return true; } public static Command FindCommand(string commandName) { commandName = commandName.ToLowerInvariant(); return commands.Find(c => c.names.Any(n => n.ToLowerInvariant() == commandName)); } public static void Log(string message) { if (GameSettings.VerboseLogging) NewMessage(message, Color.Gray); } public static void ThrowError(string error, Exception e = null) { if (e != null) { error += " {" + e.Message + "}\n" + e.StackTrace; } System.Diagnostics.Debug.WriteLine(error); NewMessage(error, Color.Red); #if CLIENT isOpen = true; #endif } public static void SaveLogs() { if (unsavedMessages.Count == 0) return; if (!Directory.Exists(SavePath)) { try { Directory.CreateDirectory(SavePath); } catch (Exception e) { ThrowError("Failed to create a folder for debug console logs", e); return; } } string fileName = "DebugConsoleLog_" + DateTime.Now.ToShortDateString() + "_" + DateTime.Now.ToShortTimeString() + ".txt"; var invalidChars = Path.GetInvalidFileNameChars(); foreach (char invalidChar in invalidChars) { fileName = fileName.Replace(invalidChar.ToString(), ""); } string filePath = Path.Combine(SavePath, fileName); if (File.Exists(filePath)) { int fileNum = 2; while (File.Exists(filePath + " (" + fileNum + ")")) { fileNum++; } filePath = filePath + " (" + fileNum + ")"; } try { File.WriteAllLines(filePath, unsavedMessages.Select(l => "[" + l.Time + "] " + l.Text)); } catch (Exception e) { unsavedMessages.Clear(); ThrowError("Saving debug console log to " + filePath + " failed", e); } } } }