From 6ad5dd6c1e3544b34448755a8cb6d33ef5a41477 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 10 Jul 2017 21:10:54 +0300 Subject: [PATCH] DebugConsole refactoring : available console commands are stored in a list of "Command" objects which contain the name, help text and the action that's invoked when the command is entered. Commands can also now be autocompleted in the client project by pressing tab (TODO: implement in the server project). + Now it should be easier to implement giving clients the permission to use specific console commands. --- .../BarotraumaClient/Source/DebugConsole.cs | 583 ++++----- .../BarotraumaClient/Source/GUI/GUITextBox.cs | 10 +- .../BarotraumaServer/Source/DebugConsole.cs | 277 ++--- .../BarotraumaShared/Source/DebugConsole.cs | 1108 ++++++++++------- 4 files changed, 1018 insertions(+), 960 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index c4af4d6fe..d8d751efd 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -42,17 +42,13 @@ namespace Barotrauma //listBox.Color = Color.Black * 0.7f; textBox = new GUITextBox(new Rectangle(0, 0, 0, 20), Color.Black, Color.White, Alignment.BottomLeft, Alignment.Left, "", frame); - //textBox.Color = Color.Black * 0.7f; - - //messages already added before initialization -> add them to the listbox - List unInitializedMessages = new List(Messages); - Messages.Clear(); - - foreach (ColoredText msg in unInitializedMessages) - { - NewMessage(msg.Text, msg.Color); - } + textBox.OnTextChanged += (textBox, text) => + { + ResetAutoComplete(); + return true; + }; + NewMessage("Press F3 to open/close the debug console", Color.Cyan); NewMessage("Enter \"help\" for a list of available console commands", Color.Cyan); @@ -105,6 +101,10 @@ namespace Barotrauma { SelectMessage(1); } + else if (PlayerInput.KeyHit(Keys.Tab)) + { + textBox.Text = AutoComplete(textBox.Text); + } if (PlayerInput.KeyHit(Keys.Enter)) { @@ -195,390 +195,285 @@ namespace Barotrauma } } - private static bool ExecProjSpecific(string[] commands) + private static void InitProjectSpecific() { - switch (commands[0].ToLowerInvariant()) + commands.Add(new Command("startclient", "", (string[] args) => { - case "help": - NewMessage("menu: go to main menu", Color.Cyan); - NewMessage("game: enter the \"game screen\"", Color.Cyan); - NewMessage("edit: switch to submarine editor", Color.Cyan); - NewMessage("edit [submarine name]: load a submarine and switch to submarine editor", Color.Cyan); - NewMessage("load [submarine name]: load a submarine", Color.Cyan); - NewMessage("save [submarine name]: save the current submarine using the specified name", Color.Cyan); + if (args.Length == 0) return; + + if (GameMain.Client == null) + { + GameMain.NetworkMember = new GameClient("Name"); + GameMain.Client.ConnectToServer(args[0]); + } + })); - NewMessage(" ", Color.Cyan); + commands.Add(new Command("mainmenuscreen|mainmenu|menu", "mainmenu/menu: Go to the main menu.", (string[] args) => + { + GameMain.GameSession = null; - NewMessage("spawn [creaturename] [near/inside/outside]: spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine)", Color.Cyan); - NewMessage("spawnitem [itemname] [cursor/inventory]: spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted", Color.Cyan); + List characters = new List(Character.CharacterList); + foreach (Character c in characters) + { + c.Remove(); + } - NewMessage(" ", Color.Cyan); + GameMain.MainMenuScreen.Select(); + })); - NewMessage("lights: disable lighting", Color.Cyan); - NewMessage("los: disable the line of sight effect", Color.Cyan); - NewMessage("freecam: detach the camera from the controlled character", Color.Cyan); - NewMessage("control [character name]: start controlling the specified character", Color.Cyan); + commands.Add(new Command("gamescreen|game", "gamescreen/game: Go to the \"in-game\" view.", (string[] args) => + { - NewMessage(" ", Color.Cyan); + })); - NewMessage("water: allows adding water into rooms or removing it by holding the left/right mouse buttons", Color.Cyan); - NewMessage("fire: allows putting up fires by left clicking", Color.Cyan); + commands.Add(new Command("editsubscreen|editsub|subeditor", "editsub/subeditor: Switch to the submarine editor.", (string[] args) => + { + if (args.Length > 0) + { + Submarine.Load(string.Join(" ", args), true); + } + GameMain.EditMapScreen.Select(); + })); - NewMessage(" ", Color.Cyan); + commands.Add(new Command("editcharacter", "", (string[] args) => + { + GameMain.EditCharacterScreen.Select(); - NewMessage("teleport: teleport the controlled character to the position of the cursor", Color.Cyan); - NewMessage("teleport [character name]: teleport the specified character to the position of the cursor", Color.Cyan); - NewMessage("heal: restore the controlled character to full health", Color.Cyan); - NewMessage("heal [character name]: restore the specified character to full health", Color.Cyan); - NewMessage("revive: bring the controlled character back from the dead", Color.Cyan); - NewMessage("revive [character name]: bring the specified character back from the dead", Color.Cyan); - NewMessage("killmonsters: immediately kills all AI-controlled enemies in the level", Color.Cyan); + })); - NewMessage(" ", Color.Cyan); + commands.Add(new Command("control|controlcharacter", "control [character name]: Start controlling the specified character.", (string[] args) => + { + if (args.Length < 1) return; - NewMessage("fixwalls: fixes all the walls", Color.Cyan); - NewMessage("fixitems: fixes every item/device in the sub", Color.Cyan); - NewMessage("oxygen: replenishes the oxygen in every room to 100%", Color.Cyan); - NewMessage("power [amount]: immediately sets the temperature of the reactor to the specified value", Color.Cyan); + var character = FindMatchingCharacter(args, true); - NewMessage(" ", Color.Cyan); + if (character != null) + { + Character.Controlled = character; + } + })); - NewMessage("kick [name]: kick a player out from the server", Color.Cyan); - NewMessage("ban [name]: kick and ban the player from the server", Color.Cyan); - NewMessage("banip [IP address]: ban the IP address from the server", Color.Cyan); - NewMessage("debugdraw: toggles the \"debug draw mode\"", Color.Cyan); - NewMessage("netstats: toggles the visibility of the network statistics panel", Color.Cyan); + commands.Add(new Command("shake", "", (string[] args) => + { + GameMain.GameScreen.Cam.Shake = 10.0f; + })); - break; - case "startclient": - if (commands.Length == 1) return true; - if (GameMain.Client == null) + commands.Add(new Command("los", "los: Toggle the line of sight effect on/off.", (string[] args) => + { + GameMain.LightManager.LosEnabled = !GameMain.LightManager.LosEnabled; + NewMessage("Line of sight effect " + (GameMain.LightManager.LosEnabled ? "enabled" : "disabled"), Color.White); + })); + + commands.Add(new Command("lighting|lights", "Toggle lighting on/off.", (string[] args) => + { + GameMain.LightManager.LightingEnabled = !GameMain.LightManager.LightingEnabled; + NewMessage("Lighting " + (GameMain.LightManager.LightingEnabled ? "enabled" : "disabled"), Color.White); + })); + + commands.Add(new Command("tutorial", "", (string[] args) => + { + TutorialMode.StartTutorial(Tutorials.TutorialType.TutorialTypes[0]); + })); + + commands.Add(new Command("lobby|lobbyscreen", "", (string[] args) => + { + GameMain.LobbyScreen.Select(); + })); + + commands.Add(new Command("save|savesub", "save [submarine name]: Save the currently loaded submarine using the specified name.", (string[] args) => + { + if (args.Length < 1) return; + + if (GameMain.EditMapScreen.CharacterMode) + { + GameMain.EditMapScreen.ToggleCharacterMode(); + } + + string fileName = string.Join(" ", args); + if (fileName.Contains("../")) + { + ThrowError("Illegal symbols in filename (../)"); + return; + } + + if (Submarine.SaveCurrent(System.IO.Path.Combine(Submarine.SavePath, fileName + ".sub"))) + { + NewMessage("Sub saved", Color.Green); + } + })); + + commands.Add(new Command("load|loadsub", "load [submarine name]: Load a submarine.", (string[] args) => + { + if (args.Length == 0) return; + Submarine.Load(string.Join(" ", args), true); + })); + + commands.Add(new Command("cleansub", "", (string[] args) => + { + for (int i = MapEntity.mapEntityList.Count - 1; i >= 0; i--) + { + MapEntity me = MapEntity.mapEntityList[i]; + + if (me.SimPosition.Length() > 2000.0f) { - GameMain.NetworkMember = new GameClient("Name"); - GameMain.Client.ConnectToServer(commands[1]); + NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange); + MapEntity.mapEntityList.RemoveAt(i); } - break; - case "mainmenuscreen": - case "mainmenu": - case "menu": - GameMain.GameSession = null; - - List characters = new List(Character.CharacterList); - foreach (Character c in characters) + else if (me.MoveWithLevel) { - c.Remove(); + NewMessage("Removed " + me.Name + " (MoveWithLevel==true)", Color.Orange); + MapEntity.mapEntityList.RemoveAt(i); } - - GameMain.MainMenuScreen.Select(); - break; - case "gamescreen": - case "game": - GameMain.GameScreen.Select(); - break; - case "editmapscreen": - case "editmap": - case "edit": - if (commands.Length > 1) + else if (me is Item) { - Submarine.Load(string.Join(" ", commands.Skip(1)), true); - } - GameMain.EditMapScreen.Select(); - break; - case "editcharacter": - case "editchar": - GameMain.EditCharacterScreen.Select(); - break; - case "controlcharacter": - case "control": - { - if (commands.Length < 2) break; + Item item = me as Item; + var wire = item.GetComponent(); + if (wire == null) continue; - var character = FindMatchingCharacter(commands, true); - - if (character != null) + if (wire.GetNodes().Count > 0 && !wire.Connections.Any(c => c != null)) { - Character.Controlled = character; + wire.Item.Drop(null); + NewMessage("Dropped wire (ID: " + wire.Item.ID + ") - attached on wall but no connections found", Color.Orange); } } - break; - case "setclientcharacter": - { - if (GameMain.Server == null) break; + } + })); - int separatorIndex = Array.IndexOf(commands, ";"); + commands.Add(new Command("messagebox", "", (string[] args) => + { + new GUIMessageBox("", string.Join(" ", args)); + })); - if (separatorIndex == -1 || commands.Length < 4) - { - ThrowError("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\""); - break; - } + commands.Add(new Command("debugdraw", "debugdraw: Toggle the debug drawing mode on/off.", (string[] args) => + { + GameMain.DebugDraw = !GameMain.DebugDraw; + NewMessage("Debug draw mode " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.White); - string[] commandsLeft = commands.Take(separatorIndex).ToArray(); - string[] commandsRight = commands.Skip(separatorIndex).ToArray(); + })); - string clientName = String.Join(" ", commandsLeft.Skip(1)); + commands.Add(new Command("togglehud|hud", "togglehud/hud: Toggle the character HUD (inventories, icons, buttons, etc) on/off.", (string[] args) => + { + GUI.DisableHUD = !GUI.DisableHUD; + GameMain.Instance.IsMouseVisible = !GameMain.Instance.IsMouseVisible; + NewMessage(GUI.DisableHUD ? "Disabled HUD" : "Enabled HUD", Color.White); + })); - var client = GameMain.Server.ConnectedClients.Find(c => c.name == clientName); - if (client == null) - { - ThrowError("Client \"" + clientName + "\" not found."); - } + commands.Add(new Command("followsub", "followsub: Toggle whether the ", (string[] args) => + { + Camera.FollowSub = !Camera.FollowSub; + NewMessage(Camera.FollowSub ? "Set the camera to follow the closest submarine" : "Disabled submarine following.", Color.White); + })); - var character = FindMatchingCharacter(commandsRight, false); - GameMain.Server.SetClientCharacter(client, character); - } - break; - case "test": - Submarine.Load("aegir mark ii", true); - GameMain.DebugDraw = true; - GameMain.LightManager.LosEnabled = false; - GameMain.EditMapScreen.Select(); - break; - case "shake": - GameMain.GameScreen.Cam.Shake = 10.0f; - break; - case "losenabled": - case "los": - case "drawlos": - GameMain.LightManager.LosEnabled = !GameMain.LightManager.LosEnabled; - break; - case "lighting": - case "lightingenabled": - case "light": - case "lights": - GameMain.LightManager.LightingEnabled = !GameMain.LightManager.LightingEnabled; - break; - case "tutorial": - TutorialMode.StartTutorial(Tutorials.TutorialType.TutorialTypes[0]); - break; - case "editortutorial": - GameMain.EditMapScreen.Select(); - GameMain.EditMapScreen.StartTutorial(); - break; - case "lobbyscreen": - case "lobby": - GameMain.LobbyScreen.Select(); - break; - case "savemap": - case "savesub": - case "save": - if (commands.Length < 2) break; - - if (GameMain.EditMapScreen.CharacterMode) - { - GameMain.EditMapScreen.ToggleCharacterMode(); - } - - string fileName = string.Join(" ", commands.Skip(1)); - if (fileName.Contains("../")) - { - DebugConsole.ThrowError("Illegal symbols in filename (../)"); - return true; - } - - if (Submarine.SaveCurrent(System.IO.Path.Combine(Submarine.SavePath, fileName + ".sub"))) - { - NewMessage("Sub saved", Color.Green); - //Submarine.Loaded.First().CheckForErrors(); - } - - break; - case "loadmap": - case "loadsub": - case "load": - if (commands.Length < 2) break; - - Submarine.Load(string.Join(" ", commands.Skip(1)), true); - break; - case "cleansub": - for (int i = MapEntity.mapEntityList.Count - 1; i >= 0; i--) - { - MapEntity me = MapEntity.mapEntityList[i]; - - if (me.SimPosition.Length() > 2000.0f) - { - DebugConsole.NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange); - MapEntity.mapEntityList.RemoveAt(i); - } - else if (me.MoveWithLevel) - { - DebugConsole.NewMessage("Removed " + me.Name + " (MoveWithLevel==true)", Color.Orange); - MapEntity.mapEntityList.RemoveAt(i); - } - else if (me is Item) - { - Item item = me as Item; - var wire = item.GetComponent(); - if (wire == null) continue; - - if (wire.GetNodes().Count > 0 && !wire.Connections.Any(c => c != null)) - { - wire.Item.Drop(null); - DebugConsole.NewMessage("Dropped wire (ID: " + wire.Item.ID + ") - attached on wall but no connections found", Color.Orange); - } - } - - } - break; - case "messagebox": - if (commands.Length < 3) break; - new GUIMessageBox(commands[1], commands[2]); - break; - case "debugdraw": - GameMain.DebugDraw = !GameMain.DebugDraw; - break; - case "disablehud": - case "hud": - GUI.DisableHUD = !GUI.DisableHUD; - GameMain.Instance.IsMouseVisible = !GameMain.Instance.IsMouseVisible; - break; - case "followsub": - Camera.FollowSub = !Camera.FollowSub; - break; - case "drawaitargets": - case "showaitargets": - AITarget.ShowAITargets = !AITarget.ShowAITargets; - break; + commands.Add(new Command("toggleaitargets|aitargets", "toggleaitargets/aitargets: Toggle the visibility of AI targets (= targets that enemies can detect and attack/escape from).", (string[] args) => + { + AITarget.ShowAITargets = !AITarget.ShowAITargets; + NewMessage(AITarget.ShowAITargets ? "Enabled AI target drawing" : "Disabled AI target drawing", Color.White); + })); #if DEBUG - case "spamevents": - foreach (Item item in Item.ItemList) + commands.Add(new Command("spamchatmessages", "", (string[] args) => + { + int msgCount = 1000; + if (args.Length > 0) int.TryParse(args[0], out msgCount); + int msgLength = 50; + if (args.Length > 1) int.TryParse(args[1], out msgLength); + + for (int i = 0; i < msgCount; i++) + { + if (GameMain.Client != null) { - for (int i = 0; i(); - if (itemContainer != null) - { - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState }); - } - - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); - - item.NeedsPositionUpdate = true; - } + GameMain.Server.SendChatMessage(ToolBox.RandomSeed(msgLength), ChatMessageType.Default); } - - foreach (Character c in Character.CharacterList) + else { - GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); + GameMain.Client.SendChatMessage(ToolBox.RandomSeed(msgLength)); } - - foreach (Structure wall in Structure.WallList) - { - GameMain.Server.CreateEntityEvent(wall); - } - break; - case "spamchatmessages": - int msgCount = 1000; - if (commands.Length > 1) int.TryParse(commands[1], out msgCount); - int msgLength = 50; - if (commands.Length > 2) int.TryParse(commands[2], out msgLength); - - for (int i = 0; i < msgCount; i++) - { - if (GameMain.Server != null) - { - GameMain.Server.SendChatMessage(ToolBox.RandomSeed(msgLength), ChatMessageType.Default); - } - else - { - GameMain.Client.SendChatMessage(ToolBox.RandomSeed(msgLength)); - } - } - break; + } + })); #endif - case "cleanbuild": - GameMain.Config.MusicVolume = 0.5f; - GameMain.Config.SoundVolume = 0.5f; - DebugConsole.NewMessage("Music and sound volume set to 0.5", Color.Green); + commands.Add(new Command("cleanbuild", "", (string[] args) => + { + GameMain.Config.MusicVolume = 0.5f; + GameMain.Config.SoundVolume = 0.5f; + NewMessage("Music and sound volume set to 0.5", Color.Green); - GameMain.Config.GraphicsWidth = 0; - GameMain.Config.GraphicsHeight = 0; - GameMain.Config.WindowMode = WindowMode.Fullscreen; - DebugConsole.NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green); - DebugConsole.NewMessage("Fullscreen enabled", Color.Green); + GameMain.Config.GraphicsWidth = 0; + GameMain.Config.GraphicsHeight = 0; + GameMain.Config.WindowMode = WindowMode.Fullscreen; + NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green); + NewMessage("Fullscreen enabled", Color.Green); - GameSettings.VerboseLogging = false; + GameSettings.VerboseLogging = false; - if (GameMain.Config.MasterServerUrl != "http://www.undertowgames.com/baromaster") + if (GameMain.Config.MasterServerUrl != "http://www.undertowgames.com/baromaster") + { + ThrowError("MasterServerUrl \"" + GameMain.Config.MasterServerUrl + "\"!"); + } + + GameMain.Config.Save("config.xml"); + + var saveFiles = System.IO.Directory.GetFiles(SaveUtil.SaveFolder); + + foreach (string saveFile in saveFiles) + { + System.IO.File.Delete(saveFile); + NewMessage("Deleted " + saveFile, Color.Green); + } + + if (System.IO.Directory.Exists(System.IO.Path.Combine(SaveUtil.SaveFolder, "temp"))) + { + System.IO.Directory.Delete(System.IO.Path.Combine(SaveUtil.SaveFolder, "temp"), true); + NewMessage("Deleted temp save folder", Color.Green); + } + + if (System.IO.Directory.Exists(ServerLog.SavePath)) + { + var logFiles = System.IO.Directory.GetFiles(ServerLog.SavePath); + + foreach (string logFile in logFiles) { - DebugConsole.ThrowError("MasterServerUrl \"" + GameMain.Config.MasterServerUrl + "\"!"); + System.IO.File.Delete(logFile); + NewMessage("Deleted " + logFile, Color.Green); } + } - GameMain.Config.Save("config.xml"); - - var saveFiles = System.IO.Directory.GetFiles(SaveUtil.SaveFolder); - - foreach (string saveFile in saveFiles) - { - System.IO.File.Delete(saveFile); - DebugConsole.NewMessage("Deleted " + saveFile, Color.Green); - } - - if (System.IO.Directory.Exists(System.IO.Path.Combine(SaveUtil.SaveFolder, "temp"))) - { - System.IO.Directory.Delete(System.IO.Path.Combine(SaveUtil.SaveFolder, "temp"), true); - DebugConsole.NewMessage("Deleted temp save folder", Color.Green); - } - - if (System.IO.Directory.Exists(ServerLog.SavePath)) - { - var logFiles = System.IO.Directory.GetFiles(ServerLog.SavePath); - - foreach (string logFile in logFiles) - { - System.IO.File.Delete(logFile); - DebugConsole.NewMessage("Deleted " + logFile, Color.Green); - } - } - - if (System.IO.File.Exists("filelist.xml")) - { - System.IO.File.Delete("filelist.xml"); - DebugConsole.NewMessage("Deleted filelist", Color.Green); - } + if (System.IO.File.Exists("filelist.xml")) + { + System.IO.File.Delete("filelist.xml"); + NewMessage("Deleted filelist", Color.Green); + } - if (System.IO.File.Exists("Submarines/TutorialSub.sub")) - { - System.IO.File.Delete("Submarines/TutorialSub.sub"); + if (System.IO.File.Exists("Submarines/TutorialSub.sub")) + { + System.IO.File.Delete("Submarines/TutorialSub.sub"); - DebugConsole.NewMessage("Deleted TutorialSub from the submarine folder", Color.Green); - } + NewMessage("Deleted TutorialSub from the submarine folder", Color.Green); + } - if (System.IO.File.Exists(GameServer.SettingsFile)) - { - System.IO.File.Delete(GameServer.SettingsFile); - DebugConsole.NewMessage("Deleted server settings", Color.Green); - } + if (System.IO.File.Exists(GameServer.SettingsFile)) + { + System.IO.File.Delete(GameServer.SettingsFile); + NewMessage("Deleted server settings", Color.Green); + } - if (System.IO.File.Exists(GameServer.ClientPermissionsFile)) - { - System.IO.File.Delete(GameServer.ClientPermissionsFile); - DebugConsole.NewMessage("Deleted client permission file", Color.Green); + if (System.IO.File.Exists(GameServer.ClientPermissionsFile)) + { + System.IO.File.Delete(GameServer.ClientPermissionsFile); + NewMessage("Deleted client permission file", Color.Green); + } - } + if (System.IO.File.Exists("crashreport.txt")) + { + System.IO.File.Delete("crashreport.txt"); + NewMessage("Deleted crashreport.txt", Color.Green); + } - if (System.IO.File.Exists("crashreport.txt")) - { - System.IO.File.Delete("crashreport.txt"); - DebugConsole.NewMessage("Deleted crashreport.txt", Color.Green); - } + if (!System.IO.File.Exists("Content/Map/TutorialSub.sub")) + { + ThrowError("TutorialSub.sub not found!"); + } + })); - if (!System.IO.File.Exists("Content/Map/TutorialSub.sub")) - { - DebugConsole.ThrowError("TutorialSub.sub not found!"); - } - - break; - default: - return false; //command not found - break; - } - return true; //command found } } } diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs index 38dd5ac45..bc0185604 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs @@ -304,18 +304,10 @@ namespace Barotrauma { case '\b': //backspace if (Text.Length > 0) Text = Text.Substring(0, Text.Length - 1); + if (OnTextChanged != null) OnTextChanged(this, Text); break; - //case '\r': //return - // if (OnEnterPressed != null) - // OnEnterPressed(this); - // break; - //case '\t': //tab - // if (OnTabPressed != null) - // OnTabPressed(this); - // break; } - if (OnTextChanged != null) OnTextChanged(this, Text); } public void ReceiveSpecialInput(Keys key) diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs index ec1df4a4d..84f035900 100644 --- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs @@ -22,169 +22,148 @@ namespace Barotrauma } } - private static bool ExecProjSpecific(string[] commands) + private static void InitProjectSpecific() { - switch (commands[0].ToLower()) + commands.Add(new Command("restart|reset", "restart/reset: Close and restart the server.", (string[] args) => { - case "help": + NewMessage("*****************", Color.Lime); + NewMessage("RESTARTING SERVER", Color.Lime); + NewMessage("*****************", Color.Lime); + GameMain.Instance.CloseServer(); + GameMain.Instance.StartServer(); + })); - NewMessage("start: start a new round", Color.Cyan); - NewMessage("end: end the current round", Color.Cyan); - NewMessage("restart: restart the server", Color.Cyan); - NewMessage("quit: exit the game", Color.Cyan); + commands.Add(new Command("exit|quit|close", "exit/quit/close: Exit the application.", (string[] args) => + { + GameMain.ShouldRun = false; + })); - NewMessage(" ", Color.Cyan); + commands.Add(new Command("say", "say [message]: Send a chat message that displays \"HOST\" as the sender.", (string[] args) => + { + string text = string.Join(" ", args); + text = "HOST: " + text; + GameMain.Server.SendChatMessage(text, ChatMessageType.Server); + })); - NewMessage("say [chat message]: send a chat message", Color.Cyan); - NewMessage("clientlist: list the names and IPs of the connected clients", Color.Cyan); - NewMessage("kick [name]: kick a player out from the server", Color.Cyan); - NewMessage("ban [name]: kick and ban the player from the server", Color.Cyan); - NewMessage("banip [IP address]: ban the IP address from the server", Color.Cyan); - NewMessage("debugdraw: toggles the \"debug draw mode\"", Color.Cyan); - NewMessage("netstats: toggles the visibility of the network statistics panel", Color.Cyan); + commands.Add(new Command("msg", "msg [message]: Send a chat message with no sender specified.", (string[] args) => + { + string text = string.Join(" ", args); + GameMain.Server.SendChatMessage(text, ChatMessageType.Server); + })); - NewMessage(" ", Color.Cyan); + commands.Add(new Command("servername", "servername [name]: Change the name of the server.", (string[] args) => + { + GameMain.Server.Name = string.Join(" ", args); + GameMain.NetLobbyScreen.ChangeServerName(string.Join(" ", args)); + })); - NewMessage("servername [name]: change the name of the server", Color.Cyan); - NewMessage("servermsg [message]: change the message in the server lobby", Color.Cyan); - NewMessage("seed [seed]: changes the level seed for the next round", Color.Cyan); - NewMessage("gamemode [name]: select the specified game mode for the next round", Color.Cyan); - NewMessage("gamemode [index]: select the specified game mode (0 = sandbox, 1 = mission, etc)", Color.Cyan); - NewMessage("submarine [name]: select the specified game mode for the next round", Color.Cyan); - NewMessage("shuttle [name]: select the specified submarine as the respawn shuttle for the next round", Color.Cyan); + commands.Add(new Command("servermsg", "servermsg [message]: Change the message displayed in the server lobby.", (string[] args) => + { + GameMain.NetLobbyScreen.ChangeServerMessage(string.Join(" ", args)); + })); - NewMessage(" ", Color.Cyan); + commands.Add(new Command("seed|levelseed", "seed/levelseed: Changes the level seed for the next round.", (string[] args) => + { + GameMain.NetLobbyScreen.LevelSeed = string.Join(" ", args); + })); - NewMessage("spawn [creaturename] [near/inside/outside]: spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine)", Color.Cyan); - - NewMessage(" ", Color.Cyan); - - NewMessage("heal [character name]: restore the specified character to full health", Color.Cyan); - NewMessage("revive [character name]: bring the specified character back from the dead", Color.Cyan); - NewMessage("killmonsters: immediately kills all AI-controlled enemies in the level", Color.Cyan); + commands.Add(new Command("gamemode", "gamemode [name]/[index]: Select the game mode for the next round. The parameter can either be the name or the index number of the game mode (0 = sandbox, 1 = mission, etc).", (string[] args) => + { + int index = -1; + if (int.TryParse(string.Join(" ", args), out index)) + { + GameMain.NetLobbyScreen.SelectedModeIndex = index; + } + else + { + GameMain.NetLobbyScreen.SelectedModeName = string.Join(" ", args); + } + NewMessage("Set gamemode to " + GameMain.NetLobbyScreen.SelectedModeName, Color.Cyan); + })); - NewMessage(" ", Color.Cyan); + commands.Add(new Command("mission", "mission [name]/[index]: Select the mission type for the next round. The parameter can either be the name or the index number of the mission type (0 = first mission type, 1 = second mission type, etc).", (string[] args) => + { + int index = -1; + if (int.TryParse(string.Join(" ", args), out index)) + { + GameMain.NetLobbyScreen.MissionTypeIndex = index; + } + else + { + GameMain.NetLobbyScreen.MissionTypeName = string.Join(" ", args); + } + NewMessage("Set mission to " + GameMain.NetLobbyScreen.MissionTypeName, Color.Cyan); + })); - NewMessage("fixwalls: fixes all the walls", Color.Cyan); - NewMessage("fixitems: fixes every item/device in the sub", Color.Cyan); - NewMessage("oxygen: replenishes the oxygen in every room to 100%", Color.Cyan); - NewMessage("power [amount]: immediately sets the temperature of the reactor to the specified value", Color.Cyan); - - break; - case "restart": - case "reset": - NewMessage("*****************", Color.Lime); - NewMessage("RESTARTING SERVER", Color.Lime); - NewMessage("*****************", Color.Lime); - GameMain.Instance.CloseServer(); - GameMain.Instance.StartServer(); - break; - case "exit": - case "close": - case "quit": - GameMain.ShouldRun = false; - break; - case "say": - case "msg": - string text = string.Join(" ", commands.Skip(1)); - if (commands[0].ToLower() == "say") text = "HOST: " + text; - GameMain.Server.SendChatMessage(text, ChatMessageType.Server); - break; - case "servername": - GameMain.Server.Name = string.Join(" ", commands.Skip(1)); - GameMain.NetLobbyScreen.ChangeServerName(string.Join(" ", commands.Skip(1))); - break; - case "servermsg": - GameMain.NetLobbyScreen.ChangeServerMessage(string.Join(" ", commands.Skip(1))); - break; - case "seed": - GameMain.NetLobbyScreen.LevelSeed = string.Join(" ", commands.Skip(1)); - break; - case "gamemode": - { - int index = -1; - if (int.TryParse(string.Join(" ", commands.Skip(1)), out index)) - { - GameMain.NetLobbyScreen.SelectedModeIndex = index; - } - else - { - GameMain.NetLobbyScreen.SelectedModeName = string.Join(" ", commands.Skip(1)); - } - NewMessage("Set gamemode to " + GameMain.NetLobbyScreen.SelectedModeName, Color.Cyan); - } - break; - case "mission": - { - int index = -1; - if (int.TryParse(string.Join(" ", commands.Skip(1)), out index)) - { - GameMain.NetLobbyScreen.MissionTypeIndex = index; - } - else - { - GameMain.NetLobbyScreen.MissionTypeName = string.Join(" ", commands.Skip(1)); - } - NewMessage("Set mission to " + GameMain.NetLobbyScreen.MissionTypeName, Color.Cyan); - } - break; - case "sub": - case "submarine": - { - Submarine sub = GameMain.NetLobbyScreen.GetSubList().Find(s => s.Name.ToLower() == string.Join(" ", commands.Skip(1)).ToLower()); + commands.Add(new Command("sub|submarine", "submarine [name]: Select the submarine for the next round.", (string[] args) => + { + Submarine sub = GameMain.NetLobbyScreen.GetSubList().Find(s => s.Name.ToLower() == string.Join(" ", args).ToLower()); - if (sub != null) - { - GameMain.NetLobbyScreen.SelectedSub = sub; - } - sub = GameMain.NetLobbyScreen.SelectedSub; - NewMessage("Selected sub: " + sub.Name + (sub.HasTag(SubmarineTag.Shuttle) ? " (shuttle)" : ""), Color.Cyan); - } - break; - case "shuttle": - { - Submarine shuttle = GameMain.NetLobbyScreen.GetSubList().Find(s => s.Name.ToLower() == string.Join(" ", commands.Skip(1)).ToLower()); + if (sub != null) + { + GameMain.NetLobbyScreen.SelectedSub = sub; + } + sub = GameMain.NetLobbyScreen.SelectedSub; + NewMessage("Selected sub: " + sub.Name + (sub.HasTag(SubmarineTag.Shuttle) ? " (shuttle)" : ""), Color.Cyan); + })); - if (shuttle != null) - { - GameMain.NetLobbyScreen.SelectedShuttle = shuttle; - } - shuttle = GameMain.NetLobbyScreen.SelectedShuttle; - NewMessage("Selected shuttle: " + shuttle.Name + (shuttle.HasTag(SubmarineTag.Shuttle) ? "" : " (not shuttle)"), Color.Cyan); - } - break; - case "startgame": - case "startround": - case "start": - if (Screen.Selected == GameMain.GameScreen) break; - if (!GameMain.Server.StartGame()) NewMessage("Failed to start a new round", Color.Yellow); - break; - case "endgame": - case "endround": - case "end": - if (Screen.Selected == GameMain.NetLobbyScreen) break; - GameMain.Server.EndGame(); - break; - case "entitydata": - Entity ent = Entity.FindEntityByID(Convert.ToUInt16(commands[1])); - if (ent != null) - { - NewMessage(ent.ToString(), Color.Lime); - } - break; + commands.Add(new Command("shuttle", "shuttle [name]: Select the specified submarine as the respawn shuttle for the next round.", (string[] args) => + { + Submarine shuttle = GameMain.NetLobbyScreen.GetSubList().Find(s => s.Name.ToLower() == string.Join(" ", args).ToLower()); + + if (shuttle != null) + { + GameMain.NetLobbyScreen.SelectedShuttle = shuttle; + } + shuttle = GameMain.NetLobbyScreen.SelectedShuttle; + NewMessage("Selected shuttle: " + shuttle.Name + (shuttle.HasTag(SubmarineTag.Shuttle) ? "" : " (not shuttle)"), Color.Cyan); + })); + + commands.Add(new Command("startgame|startround|start", "start/startgame/startround: Start a new round.", (string[] args) => + { + if (Screen.Selected == GameMain.GameScreen) return; + if (!GameMain.Server.StartGame()) NewMessage("Failed to start a new round", Color.Yellow); + })); + + commands.Add(new Command("endgame|endround|end", "end/endgame/endround: End the current round.", (string[] args) => + { + if (Screen.Selected == GameMain.NetLobbyScreen) return; + GameMain.Server.EndGame(); + })); + + commands.Add(new Command("entitydata", "", (string[] args) => + { + if (args.Length == 0) return; + Entity ent = Entity.FindEntityByID(Convert.ToUInt16(args[0])); + if (ent != null) + { + NewMessage(ent.ToString(), Color.Lime); + } + })); #if DEBUG - case "eventdata": - ServerEntityEvent ev = GameMain.Server.EntityEventManager.Events[Convert.ToUInt16(commands[1])]; - if (ev != null) - { - NewMessage(ev.StackTrace, Color.Lime); - } - break; + commands.Add(new Command("eventdata", "", (string[] args) => + { + if (args.Length == 0) return; + ServerEntityEvent ev = GameMain.Server.EntityEventManager.Events[Convert.ToUInt16(args[0])]; + if (ev != null) + { + NewMessage(ev.StackTrace, Color.Lime); + } + })); + + commands.Add(new Command("spamchatmessages", "", (string[] args) => + { + int msgCount = 1000; + if (args.Length > 0) int.TryParse(args[0], out msgCount); + int msgLength = 50; + if (args.Length > 1) int.TryParse(args[1], out msgLength); + + for (int i = 0; i < msgCount; i++) + { + GameMain.Server.SendChatMessage(ToolBox.RandomSeed(msgLength), ChatMessageType.Default); + } + })); #endif - default: - return false; - } - return true; //command found - } + } } } diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index a27068b56..1b08f7a6e 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -26,6 +26,26 @@ namespace Barotrauma static partial class DebugConsole { + class Command + { + public readonly string[] names; + public readonly string help; + private Action onExecute; + + public Command(string name, string help, Action onExecute) + { + names = name.Split('|'); + this.help = help; + + this.onExecute = onExecute; + } + + public void Execute(string[] args) + { + onExecute(args); + } + } + const int MaxMessages = 200; public static List Messages = new List(); @@ -36,6 +56,586 @@ namespace Barotrauma private static GUIComponent activeQuestionText; #endif + private static List commands = new List(); + + 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.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); + } + NewMessage("***************", Color.Cyan); + })); + + + commands.Add(new Command("createfilelist", "", (string[] args) => + { + UpdaterUtil.SaveFileList("filelist.xml"); + })); + + commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) => + { + 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 = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); + 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) + { + SinglePlayerMode mode = GameMain.GameSession.gameMode as SinglePlayerMode; + if (mode != null) + { + Character.Controlled = spawnedCharacter; + GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled); + GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled); + } + } +#endif + } + else + { + spawnedCharacter = Character.Create( + "Content/Characters/" + + args[0].First().ToString().ToUpper() + args[0].Substring(1) + + "/" + args[0].ToLower() + ".xml", spawnPosition); + } + })); + + commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory]: Spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted.", (string[] args) => + { + if (args.Length < 1) return; + + Vector2? spawnPos = null; + Inventory spawnInventory = null; + + int extraParams = 0; + switch (args.Last()) + { + case "cursor": + extraParams = 1; + spawnPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); + break; + case "inventory": + extraParams = 1; + spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory; + break; + default: + extraParams = 0; + break; + } + + string itemName = string.Join(" ", commands.Take(args.Length - extraParams - 1)).ToLowerInvariant(); + + var itemPrefab = MapEntityPrefab.list.Find(ip => ip.Name.ToLowerInvariant() == itemName) as ItemPrefab; + if (itemPrefab == null) + { + ThrowError("Item \"" + itemName + "\" not found!"); + return; + } + + if (spawnPos == null && 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); + } + })); + + 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); + })); + + 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); + })); + + 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); + }); + })); + + commands.Add(new Command("kickid", "kickid [id]: Kick the player with the specified client ID out of the server.", (string[] args) => + { + if (GameMain.Server == null || args.Length == 0) return; + + int id = 0; + 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("Reason for kicking \"" + client.name + "\"?", (reason) => + { + GameMain.Server.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); + }); + }); + })); + + commands.Add(new Command("banid", "banid [id]: Kick and ban the player with the specified client ID from the server.", (string[] args) => + { + if (GameMain.Server == null || args.Length == 0) return; + + int id = 0; + 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("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)) + { + 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.Server.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 \"" + commands[1] + "\"?", (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; + } + + var client = GameMain.Server.ConnectedClients.Find(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); + if (client == null) + { + GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, banDuration); + } + else + { + GameMain.Server.KickClient(client, reason); + } + }); + }); + + })); + + 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 = null; + + if (args.Length == 0) + { + tpCharacter = Character.Controlled; + } + else + { + 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); + })); + + 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); + })); + + commands.Add(new Command("lockx", "lockx: Lock horizontal movement of the main submarine.", (string[] args) => + { + Submarine.LockX = !Submarine.LockX; + })); + + commands.Add(new Command("locky", "loxky: Lock vertical movement of the main submarine.", (string[] args) => + { + Submarine.LockY = !Submarine.LockY; + })); + + 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("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 = null; + if (args.Length == 0) + { + healedCharacter = Character.Controlled; + } + else + { + healedCharacter = 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); + } + })); + + 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 = null; + if (args.Length == 0) + { + revivedCharacter = Character.Controlled; + } + else + { + revivedCharacter = 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; + } + } + })); + + commands.Add(new Command("freeze", "", (string[] args) => + { + if (Character.Controlled != null) Character.Controlled.AnimController.Frozen = !Character.Controlled.AnimController.Frozen; + })); + + 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]: 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; + 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); + new Explosion(range, force, damage, structureDamage).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; + } + })); + + 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); + } + } + })); + + 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); + } + })); + + 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; + } + })); + + 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, 10000.0f, 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).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); + })); +#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); + } + })); +#endif + InitProjectSpecific(); + + commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); + } + private static string[] SplitCommand(string command) { command = command.Trim(); @@ -71,6 +671,41 @@ namespace Barotrauma return commands.ToArray(); } + + private static string currentAutoCompletedCommand; + private static int currentAutoCompletedIndex; + + public static string AutoComplete(string command) + { + 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++]; + } + + public static void ResetAutoComplete() + { + currentAutoCompletedCommand = ""; + currentAutoCompletedIndex = 0; + } public static void ExecuteCommand(string command, GameMain game) { @@ -89,487 +724,44 @@ namespace Barotrauma if (string.IsNullOrWhiteSpace(command)) return; - string[] commands = SplitCommand(command); + string[] splitCommand = SplitCommand(command); - if (!commands[0].ToLowerInvariant().Equals("admin")) + if (!splitCommand[0].ToLowerInvariant().Equals("admin")) { NewMessage(command, Color.White); } #if !DEBUG && CLIENT - if (GameMain.Client != null && !IsCommandPermitted(commands[0].ToLowerInvariant(), GameMain.Client)) + if (GameMain.Client != null && !IsCommandPermitted(args[0].ToLowerInvariant(), GameMain.Client)) { - ThrowError("You're not permitted to use the command \"" + commands[0].ToLowerInvariant()+"\"!"); + ThrowError("You're not permitted to use the command \"" + args[0].ToLowerInvariant()+"\"!"); return; } #endif - switch (commands[0].ToLowerInvariant()) + foreach (Command c in commands) { - case "clientlist": - if (GameMain.Server == null) break; - NewMessage("***************", Color.Cyan); - foreach (Client c in GameMain.Server.ConnectedClients) - { - NewMessage("- " + c.ID.ToString() + ": " + c.name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); - } - NewMessage("***************", Color.Cyan); - break; - case "createfilelist": - UpdaterUtil.SaveFileList("filelist.xml"); - break; - case "spawn": - case "spawncharacter": - if (commands.Length == 1) return; - - Character spawnedCharacter = null; - - Vector2 spawnPosition = Vector2.Zero; - WayPoint spawnPoint = null; - - if (commands.Length > 2) - { - switch (commands[2].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 = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - break; - default: - spawnPoint = WayPoint.GetRandom(commands[1].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); - break; - } - } - else - { - spawnPoint = WayPoint.GetRandom(commands[1].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); - } - - if (string.IsNullOrWhiteSpace(commands[1])) return; - - if (spawnPoint != null) spawnPosition = spawnPoint.WorldPosition; - - if (commands[1].ToLowerInvariant() == "human") - { - spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition); - -#if CLIENT - if (GameMain.GameSession != null) - { - SinglePlayerMode mode = GameMain.GameSession.gameMode as SinglePlayerMode; - if (mode != null) - { - Character.Controlled = spawnedCharacter; - GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled); - GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled); - } - } -#endif - } - else - { - spawnedCharacter = Character.Create( - "Content/Characters/" - + commands[1].First().ToString().ToUpper() + commands[1].Substring(1) - + "/" + commands[1].ToLower() + ".xml", spawnPosition); - } - - break; - case "spawnitem": - if (commands.Length < 2) return; - - Vector2? spawnPos = null; - Inventory spawnInventory = null; - - int extraParams = 0; - switch (commands.Last()) - { - case "cursor": - extraParams = 1; - spawnPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - break; - case "inventory": - extraParams = 1; - spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory; - break; - default: - extraParams = 0; - break; - } - - string itemName = string.Join(" ", commands.Skip(1).Take(commands.Length - extraParams - 1)).ToLowerInvariant(); - - var itemPrefab = MapEntityPrefab.list.Find(ip => ip.Name.ToLowerInvariant() == itemName) as ItemPrefab; - if (itemPrefab == null) - { - ThrowError("Item \"" + itemName + "\" not found!"); - return; - } - - if (spawnPos == null && spawnInventory == null) - { - var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); - spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; - } - - if (spawnPos != null) - { - Item.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); - - } - else if (spawnInventory != null) - { - Item.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory); - } - - break; - case "disablecrewai": - HumanAIController.DisableCrewAI = !HumanAIController.DisableCrewAI; - break; - case "enablecrewai": - HumanAIController.DisableCrewAI = false; - break; - /*case "admin": - if (commands.Length < 2) break; - - if (GameMain.Server != null) - { - GameMain.Server.AdminAuthPass = commands[1]; - - } - else if (GameMain.Client != null) - { - GameMain.Client.RequestAdminAuth(commands[1]); - } - break;*/ - case "kick": - if (GameMain.NetworkMember != null && commands.Length >= 2) - { - string playerName = string.Join(" ", commands.Skip(1)); - - ShowQuestionPrompt("Reason for kicking \"" + playerName + "\"?", (reason) => - { - GameMain.NetworkMember.KickPlayer(playerName, reason); - }); - } - break; - case "kickid": - case "banid": - if (GameMain.Server != null && commands.Length >= 2) - { - bool ban = commands[0].ToLowerInvariant() == "banid"; - - int id = 0; - int.TryParse(commands[1], out id); - var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); - if (client == null) - { - ThrowError("Client id \"" + id + "\" not found."); - return; - } - - ShowQuestionPrompt(ban ? "Reason for banning \"" + client.name + "\"?" : "Reason for kicking \"" + client.name + "\"?", (reason) => - { - if (ban) - { - 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.Server.BanPlayer(client.name, reason, false, banDuration); - }); - } - else - { - GameMain.Server.KickPlayer(client.name, reason); - } - }); - } - break; - case "ban": - if (GameMain.NetworkMember != null || commands.Length >= 2) - { - string clientName = string.Join(" ", commands.Skip(1)); - 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); - }); - }); - } - break; - case "banip": - if (GameMain.Server != null || commands.Length >= 2) - { - ShowQuestionPrompt("Reason for banning the ip \"" + commands[1] + "\"?", (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; - } - - var client = GameMain.Server.ConnectedClients.Find(c => c.Connection.RemoteEndPoint.Address.ToString() == commands[1]); - if (client == null) - { - GameMain.Server.BanList.BanPlayer("Unnamed", commands[1], reason, banDuration); - } - else - { - GameMain.Server.KickClient(client, reason); - } - }); - }); - } - break; - case "teleportcharacter": - case "teleport": - var tpCharacter = FindMatchingCharacter(commands, false); - - if (commands.Length < 2) - { - tpCharacter = Character.Controlled; - } - - if (tpCharacter != null) - { - 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); - } - break; - case "godmode": - if (Submarine.MainSub == null) return; - - Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode; - NewMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", Color.White); - break; - case "lockx": - Submarine.LockX = !Submarine.LockX; - break; - case "locky": - Submarine.LockY = !Submarine.LockY; - break; - case "dumpids": - try - { - int count = commands.Length < 2 ? 10 : int.Parse(commands[1]); - Entity.DumpIds(count); - } - catch - { - return; - } - break; - case "heal": - Character healedCharacter = null; - if (commands.Length == 1) - { - healedCharacter = Character.Controlled; - } - else - { - healedCharacter = FindMatchingCharacter(commands); - } - - if (healedCharacter != null) - { - healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); - healedCharacter.Oxygen = 100.0f; - healedCharacter.Bleeding = 0.0f; - healedCharacter.SetStun(0.0f, true); - } - - break; - case "revive": - Character revivedCharacter = null; - if (commands.Length == 1) - { - revivedCharacter = Character.Controlled; - } - else - { - revivedCharacter = FindMatchingCharacter(commands); - } - - if (revivedCharacter != null) - { - 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; - } - } - } - break; - case "freeze": - if (Character.Controlled != null) Character.Controlled.AnimController.Frozen = !Character.Controlled.AnimController.Frozen; - break; - case "freecamera": - case "freecam": - Character.Controlled = null; - GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; - break; - case "editwater": - case "water": - if (GameMain.Client == null) - { - Hull.EditWater = !Hull.EditWater; - NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); - } - - break; - case "explosion": - Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - float range = 500, force = 10, damage=50; - if (commands.Length > 1) float.TryParse(commands[1], out range); - if (commands.Length > 2) float.TryParse(commands[2], out force); - if (commands.Length > 3) float.TryParse(commands[3], out damage); - new Explosion(range, force, damage, damage).Explode(explosionPos); - break; - case "fire": - if (GameMain.Client == null) - { - Hull.EditFire = !Hull.EditFire; - NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White); - } - - break; - case "fixitems": - foreach (Item it in Item.ItemList) - { - it.Condition = it.Prefab.Health; - } - break; - case "fixhull": - case "fixwalls": - foreach (Structure w in Structure.WallList) - { - for (int i = 0 ; i < w.SectionCount; i++) - { - w.AddDamage(i, -100000.0f); - } - } - break; - case "power": - Item reactorItem = Item.ItemList.Find(i => i.GetComponent() != null); - if (reactorItem == null) return; - - float power = 5000.0f; - if (commands.Length>1) float.TryParse(commands[1], 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); - } - break; - case "oxygen": - case "air": - foreach (Hull hull in Hull.hullList) - { - hull.OxygenPercentage = 100.0f; - } - break; - - case "killmonsters": - foreach (Character c in Character.CharacterList) - { - if (!(c.AIController is EnemyAIController)) continue; - c.AddDamage(CauseOfDeath.Damage, 10000.0f, null); - } - break; - case "netstats": - if (GameMain.Server == null) return; - - GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats; - break; - - default: - if (!ExecProjSpecific(commands)) NewMessage("Command not found", Color.Red); + if (c.names.Contains(splitCommand[0].ToLowerInvariant())) + { + c.Execute(splitCommand.Skip(1).ToArray()); break; + } } } - private static Character FindMatchingCharacter(string[] commands, bool ignoreRemotePlayers = false) + private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false) { - if (commands.Length < 2) return null; + if (args.Length == 0) return null; int characterIndex; string characterName; - if (int.TryParse(commands.Last(), out characterIndex) && commands.Length > 2) + if (int.TryParse(args.Last(), out characterIndex) && args.Length > 2) { - characterName = string.Join(" ", commands.Skip(1).Take(commands.Length - 2)).ToLowerInvariant(); + characterName = string.Join(" ", args.Take(args.Length - 2)).ToLowerInvariant(); } else { - characterName = string.Join(" ", commands.Skip(1)).ToLowerInvariant(); + characterName = string.Join(" ", args).ToLowerInvariant(); characterIndex = -1; } @@ -587,7 +779,7 @@ namespace Barotrauma { NewMessage( "Found multiple matching characters. " + - "Use \"" + commands[0] + " [charactername] [0-" + (matchingCharacters.Count - 1) + "]\" to choose a specific character.", + "Use \"[charactername] [0-" + (matchingCharacters.Count - 1) + "]\" to choose a specific character.", Color.LightGray); } return matchingCharacters[0];