-
Notifications
You must be signed in to change notification settings - Fork 1
Home
This wiki is intended for anyone looking for information how to create new Commander commands, persisting them so they don't banish after closing Foundry, and maybe contributing to Commander itself!
You can find examples of commands for reference HERE.
The module provides an API available at the Module instance, alongside some helper functions. You can access them by doing:
const {api, helpers} = game.modules.get('commander');
Array of all registered commands.
Tries to execute the command matching the commandString; first it parses the command name and checked if the command exists and is allowed. If so, then gets the command schema and tries to match the input to it, extracting the arguments. Then calls the command's handler function with said arguments.
Receives a Command to register; the command is only registered if it passes integrity checks and it does not already exist (commands are identified by name
). You can replace existing commands by sending a boolean flag as second argument. Commands have to be of the following shape:
interface Command {
name: string; // must be lowercase
namespace: string; // must be lowercase; unused for now but mandatory
description?: string;
schema: string; // must start with name, followed by argument names prefixed with '$'
args: Argument[];
allow?: () => boolean;
handler: (...params: any) => any;
}
interface Argument {
name: string; // 'string'|'number'|'boolean'|'raw' names are reserved
type: ARGUMENT_TYPES;
suggestions?: (...params: any) => Suggestion[];
}
enum ARGUMENT_TYPES {
'string', // accepts spaces ONLY IF you write the next between quotes.
'number', // accepts numbers with decimals. It's just parseFloat(arg), so be tame with the decimals. Consider yourself warned!
'boolean', // accepts 'true', 'on', 'false', 'off'
'raw', // returns the whole remaining input string. If used with other arguments this MUST BE LAST.
}
interface Suggestion {
content: string; // what is shown on the suggestion
icon?: string; // icon is a font-awesome class name, takes precedence over img
img?: string;
bold?: boolean; // not implemented yet
italics?: boolean; // not implemented yet
}
Receives a string and tries to match it to a role from CONST.USER_ROLES
. If it's a valid role, returns the allow callback function that will check for this role whenever a command using that function is invoked. Example:
// while defining a new command..
api.register({
name: "mycommand",
allow: helpers.hasRole('TRUSTED'), // this command can be invoked by a user with role TRUSTED or more
...
})
Receives a list of permission strings, and tries to match them to a permission from CONST.USER_PERMISSIONS
. If all listed permissions are valid, returns the allow callback function that will check for ALL of these permissions whenever a command using that function is invoked. Example:
// while defining a new command..
api.register({
name: "mycommand",
allow: helpers.hasPermissions('ACTOR_CREATE', 'ITEM_CREATE'), // this command can be invoked by a user with both the ACTOR_CREATE and ITEM_CREATE permissions
...
})
The tool provides the following hooks:
Called when Commander has finished initialization. At this point, the keybinding is functional and the API is available. Receives the Commander instance ready to be used. This hook will be called on
ready
.
Called when Commander has been asked to run a command, either via the provided widget or directly via the API. Receives the execution string requested to be executed.
Currently the module doesn't provide yet a way to persist the registered Commands long-term (It's the reason the api doesn't provide any unregister
too, as Commands will disappear on reload), therefore you will need to register
your Commands on every session. Alternatives are:
-
Commands that come bundled with Commander are registered on
setup
, before calling theCommanderReady
hook, so if you intend on overwriting any of the bundled Commands, you can do so safely on that hook by using the replace flag onregister
. -
The simplest way would be adding all your Commands on a single macro you run once every game (though it's a rather lame solution, you can do better!)
-
Next best would be hooking on
CommanderReady
on a world script, that would execute itself on each reload.
// Example world script
console.info("Running world script");
Hooks.on("commanderReady", () => {
let { api, helpers } = game.modules.get('commander');
const newCommand = {...} // fill in the command data here - the command name doesn't match an existing command
api.register(newCommand );
const existingCommand = {...} // fill in the command data here - the command name matches an existing command
api.register(existingCommand, true); // without this flag, this register will throw an exception
});
- Alternatively, if you have a system/module that provides Commands, you can follow the world-script advice and hook to
CommanderReady
.