From cd056b9daa9714f14198dd1dd51ea608547b99d7 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sun, 23 Oct 2022 17:53:08 +0700 Subject: [PATCH] Document commands-related classes --- .../Commands/BuiltInCommands/ACommandHelp.uc | 50 +++++++++++++- sources/Commands/Command.uc | 69 +++++++++++++++++++ sources/Commands/CommandDataBuilder.uc | 44 +++++++++--- sources/Commands/CommandParser.uc | 58 +++++++++++++++- sources/Commands/Commands_Feature.uc | 52 +++++++++++--- sources/Commands/PlayersParser.uc | 22 ++++++ 6 files changed, 270 insertions(+), 25 deletions(-) diff --git a/sources/Commands/BuiltInCommands/ACommandHelp.uc b/sources/Commands/BuiltInCommands/ACommandHelp.uc index 14f48ef..2c2b7ba 100644 --- a/sources/Commands/BuiltInCommands/ACommandHelp.uc +++ b/sources/Commands/BuiltInCommands/ACommandHelp.uc @@ -20,12 +20,58 @@ class ACommandHelp extends Command dependson(LoggerAPI); +/** + * # `ACommandHelp` + * + * This built-in command is meant to do two things: + * + * 1. List all available commands and aliases related to them; + * 2. Display help pages for available commands (both by command and + * alias names). + * + * ## Implementation + * + * ### Aliases loading + * + * First thing this command tries to do upon creation is to read all + * command aliases and build a reverse map that allows to access aliases names + * by command + subcommand (even if latter one is empty) that these names + * refer to. This allows us to display relevant aliases names alongside + * command names. Map is stored inside `commandToAliasesMap` variable. + * (If aliases feature isn't yet available, `ACommandHelp` connects to + * `OnFeatureEnabled()` signal to do its job the moment required feature is + * enabled). + * This map is also made to be two-level one - it is... + * + * * a `HashTable` with command names as keys, + * * that points to `HashTable`s with subcommand names as keys, + * * that points at `ArrayList` of aliases. + * + * This allows us to also sort aliases by the subcommand name when displaying + * their list. This work is done by `FillCommandToAliasesMap()` method (that + * uses `ParseCommandNames()` method, which is also used for displaying + * command list). + * + * ### Command list displaying + * + * This is a simple process performed by `DisplayCommandLists()` that calls on + * a bunch of auxiliary `Print...()` methods. + * + * ### Displaying help pages + * + * Similar to the above, this is mostly a simple process that displays + * `Command`s' `CommandData` as a help page. Work is performed by + * `DisplayCommandHelpPages()` method that calls on a bunch of auxiliary + * `Print...()` methods, most notably `PrintHelpPageFor()` that can display + * help pages both for full commands, as well as only for a singular + * subcommand, which allows us to print proper help pages for command aliases + * that refer to a particular subcommand. + */ + // For each key (given by lower case command name) stores another `HashMap` // that uses sub-command names as keys and returns `ArrayList` of aliases. var private HashTable commandToAliasesMap; -var LoggerAPI.Definition testMsg; - var public const int TSPACE, TCOMMAND_NAME_FALLBACK, TPLUS; var public const int TOPEN_BRACKET, TCLOSE_BRACKET, TCOLON_SPACE; var public const int TKEY, TDOUBLE_KEY, TCOMMA_SPACE, TBOOLEAN, TINDENT; diff --git a/sources/Commands/Command.uc b/sources/Commands/Command.uc index 747459e..d3f67d9 100644 --- a/sources/Commands/Command.uc +++ b/sources/Commands/Command.uc @@ -26,6 +26,69 @@ class Command extends AcediaObject dependson(BaseText); +/** + * # `Command` + * + * Command class provides an automated way to add a command to a server through + * AcediaCore's features. It takes care of: + * + * 1. Verifying that player has passed correct (expected parameters); + * 2. Parsing these parameters into usable values (both standard, built-in + * types like `bool`, `int`, `float`, etc. and more advanced types such + * as players lists and JSON values); + * 3. Allowing you to easily specify a set of players you are targeting by + * supporting several ways to refer to them, such as *by name*, *by id* + * and *by selector* (@ and @self refer to caller player, @all refers + * to all players). + * 4. It can be registered inside AcediaCore's commands feature and be + * automatically called through the unified system that supports *chat* + * and *mutate* inputs (as well as allowing you to hook in any other + * input source); + * 5. Will also automatically provide a help page through built-in "help" + * command; + * 6. Subcommand support - when one command can have several distinct + * functions, depending on how its called (e.g. "inventory add" vs + * "inventory remove"). These subcommands have a special treatment in + * help pages, which makes them more preferable, compared to simply + * matching first `Text` argument; + * 7. Add support for "options" - additional flags that can modify commands + * behavior and behave like usual command options "--force"/"-f". + * Their short versions can even be combined: + * "give@ $ebr --ammo --force" can be rewritten as "give@ $ebr -af". + * And they can have their own parameters: "give@all --list sharp". + * + * ## Usage + * + * To create a custom command you need to simply: + * + * 1. Create a custom command class derived from `Command`; + * 2. Define `BuildData()` function and use given `CommandDataBuilder` to + * fill-in data about what parameters your command takes. You can also + * add optional descriptions that would appear in your command's + * help page. + * 3. Overload `Executed()` or `ExecutedFor()` (or both) method and add + * whatever logic you want to execute once your command was called. + * All parameters and options will be listed inside passed `CallData` + * parameter. These methods will only be called if all necessary + * parameters were correctly specified. + * + * ## Implementation + * + * The idea of `Command`'s implementation is simple: command is basically + * the `Command.Data` struct that is filled via `CommandDataBuilder`. + * Whenever command is called it uses `CommandParser` to parse user's input + * based on its `Command.Data` and either report error (in case of failure) or + * pass make `Executed()`/`ExecutedFor()` calls (in case of success). + * When command is called is decided by `Commands_Feature` that tracks + * possible user inputs (and provides `HandleInput()`/`HandleInputWith()` + * methods for adding custom command inputs). That feature basically parses + * first part of the command: its name (not the subcommand's names) and target + * players (using `PlayersParser`, but only if command is targeted). + * + * Majority of the command-related code either serves to build + * `Command.Data` or to parse command input by using it (`CommandParser`). + */ + /** * Possible errors that can arise when parsing command parameters from * user input @@ -227,6 +290,7 @@ protected function Finalizer() local int i; local array subCommands; local array