Creating a New Command
To create a new Toolshed command, you have to create a new class that:- Inherits from
ToolshedCommand - Has a class name that ends with “Command”
- Is annotated with the
ToolshedCommandAttribute - Has one or more (non-static) methods that are annotated with the
CommandImplementationAttribute.
foo command would be:
FooCommand class gets mapped to foo. Alternatively, the command name can be specified using the class attribute, e.g., [ToolshedCommand(Name = "foo")]. If the name is explicitly specified, the name of the class doesn’t have to end in “Command”, but it is still a good convention to follow.
Auto-generated command names can be configure per-project to use snake_case. So to support conversion CamelCase class names, you should should avoid using class names with abbreviations that have consecutive capitals. I.e., use something like
GetNpcCommand instead of GetNPCCommand, as the latter would be converted to “get_n_p_c”.Arguments & Return Values
To define a command that returns some value that can then be piped into another command, you just have to give the method some return value. To give the command some arguments you just add arguments to the method. For example,IInvocationContext type) is assumed to be a normal command argument will attempt to be parsed from the command string by Toolshed. Optionally, these can also be explicitly annotated with the CommandArgumentAttribute.
Toolshed also supports methods with optional and params [] arguments. E.g.,
Argument Parsers
Toolshed can parse any type of argument that has a correspondingTypeParser<T> implementation. For example, string arguments are parsed by the StringTypeParser : TypeParser<string> class. The parser is responsible for generating console command auto-completion options & hints. If a type is not yet supported, you can always just create your own parser.
If you want more control over how one of your arguments is parsed, or more control over auto-completion suggestions, you can also use the argument’s attribute to specify that it should use a custom parser.
Piped Input Arguments
In order to create a command that can accept input values that get piped in from another command, you have to give the method an argument annotated with thePipedArgumentAttribute. For example, this creates a simple addition command:
Invertible Commands
In order to create a command whose behaviour can be inverted by prefixing it with the “not” keyword, you have to give the method has abool argument that is annotated with the CommandInvertedAttribute. For example, this is a simple command that looks for a specific number in a sequence:
Invocation Contexts
If you want to create a command that writes output to the console or that can read & write Toolshed variables, you need to have a method that takes in anIInvocationContext argument. This argument can also optionally be annotated with the CommandInvocationContextAttribute. E.g., this is a simple command that will give out one-time greetings:
OldShellInvocationContext, where each player will have their own context that persists across disconnects & reconnects, but not server restarts. The context is also not networked, so commands executed client-side & server-side will use different invocation contexts.
Dependencies
Toolshed commands support normalEntitySystem & manager dependency injections. So if your command needs to work with entity transforms, you can just give your class a normal dependency field, i.e.,
ToolshedCommand class already provides the ToolshedManager, ILocalizationManager, and IEntityManager dependencies. It also defines some helpful IEntityManager proxy methods (e.g., TryComp<T>, Spawn, etc). So in general, you can just write code like you normally would within an EntitySystem.
Multiple Implementations & Subcommands
So far, all of the examples have defined a command with a single implementation method. Commands can have more than one implementation, however each implementation must take in a different piped type. For example, this would result in a valid command that can take in either an integer or float:CommandImplementationAttribute. Note that if a command contains any named implementations, then all of them must be given a name. As an example, our earlier command could be fixed by naming the implementations:
tp:ent and tp:map “sub”-commands
By convention, any new commands should use snake_case when naming commands or subcommands.
Generics
Toolshed commands have some support for C# generics, though there are a few limitations. The most common use case is when you want to define a method that takes in some arbitrary piped input type and should use the type of the input as the generic argument. In that case, you just give your generic method theTakesPipedTypeAsGenericAttribute. E.g., this is part of how the actual addition command is defined:
TakesPipedTypeAsGeneric attribute also supports extracting the generic type even if it doesn’t directly correspond to the type of the piped argument. E.g., if the piped argument is an IEnumerable<T>, it can still extract the generic type T from the piped value. For example, this is what the append command does:
Foo<T>([PipedArgument] Dictionary<int, List<(T, string)>> input) will probably fail to extract T from a given piped input value.
There is also no support for automatically determining multiple generic arguments from the piped input. If you want commands that use more complex generics, you will generally need to define a command that has explicit type arguments.
Type Arguments
If you need to create a command that uses multiple generic arguments or has generics that can’t automatically be inferred from the piped input, you need to use explicit type arguments. When writing out a shell command the type arguments look just like regular arguments, but they always precede any other argument and are used to determine the types for a generic implementation. To make your command require type arguments, you have to override the command’sTypeParameterParsers property. This should return an array of types that inherit from TypeParser<Type>, and will be used to actually parse the type arguments from the command string. As this is a class-wide property this means that all implementations or subcommands must require the same number of type arguments. You can also combine explicit type arguments with the TakesPipedTypeAsGenericAttribute. Note that the automatically inferred type argument must always be the last type argument of that function.
For example, these two commands make use of explicit type arguments to print a C# style method invocation syntax:
Automatic Type Conversions
As mentioned elsewhere int the docs, Toolshed will perform some automatic type conversions. Most notably, any command that expects anIEnumerable<T> will also accept being piped a T, as Toolshed will automatically converted it into an IEnumerable<T> with one element.
Toolshed will also automatically cast any type that implements the IAsType<T> interface. E.g., Entity<T> implements IAsType<EntityUid>. So Toolshed will allow you to pipe an Entity<T> output into a method that expects an EntityUid input.
Custom Type Parsers
If you want to create a method that uses a custom parser, you can specify a custom parser via the an argument’sCommandArgumentAttribute. This is useful if you want more control over the parsing or console auto-completion options/hints.
For example, the following defines a method that uses a custom parser to get an integer from a binary string. Though in this specific case, you could just as easily have made the command take in a string and done the conversion within the command’s own method, though then the argument would need to be wrapped in quotes (all string arguments need to be wrapped in quotes).
Permissions
All Toolshed commands need to specify some permissions in order to be executable, and there is an integration test that checks that this is the case (AdminTest.AllCommandsHavePermissions). The permissions for commands defined in the engine are specified in /Resources/toolshedEngineCommandPerms.yml, while Content commands can be given permissions by annotating the command class with the usual attributes (AnyCommandAttribute, AdminCommandAttribute). Permissions can’t be specified per-subcommands, all subcommands must have the same permissions.
Auto-Completion, Hints, & localization
Every Toolshed command should have a localized description. The key for the localized string is based on the (sub)command name. E.g.,foo or foo:bar uses “command-description-foo” or “command-description-foo-bar”. If the command name contains non-ascii characters, it will instead use the name of the class. E.g., the addition command (+) is defined in the AddCommand class, thus it uses “command-description-AddCommand”.
Toolshed will automatically generate help strings for commands in the form of the method’s signature. The auto-generated help string can be overridden by defining a localized string. E.g., the foo command’s help can be overridden by defining a localized string with the key “command-help-foo”.
Argument Hints
Most of the Toolshed argument parsers will automatically generate console-completion hints while writing the arguments for a command. E.g., while writing the argument for a method likeFoo(int myNumber) it will generate the hint [myNumber (int)]. If you want to override the auto-generated hint, you can do so by defining a localized string with the key “command-arg-hint-foo-myNumber”. If you want more control over the hint or auto-completion suggestions, you can use a custom parser.