Browse Source

Add command alias support for `Commands-Feature`

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
0f0f2b6dae
  1. 21
      sources/Commands/Command.uc
  2. 22
      sources/Commands/CommandParser.uc
  3. 70
      sources/Commands/Commands_Feature.uc

21
sources/Commands/Command.uc

@ -40,6 +40,9 @@ enum ErrorType
// Sub-command name was not specified or was incorrect
// (this should not be possible)
CET_NoSubCommands,
// Specified sub-command does not exist
// (only relevant when it is enforced for parser, e.g. by an alias)
CET_BadSubCommand,
// Required param for command / option was not specified
CET_NoRequiredParam,
CET_NoRequiredParamForOption,
@ -321,14 +324,18 @@ public final static function Command GetInstance()
* @param callerPlayer Player that initiated this command's call,
* necessary for parsing player list (since it can point at
* the caller player).
* @param subCommandName This method can optionally specify sub-command to
* caller command to use. If this argument's value is `none` - sub-command
* 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(
Parser parser,
EPlayer callerPlayer)
Parser parser,
EPlayer callerPlayer,
optional BaseText subCommandName)
{
local array<EPlayer> targetPlayers;
local CommandParser commandParser;
@ -355,7 +362,11 @@ public final function CallData ParseInputWith(
}
// Parse parameters themselves
commandParser = CommandParser(_.memory.Allocate(class'CommandParser'));
callData = commandParser.ParseWith(parser, commandData, callerPlayer);
callData = commandParser.ParseWith(
parser,
commandData,
callerPlayer,
subCommandName);
callData.targetPlayers = targetPlayers;
commandParser.FreeSelf();
return callData;
@ -500,6 +511,10 @@ private final function Text PrintErrorMessage(CallData callData)
case CET_NoSubCommands:
builder.Append(P("Ill defined command: no subcommands"));
break;
case CET_BadSubCommand:
builder.Append(P("Ill defined sub-command: "))
.Append(callData.errorCause);
break;
case CET_NoRequiredParam:
builder.Append(P("Missing required parameter: "))
.Append(callData.errorCause);

22
sources/Commands/CommandParser.uc

@ -161,12 +161,17 @@ private final function DeclareError(
// Assumes `commandParser != none`, is in successful state.
// Picks a sub command based on it's contents (parser's pointer must be
// before where subcommand's name is specified).
private final function PickSubCommand(Command.Data commandData)
// If `specifiedSubCommand` is not `none` - will always use that value
// instead of parsing it from `commandParser`.
private final function PickSubCommand(
Command.Data commandData,
BaseText specifiedSubCommand)
{
local int i;
local MutableText candidateSubCommandName;
local Command.SubCommand emptySubCommand;
local array<Command.SubCommand> allSubCommands;
allSubCommands = commandData.subCommands;
if (allSubcommands.length == 0)
{
@ -176,7 +181,12 @@ private final function PickSubCommand(Command.Data commandData)
}
// Get candidate name
confirmedState = commandParser.GetCurrentState();
commandParser.Skip().MUntil(candidateSubCommandName,, true);
if (specifiedSubCommand != none) {
candidateSubCommandName = specifiedSubCommand.MutableCopy();
}
else {
commandParser.Skip().MUntil(candidateSubCommandName,, true);
}
// Try matching it to sub commands
pickedSubCommand = allSubcommands[0];
if (candidateSubCommandName.IsEmpty())
@ -210,13 +220,17 @@ private final function PickSubCommand(Command.Data commandData)
* inside resulting `Command.CallData`, so deallocating them can
* invalidate returned value.
* @param callerPlayer Player that called this command, if applicable.
* @param specifiedSubCommand Optionally, sub-command can be specified for
* the `CommandParser` to use. If this argument's value is `none` - it will
* be parsed from `parser`'s data instead.
* @return Results of parsing, described by `Command.CallData`.
* Returned object is guaranteed to be not `none`.
*/
public final function Command.CallData ParseWith(
Parser parser,
Command.Data commandData,
optional EPlayer callerPlayer)
optional EPlayer callerPlayer,
optional BaseText specifiedSubCommand)
{
local HashTable commandParameters;
// Temporary object to return `nextResult` while setting variable to `none`
@ -243,7 +257,7 @@ public final function Command.CallData ParseWith(
PlayersParser(_.memory.Allocate(class'PlayersParser'));
currentPlayersParser.SetSelf(callerPlayer);
// (subcommand) (parameters, possibly with options) and nothing else!
PickSubCommand(commandData);
PickSubCommand(commandData, specifiedSubCommand);
nextResult.subCommandName = pickedSubCommand.name.Copy();
commandParameters = ParseParameterArrays( pickedSubCommand.required,
pickedSubCommand.optional);

70
sources/Commands/Commands_Feature.uc

@ -50,6 +50,17 @@ var private /*config*/ Text chatCommandPrefix;
// Temporary measure until a better solution is finished.
var private /*config*/ array<string> allowedPlayers;
// Contains name of the command to call plus, optionally,
// additional sub-command name.
// Normally sub-command name is parsed by the command itself, however
// command aliases can try to enforce one.
struct CommandCallPair
{
var MutableText commandName;
// In case it is enforced by an alias
var MutableText subCommandName;
};
var LoggerAPI.Definition errCommandDuplicate;
protected function OnEnabled()
@ -452,7 +463,7 @@ public final function HandleInputWith(Parser parser, EPlayer callerPlayer)
local PlayerController controller;
local Command commandInstance;
local Command.CallData callData;
local MutableText commandName;
local CommandCallPair callPair;
if (parser == none) return;
if (callerPlayer == none) return;
@ -472,8 +483,8 @@ public final function HandleInputWith(Parser parser, EPlayer callerPlayer)
if (!foundID) {
return;
}
parser.MUntilMany(commandName, commandDelimiters, true, true);
commandInstance = GetCommand(commandName);
callPair = ParseCommandCallPairWith(parser);
commandInstance = GetCommand(callPair.commandName);
if ( commandInstance == none
&& callerPlayer != none && callerPlayer.IsExistent())
{
@ -482,15 +493,64 @@ public final function HandleInputWith(Parser parser, EPlayer callerPlayer)
.Flush()
.Say(F("{$TextFailure Command not found!}"));
}
commandName.FreeSelf();
if (parser.Ok() && commandInstance != none)
{
callData = commandInstance.ParseInputWith(parser, callerPlayer);
callData = commandInstance
.ParseInputWith(parser, callerPlayer, callPair.subCommandName);
commandInstance.Execute(callData, callerPlayer);
commandInstance.DeallocateCallData(callData);
}
_.memory.Free(callPair.commandName);
_.memory.Free(callPair.subCommandName);
}
// Parses command's name into `CommandCallPair` - sub-command is filled in case
// specified name is an alias with specified sub-command name.
private final function CommandCallPair ParseCommandCallPairWith(Parser parser)
{
local Text resolvedValue;
local MutableText userSpecifiedName;
local CommandCallPair result;
local Text.Character dotCharacter;
if (parser == none) return result;
if (!parser.Ok()) return result;
parser.MUntilMany(userSpecifiedName, commandDelimiters, true, true);
resolvedValue = _.alias.ResolveCommand(userSpecifiedName);
// This isn't an alias
if (resolvedValue == none)
{
result.commandName = userSpecifiedName;
return result;
}
// It is an alias - parse it
dotCharacter = _.text.GetCharacter(".");
resolvedValue.Parse()
.MUntil(result.commandName, dotCharacter)
.MatchS(".")
.MUntil(result.subCommandName, dotCharacter)
.FreeSelf();
if (result.subCommandName.IsEmpty())
{
result.subCommandName.FreeSelf();
result.subCommandName = none;
}
resolvedValue.FreeSelf();
return result;
}
/*// Contains name of the command to call plus, optionally,
// additional sub-command name.
// Normally sub-command name is parsed by the command itself, however
// command aliases can try to enforce one.
struct CommandCallPair
{
var MutableText commandName;
// In case it is enforced by an alias
var MutableText subCommandName;
}; */
private function bool HandleCommands(
EPlayer sender,
MutableText message,

Loading…
Cancel
Save