/** * This object describes a call attempt for one of the `Command`s. * `Command`s are meant to be be executed from user's console input, * so this object should only be created while parsing their input. Any other * use of this object is not guaranteed to be supported. * 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 . */ class CommandCall extends AcediaObject dependson(Command); // Once this value is set to `true`, the command call is considered fully // described and will prevent any changes to it's internal state // (except deallocation). var private bool locked; // Player who initiated the call and targeted players (if applicable) var private APlayer callerPlayer; var private array targetPlayers; // Specified sub-command and parameters/options var private Text subCommandName; var private AssociativeArray commandParameters, commandOptions; // Errors that occurred during command call processing are described by // error type and optional error textual name of the object // (parameter, option, etc.) that caused it. var private Command.ErrorType parsingError; var private Text errorCause; var public const int TBAD_PARSER, TNOSUB_COMMAND, TNO_REQ_PARAM; var public const int TNO_REQ_PARAM_FOR_OPTION, TUNKNOW_NOPTION; var public const int TUNKNOWN_SHORT_OPTION, TREPEATED_OPTION, TUNUSED_INPUT; var public const int TMULTIPLE_OPTIONS_WITH_PARAMS; var public const int TINCORRECT_TARGET, TEMPTY_TARGETS; protected function Constructor() { // We simply take ownership and record into `commandParameters` whatever // `AssociativeArray` was passed to us, but fill (and therefore create) // `commandOptions` ourselves. commandOptions = _.collections.EmptyAssociativeArray(); } protected function Finalizer() { _.memory.Free(commandParameters); _.memory.Free(commandOptions); _.memory.Free(subCommandName); _.memory.Free(errorCause); commandParameters = none; commandOptions = none; subCommandName = none; errorCause = none; parsingError = CET_None; targetPlayers.length = 0; locked = false; } /** * Method for producing erroneous `CommandCall` for the needs of * error reporting. * * @param type Type of error resulting `CommandCall` must have. * @param callerPlayer Player that caused erroneous command call. * @return `CommandCall` with specified error type and `APlayer`. */ public final static function CommandCall MakeError( Command.ErrorType type, APlayer callerPlayer) { return CommandCall(__().memory.Allocate(class'CommandCall')) .DeclareError(type) .SetCallerPlayer(callerPlayer); } /** * Put caller `CommandCall` into erroneous state. * Does nothing after `CommandCall` was locked for change (see `Finish()`). * * @param type Type of error caller `CommandCall` must have. * Once error (not `CET_None`) was set - calling this method with * `CET_None` to erase error will not work. * @param cause Textual description of offender command part to supplement * error report (will be used when reporting error to the caller). * @return Caller `CommandCall` to allow for method chaining. */ public final function CommandCall DeclareError( Command.ErrorType type, optional Text cause) { if (locked) return self; if (parsingError != CET_None) return self; parsingError = type; _.memory.Free(errorCause); errorCause = none; if (cause != none) { errorCause = cause.Copy(); } return self; } /** * Checks if caller `CommandCall` is in erroneous state. * * @return `true` if `CommandCall` has not recorded any errors so far and * `false` otherwise. */ public final function bool IsSuccessful() { return parsingError == CET_None; } /** * Returns current error type (including `CET_None` if there were no errors). * * @return Error type for caller `CommandCall` error. */ public final function Command.ErrorType GetError() { return parsingError; } /** * In case there were any errors - returns textual description of offender * command part. Mostly used for reporting errors to players. * * @return Textual description of command part that caused an error. */ public final function Text GetErrorCause() { if (errorCause != none) { return errorCause.Copy(); } return none; } /** * After this method is called - changes to the `CommandCall` will be * prevented. * * @return Caller `CommandCall` to allow for method chaining. */ public final function CommandCall Finish() { locked = true; return self; } /** * Returns player, that initiated command, that produced caller `CommandCall`. * * @return Player, that initiated command, that produced caller `CommandCall` */ public final function APlayer GetCallerPlayer() { return callerPlayer; } /** * Sets player, that initiated command, that produced caller `CommandCall`. * Does nothing after `CommandCall` was locked for change (see `Finish()`). * * @return Caller `CommandCall` to allow for method chaining. */ public final function CommandCall SetCallerPlayer(APlayer player) { callerPlayer = player; return self; } /** * Returns players that were targeted by command that produced caller * `CommandCall`. * * @return Players, targeted by caller `CommandCall`. */ public final function array GetTargetPlayers() { return targetPlayers; } /** * Sets players, targeted by command that produced caller `CommandCall`. * Does nothing after `CommandCall` was locked for change (see `Finish()`). * * @return Caller `CommandCall` to allow for method chaining. */ public final function CommandCall SetTargetPlayers(array newTargets) { if (!locked) { targetPlayers = newTargets; } return self; } /** * Returns picked sub-command of command that produced caller `CommandCall`. * * @return Sub-command of command that produced caller `CommandCall`. * Returns stored value that will be deallocated along with * caller `CommandCall` - do not deallocate returned `Text` manually. */ public final function Text GetSubCommand() { return subCommandName; } /** * Sets sub-command of command that produced caller `CommandCall`. * Does nothing after `CommandCall` was locked for change (see `Finish()`). * * @param newSubCommandName New sub command name. * Copy of passed object will be stored. * @return Caller `CommandCall` to allow for method chaining. */ public final function CommandCall SetSubCommand(Text newSubCommandName) { if (!locked) { _.memory.Free(subCommandName); subCommandName = newSubCommandName.Copy(); } return self; } /** * Returns parameters of command that produced caller `CommandCall`. * * @return Parameters of command that produced caller `CommandCall`. * Returns stored value that will be deallocated along with * caller `CommandCall` - do not deallocate returned `AssociativeArray` * manually. */ public final function AssociativeArray GetParameters() { return commandParameters; } /** * Sets parameters of command that produced caller `CommandCall`. * Does nothing after `CommandCall` was locked for change (see `Finish()`). * * @param parameters New set of parameters. Passed value will be managed by * caller `CommandCall` and should not be deallocated manually after * calling `SetParameters()`. * @return Caller `CommandCall` to allow for method chaining. */ public final function CommandCall SetParameters(AssociativeArray parameters) { if (!locked) { _.memory.Free(commandParameters); commandParameters = parameters; } return self; } /** * Returns options of command that produced caller `CommandCall`. * * If option without parameters was specified - it will be recorded as * a key with value `none`. * If option has parameters - `AssociativeArray` with them will be * recorded as value instead. * * @return Options of command that produced caller `CommandCall`. * Returns stored value that will be deallocated along with * caller `CommandCall` - do not deallocate returned `AssociativeArray` * manually. */ public final function AssociativeArray GetOptions() { return commandOptions; } /** * Sets option parameters of command that produced caller `CommandCall`. * Does nothing after `CommandCall` was locked for change (see `Finish()`). * * For recording options without parameters simply pass `none` instead of them. * * @param option Option to record (along with it's possible parameters). * @param parameters Option parameters. Passed value will be managed by * caller `CommandCall` and should not be deallocated manually after * calling `SetParameters()`. * Pass `none` if option has no parameters. * @return Caller `CommandCall` to allow for method chaining. */ public final function CommandCall SetOptionParameters( Command.Option option, optional AssociativeArray parameters) { if (locked) return self; if (commandOptions == none) return self; commandOptions.SetItem(option.longName, parameters, true); return self; } /** * Prints error message as a human-readable message that can be reported to * the caller player. * * In case there was no error - empty text is returned. * * @return Error message in a human-readable form. */ public final function Text PrintErrorMessage() { local Text result; local MutableText builder; builder = _.text.Empty(); switch (parsingError) { case CET_BadParser: builder.Append(T(TBAD_PARSER)); break; case CET_NoSubCommands: builder.Append(T(TNOSUB_COMMAND)); break; case CET_NoRequiredParam: builder.Append(T(TNO_REQ_PARAM)).Append(errorCause); break; case CET_NoRequiredParamForOption: builder.Append(T(TNO_REQ_PARAM_FOR_OPTION)).Append(errorCause); break; case CET_UnknownOption: builder.Append(T(TUNKNOW_NOPTION)).Append(errorCause); break; case CET_UnknownShortOption: builder.Append(T(TUNKNOWN_SHORT_OPTION)); break; case CET_RepeatedOption: builder.Append(T(TREPEATED_OPTION)).Append(errorCause); break; case CET_UnusedCommandParameters: builder.Append(T(TUNUSED_INPUT)).Append(errorCause); break; case CET_MultipleOptionsWithParams: builder.Append(T(TMULTIPLE_OPTIONS_WITH_PARAMS)).Append(errorCause); break; case CET_IncorrectTargetList: builder.Append(T(TINCORRECT_TARGET)).Append(errorCause); break; case CET_EmptyTargetList: builder.Append(T(TEMPTY_TARGETS)).Append(errorCause); break; default: } result = builder.Copy(); builder.FreeSelf(); return result; } defaultproperties { TBAD_PARSER = 0 stringConstants(0) = "Internal error occurred: invalid parser." TNOSUB_COMMAND = 1 stringConstants(1) = "Ill defined command: no subcommands" TNO_REQ_PARAM = 2 stringConstants(2) = "Missing required parameter: " TNO_REQ_PARAM_FOR_OPTION = 3 stringConstants(3) = "Missing required parameter for option: " TUNKNOW_NOPTION = 4 stringConstants(4) = "Invalid option specified: " TUNKNOWN_SHORT_OPTION = 5 stringConstants(5) = "Invalid short option specified." TREPEATED_OPTION = 6 stringConstants(6) = "Option specified several times: " TUNUSED_INPUT = 7 stringConstants(7) = "Part of command could not be parsed: " TMULTIPLE_OPTIONS_WITH_PARAMS = 8 stringConstants(8) = "Multiple short options in one declarations require parameters:" TINCORRECT_TARGET = 9 stringConstants(9) = "Target players are incorrectly specified." TEMPTY_TARGETS = 10 stringConstants(10) = "List of target players is empty." }