diff --git a/sources/Commands/Command.uc b/sources/Commands/Command.uc index aefac81..85d0427 100644 --- a/sources/Commands/Command.uc +++ b/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 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); diff --git a/sources/Commands/CommandParser.uc b/sources/Commands/CommandParser.uc index e4f18a4..b83ff59 100644 --- a/sources/Commands/CommandParser.uc +++ b/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 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); diff --git a/sources/Commands/Commands_Feature.uc b/sources/Commands/Commands_Feature.uc index 9bc9c20..0ddbe21 100644 --- a/sources/Commands/Commands_Feature.uc +++ b/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 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,