Browse Source

Fix style for remaining `CommandAPI`-related classes

pull/12/head
Anton Tarasenko 2 years ago
parent
commit
677dd84e90
  1. 766
      sources/BaseAPI/API/Commands/Command.uc
  2. 444
      sources/BaseAPI/API/Commands/PlayersParser.uc

766
sources/BaseAPI/API/Commands/Command.uc

@ -1,12 +1,8 @@
/** /**
* This class is meant to represent a command type: to create new command * Author: dkanus
* one should extend it, then simply define required sub-commands/options and * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* parameters in `BuildData()` and overload `Executed()` / `ExecutedFor()` * License: GPL
* to perform required actions when command is executed by a player. * Copyright 2021-2023 Anton Tarasenko
* `Executed()` is called first, whenever command is executed and
* `ExecuteFor()` is called only for targeted commands, once for each
* targeted player.
* Copyright 2021-2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -26,277 +22,234 @@
class Command extends AcediaObject class Command extends AcediaObject
dependson(BaseText); dependson(BaseText);
/** //! This class is meant to represent a command type.
* # `Command` //!
* //! Command class provides an automated way to add a command to a server through
* Command class provides an automated way to add a command to a server through //! AcediaCore's features. It takes care of:
* AcediaCore's features. It takes care of: //!
* //! 1. Verifying that player has passed correct (expected parameters);
* 1. Verifying that player has passed correct (expected parameters); //! 2. Parsing these parameters into usable values (both standard, built-in
* 2. Parsing these parameters into usable values (both standard, built-in //! types like `bool`, `int`, `float`, etc. and more advanced types such
* types like `bool`, `int`, `float`, etc. and more advanced types such //! as players lists and JSON values);
* as players lists and JSON values); //! 3. Allowing you to easily specify a set of players you are targeting by
* 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*
* supporting several ways to refer to them, such as *by name*, *by id* //! and *by selector* (@ and @self refer to caller player, @all refers
* and *by selector* (@ and @self refer to caller player, @all refers //! to all players).
* to all players). //! 4. It can be registered inside AcediaCore's commands feature and be
* 4. It can be registered inside AcediaCore's commands feature and be //! automatically called through the unified system that supports *chat*
* automatically called through the unified system that supports *chat* //! and *mutate* inputs (as well as allowing you to hook in any other
* and *mutate* inputs (as well as allowing you to hook in any other //! input source);
* input source); //! 5. Will also automatically provide a help page through built-in "help"
* 5. Will also automatically provide a help page through built-in "help" //! command;
* command; //! 6. Subcommand support - when one command can have several distinct
* 6. Subcommand support - when one command can have several distinct //! functions, depending on how its called (e.g. "inventory add" vs
* functions, depending on how its called (e.g. "inventory add" vs //! "inventory remove"). These subcommands have a special treatment in
* "inventory remove"). These subcommands have a special treatment in //! help pages, which makes them more preferable, compared to simply
* help pages, which makes them more preferable, compared to simply //! matching first `Text` argument;
* matching first `Text` argument; //! 7. Add support for "options" - additional flags that can modify commands
* 7. Add support for "options" - additional flags that can modify commands //! behavior and behave like usual command options "--force"/"-f".
* behavior and behave like usual command options "--force"/"-f". //! Their short versions can even be combined:
* Their short versions can even be combined: //! "give@ $ebr --ammo --force" can be rewritten as "give@ $ebr -af".
* "give@ $ebr --ammo --force" can be rewritten as "give@ $ebr -af". //! And they can have their own parameters: "give@all --list sharp".
* And they can have their own parameters: "give@all --list sharp". //!
* //! # Implementation
* ## Usage //!
* //! The idea of `Command`'s implementation is simple: command is basically the `Command.Data` struct
* To create a custom command you need to simply: //! that is filled via `CommandDataBuilder`.
* //! Whenever command is called it uses `CommandParser` to parse user's input based on its
* 1. Create a custom command class derived from `Command`; //! `Command.Data` and either report error (in case of failure) or pass make
* 2. Define `BuildData()` function and use given `CommandDataBuilder` to //! `Executed()`/`ExecutedFor()` calls (in case of success).
* fill-in data about what parameters your command takes. You can also //!
* add optional descriptions that would appear in your command's //! When command is called is decided by `Commands_Feature` that tracks possible user inputs
* help page. //! (and provides `HandleInput()`/`HandleInputWith()` methods for adding custom command inputs).
* 3. Overload `Executed()` or `ExecutedFor()` (or both) method and add //! That feature basically parses first part of the command: its name (not the subcommand's names)
* whatever logic you want to execute once your command was called. //! and target players (using `PlayersParser`, but only if command is targeted).
* All parameters and options will be listed inside passed `CallData` //!
* parameter. These methods will only be called if all necessary //! Majority of the command-related code either serves to build `Command.Data` or to parse command
* parameters were correctly specified. //! input by using it (`CommandParser`).
*
* ## Implementation /// Possible errors that can arise when parsing command parameters from user input
* enum ErrorType {
* The idea of `Command`'s implementation is simple: command is basically // No error
* 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
*/
enum ErrorType
{
// No error
CET_None, CET_None,
// Bad parser was provided to parse user input // Bad parser was provided to parse user input (this should not be possible)
// (this should not be possible)
CET_BadParser, CET_BadParser,
// Sub-command name was not specified or was incorrect // Sub-command name was not specified or was incorrect
// (this should not be possible) // (this should not be possible)
CET_NoSubCommands, CET_NoSubCommands,
// Specified sub-command does not exist // Specified sub-command does not exist
// (only relevant when it is enforced for parser, e.g. by an alias) // (only relevant when it is enforced for parser, e.g. by an alias)
CET_BadSubCommand, CET_BadSubCommand,
// Required param for command / option was not specified // Required param for command / option was not specified
CET_NoRequiredParam, CET_NoRequiredParam,
CET_NoRequiredParamForOption, CET_NoRequiredParamForOption,
// Unknown option key was specified // Unknown option key was specified
CET_UnknownOption, CET_UnknownOption,
CET_UnknownShortOption, CET_UnknownShortOption,
// Same option appeared twice in one command call // Same option appeared twice in one command call
CET_RepeatedOption, CET_RepeatedOption,
// Part of user's input could not be interpreted as a part of // Part of user's input could not be interpreted as a part of
// command's call // command's call
CET_UnusedCommandParameters, CET_UnusedCommandParameters,
// In one short option specification (e.g. '-lah') several options // In one short option specification (e.g. '-lah') several options require parameters:
// require parameters: this introduces ambiguity and is not allowed // this introduces ambiguity and is not allowed
CET_MultipleOptionsWithParams, CET_MultipleOptionsWithParams,
// (For targeted commands only) // (For targeted commands only)
// Targets are specified incorrectly (or none actually specified) // Targets are specified incorrectly (or none actually specified)
CET_IncorrectTargetList, CET_IncorrectTargetList,
CET_EmptyTargetList CET_EmptyTargetList
}; };
/** /// Structure that contains all the information about how `Command` was called.
* Structure that contains all the information about how `Command` was called. struct CallData {
*/ // Targeted players (if applicable)
struct CallData var public array<EPlayer> targetPlayers;
{ // Specified sub-command and parameters/options
// Targeted players (if applicable) var public Text subCommandName;
var public array<EPlayer> targetPlayers; // Provided parameters and specified options
// Specified sub-command and parameters/options var public HashTable parameters;
var public Text subCommandName; var public HashTable options;
// Provided parameters and specified options // Errors that occurred during command call processing are described by
var public HashTable parameters; // error type and optional error textual name of the object
var public HashTable options; // (parameter, option, etc.) that caused it.
// Errors that occurred during command call processing are described by var public ErrorType parsingError;
// error type and optional error textual name of the object var public Text errorCause;
// (parameter, option, etc.) that caused it.
var public ErrorType parsingError;
var public Text errorCause;
}; };
/** /// Possible types of parameters.
* Possible types of parameters. enum ParameterType {
*/ // Parses into `BoolBox`
enum ParameterType
{
// Parses into `BoolBox`
CPT_Boolean, CPT_Boolean,
// Parses into `IntBox` // Parses into `IntBox`
CPT_Integer, CPT_Integer,
// Parses into `FloatBox` // Parses into `FloatBox`
CPT_Number, CPT_Number,
// Parses into `Text` // Parses into `Text`
CPT_Text, CPT_Text,
// Special parameter that consumes the rest of the input into `Text` // Special parameter that consumes the rest of the input into `Text`
CPT_Remainder, CPT_Remainder,
// Parses into `HashTable` // Parses into `HashTable`
CPT_Object, CPT_Object,
// Parses into `ArrayList` // Parses into `ArrayList`
CPT_Array, CPT_Array,
// Parses into any JSON value // Parses into any JSON value
CPT_JSON, CPT_JSON,
// Parses into an array of specified players // Parses into an array of specified players
CPT_Players CPT_Players
}; };
/** /// Possible forms a boolean variable can be used as.
* 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.
* Boolean parameter can define it's preferred format, which will be used enum PreferredBooleanFormat {
* for help page generation.
*/
enum PreferredBooleanFormat
{
PBF_TrueFalse, PBF_TrueFalse,
PBF_EnableDisable, PBF_EnableDisable,
PBF_OnOff, PBF_OnOff,
PBF_YesNo PBF_YesNo
}; };
// Defines a singular command parameter // Defines a singular command parameter
struct Parameter struct Parameter {
{ // Display name (for the needs of help page displaying)
// Display name (for the needs of help page displaying) var Text displayName;
var Text displayName; // Type of value this parameter would store
// Type of value this parameter would store var ParameterType type;
var ParameterType type; // Does it take only a singular value or can it contain several of them,
// Does it take only a singular value or can it contain several of them, // written in a list
// written in a list var bool allowsList;
var bool allowsList; // Variable name that will be used as a key to store parameter's value
// Variable name that will be used as a key to store parameter's value var Text variableName;
var Text variableName; // (For `CPT_Boolean` type variables only) - preferred boolean format,
// (For `CPT_Boolean` type variables only) - preferred boolean format, // used in help pages
// used in help pages
var PreferredBooleanFormat booleanFormat; var PreferredBooleanFormat booleanFormat;
// `CPT_Text` can be attempted to be auto-resolved as an alias from // `CPT_Text` can be attempted to be auto-resolved as an alias from some source during parsing.
/// some source during parsing. For command to attempt that, this field must // For command to attempt that, this field must be not-`none` and contain the name of
// be not-`none` and contain the name of the alias source (either "weapon", // the alias source (either "weapon", "color", "feature", "entity" or some kind of custom alias
// "color", "feature", "entity" or some kind of custom alias source name). // source name).
// Only relevant when given value is prefixed with "$" character. //
var Text aliasSourceName; // Only relevant when given value is prefixed with "$" character.
var Text aliasSourceName;
}; };
// Defines a sub-command of a this command (specified as // Defines a sub-command of a this command (specified as "<command> <sub_command>").
// "<command> <sub_command>"). //
// Using sub-command is not optional, but if none defined // Using sub-command is not optional, but if none defined (in `BuildData()`) / specified by
// (in `BuildData()`) / specified by the player - an empty (`name.IsEmpty()`) // the player - an empty (`name.IsEmpty()`) one is automatically created / used.
// one is automatically created / used. struct SubCommand {
struct SubCommand // Cannot be `none`
{ var Text name;
// Cannot be `none` // Can be `none`
var Text name;
// Can be `none`
var Text description; var Text description;
var array<Parameter> required; var array<Parameter> required;
var array<Parameter> optional; var array<Parameter> optional;
}; };
// Defines command's option (options are specified by "--long" or "-l"). // Defines command's option (options are specified by "--long" or "-l").
// Options are independent from sub-commands. // Options are independent from sub-commands.
struct Option struct Option {
{ var BaseText.Character shortName;
var BaseText.Character shortName; var Text longName;
var Text longName; var Text description;
var Text description; // Option can also have their own parameters
// Option can also have their own parameters var array<Parameter> required;
var array<Parameter> required; var array<Parameter> optional;
var array<Parameter> optional;
}; };
// Structure that defines what sub-commands and options command has // Structure that defines what sub-commands and options command has
// (and what parameters they take) // (and what parameters they take)
struct Data struct Data {
{ // Default command name that will be used unless Acedia is configured to
// Default command name that will be used unless Acedia is configured to // do otherwise
// do otherwise var protected Text name;
var protected Text name; // Command group this command belongs to
// Command group this command belongs to var protected Text group;
var protected Text group; // Short summary of what command does (recommended to
// Short summary of what command does (recommended to // keep it to 80 characters)
// keep it to 80 characters) var protected Text summary;
var protected Text summary;
var protected array<SubCommand> subCommands; var protected array<SubCommand> subCommands;
var protected array<Option> options; var protected array<Option> options;
var protected bool requiresTarget; var protected bool requiresTarget;
}; };
var private Data commandData; var private Data commandData;
// We do not really ever need to create more than one instance of each class // 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. // of `Command`, so we will simply store and reuse one created instance.
var private Command mainInstance; var private Command mainInstance;
/**
* When command is being executed we create several instances of // When command is being executed we create several instances of `ConsoleWriter` that can be used
* `ConsoleWriter` that can be used for command output. They will also be // for command output. They will also be automatically deallocated once command is executed.
* automatically deallocated once command is executed. //
* DO NOT modify them or deallocate any of them manually. // DO NOT modify them or deallocate any of them manually.
* This should make output more convenient and standardized. //
* // This should make output more convenient and standardized.
* 1. `publicConsole` - sends messages to all present players; //
* 2. `callerConsole` - sends messages to the player that // 1. `publicConsole` - sends messages to all present players;
* called the command; // 2. `callerConsole` - sends messages to the player that called the command;
* 3. `targetConsole` - sends messages to the player that is currently // 3. `targetConsole` - sends messages to the player that is currently being targeted
* being targeted (different each call of `ExecutedFor()` and // (different each call of `ExecutedFor()` and `none` during `Executed()` call);
* `none` during `Executed()` call); // 4. `othersConsole` - sends messaged to every player that is neither "caller" or "target".
* 4. `othersConsole` - sends messaged to every player that is
* neither "caller" or "target".
*/
var protected ConsoleWriter publicConsole, othersConsole; var protected ConsoleWriter publicConsole, othersConsole;
var protected ConsoleWriter callerConsole, targetConsole; var protected ConsoleWriter callerConsole, targetConsole;
protected function Constructor() protected function Constructor() {
{
local CommandDataBuilder dataBuilder; local CommandDataBuilder dataBuilder;
dataBuilder = dataBuilder = CommandDataBuilder(_.memory.Allocate(class'CommandDataBuilder'));
CommandDataBuilder(_.memory.Allocate(class'CommandDataBuilder'));
BuildData(dataBuilder); BuildData(dataBuilder);
commandData = dataBuilder.BorrowData(); commandData = dataBuilder.BorrowData();
dataBuilder.FreeSelf(); dataBuilder.FreeSelf();
dataBuilder = none; dataBuilder = none;
} }
protected function Finalizer() protected function Finalizer() {
{ local int i;
local int i;
local array<SubCommand> subCommands; local array<SubCommand> subCommands;
local array<Option> options; local array<Option> options;
DeallocateConsoles(); DeallocateConsoles();
_.memory.Free(commandData.name); _.memory.Free(commandData.name);
_.memory.Free(commandData.summary); _.memory.Free(commandData.summary);
subCommands = commandData.subCommands; subCommands = commandData.subCommands;
for (i = 0; i < options.length; i += 1) for (i = 0; i < options.length; i += 1) {
{
_.memory.Free(subCommands[i].name); _.memory.Free(subCommands[i].name);
_.memory.Free(subCommands[i].description); _.memory.Free(subCommands[i].description);
CleanParameters(subCommands[i].required); CleanParameters(subCommands[i].required);
@ -306,8 +259,7 @@ protected function Finalizer()
} }
commandData.subCommands.length = 0; commandData.subCommands.length = 0;
options = commandData.options; options = commandData.options;
for (i = 0; i < options.length; i += 1) for (i = 0; i < options.length; i += 1) {
{
_.memory.Free(options[i].longName); _.memory.Free(options[i].longName);
_.memory.Free(options[i].description); _.memory.Free(options[i].description);
CleanParameters(options[i].required); CleanParameters(options[i].required);
@ -318,115 +270,88 @@ protected function Finalizer()
commandData.options.length = 0; commandData.options.length = 0;
} }
private final function CleanParameters(array<Parameter> parameters) private final function CleanParameters(array<Parameter> parameters) {
{
local int i; local int i;
for (i = 0; i < parameters.length; i += 1) for (i = 0; i < parameters.length; i += 1) {
{
_.memory.Free(parameters[i].displayName); _.memory.Free(parameters[i].displayName);
_.memory.Free(parameters[i].variableName); _.memory.Free(parameters[i].variableName);
_.memory.Free(parameters[i].aliasSourceName); _.memory.Free(parameters[i].aliasSourceName);
} }
} }
/** /// Overload this method to use `builder` to define parameters and options for your command.
* 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){} protected function BuildData(CommandDataBuilder builder){}
/** /// Overload this method to perform required actions when your command is called.
* Overload this method to perform required actions when ///
* your command is called. /// [`arguments`] is a `struct` filled with parameters that your command has been called with.
* /// Guaranteed to not be in error state.
* @param arguments `struct` filled with parameters that your command ///
* has been called with. Guaranteed to not be in error state. /// [`instigator`] is a player that instigated this execution.
* @param instigator Player that instigated this execution.
*/
protected function Executed(CallData arguments, EPlayer instigator){} protected function Executed(CallData arguments, EPlayer instigator){}
/** /// Overload this method to perform required actions when your command is called with a given player
* Overload this method to perform required actions when your command is called /// as a target.
* with a given player as a target. If several players have been specified - ///
* this method will be called once for each. /// 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. /// If your command does not require a target - this method will not be called.
* ///
* @param target Player that this command must perform an action on. /// [`target`] is a player that this command must perform an action on.
* @param arguments `struct` filled with parameters that your command /// [`arguments`] is a `struct` filled with parameters that your command has been called with.
* has been called with. Guaranteed to not be in error state and contain /// Guaranteed to not be in error state.
* all the required data. ///
* @param instigator Player that instigated this call. /// [`instigator`] is a player that instigated this execution.
*/ protected function ExecutedFor(EPlayer target, CallData arguments, EPlayer instigator) {}
protected function ExecutedFor(
EPlayer target, /// Returns an instance of command (of particular class) that is stored "as a singleton" in
CallData arguments, /// command's class itself. Do not deallocate it.
EPlayer instigator){} public final static function Command GetInstance() {
/**
* 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) { if (default.mainInstance == none) {
default.mainInstance = Command(__().memory.Allocate(default.class)); default.mainInstance = Command(__().memory.Allocate(default.class));
} }
return default.mainInstance; return default.mainInstance;
} }
/** /// Forces command to process (parse) player's input, producing a structure with parsed data in
* Forces command to process (parse) player's input, producing a structure /// Acedia's format instead.
* with parsed data in Acedia's format instead. ///
* /// Use `Execute()` for actually performing command's actions.
* @see `Execute()` for actually performing command's actions. ///
* /// [`subCommandName`] can be optionally specified to use as sub-command.
* @param parser Parser that contains command input. /// If this argument's value is `none` - sub-command name will be parsed from the `parser`'s data.
* @param callerPlayer Player that initiated this command's call, ///
* necessary for parsing player list (since it can point at /// Returns `CallData` structure that contains all the information about parameters specified in
* the caller player). /// `parser`'s contents.
* @param subCommandName This method can optionally specify sub-command to /// Returned structure contains objects that must be deallocated, which can easily be done by
* caller command to use. If this argument's value is `none` - sub-command /// the auxiliary `DeallocateCallData()` method.
* name will be parsed from the `parser`'s data.
* @return `CallData` structure that contains all the information about
* parameters specified in `parser`'s contents.
* Returned structure contains objects that must be deallocated,
* which can easily be done by the auxiliary `DeallocateCallData()` method.
*/
public final function CallData ParseInputWith( public final function CallData ParseInputWith(
Parser parser, Parser parser,
EPlayer callerPlayer, EPlayer callerPlayer,
optional BaseText subCommandName) optional BaseText subCommandName
{ ) {
local array<EPlayer> targetPlayers; local array<EPlayer> targetPlayers;
local CommandParser commandParser; local CommandParser commandParser;
local CallData callData; local CallData callData;
if (parser == none || !parser.Ok()) if (parser == none || !parser.Ok()) {
{
callData.parsingError = CET_BadParser; callData.parsingError = CET_BadParser;
return callData; return callData;
} }
// Parse targets and handle errors that can arise here // Parse targets and handle errors that can arise here
if (commandData.requiresTarget) if (commandData.requiresTarget) {
{
targetPlayers = ParseTargets(parser, callerPlayer); targetPlayers = ParseTargets(parser, callerPlayer);
if (!parser.Ok()) if (!parser.Ok()) {
{
callData.parsingError = CET_IncorrectTargetList; callData.parsingError = CET_IncorrectTargetList;
return callData; return callData;
} }
if (targetPlayers.length <= 0) if (targetPlayers.length <= 0) {
{
callData.parsingError = CET_EmptyTargetList; callData.parsingError = CET_EmptyTargetList;
return callData; return callData;
} }
} }
// Parse parameters themselves // Parse parameters themselves
commandParser = CommandParser(_.memory.Allocate(class'CommandParser')); commandParser = CommandParser(_.memory.Allocate(class'CommandParser'));
callData = commandParser.ParseWith( callData = commandParser.ParseWith(
parser, parser,
@ -438,33 +363,24 @@ public final function CallData ParseInputWith(
return callData; return callData;
} }
/** /// Executes caller `Command` with data provided by `callData` if it is in a correct state and
* Executes caller `Command` with data provided by `callData` if it is in /// reports error to `callerPlayer` if `callData` is invalid.
* a correct state and reports error to `callerPlayer` if ///
* `callData` is invalid. /// Returns `true` if command was successfully executed and `false` otherwise.
* /// Execution is considered successful if `Execute()` call was made, regardless of whether `Command`
* @param callData Data about parameters, options, etc. with which /// can actually perform required action.
* caller `Command` is to be executed. /// For example, giving a weapon to a player can fail because he does not have enough space in his
* @param callerPlayer Player that should be considered responsible for /// inventory, but it will still be considered a successful execution as far as return value is
* executing this `Command`. /// concerned.
* @return `true` if command was successfully executed and `false` otherwise. public final function bool Execute(CallData callData, EPlayer callerPlayer) {
* Execution is considered successful if `Execute()` call was made, local int i;
* regardless of whether `Command` can actually perform required action. local array<EPlayer> targetPlayers;
* For example, giving a weapon to a player can fail because he does not
* have enough space in his inventory, but it will still be considered
* a successful execution as far as return value is concerned.
*/
public final function bool Execute(CallData callData, EPlayer callerPlayer)
{
local int i;
local array<EPlayer> targetPlayers;
if (callerPlayer == none) return false; if (callerPlayer == none) return false;
if (!callerPlayer.IsExistent()) return false; if (!callerPlayer.IsExistent()) return false;
// Report or execute // Report or execute
if (callData.parsingError != CET_None) if (callData.parsingError != CET_None) {
{
ReportError(callData, callerPlayer); ReportError(callData, callerPlayer);
return false; return false;
} }
@ -475,14 +391,12 @@ public final function bool Execute(CallData callData, EPlayer callerPlayer)
.Write(P("Executing command `")) .Write(P("Executing command `"))
.Write(commandData.name) .Write(commandData.name)
.Say(P("`")); .Say(P("`"));
// `othersConsole` should also exist in time for `Executed()` call // `othersConsole` should also exist in time for `Executed()` call
othersConsole = _.console.ForAll().ButPlayer(callerPlayer); othersConsole = _.console.ForAll().ButPlayer(callerPlayer);
Executed(callData, callerPlayer); Executed(callData, callerPlayer);
_.memory.Free(othersConsole); _.memory.Free(othersConsole);
if (commandData.requiresTarget) if (commandData.requiresTarget) {
{ for (i = 0; i < targetPlayers.length; i += 1) {
for (i = 0; i < targetPlayers.length; i += 1)
{
targetConsole = _.console.For(targetPlayers[i]); targetConsole = _.console.For(targetPlayers[i]);
othersConsole = _.console othersConsole = _.console
.ForAll() .ForAll()
@ -499,8 +413,7 @@ public final function bool Execute(CallData callData, EPlayer callerPlayer)
return true; return true;
} }
private final function DeallocateConsoles() private final function DeallocateConsoles() {
{
if (publicConsole != none && publicConsole.IsAllocated()) { if (publicConsole != none && publicConsole.IsAllocated()) {
_.memory.Free(publicConsole); _.memory.Free(publicConsole);
} }
@ -519,16 +432,8 @@ private final function DeallocateConsoles()
othersConsole = none; othersConsole = none;
} }
/** /// Auxiliary method that cleans up all data and deallocates all objects inside provided structure.
* Auxiliary method that cleans up all data and deallocates all objects inside public final static function DeallocateCallData(/* take */ CallData callData) {
* provided `callData` structure.
*
* @param callData Structure to clean. All stored data will be cleared,
* meaning that `DeallocateCallData()` method takes ownership of
* this parameter.
*/
public final static function DeallocateCallData(/* take */ CallData callData)
{
__().memory.Free(callData.subCommandName); __().memory.Free(callData.subCommandName);
__().memory.Free(callData.parameters); __().memory.Free(callData.parameters);
__().memory.Free(callData.options); __().memory.Free(callData.options);
@ -539,100 +444,100 @@ public final static function DeallocateCallData(/* take */ CallData callData)
} }
} }
// Reports given error to the `callerPlayer`, appropriately picking // Reports given error to the `callerPlayer`, appropriately picking
// message color // message color
private final function ReportError(CallData callData, EPlayer callerPlayer) private final function ReportError(CallData callData, EPlayer callerPlayer) {
{ local Text errorMessage;
local Text errorMessage;
local ConsoleWriter console; local ConsoleWriter console;
if (callerPlayer == none) return; if (callerPlayer == none) return;
if (!callerPlayer.IsExistent()) return; if (!callerPlayer.IsExistent()) return;
// Setup console color // Setup console color
console = callerPlayer.BorrowConsole(); console = callerPlayer.BorrowConsole();
if (callData.parsingError == CET_EmptyTargetList) { if (callData.parsingError == CET_EmptyTargetList) {
console.UseColor(_.color.textWarning); console.UseColor(_.color.textWarning);
} } else {
else {
console.UseColor(_.color.textFailure); console.UseColor(_.color.textFailure);
} }
// Send message // Send message
errorMessage = PrintErrorMessage(callData); errorMessage = PrintErrorMessage(callData);
console.Say(errorMessage); console.Say(errorMessage);
errorMessage.FreeSelf(); errorMessage.FreeSelf();
// Restore console color // Restore console color
console.ResetColor().Flush(); console.ResetColor().Flush();
} }
private final function Text PrintErrorMessage(CallData callData) private final function Text PrintErrorMessage(CallData callData) {
{ local Text result;
local Text result; local MutableText builder;
local MutableText builder;
builder = _.text.Empty(); builder = _.text.Empty();
switch (callData.parsingError) switch (callData.parsingError) {
{ case CET_BadParser:
case CET_BadParser: builder.Append(P("Internal error occurred: invalid parser"));
builder.Append(P("Internal error occurred: invalid parser")); break;
break; case CET_NoSubCommands:
case CET_NoSubCommands: builder.Append(P("Ill defined command: no subcommands"));
builder.Append(P("Ill defined command: no subcommands")); break;
break; case CET_BadSubCommand:
case CET_BadSubCommand: builder
builder.Append(P("Ill defined sub-command: ")) .Append(P("Ill defined sub-command: "))
.Append(callData.errorCause); .Append(callData.errorCause);
break; break;
case CET_NoRequiredParam: case CET_NoRequiredParam:
builder.Append(P("Missing required parameter: ")) builder
.Append(callData.errorCause); .Append(P("Missing required parameter: "))
break; .Append(callData.errorCause);
case CET_NoRequiredParamForOption: break;
builder.Append(P("Missing required parameter for option: ")) case CET_NoRequiredParamForOption:
.Append(callData.errorCause); builder
break; .Append(P("Missing required parameter for option: "))
case CET_UnknownOption: .Append(callData.errorCause);
builder.Append(P("Invalid option specified: ")) break;
.Append(callData.errorCause); case CET_UnknownOption:
break; builder
case CET_UnknownShortOption: .Append(P("Invalid option specified: "))
builder.Append(P("Invalid short option specified")); .Append(callData.errorCause);
break; break;
case CET_RepeatedOption: case CET_UnknownShortOption:
builder.Append(P("Option specified several times: ")) builder.Append(P("Invalid short option specified"));
.Append(callData.errorCause); break;
break; case CET_RepeatedOption:
case CET_UnusedCommandParameters: builder
builder.Append(P("Part of command could not be parsed: ")) .Append(P("Option specified several times: "))
.Append(callData.errorCause); .Append(callData.errorCause);
break; break;
case CET_MultipleOptionsWithParams: case CET_UnusedCommandParameters:
builder.Append(P( "Multiple short options in one declarations" builder.Append(P("Part of command could not be parsed: "))
@ "require parameters: ")) .Append(callData.errorCause);
.Append(callData.errorCause); break;
break; case CET_MultipleOptionsWithParams:
case CET_IncorrectTargetList: builder
builder.Append(P("Target players are incorrectly specified.")) .Append(P("Multiple short options in one declarations require parameters: "))
.Append(callData.errorCause); .Append(callData.errorCause);
break; break;
case CET_EmptyTargetList: case CET_IncorrectTargetList:
builder.Append(P("List of target players is empty")) builder
.Append(callData.errorCause); .Append(P("Target players are incorrectly specified."))
break; .Append(callData.errorCause);
default: break;
case CET_EmptyTargetList:
builder
.Append(P("List of target players is empty"))
.Append(callData.errorCause);
break;
default:
} }
result = builder.Copy(); result = builder.Copy();
builder.FreeSelf(); builder.FreeSelf();
return result; return result;
} }
// Auxiliary method for parsing list of targeted players. // Auxiliary method for parsing list of targeted players.
// Assumes given parser is not `none` and not in a failed state. // Assumes given parser is not `none` and not in a failed state.
// If parsing failed, guaranteed to return an empty array. // If parsing failed, guaranteed to return an empty array.
private final function array<EPlayer> ParseTargets( private final function array<EPlayer> ParseTargets(Parser parser, EPlayer callerPlayer) {
Parser parser,
EPlayer callerPlayer)
{
local array<EPlayer> targetPlayers; local array<EPlayer> targetPlayers;
local PlayersParser targetsParser; local PlayersParser targetsParser;
@ -646,52 +551,35 @@ private final function array<EPlayer> ParseTargets(
return targetPlayers; return targetPlayers;
} }
/** /// Returns name (in lower case) of the caller command class.
* Returns name (in lower case) of the caller command class. public final function Text GetName() {
*
* @return Name (in lower case) of the caller command class.
* Guaranteed to be not `none`.
*/
public final function Text GetName()
{
if (commandData.name == none) { if (commandData.name == none) {
return P("").Copy(); return P("").Copy();
} }
return commandData.name.LowerCopy(); return commandData.name.LowerCopy();
} }
/** /// Returns group name (in lower case) of the caller command class.
* Returns group name (in lower case) of the caller command class. public final function Text GetGroupName() {
*
* @return Group name (in lower case) of the caller command class.
* Guaranteed to be not `none`.
*/
public final function Text GetGroupName()
{
if (commandData.group == none) { if (commandData.group == none) {
return P("").Copy(); return P("").Copy();
} }
return commandData.group.LowerCopy(); return commandData.group.LowerCopy();
} }
/** /// Returns `Command.Data` struct that describes caller `Command`.
* Returns `Command.Data` struct that describes caller `Command`. ///
* /// Returned struct contains `Text` references that are used internally by the `Command` and
* @return `Command.Data` that describes caller `Command`. Returned struct /// not their copies.
* 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,
* Generally this is undesired approach and leaves `Command` more /// but copying all the data inside would not only introduce a largely pointless computational
* vulnerable to modification, but copying all the data inside would not /// overhead, but also would require some cumbersome logic.
* only introduce a largely pointless computational overhead, but also /// This might change in the future, so deallocating any objects in the returned `struct` would lead
* would require some cumbersome logic. This might change in the future, /// to undefined behavior.
* so deallocating any objects in the returned `struct` would lead to public final function Data BorrowData() {
* undefined behavior.
*/
public final function Data BorrowData()
{
return commandData; return commandData;
} }
defaultproperties defaultproperties {
{
} }

444
sources/BaseAPI/API/Commands/PlayersParser.uc

@ -1,7 +1,8 @@
/** /**
* Object for parsing what converting textual description of a group of * Author: dkanus
* players into array of `EPlayer`s. Depends on the game context. * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* Copyright 2021-2022 Anton Tarasenko * License: GPL
* Copyright 2021-2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -9,7 +10,7 @@
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or * the Free Software Foundation, version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *\
* Acedia is distributed in the hope that it will be useful, * Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@ -21,107 +22,95 @@
class PlayersParser extends AcediaObject class PlayersParser extends AcediaObject
dependson(Parser); dependson(Parser);
/** //! This parser is supposed to parse player set definitions as they
* # `PlayersParser` //! are used in commands.
* //!
* This parser is supposed to parse player set definitions as they //! Basic use is to specify one of the selectors:
* are used in commands. //! 1. Key selector: "#<integer>" (examples: "#1", "#5").
* Basic use is to specify one of the selectors: //! This one is used to specify players by their key, assigned to them when they enter the game.
* 1. Key selector: "#<integer>" (examples: "#1", "#5"). //! This type of selectors can be used when players have hard to type names.
* This one is used to specify players by their key, assigned to //! 2. Macro selector: "@self", "@me", "@all", "@admin" or just "@".
* them when they enter the game. This type of selectors can be used //! "@", "@me", and "@self" are identical and can be used to specify player that called
* when players have hard to type names. //! the command.
* 2. Macro selector: "@self", "@me", "@all", "@admin" or just "@". //! "@admin" can be used to specify all admins in the game at once.
* "@", "@me", and "@self" are identical and can be used to //! "@all" specifies all current players.
* specify player that called the command. //! In future it is planned to make macros extendable by allowing to bind more names to specific
* "@admin" can be used to specify all admins in the game at once. //! groups of players.
* "@all" specifies all current players. //! 3. Name selectors: quoted strings and any other types of string that do not start with
* In future it is planned to make macros extendable by allowing to //! either "#" or "@".
* bind more names to specific groups of players. //! These specify name prefixes: any player with specified prefix will be considered to match
* 3. Name selectors: quoted strings and any other types of string that //! such selector.
* do not start with either "#" or "@". //!
* These specify name prefixes: any player with specified prefix //! Negated selectors: "!<selector>". Specifying "!" in front of selector will select all players
* will be considered to match such selector. //! that do not match it instead.
* //!
* Negated selectors: "!<selector>". Specifying "!" in front of selector //! Grouped selectors: "['<selector1>', '<selector2>', ... '<selectorN>']".
* will select all players that do not match it instead. //! Specified selectors are process in order: from left to right.
* //! First selector works as usual and selects a set of players.
* Grouped selectors: "['<selector1>', '<selector2>', ... '<selectorN>']". //! All the following selectors either expand that list (additive ones, without "!" prefix) or
* Specified selectors are process in order: from left to right. //! remove specific players from the list (the ones with "!" prefix).
* First selector works as usual and selects a set of players. //! Examples of that:
* All the following selectors either //!
* expand that list (additive ones, without "!" prefix) //! * "[@admin, !@self]" - selects all admins, except the one who called the command
* or remove specific players from the list (the ones with "!" prefix). //! (whether he is admin or not).
* Examples of that: //! * "[dkanus, 'mate']" - will select players "dkanus" and "mate". Order also matters, since:
* *. "[@admin, !@self]" - selects all admins, except the one who called //! * "[@admin, !@admin]" - won't select anyone, since it will first add all the admins and
* the command (whether he is admin or not). //! then remove them.
* *. "[dkanus, 'mate']" - will select players "dkanus" and "mate". //! * "[!@admin, @admin]" - will select everyone, since it will first select everyone who is
* Order also matters, since: //! not an admin and then adds everyone else.
* *. "[@admin, !@admin]" - won't select anyone, since it will first //!
* add all the admins and then remove them. //! # Usage
* *. "[!@admin, @admin]" - will select everyone, since it will first //!
* select everyone who is not an admin and then adds everyone else. //! 1. Allocate `PlayerParser`;
* //! 2. Set caller player through `SetSelf()` method to make "@" and "@me" selectors usable;
* ## Usage //! 3. Call `Parse()` or `ParseWith()` method with `BaseText`/`Parser` that starts with proper
* //! players selector;
* 1. Allocate `PlayerParser`; //! 4. Call `GetPlayers()` to obtain selected players array.
* 2. Set caller player through `SetSelf()` method to make "@" and "@me" //!
* selectors usable; //! # Implementation
* 3. Call `Parse()` or `ParseWith()` method with `BaseText`/`Parser` that //!
* starts with proper players selector; //! When created, `PlayersParser` takes a snapshot (array) of current players on the server.
* 4. Call `GetPlayers()` to obtain selected players array. //! Then `currentSelection` is decided based on whether first selector is positive
* //! (initial selection is taken as empty array) or negative
* ## Implementation //! (initial selection is taken as full snapshot).
* //!
* When created, `PlayersParser` takes a snapshot (array) of current //! After that `PlayersParser` simply goes through specified selectors
* players on the server. Then `currentSelection` is decided based on whether //! (in case more than one is specified) and adds or removes appropriate players in
* first selector is positive (initial selection is taken as empty array) or //! `currentSelection`, assuming that `playersSnapshot` is a current full array of players.
* negative (initial selection is taken as full snapshot).
* After that `PlayersParser` simply goes through specified selectors // Player for which "@", "@me", and "@self" macros will refer
* (in case more than one is specified) and adds or removes appropriate players
* in `currentSelection`, assuming that `playersSnapshot` is a current full
* array of players.
*/
// Player for which "@", "@me", and "@self" macros will refer
var private EPlayer selfPlayer; var private EPlayer selfPlayer;
// Copy of the list of current players at the moment of allocation of // Copy of the list of current players at the moment of allocation of
// this `PlayersParser`. // this `PlayersParser`.
var private array<EPlayer> playersSnapshot; var private array<EPlayer> playersSnapshot;
// Players, selected according to selectors we have parsed so far // Players, selected according to selectors we have parsed so far
var private array<EPlayer> currentSelection; var private array<EPlayer> currentSelection;
// Have we parsed our first selector? // Have we parsed our first selector?
// We need this to know whether to start with the list of // We need this to know whether to start with the list of
// all players (if first selector removes them) or // all players (if first selector removes them) or
// with empty list (if first selector adds them). // with empty list (if first selector adds them).
var private bool parsedFirstSelector; var private bool parsedFirstSelector;
// Will be equal to a single-element array [","], used for parsing // Will be equal to a single-element array [","], used for parsing
var private array<Text> selectorDelimiters; var private array<Text> selectorDelimiters;
var const int TSELF, TME, TADMIN, TALL, TNOT, TKEY, TMACRO, TCOMMA; var const int TSELF, TME, TADMIN, TALL, TNOT, TKEY, TMACRO, TCOMMA;
var const int TOPEN_BRACKET, TCLOSE_BRACKET; var const int TOPEN_BRACKET, TCLOSE_BRACKET;
protected function Finalizer() protected function Finalizer() {
{ // No need to deallocate `currentSelection`,
// No need to deallocate `currentSelection`, // since it has `EPlayer`s from `playersSnapshot` or `selfPlayer`
// since it has `EPlayer`s from `playersSnapshot` or `selfPlayer`
_.memory.Free(selfPlayer); _.memory.Free(selfPlayer);
_.memory.FreeMany(playersSnapshot); _.memory.FreeMany(playersSnapshot);
selfPlayer = none; selfPlayer = none;
parsedFirstSelector = false; parsedFirstSelector = false;
playersSnapshot.length = 0; playersSnapshot.length = 0;
currentSelection.length = 0; currentSelection.length = 0;
} }
/** /// Set a player who will be referred to by "@", "@me" and "@self" macros.
* Set a player who will be referred to by "@", "@me" and "@self" macros. ///
* /// Passing `none` will make it so no one is referred by these macros.
* @param newSelfPlayer Player who will be referred to by "@", "@me" and public final function SetSelf(EPlayer newSelfPlayer) {
* "@self" macros. Passing `none` will make it so no one is
* referred by them.
*/
public final function SetSelf(EPlayer newSelfPlayer)
{
_.memory.Free(selfPlayer); _.memory.Free(selfPlayer);
selfPlayer = none; selfPlayer = none;
if (newSelfPlayer != none) { if (newSelfPlayer != none) {
@ -129,18 +118,17 @@ public final function SetSelf(EPlayer newSelfPlayer)
} }
} }
// Insert a new player into currently selected list of players // Insert a new player into currently selected list of players (`currentSelection`) such that there
// (`currentSelection`) such that there will be no duplicates. // will be no duplicates.
// `none` values are auto-discarded. //
private final function InsertPlayer(EPlayer toInsert) // `none` values are auto-discarded.
{ private final function InsertPlayer(EPlayer toInsert) {
local int i; local int i;
if (toInsert == none) { if (toInsert == none) {
return; return;
} }
for (i = 0; i < currentSelection.length; i += 1) for (i = 0; i < currentSelection.length; i += 1) {
{
if (currentSelection[i] == toInsert) { if (currentSelection[i] == toInsert) {
return; return;
} }
@ -148,47 +136,40 @@ private final function InsertPlayer(EPlayer toInsert)
currentSelection[currentSelection.length] = toInsert; currentSelection[currentSelection.length] = toInsert;
} }
// Adds all the players with specified key (`key`) to the current selection. // Adds all the players with specified key (`key`) to the current selection.
private final function AddByKey(int key) private final function AddByKey(int key) {
{
local int i; local int i;
for (i = 0; i < playersSnapshot.length; i += 1) for (i = 0; i < playersSnapshot.length; i += 1) {
{
if (playersSnapshot[i].GetIdentity().GetKey() == key) { if (playersSnapshot[i].GetIdentity().GetKey() == key) {
InsertPlayer(playersSnapshot[i]); InsertPlayer(playersSnapshot[i]);
} }
} }
} }
// Removes all the players with specified key (`key`) from // Removes all the players with specified key (`key`) from
// the current selection. // the current selection.
private final function RemoveByKey(int key) private final function RemoveByKey(int key) {
{
local int i; local int i;
while (i < currentSelection.length) while (i < currentSelection.length) {
{
if (currentSelection[i].GetIdentity().GetKey() == key) { if (currentSelection[i].GetIdentity().GetKey() == key) {
currentSelection.Remove(i, 1); currentSelection.Remove(i, 1);
} } else {
else {
i += 1; i += 1;
} }
} }
} }
// Adds all the players with specified name (`name`) to the current selection. // Adds all the players with specified name (`name`) to the current selection.
private final function AddByName(BaseText name) private final function AddByName(BaseText name) {
{ local int i;
local int i; local Text nextPlayerName;
local Text nextPlayerName;
if (name == none) { if (name == none) {
return; return;
} }
for (i = 0; i < playersSnapshot.length; i += 1) for (i = 0; i < playersSnapshot.length; i += 1) {
{
nextPlayerName = playersSnapshot[i].GetName(); nextPlayerName = playersSnapshot[i].GetName();
if (nextPlayerName.StartsWith(name, SCASE_INSENSITIVE)) { if (nextPlayerName.StartsWith(name, SCASE_INSENSITIVE)) {
InsertPlayer(playersSnapshot[i]); InsertPlayer(playersSnapshot[i]);
@ -197,127 +178,104 @@ private final function AddByName(BaseText name)
} }
} }
// Removes all the players with specified name (`name`) from // Removes all the players with specified name (`name`) from
// the current selection. // the current selection.
private final function RemoveByName(BaseText name) private final function RemoveByName(BaseText name) {
{ local int i;
local int i; local Text nextPlayerName;
local Text nextPlayerName;
while (i < currentSelection.length) while (i < currentSelection.length) {
{
nextPlayerName = currentSelection[i].GetName(); nextPlayerName = currentSelection[i].GetName();
if (nextPlayerName.StartsWith(name, SCASE_INSENSITIVE)) { if (nextPlayerName.StartsWith(name, SCASE_INSENSITIVE)) {
currentSelection.Remove(i, 1); currentSelection.Remove(i, 1);
} } else {
else {
i += 1; i += 1;
} }
nextPlayerName.FreeSelf(); nextPlayerName.FreeSelf();
} }
} }
// Adds all the admins to the current selection. // Adds all the admins to the current selection.
private final function AddAdmins() private final function AddAdmins() {
{
local int i; local int i;
for (i = 0; i < playersSnapshot.length; i += 1) for (i = 0; i < playersSnapshot.length; i += 1) {
{
if (playersSnapshot[i].IsAdmin()) { if (playersSnapshot[i].IsAdmin()) {
InsertPlayer(playersSnapshot[i]); InsertPlayer(playersSnapshot[i]);
} }
} }
} }
// Removes all the admins from the current selection. // Removes all the admins from the current selection.
private final function RemoveAdmins() private final function RemoveAdmins() {
{
local int i; local int i;
while (i < currentSelection.length) while (i < currentSelection.length) {
{
if (currentSelection[i].IsAdmin()) { if (currentSelection[i].IsAdmin()) {
currentSelection.Remove(i, 1); currentSelection.Remove(i, 1);
} } else {
else {
i += 1; i += 1;
} }
} }
} }
// Add all the players specified by `macroText` (from macro "@<macroText>"). // Add all the players specified by `macroText` (from macro "@<macroText>").
// Does nothing if there is no such macro. // Does nothing if there is no such macro.
private final function AddByMacro(BaseText macroText) private final function AddByMacro(BaseText macroText) {
{ if (macroText.Compare(T(TADMIN), SCASE_INSENSITIVE)) {
if (macroText.Compare(T(TADMIN), SCASE_INSENSITIVE))
{
AddAdmins(); AddAdmins();
return; return;
} }
if (macroText.Compare(T(TALL), SCASE_INSENSITIVE)) if (macroText.Compare(T(TALL), SCASE_INSENSITIVE)) {
{
currentSelection = playersSnapshot; currentSelection = playersSnapshot;
return; return;
} }
if ( macroText.IsEmpty() if ( macroText.IsEmpty()
|| macroText.Compare(T(TSELF), SCASE_INSENSITIVE) || macroText.Compare(T(TSELF), SCASE_INSENSITIVE)
|| macroText.Compare(T(TME), SCASE_INSENSITIVE)) || macroText.Compare(T(TME), SCASE_INSENSITIVE)) {
{
InsertPlayer(selfPlayer); InsertPlayer(selfPlayer);
} }
} }
// Removes all the players specified by `macroText` // Removes all the players specified by `macroText` (from macro "@<macroText>").
// (from macro "@<macroText>"). // Does nothing if there is no such macro.
// Does nothing if there is no such macro. private final function RemoveByMacro(BaseText macroText) {
private final function RemoveByMacro(BaseText macroText)
{
local int i; local int i;
if (macroText.Compare(T(TADMIN), SCASE_INSENSITIVE)) if (macroText.Compare(T(TADMIN), SCASE_INSENSITIVE)) {
{
RemoveAdmins(); RemoveAdmins();
return; return;
} }
if (macroText.Compare(T(TALL), SCASE_INSENSITIVE)) if (macroText.Compare(T(TALL), SCASE_INSENSITIVE)) {
{
currentSelection.length = 0; currentSelection.length = 0;
return; return;
} }
if (macroText.IsEmpty() || macroText.Compare(T(TSELF), SCASE_INSENSITIVE)) if (macroText.IsEmpty() || macroText.Compare(T(TSELF), SCASE_INSENSITIVE)) {
{ while (i < currentSelection.length) {
while (i < currentSelection.length)
{
if (currentSelection[i] == selfPlayer) { if (currentSelection[i] == selfPlayer) {
currentSelection.Remove(i, 1); currentSelection.Remove(i, 1);
} } else {
else {
i += 1; i += 1;
} }
} }
} }
} }
// Parses one selector from `parser`, while accordingly modifying current // Parses one selector from `parser`, while accordingly modifying current player selection list.
// player selection list. private final function ParseSelector(Parser parser) {
private final function ParseSelector(Parser parser) local bool additiveSelector;
{ local Parser.ParserState confirmedState;
local bool additiveSelector;
local Parser.ParserState confirmedState;
if (parser == none) return; if (parser == none) return;
if (!parser.Ok()) return; if (!parser.Ok()) return;
confirmedState = parser.GetCurrentState(); confirmedState = parser.GetCurrentState();
if (!parser.Match(T(TNOT)).Ok()) if (!parser.Match(T(TNOT)).Ok()) {
{
additiveSelector = true; additiveSelector = true;
parser.RestoreState(confirmedState); parser.RestoreState(confirmedState);
} }
// Determine whether we stars with empty or full player list // Determine whether we stars with empty or full player list
if (!parsedFirstSelector) if (!parsedFirstSelector) {
{
parsedFirstSelector = true; parsedFirstSelector = true;
if (additiveSelector) { if (additiveSelector) {
currentSelection.length = 0; currentSelection.length = 0;
@ -326,16 +284,14 @@ private final function ParseSelector(Parser parser)
currentSelection = playersSnapshot; currentSelection = playersSnapshot;
} }
} }
// Try all selector types // Try all selector types
confirmedState = parser.GetCurrentState(); confirmedState = parser.GetCurrentState();
if (parser.Match(T(TKEY)).Ok()) if (parser.Match(T(TKEY)).Ok()) {
{
ParseKeySelector(parser, additiveSelector); ParseKeySelector(parser, additiveSelector);
return; return;
} }
parser.RestoreState(confirmedState); parser.RestoreState(confirmedState);
if (parser.Match(T(TMACRO)).Ok()) if (parser.Match(T(TMACRO)).Ok()) {
{
ParseMacroSelector(parser, additiveSelector); ParseMacroSelector(parser, additiveSelector);
return; return;
} }
@ -343,10 +299,9 @@ private final function ParseSelector(Parser parser)
ParseNameSelector(parser, additiveSelector); ParseNameSelector(parser, additiveSelector);
} }
// Parse key selector (assuming "#" is already consumed), while accordingly // Parse key selector (assuming "#" is already consumed), while accordingly modifying current player
// modifying current player selection list. // selection list.
private final function ParseKeySelector(Parser parser, bool additiveSelector) private final function ParseKeySelector(Parser parser, bool additiveSelector) {
{
local int key; local int key;
if (parser == none) return; if (parser == none) return;
@ -355,26 +310,23 @@ private final function ParseKeySelector(Parser parser, bool additiveSelector)
if (additiveSelector) { if (additiveSelector) {
AddByKey(key); AddByKey(key);
} } else {
else {
RemoveByKey(key); RemoveByKey(key);
} }
} }
// Parse macro selector (assuming "@" is already consumed), while accordingly // Parse macro selector (assuming "@" is already consumed), while accordingly modifying current
// modifying current player selection list. // player selection list.
private final function ParseMacroSelector(Parser parser, bool additiveSelector) private final function ParseMacroSelector(Parser parser, bool additiveSelector) {
{ local MutableText macroName;
local MutableText macroName; local Parser.ParserState confirmedState;
local Parser.ParserState confirmedState;
if (parser == none) return; if (parser == none) return;
if (!parser.Ok()) return; if (!parser.Ok()) return;
confirmedState = parser.GetCurrentState(); confirmedState = parser.GetCurrentState();
macroName = ParseLiteral(parser); macroName = ParseLiteral(parser);
if (!parser.Ok()) if (!parser.Ok()) {
{
_.memory.Free(macroName); _.memory.Free(macroName);
return; return;
} }
@ -387,20 +339,17 @@ private final function ParseMacroSelector(Parser parser, bool additiveSelector)
_.memory.Free(macroName); _.memory.Free(macroName);
} }
// Parse name selector, while accordingly modifying current player // Parse name selector, while accordingly modifying current player selection list.
// selection list. private final function ParseNameSelector(Parser parser, bool additiveSelector) {
private final function ParseNameSelector(Parser parser, bool additiveSelector) local MutableText playerName;
{ local Parser.ParserState confirmedState;
local MutableText playerName;
local Parser.ParserState confirmedState;
if (parser == none) return; if (parser == none) return;
if (!parser.Ok()) return; if (!parser.Ok()) return;
confirmedState = parser.GetCurrentState(); confirmedState = parser.GetCurrentState();
playerName = ParseLiteral(parser); playerName = ParseLiteral(parser);
if (!parser.Ok() || playerName.IsEmpty()) if (!parser.Ok() || playerName.IsEmpty()) {
{
_.memory.Free(playerName); _.memory.Free(playerName);
return; return;
} }
@ -413,40 +362,33 @@ private final function ParseNameSelector(Parser parser, bool additiveSelector)
_.memory.Free(playerName); _.memory.Free(playerName);
} }
// Reads a string that can either be a body of name selector // Reads a string that can either be a body of name selector (some player's name prefix) or
// (some player's name prefix) or of a macro selector (what comes after "@"). // of a macro selector (what comes after "@").
// This is different from `parser.MString()` because it also uses //
// "," as a separator. // This is different from `parser.MString()` because it also uses "," as a separator.
private final function MutableText ParseLiteral(Parser parser) private final function MutableText ParseLiteral(Parser parser) {
{ local MutableText literal;
local MutableText literal; local Parser.ParserState confirmedState;
local Parser.ParserState confirmedState;
if (parser == none) return none; if (parser == none) return none;
if (!parser.Ok()) return none; if (!parser.Ok()) return none;
confirmedState = parser.GetCurrentState(); confirmedState = parser.GetCurrentState();
if (!parser.MStringLiteral(literal).Ok()) if (!parser.MStringLiteral(literal).Ok()) {
{
parser.RestoreState(confirmedState); parser.RestoreState(confirmedState);
parser.MUntilMany(literal, selectorDelimiters, true); parser.MUntilMany(literal, selectorDelimiters, true);
} }
return literal; return literal;
} }
/** /// Returns players parsed by the last `ParseWith()` or `Parse()` call.
* Returns players parsed by the last `ParseWith()` or `Parse()` call. ///
* If neither were yet called - returns an empty array. /// If neither were yet called - returns an empty array.
* public final function array<EPlayer> GetPlayers() {
* @return players parsed by the last `ParseWith()` or `Parse()` call. local int i;
*/ local array<EPlayer> result;
public final function array<EPlayer> GetPlayers()
{
local int i;
local array<EPlayer> result;
for (i = 0; i < currentSelection.length; i += 1) for (i = 0; i < currentSelection.length; i += 1) {
{
if (currentSelection[i].IsExistent()) { if (currentSelection[i].IsExistent()) {
result[result.length] = EPlayer(currentSelection[i].Copy()); result[result.length] = EPlayer(currentSelection[i].Copy());
} }
@ -454,17 +396,12 @@ public final function array<EPlayer> GetPlayers()
return result; return result;
} }
/** /// Parses players from `parser` according to the currently present players.
* Parses players from `parser` according to the currently present players. ///
* /// Array of parsed players can be retrieved by `self.GetPlayers()` method.
* Array of parsed players can be retrieved by `self.GetPlayers()` method. ///
* /// Returns `true` if parsing was successful and `false` otherwise.
* @param parser `Parser` from which to parse player list. public final function bool ParseWith(Parser parser) {
* It's state will be set to failed in case the parsing fails.
* @return `true` if parsing was successful and `false` otherwise.
*/
public final function bool ParseWith(Parser parser)
{
local Parser.ParserState confirmedState; local Parser.ParserState confirmedState;
if (parser == none) return false; if (parser == none) return false;
@ -473,16 +410,14 @@ public final function bool ParseWith(Parser parser)
Reset(); Reset();
confirmedState = parser.Skip().GetCurrentState(); confirmedState = parser.Skip().GetCurrentState();
if (!parser.Match(T(TOPEN_BRACKET)).Ok()) if (!parser.Match(T(TOPEN_BRACKET)).Ok()) {
{
ParseSelector(parser.RestoreState(confirmedState)); ParseSelector(parser.RestoreState(confirmedState));
if (parser.Ok()) { if (parser.Ok()) {
return true; return true;
} }
return false; return false;
} }
while (parser.Ok() && !parser.HasFinished()) while (parser.Ok() && !parser.HasFinished()) {
{
confirmedState = parser.Skip().GetCurrentState(); confirmedState = parser.Skip().GetCurrentState();
if (parser.Match(T(TCLOSE_BRACKET)).Ok()) { if (parser.Match(T(TCLOSE_BRACKET)).Ok()) {
return true; return true;
@ -498,11 +433,10 @@ public final function bool ParseWith(Parser parser)
return false; return false;
} }
// Resets this object to initial state before parsing and update // Resets this object to initial state before parsing and update
// `playersSnapshot` to contain current players. // `playersSnapshot` to contain current players.
private final function Reset() private final function Reset() {
{ parsedFirstSelector = false;
parsedFirstSelector = false;
currentSelection.length = 0; currentSelection.length = 0;
_.memory.FreeMany(playersSnapshot); _.memory.FreeMany(playersSnapshot);
playersSnapshot.length = 0; playersSnapshot.length = 0;
@ -512,18 +446,13 @@ private final function Reset()
selectorDelimiters[1] = T(TCLOSE_BRACKET); selectorDelimiters[1] = T(TCLOSE_BRACKET);
} }
/** /// Parses players from according to the currently present players.
* Parses players from `toParse` according to the currently present players. ///
* /// Array of parsed players can be retrieved by `self.GetPlayers()` method.
* Array of parsed players can be retrieved by `self.GetPlayers()` method. /// Returns `true` if parsing was successful and `false` otherwise.
* public final function bool Parse(BaseText toParse) {
* @param toParse `Text` from which to parse player list. local bool wasSuccessful;
* @return `true` if parsing was successful and `false` otherwise. local Parser parser;
*/
public final function bool Parse(BaseText toParse)
{
local bool wasSuccessful;
local Parser parser;
if (toParse == none) { if (toParse == none) {
return false; return false;
@ -534,8 +463,7 @@ public final function bool Parse(BaseText toParse)
return wasSuccessful; return wasSuccessful;
} }
defaultproperties defaultproperties {
{
TSELF = 0 TSELF = 0
stringConstants(0) = "self" stringConstants(0) = "self"
TADMIN = 1 TADMIN = 1

Loading…
Cancel
Save