diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index fcaf6de3f..1c69006aa 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -266,6 +266,13 @@ namespace Barotrauma { Character.Controlled = character; } + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; })); commands.Add(new Command("shake", "", (string[] args) => diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs index ae33baf0d..51fad9c11 100644 --- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs @@ -1,6 +1,7 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; +using System.Linq; using System.Collections.Generic; namespace Barotrauma @@ -94,6 +95,13 @@ namespace Barotrauma } } NewMessage("Set gamemode to " + GameMain.NetLobbyScreen.SelectedModeName, Color.Cyan); + }, + () => + { + return new string[][] + { + GameModePreset.list.Select(gm => gm.Name).ToArray() + }; })); 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) => @@ -108,6 +116,13 @@ namespace Barotrauma GameMain.NetLobbyScreen.MissionTypeName = string.Join(" ", args); } NewMessage("Set mission to " + GameMain.NetLobbyScreen.MissionTypeName, Color.Cyan); + }, + () => + { + return new string[][] + { + Mission.MissionTypes.ToArray() + }; })); commands.Add(new Command("sub|submarine", "submarine [name]: Select the submarine for the next round.", (string[] args) => @@ -120,6 +135,13 @@ namespace Barotrauma } sub = GameMain.NetLobbyScreen.SelectedSub; NewMessage("Selected sub: " + sub.Name + (sub.HasTag(SubmarineTag.Shuttle) ? " (shuttle)" : ""), Color.Cyan); + }, + () => + { + return new string[][] + { + Submarine.Loaded.Select(s => s.Name).ToArray() + }; })); commands.Add(new Command("shuttle", "shuttle [name]: Select the specified submarine as the respawn shuttle for the next round.", (string[] args) => @@ -132,6 +154,13 @@ namespace Barotrauma } shuttle = GameMain.NetLobbyScreen.SelectedShuttle; NewMessage("Selected shuttle: " + shuttle.Name + (shuttle.HasTag(SubmarineTag.Shuttle) ? "" : " (not shuttle)"), Color.Cyan); + }, + () => + { + return new string[][] + { + Submarine.Loaded.Select(s => s.Name).ToArray() + }; })); commands.Add(new Command("startgame|startround|start", "start/startgame/startround: Start a new round.", (string[] args) => diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index f515a6793..c5ffe793d 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -46,6 +46,8 @@ namespace Barotrauma /// private Action onClientRequestExecute; + public Func GetValidArgs; + public bool RelayToServer { get { return onClientExecute == null; } @@ -56,7 +58,7 @@ namespace Barotrauma /// 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) + public Command(string name, string help, Action onExecute, Action onClientExecute, Action onClientRequestExecute, Func getValidArgs = null) { names = name.Split('|'); this.help = help; @@ -64,19 +66,23 @@ namespace Barotrauma 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) + 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) @@ -196,7 +202,7 @@ namespace Barotrauma 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) => + 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); @@ -214,6 +220,20 @@ namespace Barotrauma { 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]: 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.", @@ -235,6 +255,21 @@ namespace Barotrauma { ThrowError(errorMsg); } + }, + () => + { + List itemNames = new List(); + foreach (MapEntityPrefab prefab in MapEntityPrefab.List) + { + ItemPrefab itemPrefab = prefab as ItemPrefab; + if (itemPrefab != null) 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) => @@ -572,6 +607,15 @@ namespace Barotrauma { 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) => @@ -617,6 +661,15 @@ namespace Barotrauma 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) => @@ -730,6 +783,13 @@ namespace Barotrauma 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) => @@ -812,6 +872,13 @@ namespace Barotrauma 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) => @@ -868,6 +935,13 @@ namespace Barotrauma break; } } + }, + () => + { + return new string[][] + { + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; })); commands.Add(new Command("freeze", "", (string[] args) => @@ -896,6 +970,13 @@ namespace Barotrauma { 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) => @@ -1069,6 +1150,16 @@ namespace Barotrauma 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) => @@ -1269,28 +1360,80 @@ namespace Barotrauma public static string AutoComplete(string command) { - if (string.IsNullOrWhiteSpace(currentAutoCompletedCommand)) - { - currentAutoCompletedCommand = command; - } + string[] splitCommand = SplitCommand(command); + string[] args = splitCommand.Skip(1).ToArray(); - List matchingCommands = new List(); - foreach (Command c in commands) + //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() == ' ')) { - foreach (string name in c.names) + 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)) { - if (currentAutoCompletedCommand.Length > name.Length) continue; - if (currentAutoCompletedCommand == name.Substring(0, currentAutoCompletedCommand.Length)) + 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) { - matchingCommands.Add(name); + 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++]; } + } - 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()