Browse Source

Add command alias support for `Commands-Feature`

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

19
sources/Commands/Command.uc

@ -40,6 +40,9 @@ enum ErrorType
// 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
// (only relevant when it is enforced for parser, e.g. by an alias)
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,
@ -321,6 +324,9 @@ public final static function Command GetInstance()
* @param callerPlayer Player that initiated this command's call, * @param callerPlayer Player that initiated this command's call,
* necessary for parsing player list (since it can point at * necessary for parsing player list (since it can point at
* the caller player). * 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 * @return `CallData` structure that contains all the information about
* parameters specified in `parser`'s contents. * parameters specified in `parser`'s contents.
* Returned structure contains objects that must be deallocated, * Returned structure contains objects that must be deallocated,
@ -328,7 +334,8 @@ public final static function Command GetInstance()
*/ */
public final function CallData ParseInputWith( public final function CallData ParseInputWith(
Parser parser, Parser parser,
EPlayer callerPlayer) EPlayer callerPlayer,
optional BaseText subCommandName)
{ {
local array<EPlayer> targetPlayers; local array<EPlayer> targetPlayers;
local CommandParser commandParser; local CommandParser commandParser;
@ -355,7 +362,11 @@ public final function CallData ParseInputWith(
} }
// Parse parameters themselves // Parse parameters themselves
commandParser = CommandParser(_.memory.Allocate(class'CommandParser')); commandParser = CommandParser(_.memory.Allocate(class'CommandParser'));
callData = commandParser.ParseWith(parser, commandData, callerPlayer); callData = commandParser.ParseWith(
parser,
commandData,
callerPlayer,
subCommandName);
callData.targetPlayers = targetPlayers; callData.targetPlayers = targetPlayers;
commandParser.FreeSelf(); commandParser.FreeSelf();
return callData; return callData;
@ -500,6 +511,10 @@ private final function Text PrintErrorMessage(CallData callData)
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:
builder.Append(P("Ill defined sub-command: "))
.Append(callData.errorCause);
break;
case CET_NoRequiredParam: case CET_NoRequiredParam:
builder.Append(P("Missing required parameter: ")) builder.Append(P("Missing required parameter: "))
.Append(callData.errorCause); .Append(callData.errorCause);

20
sources/Commands/CommandParser.uc

@ -161,12 +161,17 @@ private final function DeclareError(
// Assumes `commandParser != none`, is in successful state. // Assumes `commandParser != none`, is in successful state.
// Picks a sub command based on it's contents (parser's pointer must be // Picks a sub command based on it's contents (parser's pointer must be
// before where subcommand's name is specified). // 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 int i;
local MutableText candidateSubCommandName; local MutableText candidateSubCommandName;
local Command.SubCommand emptySubCommand; local Command.SubCommand emptySubCommand;
local array<Command.SubCommand> allSubCommands; local array<Command.SubCommand> allSubCommands;
allSubCommands = commandData.subCommands; allSubCommands = commandData.subCommands;
if (allSubcommands.length == 0) if (allSubcommands.length == 0)
{ {
@ -176,7 +181,12 @@ private final function PickSubCommand(Command.Data commandData)
} }
// Get candidate name // Get candidate name
confirmedState = commandParser.GetCurrentState(); confirmedState = commandParser.GetCurrentState();
if (specifiedSubCommand != none) {
candidateSubCommandName = specifiedSubCommand.MutableCopy();
}
else {
commandParser.Skip().MUntil(candidateSubCommandName,, true); commandParser.Skip().MUntil(candidateSubCommandName,, true);
}
// Try matching it to sub commands // Try matching it to sub commands
pickedSubCommand = allSubcommands[0]; pickedSubCommand = allSubcommands[0];
if (candidateSubCommandName.IsEmpty()) if (candidateSubCommandName.IsEmpty())
@ -210,13 +220,17 @@ private final function PickSubCommand(Command.Data commandData)
* inside resulting `Command.CallData`, so deallocating them can * inside resulting `Command.CallData`, so deallocating them can
* invalidate returned value. * invalidate returned value.
* @param callerPlayer Player that called this command, if applicable. * @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`. * @return Results of parsing, described by `Command.CallData`.
* Returned object is guaranteed to be not `none`. * Returned object is guaranteed to be not `none`.
*/ */
public final function Command.CallData ParseWith( public final function Command.CallData ParseWith(
Parser parser, Parser parser,
Command.Data commandData, Command.Data commandData,
optional EPlayer callerPlayer) optional EPlayer callerPlayer,
optional BaseText specifiedSubCommand)
{ {
local HashTable commandParameters; local HashTable commandParameters;
// Temporary object to return `nextResult` while setting variable to `none` // 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')); PlayersParser(_.memory.Allocate(class'PlayersParser'));
currentPlayersParser.SetSelf(callerPlayer); currentPlayersParser.SetSelf(callerPlayer);
// (subcommand) (parameters, possibly with options) and nothing else! // (subcommand) (parameters, possibly with options) and nothing else!
PickSubCommand(commandData); PickSubCommand(commandData, specifiedSubCommand);
nextResult.subCommandName = pickedSubCommand.name.Copy(); nextResult.subCommandName = pickedSubCommand.name.Copy();
commandParameters = ParseParameterArrays( pickedSubCommand.required, commandParameters = ParseParameterArrays( pickedSubCommand.required,
pickedSubCommand.optional); 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. // Temporary measure until a better solution is finished.
var private /*config*/ array<string> allowedPlayers; 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; var LoggerAPI.Definition errCommandDuplicate;
protected function OnEnabled() protected function OnEnabled()
@ -452,7 +463,7 @@ public final function HandleInputWith(Parser parser, EPlayer callerPlayer)
local PlayerController controller; local PlayerController controller;
local Command commandInstance; local Command commandInstance;
local Command.CallData callData; local Command.CallData callData;
local MutableText commandName; local CommandCallPair callPair;
if (parser == none) return; if (parser == none) return;
if (callerPlayer == none) return; if (callerPlayer == none) return;
@ -472,8 +483,8 @@ public final function HandleInputWith(Parser parser, EPlayer callerPlayer)
if (!foundID) { if (!foundID) {
return; return;
} }
parser.MUntilMany(commandName, commandDelimiters, true, true); callPair = ParseCommandCallPairWith(parser);
commandInstance = GetCommand(commandName); commandInstance = GetCommand(callPair.commandName);
if ( commandInstance == none if ( commandInstance == none
&& callerPlayer != none && callerPlayer.IsExistent()) && callerPlayer != none && callerPlayer.IsExistent())
{ {
@ -482,15 +493,64 @@ public final function HandleInputWith(Parser parser, EPlayer callerPlayer)
.Flush() .Flush()
.Say(F("{$TextFailure Command not found!}")); .Say(F("{$TextFailure Command not found!}"));
} }
commandName.FreeSelf();
if (parser.Ok() && commandInstance != none) if (parser.Ok() && commandInstance != none)
{ {
callData = commandInstance.ParseInputWith(parser, callerPlayer); callData = commandInstance
.ParseInputWith(parser, callerPlayer, callPair.subCommandName);
commandInstance.Execute(callData, callerPlayer); commandInstance.Execute(callData, callerPlayer);
commandInstance.DeallocateCallData(callData); 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( private function bool HandleCommands(
EPlayer sender, EPlayer sender,
MutableText message, MutableText message,

Loading…
Cancel
Save