|
| 1 | +# LCAPI.TerminalCommands |
| 2 | +Terminal Commands API for the Lethal Company Modding API |
| 3 | + |
| 4 | +## Misc Changes |
| 5 | +This mod makes some changes to how the terminal works, that isn't fully integral to custom commands. |
| 6 | + |
| 7 | +These changes are as such: |
| 8 | + |
| 9 | +* Reduces the delay after entering the terminal before you can type by 80% |
| 10 | + |
| 11 | + * You ever open the terminal and start typing, even hearing the keyboard sound, but it doesn't write anything? This fixes that. |
| 12 | + |
| 13 | +* Disable scrolling to the top of the terminal on every command execution |
| 14 | + * This now only happens when a command clears the screen. |
| 15 | + |
| 16 | + |
| 17 | +* Trims newlines from the start of command responses |
| 18 | + * The game force adds newlines to the start of commands, which causes issues for commands that don't clear the terminal. |
| 19 | + |
| 20 | +## Creating terminal commands |
| 21 | +Terminal commands are declared as annotated methods. E.g., |
| 22 | + |
| 23 | +```cs |
| 24 | +[TerminalCommand("Ping"), CommandInfo("Test command")] |
| 25 | +public string PingCommand() |
| 26 | +{ |
| 27 | + return "Pong!"; |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +All terminal commands are annotated with the `TerminalCommand` Attribute, and optionally, the `CommandInfo` Attribute. |
| 32 | + |
| 33 | +The `CommandInfo` attribute attaches command metadata and enrols the command to be displayed in the help command. |
| 34 | + |
| 35 | +You can also attach access control attributes, e.g., `AllowedCaller` to specify who can execute a command and when. More on this later. |
| 36 | + |
| 37 | +## Registering Commands |
| 38 | + |
| 39 | +All commands have to be registered to this library. Registering commands can be done through the `CommandRegistry` API. E.g., |
| 40 | + |
| 41 | +```cs |
| 42 | +private ModCommands Commands; |
| 43 | + |
| 44 | +public void Awake() |
| 45 | +{ |
| 46 | + Commands = CommandRegistry.CreateModRegistry(); |
| 47 | + Commands.RegisterFrom(this); // Register commands from the plugin class |
| 48 | + Commands.RegisterFrom(AnotherObject); // Register commands from another instance |
| 49 | + Commands.RegisterFrom<InfoCommands>(); // Activate and register commands from a type |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +You can also deregister commands by calling `ModCommands.Deregister()`. |
| 54 | + |
| 55 | + |
| 56 | +## Handling arguments |
| 57 | + |
| 58 | +All arguments are injected into the method, either parsed from the user input, or sourced from the execution context. E.g., |
| 59 | + |
| 60 | +```cs |
| 61 | + |
| 62 | +[TerminalCommand("RickRoll")] |
| 63 | +public string RickRollCommand(Terminal caller, [RemainingText] string text) |
| 64 | +{ |
| 65 | + // Echoes the user's message back to them, and rickrolls them |
| 66 | + caller.PlayVideoFile(RickrollVideoPath); |
| 67 | + return text; |
| 68 | +} |
| 69 | + |
| 70 | +[TerminalCommand("Multiply")] |
| 71 | +public int MultiplyCommand(int a, int b) |
| 72 | +{ |
| 73 | + return a * b; |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +Notice how commands don't need to return a string but can return anything. You can return your own `TerminalNode`, which is how the game itself handles command responses, or you can return anything, and it will be converted to a string. |
| 78 | + |
| 79 | +Additionally, if you return `null`, execution will fall through to the next valid command handler. More on this later. |
| 80 | + |
| 81 | +You can even parse some extra types such as `PlayerControllerB`, which will be parsed from Steam64ID, or player name. Argument injection is a useful tool to reduce boilerplate and create command overloads. |
| 82 | + |
| 83 | +You can inject as many arguments as you want, and the method will only be executed when all specified arguments can be provided, either from context, or parsed from user input. Generally, you will never need to manually parse basic arguments yourself. However, if you do want to handle argument parsing yourself, you can inject either `string[]` or `ArgumentStream`. |
| 84 | + |
| 85 | +## Command Overloading |
| 86 | + |
| 87 | +When a player runs a command, the first step in this process is selecting candidate commands. All commands registered with the same command name, and matching signatures can be evaluated. These commands with matching/compatible signatures will be executed in descending order of priority, and then argument count. |
| 88 | + |
| 89 | +So a command with more arguments will take priority over a command with less arguments. However all these arguments still must be valid and parsed from user input. Meaning you can create command patterns such as: |
| 90 | + |
| 91 | +```cs |
| 92 | + |
| 93 | +[TerminalCommand("BuyArtefact")] |
| 94 | +public string BuyArtefactCommand() |
| 95 | +{ |
| 96 | + return $"Available Artefacts:\n" |
| 97 | + + FormatAvailableArtefacts(); |
| 98 | +} |
| 99 | + |
| 100 | +[TerminalCommand("BuyArtefact")] |
| 101 | +public string BuyArtefactCommand(string artefactName) |
| 102 | +{ |
| 103 | + if(TryBuyArtefact(artefactName)) |
| 104 | + return "Artefact purchased!"; |
| 105 | + |
| 106 | + return "Insufficient funds"; |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +Here, running `BuyArtefact` will execute the first handler, but running `BuyArtefact gun` will execute the second. This is because the 2nd one has more arguments, so it will take priority when the user's input matches the commands signature. |
| 111 | + |
| 112 | +Additionally, the 2nd command could yield execution to the first, by returning `null`. Since commands are executed in order, if one command returns null, command handling will be passed ot the next handler. |
| 113 | + |
| 114 | +This means you can conditionally override other commands, including commands built-in to the game. To aide with this, you can also set the command priority using the `CommandPriority` attribute, to make it execute before another command regardless of argument count. |
| 115 | + |
| 116 | +## Access Control |
| 117 | + |
| 118 | +You can also decorate commands with the `AllowedCaller` attribute, to set what players can execute it, e.g., host-only. For commands registered to be shown in the help command results, commands that a player doesn't have access to won't be shown. E.g., |
| 119 | + |
| 120 | +```cs |
| 121 | +[TerminalCommand("Kill")] |
| 122 | +public string KillPlayerCommand() |
| 123 | +{ |
| 124 | + return "Usage: Kill [Player]\n" + |
| 125 | + "You must be the host to use this command"; |
| 126 | +} |
| 127 | + |
| 128 | +[TerminalCommand("Kill"), AllowedCaller(AllowedCaller.Host)] |
| 129 | +[CommandInfo("Kills the target player", "[Player]")] |
| 130 | +public string KillPlayerCommand(PlayerControllerB player) |
| 131 | +{ |
| 132 | + if (StartOfRound.Instance == null) |
| 133 | + return "Game not started"; |
| 134 | + |
| 135 | + player.KillPlayer(Vector3.up); |
| 136 | + return $"Killed {player.playerUsername}"; |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +The first command can be executed by anyone, but still won't show in the help command, since it doesn't have the `CommandInfo` attribute. |
| 141 | + |
| 142 | +The second command can only be executed by the lobby host, and it will show in the help command, but only to the host. If any other player runs the `Other` help command, they will not see the command listed, since they don't have access to it. |
| 143 | + |
| 144 | +### Custom Access Controls |
| 145 | + |
| 146 | +You can also create your own access control attributes, by inheriting `AccessControlAttribute`. E.g., |
| 147 | +```cs |
| 148 | +public class TeleporterUnlockedAttribute : AccessControlAttribute |
| 149 | +{ |
| 150 | + public override bool CheckAllowed() |
| 151 | + { |
| 152 | + if (StartOfRound.Instance == null) |
| 153 | + return false; |
| 154 | + |
| 155 | + return StartOfRound.Instance.SpawnedShipUnlockables.ContainsKey(5); |
| 156 | + } |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +## Misc |
| 161 | + |
| 162 | +This mod also provides a few extension methods: |
| 163 | + |
| 164 | +* `Terminal.PlayVideoFile(string filePath)` |
| 165 | + * Allows you to play local files in the background of the terminal |
| 166 | + |
| 167 | +* `Terminal.PlayVideoLink(Uri url)` |
| 168 | + * Allows you to play remote videos in the background of the terminal |
| 169 | + |
| 170 | + |
| 171 | + |
0 commit comments