You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
366 lines
12 KiB
366 lines
12 KiB
/** |
|
* This class is meant to represent a command type: to create new command |
|
* one should extend it, then simply define required sub-commands/options and |
|
* parameters in `BuildData()` and use `Execute()` / `ExecuteFor()` to perform |
|
* necessary actions when command is executed by a player. |
|
* Copyright 2021 Anton Tarasenko |
|
*------------------------------------------------------------------------------ |
|
* This file is part of Acedia. |
|
* |
|
* Acedia is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* Acedia is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
|
*/ |
|
class Command extends AcediaObject |
|
dependson(Text); |
|
|
|
/** |
|
* Possible errors that can arise when producing `CommandCall` from user input |
|
*/ |
|
enum ErrorType |
|
{ |
|
// No error |
|
CET_None, |
|
// Bad parser was provided to parse user input |
|
// (this should not be possible) |
|
CET_BadParser, |
|
// Sub-command name was not specified or was incorrect |
|
// (this should not be possible) |
|
CET_NoSubCommands, |
|
// Required param for command / option was not specified |
|
CET_NoRequiredParam, |
|
CET_NoRequiredParamForOption, |
|
// Unknown option key was specified |
|
CET_UnknownOption, |
|
CET_UnknownShortOption, |
|
// Same option appeared twice in one command call |
|
CET_RepeatedOption, |
|
// Part of user's input could not be interpreted as a part of |
|
// command's call |
|
CET_UnusedCommandParameters, |
|
// In one short option specification (e.g. '-lah') several options |
|
// require parameters: this introduces ambiguity and is not allowed |
|
CET_MultipleOptionsWithParams, |
|
// (For targeted commands only) |
|
// Targets are specified incorrectly (or none actually specified) |
|
CET_IncorrectTargetList, |
|
CET_EmptyTargetList |
|
}; |
|
|
|
/** |
|
* Possible types of parameters. |
|
*/ |
|
enum ParameterType |
|
{ |
|
// Parses into `BoolBox` |
|
CPT_Boolean, |
|
// Parses into `IntBox` |
|
CPT_Integer, |
|
// Parses into `FloatBox` |
|
CPT_Number, |
|
// Parses into `Text` |
|
CPT_Text, |
|
// Special parameter that consumes the rest of the input into `Text` |
|
CPT_Remainder, |
|
// Parses into `AssociativeArray` |
|
CPT_Object, |
|
// Parses into `DynamicArray` |
|
CPT_Array |
|
}; |
|
|
|
/** |
|
* Possible forms a boolean variable can be used as. |
|
* Boolean parameter can define it's preferred format, which will be used |
|
* for help page generation. |
|
*/ |
|
enum PreferredBooleanFormat |
|
{ |
|
PBF_TrueFalse, |
|
PBF_EnableDisable, |
|
PBF_OnOff, |
|
PBF_YesNo |
|
}; |
|
|
|
// Defines a singular command parameter |
|
struct Parameter |
|
{ |
|
// Display name (for the needs of help page displaying) |
|
var Text displayName; |
|
// Type of value this parameter would store |
|
var ParameterType type; |
|
// Does it take only a singular value or can it contain several of them, |
|
// written in a list |
|
var bool allowsList; |
|
// Variable name that will be used as a key to store parameter's value |
|
var Text variableName; |
|
// (For `CPT_Boolean` type variables only) - preferred boolean format, |
|
// used in help pages |
|
var PreferredBooleanFormat booleanFormat; |
|
}; |
|
|
|
// Defines a sub-command of a this command (specified as |
|
// "<command> <sub_command>"). |
|
// Using sub-command is not optional, but if none defined |
|
// (in `BuildData()`) / specified by the player - an empty (`name.IsEmpty()`) |
|
// one is automatically created / used. |
|
struct SubCommand |
|
{ |
|
// Cannot be `none` |
|
var Text name; |
|
// Can be `none` |
|
var Text description; |
|
var array<Parameter> required; |
|
var array<Parameter> optional; |
|
}; |
|
|
|
// Defines command's option (options are specified by "--long" or "-l"). |
|
// Options are independent from sub-commands. |
|
struct Option |
|
{ |
|
var Text.Character shortName; |
|
var Text longName; |
|
var Text description; |
|
// Option can also have their own parameters |
|
var array<Parameter> required; |
|
var array<Parameter> optional; |
|
}; |
|
|
|
// Structure that defines what sub-commands and options command has |
|
// (and what parameters they take) |
|
struct Data |
|
{ |
|
// Default command name that will be used unless Acedia is configured to |
|
// do otherwise |
|
var protected Text name; |
|
// Short summary of what command does (recommended to |
|
// keep it to 80 characters) |
|
var protected Text summary; |
|
var protected array<SubCommand> subCommands; |
|
var protected array<Option> options; |
|
var protected bool requiresTarget; |
|
}; |
|
var private Data commandData; |
|
|
|
// We do not really ever need to create more than one instance of each class |
|
// of `Command`, so we will simply store and reuse one created instance. |
|
var private Command mainInstance; |
|
|
|
protected function Constructor() |
|
{ |
|
local CommandDataBuilder dataBuilder; |
|
dataBuilder = |
|
CommandDataBuilder(_.memory.Allocate(class'CommandDataBuilder')); |
|
BuildData(dataBuilder); |
|
commandData = dataBuilder.GetData(); |
|
dataBuilder.FreeSelf(); |
|
dataBuilder = none; |
|
} |
|
|
|
/** |
|
* Overload this method to use `builder` to define parameters and options for |
|
* your command. |
|
* |
|
* @param builder Builder that can be used to define your commands parameters |
|
* and options. Do not deallocate. |
|
*/ |
|
protected function BuildData(CommandDataBuilder builder){} |
|
|
|
/** |
|
* Overload this method to perform what is needed when your command is called. |
|
* |
|
* @param callInfo Object filled with parameters that your command has |
|
* been called with. Guaranteed to not be in error state. |
|
*/ |
|
protected function Executed(CommandCall callInfo){} |
|
|
|
/** |
|
* Overload this method to perform what is needed when your command is called |
|
* with a given player as a target. If several players have been specified - |
|
* this method will be called once for each. |
|
* |
|
* If your command does not require a target - this method will not be called. |
|
* |
|
* @param targetPlayer Player that this command must perform an action on. |
|
* @param callInfo Object filled with parameters that your command has |
|
* been called with. Guaranteed to not be in error state. |
|
*/ |
|
protected function ExecutedFor(APlayer targetPlayer, CommandCall callInfo){} |
|
|
|
/** |
|
* Returns an instance of command (of particular class) that is stored |
|
* "as a singleton" in command's class itself. Do not deallocate it. |
|
*/ |
|
public final static function Command GetInstance() |
|
{ |
|
if (default.mainInstance == none) { |
|
default.mainInstance = Command(__().memory.Allocate(default.class)); |
|
} |
|
return default.mainInstance; |
|
} |
|
|
|
/** |
|
* Forces command to process (parse and, if successful, execute itself) |
|
* player's input. |
|
* |
|
* @param parser Parser that contains player's input. |
|
* @param callerPlayer Player that initiated this command's call. |
|
* @return `CommandCall` object that described parsed command call. |
|
* Guaranteed to be not `none`. |
|
*/ |
|
public final function CommandCall ProcessInput( |
|
Parser parser, |
|
APlayer callerPlayer) |
|
{ |
|
local int i; |
|
local array<APlayer> targetPlayers; |
|
local CommandParser commandParser; |
|
local CommandCall callInfo; |
|
if (parser == none || !parser.Ok()) { |
|
return MakeAndReportError(callerPlayer, CET_BadParser); |
|
} |
|
// Parse targets and handle errors that can arise here |
|
if (commandData.requiresTarget) |
|
{ |
|
targetPlayers = ParseTargets(parser, callerPlayer); |
|
if (!parser.Ok()) { |
|
return MakeAndReportError(callerPlayer, CET_IncorrectTargetList); |
|
} |
|
if (targetPlayers.length <= 0) { |
|
return MakeAndReportError(callerPlayer, CET_EmptyTargetList); |
|
} |
|
} |
|
// Parse parameters themselves |
|
commandParser = CommandParser(_.memory.Allocate(class'CommandParser')); |
|
callInfo = commandParser.ParseWith(parser, commandData) |
|
.SetCallerPlayer(callerPlayer) |
|
.SetTargetPlayers(targetPlayers); |
|
commandParser.FreeSelf(); |
|
// Report or execute |
|
if (!callInfo.IsSuccessful()) |
|
{ |
|
ReportError(callerPlayer, callInfo); |
|
return callInfo; |
|
} |
|
Executed(callInfo); |
|
if (commandData.requiresTarget) |
|
{ |
|
for (i = 0; i < targetPlayers.length; i += 1) { |
|
ExecutedFor(targetPlayers[i], callInfo); |
|
} |
|
} |
|
return callInfo; |
|
} |
|
|
|
// Reports given error to the `callerPlayer`, appropriately picking |
|
// message color |
|
private final function ReportError( |
|
APLayer callerPlayer, |
|
CommandCall callInfo) |
|
{ |
|
local Text errorMessage; |
|
local ConsoleWriter console; |
|
if (callerPlayer == none) return; |
|
if (callInfo == none) return; |
|
if (callInfo.IsSuccessful()) return; |
|
|
|
// Setup console color |
|
console = callerPlayer.Console(); |
|
if (callInfo.GetError() == CET_EmptyTargetList) { |
|
console.UseColor(_.color.textWarning); |
|
} |
|
else { |
|
console.UseColor(_.color.textFailure); |
|
} |
|
// Send message |
|
errorMessage = callInfo.PrintErrorMessage(); |
|
console.Say(errorMessage); |
|
errorMessage.FreeSelf(); |
|
// Restore console color |
|
console.ResetColor().Flush(); |
|
} |
|
|
|
// Creates (and returns) empty `CommandCall` with given error type and |
|
// empty error cause and reports it |
|
private final function CommandCall MakeAndReportError( |
|
APLayer callerPlayer, |
|
ErrorType errorType) |
|
{ |
|
local CommandCall dummyCall; |
|
if (errorType == CET_None) return none; |
|
|
|
dummyCall = class'CommandCall'.static.MakeError(errorType, callerPlayer); |
|
ReportError(callerPlayer, dummyCall); |
|
return dummyCall; |
|
} |
|
|
|
// Auxiliary method for parsing list of targeted players. |
|
// Assumes given parser is not `none` and not in a failed state. |
|
private final function array<APlayer> ParseTargets( |
|
Parser parser, |
|
APlayer callerPlayer) |
|
{ |
|
local array<APlayer> targetPlayers; |
|
local PlayersParser targetsParser; |
|
targetsParser = PlayersParser(_.memory.Allocate(class'PlayersParser')); |
|
targetsParser.SetSelf(callerPlayer); |
|
targetsParser.ParseWith(parser); |
|
targetPlayers = targetsParser.GetPlayers(); |
|
targetsParser.FreeSelf(); |
|
return targetPlayers; |
|
} |
|
|
|
|
|
// TODO: This is a hack to insert new line symbol, |
|
// this needs to be redone in a better way |
|
private final function Text.Character GetNewLine(Text.Formatting formatting) |
|
{ |
|
local Text.Character newLine; |
|
newLine.codePoint = 10; |
|
newLine.formatting = formatting; |
|
return newLine; |
|
} |
|
|
|
/** |
|
* Returns name (in lower case) of the caller command class. |
|
* |
|
* @return Name (in lower case) of the caller command class. |
|
* Guaranteed to be not `none`. |
|
*/ |
|
public final function Text GetName() |
|
{ |
|
if (commandData.name == none) { |
|
return P("").Copy(); |
|
} |
|
return commandData.name.LowerCopy(); |
|
} |
|
|
|
/** |
|
* Returns `Command.Data` struct that describes caller `Command`. |
|
* |
|
* @return `Command.Data` that describes caller `Command`. Returned struct |
|
* contains `Text` references that are used internally by the `Command` |
|
* and not their copies. |
|
* Generally this is undesired approach and leaves `Command` more |
|
* vulnerable to modification, but copying all the data inside would not |
|
* only introduce a largely pointless computational overhead, but also |
|
* would require some cumbersome logic. This might change in the future, |
|
* so deallocating any objects in the returned `struct` would lead to |
|
* undefined behavior. |
|
*/ |
|
public final function Data GetData() |
|
{ |
|
return commandData; |
|
} |
|
|
|
defaultproperties |
|
{ |
|
} |