|
|
|
@ -1,9 +1,8 @@
|
|
|
|
|
/** |
|
|
|
|
* This feature provides a mechanism to define commands that automatically |
|
|
|
|
* parse their arguments into standard Acedia collection. It also allows to |
|
|
|
|
* manage them (and specify limitation on how they can be called) in a |
|
|
|
|
* centralized manner. |
|
|
|
|
* Copyright 2021-2023 Anton Tarasenko |
|
|
|
|
* Author: dkanus |
|
|
|
|
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
|
|
|
|
* License: GPL |
|
|
|
|
* Copyright 2023 Anton Tarasenko |
|
|
|
|
*------------------------------------------------------------------------------ |
|
|
|
|
* This file is part of Acedia. |
|
|
|
|
* |
|
|
|
@ -22,90 +21,61 @@
|
|
|
|
|
*/ |
|
|
|
|
class Commands_Feature extends Feature; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* # `Commands_Feature` |
|
|
|
|
* |
|
|
|
|
* This feature provides a mechanism to define commands that automatically |
|
|
|
|
* parse their arguments into standard Acedia collection. It also allows to |
|
|
|
|
* manage them (and specify limitation on how they can be called) in a |
|
|
|
|
* centralized manner. |
|
|
|
|
* Support command input from chat and "mutate" command. |
|
|
|
|
* |
|
|
|
|
* ## Usage |
|
|
|
|
* |
|
|
|
|
* Should be enabled like any other feature. Additionally support |
|
|
|
|
* `EmergencyEnable()` enabling method that bypasses regular settings to allow |
|
|
|
|
* admins to start this feature while forcefully enabling "mutate" command |
|
|
|
|
* input method. |
|
|
|
|
* Available configuration: |
|
|
|
|
* |
|
|
|
|
* 1. Whether to use command input from chat and what prefix is used to |
|
|
|
|
* denote a command (by default "!"); |
|
|
|
|
* 2. Whether to use command input from "mutate" command. |
|
|
|
|
* |
|
|
|
|
* To add new commands into the system - get enabled instance of this |
|
|
|
|
* feature and call its `RegisterCommand()` method to add your custom |
|
|
|
|
* `Command` class. `RemoveCommand()` can also be used to de-register |
|
|
|
|
* a command, if you need this for some reason. |
|
|
|
|
* |
|
|
|
|
* ## Implementation |
|
|
|
|
* |
|
|
|
|
* Implementation is simple: calling a method `RegisterCommand()` adds |
|
|
|
|
* command into two caches `registeredCommands` for obtaining registered |
|
|
|
|
* commands by name and `groupedCommands` for obtaining arrays of commands by |
|
|
|
|
* their group name. These arrays are used for providing methods for fetching |
|
|
|
|
* arrays of commands and obtaining pre-allocated `Command` instances by their |
|
|
|
|
* name. |
|
|
|
|
* Depending on settings, this feature also connects to corresponding |
|
|
|
|
* signals for catching "mutate"/chat input, then it checks user-specified name |
|
|
|
|
* for being an alias and picks correct command from `registeredCommands`. |
|
|
|
|
* Emergency enabling this feature sets `emergencyEnabledMutate` flag that |
|
|
|
|
* enforces connecting to the "mutate" input. |
|
|
|
|
*/ |
|
|
|
|
//! This feature manages commands that automatically parse their arguments into standard Acedia |
|
|
|
|
//! collections. |
|
|
|
|
//! |
|
|
|
|
//! # Implementation |
|
|
|
|
//! |
|
|
|
|
//! Implementation is simple: calling a method `RegisterCommand()` adds |
|
|
|
|
//! command into two caches `registeredCommands` for obtaining registered |
|
|
|
|
//! commands by name and `groupedCommands` for obtaining arrays of commands by |
|
|
|
|
//! their group name. These arrays are used for providing methods for fetching |
|
|
|
|
//! arrays of commands and obtaining pre-allocated `Command` instances by their |
|
|
|
|
//! name. |
|
|
|
|
//! Depending on settings, this feature also connects to corresponding |
|
|
|
|
//! signals for catching "mutate"/chat input, then it checks user-specified name |
|
|
|
|
//! for being an alias and picks correct command from `registeredCommands`. |
|
|
|
|
//! Emergency enabling this feature sets `emergencyEnabledMutate` flag that |
|
|
|
|
//! enforces connecting to the "mutate" input. |
|
|
|
|
|
|
|
|
|
// Auxiliary struct for passing 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; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Delimiters that always separate command name from it's parameters |
|
|
|
|
var private array<Text> commandDelimiters; |
|
|
|
|
// Registered commands, recorded as (<command_name>, <command_instance>) pairs. |
|
|
|
|
// Keys should be deallocated when their entry is removed. |
|
|
|
|
var private HashTable registeredCommands; |
|
|
|
|
// `HashTable` of "<command_group_name>" <-> `ArrayList` of commands pairs |
|
|
|
|
// to allow quick fetch of commands belonging to a single group |
|
|
|
|
// `HashTable` of "<command_group_name>" <-> `ArrayList` of commands pairs to allow quick fetch of |
|
|
|
|
// commands belonging to a single group |
|
|
|
|
var private HashTable groupedCommands; |
|
|
|
|
|
|
|
|
|
// When this flag is set to true, mutate input becomes available |
|
|
|
|
// despite `useMutateInput` flag to allow to unlock server in case of an error |
|
|
|
|
// When this flag is set to true, mutate input becomes available despite `useMutateInput` flag to |
|
|
|
|
// allow to unlock server in case of an error |
|
|
|
|
var private bool emergencyEnabledMutate; |
|
|
|
|
|
|
|
|
|
// Setting this to `true` enables players to input commands right in the chat |
|
|
|
|
// by prepending them with `chatCommandPrefix`. |
|
|
|
|
// Setting this to `true` enables players to input commands right in the chat by prepending them |
|
|
|
|
// with `chatCommandPrefix`. |
|
|
|
|
// Default is `true`. |
|
|
|
|
var private /*config*/ bool useChatInput; |
|
|
|
|
// Setting this to `true` enables players to input commands with "mutate" |
|
|
|
|
// console command. |
|
|
|
|
// Setting this to `true` enables players to input commands with "mutate" console command. |
|
|
|
|
// Default is `true`. |
|
|
|
|
var private /*config*/ bool useMutateInput; |
|
|
|
|
// Chat messages, prepended by this prefix will be treated as commands. |
|
|
|
|
// Default is "!". Empty values are also treated as "!". |
|
|
|
|
var private /*config*/ Text chatCommandPrefix; |
|
|
|
|
// List of steam IDs of players allowed to use commands. |
|
|
|
|
// 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() |
|
|
|
|
{ |
|
|
|
|
protected function OnEnabled() { |
|
|
|
|
registeredCommands = _.collections.EmptyHashTable(); |
|
|
|
|
groupedCommands = _.collections.EmptyHashTable(); |
|
|
|
|
RegisterCommand(class'ACommandHelp'); |
|
|
|
@ -116,6 +86,7 @@ protected function OnEnabled()
|
|
|
|
|
// Player array (possibly JSON array) |
|
|
|
|
commandDelimiters[2] = _.text.FromString("["); |
|
|
|
|
// Negation of the selector |
|
|
|
|
// NOT the same thing as default command prefix in chat |
|
|
|
|
commandDelimiters[3] = _.text.FromString("!"); |
|
|
|
|
if (useChatInput) { |
|
|
|
|
_.chat.OnMessage(self).connect = HandleCommands; |
|
|
|
@ -131,8 +102,7 @@ protected function OnEnabled()
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function OnDisabled() |
|
|
|
|
{ |
|
|
|
|
protected function OnDisabled() { |
|
|
|
|
if (useChatInput) { |
|
|
|
|
_.chat.OnMessage(self).Disconnect(); |
|
|
|
|
} |
|
|
|
@ -141,10 +111,7 @@ protected function OnDisabled()
|
|
|
|
|
} |
|
|
|
|
useChatInput = false; |
|
|
|
|
useMutateInput = false; |
|
|
|
|
_.memory.Free(registeredCommands); |
|
|
|
|
_.memory.Free(groupedCommands); |
|
|
|
|
_.memory.Free(chatCommandPrefix); |
|
|
|
|
_.memory.FreeMany(commandDelimiters); |
|
|
|
|
_.memory.Free3(registeredCommands, groupedCommands, chatCommandPrefix); |
|
|
|
|
registeredCommands = none; |
|
|
|
|
groupedCommands = none; |
|
|
|
|
chatCommandPrefix = none; |
|
|
|
@ -161,50 +128,131 @@ protected function SwapConfig(FeatureConfig config)
|
|
|
|
|
} |
|
|
|
|
_.memory.Free(chatCommandPrefix); |
|
|
|
|
chatCommandPrefix = _.text.FromString(newConfig.chatCommandPrefix); |
|
|
|
|
allowedPlayers = newConfig.allowedPlayers; |
|
|
|
|
useChatInput = newConfig.useChatInput; |
|
|
|
|
useMutateInput = newConfig.useMutateInput; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* `Command_Feature` is a critical command to have running on your server and, |
|
|
|
|
* if disabled by accident, there will be no way of starting it again without |
|
|
|
|
* restarting the level or even editing configs. |
|
|
|
|
* |
|
|
|
|
* This method allows to enable it along with "mutate" input in case something |
|
|
|
|
* goes wrong. |
|
|
|
|
*/ |
|
|
|
|
public final static function EmergencyEnable() |
|
|
|
|
{ |
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function bool HandleCommands(EPlayer sender, MutableText message, bool teamMessage) { |
|
|
|
|
local Parser parser; |
|
|
|
|
|
|
|
|
|
// We are only interested in messages that start with `chatCommandPrefix` |
|
|
|
|
parser = _.text.Parse(message); |
|
|
|
|
if (!parser.Match(chatCommandPrefix).Ok()) { |
|
|
|
|
parser.FreeSelf(); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
// Pass input to command feature |
|
|
|
|
HandleInputWith(parser, sender); |
|
|
|
|
parser.FreeSelf(); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function HandleMutate(string command, PlayerController sendingPlayer) { |
|
|
|
|
local Parser parser; |
|
|
|
|
local EPlayer sender; |
|
|
|
|
|
|
|
|
|
// A lot of other mutators use these commands |
|
|
|
|
if (command ~= "help") return; |
|
|
|
|
if (command ~= "version") return; |
|
|
|
|
if (command ~= "status") return; |
|
|
|
|
if (command ~= "credits") return; |
|
|
|
|
|
|
|
|
|
parser = _.text.ParseString(command); |
|
|
|
|
sender = _.players.FromController(sendingPlayer); |
|
|
|
|
HandleInputWith(parser, sender); |
|
|
|
|
sender.FreeSelf(); |
|
|
|
|
parser.FreeSelf(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private final function RemoveClassFromGroup(class<Command> commandClass, BaseText commandGroup) { |
|
|
|
|
local int i; |
|
|
|
|
local ArrayList groupArray; |
|
|
|
|
local Command nextCommand; |
|
|
|
|
|
|
|
|
|
groupArray = groupedCommands.GetArrayList(commandGroup); |
|
|
|
|
if (groupArray == none) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
while (i < groupArray.GetLength()) { |
|
|
|
|
nextCommand = Command(groupArray.GetItem(i)); |
|
|
|
|
if (nextCommand != none && nextCommand.class == commandClass) { |
|
|
|
|
groupArray.RemoveIndex(i); |
|
|
|
|
} else { |
|
|
|
|
i += 1; |
|
|
|
|
} |
|
|
|
|
_.memory.Free(nextCommand); |
|
|
|
|
} |
|
|
|
|
if (groupArray.GetLength() == 0) { |
|
|
|
|
groupedCommands.RemoveItem(commandGroup); |
|
|
|
|
} |
|
|
|
|
_.memory.Free(groupArray); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// This method allows to forcefully enable `Command_Feature` along with "mutate" input in case |
|
|
|
|
/// something goes wrong. |
|
|
|
|
/// |
|
|
|
|
/// `Command_Feature` is a critical command to have running on your server and, |
|
|
|
|
/// if disabled by accident, there will be no way of starting it again without |
|
|
|
|
/// restarting the level or even editing configs. |
|
|
|
|
public final static function EmergencyEnable() { |
|
|
|
|
local bool noWayToInputCommands; |
|
|
|
|
local Text autoConfig; |
|
|
|
|
local Commands_Feature feature; |
|
|
|
|
|
|
|
|
|
if (!IsEnabled()) |
|
|
|
|
{ |
|
|
|
|
if (!IsEnabled()) { |
|
|
|
|
autoConfig = GetAutoEnabledConfig(); |
|
|
|
|
EnableMe(autoConfig); |
|
|
|
|
__().memory.Free(autoConfig); |
|
|
|
|
} |
|
|
|
|
feature = Commands_Feature(GetEnabledInstance()); |
|
|
|
|
if ( !feature.emergencyEnabledMutate |
|
|
|
|
&& !feature.IsUsingMutateInput() && !feature.IsUsingChatInput()) |
|
|
|
|
{ |
|
|
|
|
noWayToInputCommands = !feature.emergencyEnabledMutate |
|
|
|
|
&&!feature.IsUsingMutateInput() |
|
|
|
|
&& !feature.IsUsingChatInput(); |
|
|
|
|
if (noWayToInputCommands) { |
|
|
|
|
default.emergencyEnabledMutate = true; |
|
|
|
|
feature.emergencyEnabledMutate = true; |
|
|
|
|
__server().unreal.mutator.OnMutate(feature).connect = HandleMutate; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Checks if `Commands_Feature` currently uses chat as input. |
|
|
|
|
* If `Commands_Feature` is not enabled, then it does not use anything |
|
|
|
|
* as input. |
|
|
|
|
* |
|
|
|
|
* @return `true` if `Commands_Feature` is currently enabled and is using chat |
|
|
|
|
* as input and `false` otherwise. |
|
|
|
|
*/ |
|
|
|
|
public final static function bool IsUsingChatInput() |
|
|
|
|
{ |
|
|
|
|
/// Checks if `Commands_Feature` currently uses chat as input. |
|
|
|
|
/// |
|
|
|
|
/// If `Commands_Feature` is not enabled, then it does not use anything |
|
|
|
|
/// as input. |
|
|
|
|
public final static function bool IsUsingChatInput() { |
|
|
|
|
local Commands_Feature instance; |
|
|
|
|
|
|
|
|
|
instance = Commands_Feature(GetEnabledInstance()); |
|
|
|
@ -214,16 +262,11 @@ public final static function bool IsUsingChatInput()
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Checks if `Commands_Feature` currently uses mutate command as input. |
|
|
|
|
* If `Commands_Feature` is not enabled, then it does not use anything |
|
|
|
|
* as input. |
|
|
|
|
* |
|
|
|
|
* @return `true` if `Commands_Feature` is currently enabled and is using |
|
|
|
|
* mutate command as input and `false` otherwise. |
|
|
|
|
*/ |
|
|
|
|
public final static function bool IsUsingMutateInput() |
|
|
|
|
{ |
|
|
|
|
/// Checks if `Commands_Feature` currently uses mutate command as input. |
|
|
|
|
/// |
|
|
|
|
/// If `Commands_Feature` is not enabled, then it does not use anything |
|
|
|
|
/// as input. |
|
|
|
|
public final static function bool IsUsingMutateInput() { |
|
|
|
|
local Commands_Feature instance; |
|
|
|
|
|
|
|
|
|
instance = Commands_Feature(GetEnabledInstance()); |
|
|
|
@ -233,15 +276,10 @@ public final static function bool IsUsingMutateInput()
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns prefix that will indicate that chat message is intended to be |
|
|
|
|
* a command. By default "!". |
|
|
|
|
* |
|
|
|
|
* @return Prefix that indicates that chat message is intended to be a command. |
|
|
|
|
* If `Commands_Feature` is disabled, always returns `false`. |
|
|
|
|
*/ |
|
|
|
|
public final static function Text GetChatPrefix() |
|
|
|
|
{ |
|
|
|
|
/// Returns prefix that will indicate that chat message is intended to be a command. By default "!". |
|
|
|
|
/// |
|
|
|
|
/// If `Commands_Feature` is disabled, always returns `none`. |
|
|
|
|
public final static function Text GetChatPrefix() { |
|
|
|
|
local Commands_Feature instance; |
|
|
|
|
|
|
|
|
|
instance = Commands_Feature(GetEnabledInstance()); |
|
|
|
@ -251,31 +289,29 @@ public final static function Text GetChatPrefix()
|
|
|
|
|
return none; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Registers given command class, making it available for usage. |
|
|
|
|
* |
|
|
|
|
* If `commandClass` provides command with a name that is already taken |
|
|
|
|
* (comparison is case-insensitive) by a different command - a warning will be |
|
|
|
|
* logged and newly passed `commandClass` discarded. |
|
|
|
|
* |
|
|
|
|
* @param commandClass New command class to register. |
|
|
|
|
*/ |
|
|
|
|
public final function RegisterCommand(class<Command> commandClass) |
|
|
|
|
{ |
|
|
|
|
/// Registers given command class, making it available. |
|
|
|
|
/// |
|
|
|
|
/// # Errors |
|
|
|
|
/// |
|
|
|
|
/// Returns `true` if command was successfully registered and `false` otherwise`. |
|
|
|
|
/// |
|
|
|
|
/// If `commandClass` provides command with a name that is already taken |
|
|
|
|
/// (comparison is case-insensitive) by a different command - a warning will be |
|
|
|
|
/// logged and newly passed `commandClass` discarded. |
|
|
|
|
public final function bool RegisterCommand(class<Command> commandClass) { |
|
|
|
|
local Text commandName, groupName; |
|
|
|
|
local ArrayList groupArray; |
|
|
|
|
local Command newCommandInstance, existingCommandInstance; |
|
|
|
|
|
|
|
|
|
if (commandClass == none) return; |
|
|
|
|
if (registeredCommands == none) return; |
|
|
|
|
if (commandClass == none) return false; |
|
|
|
|
if (registeredCommands == none) return false; |
|
|
|
|
|
|
|
|
|
newCommandInstance = Command(_.memory.Allocate(commandClass, true)); |
|
|
|
|
commandName = newCommandInstance.GetName(); |
|
|
|
|
groupName = newCommandInstance.GetGroupName(); |
|
|
|
|
// Check for duplicates and report them |
|
|
|
|
existingCommandInstance = Command(registeredCommands.GetItem(commandName)); |
|
|
|
|
if (existingCommandInstance != none) |
|
|
|
|
{ |
|
|
|
|
if (existingCommandInstance != none) { |
|
|
|
|
_.logger.Auto(errCommandDuplicate) |
|
|
|
|
.ArgClass(existingCommandInstance.class) |
|
|
|
|
.Arg(commandName) |
|
|
|
@ -283,7 +319,7 @@ public final function RegisterCommand(class<Command> commandClass)
|
|
|
|
|
_.memory.Free(groupName); |
|
|
|
|
_.memory.Free(newCommandInstance); |
|
|
|
|
_.memory.Free(existingCommandInstance); |
|
|
|
|
return; |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
// Otherwise record new command |
|
|
|
|
// `commandName` used as a key, do not deallocate it |
|
|
|
@ -295,24 +331,16 @@ public final function RegisterCommand(class<Command> commandClass)
|
|
|
|
|
} |
|
|
|
|
groupArray.AddItem(newCommandInstance); |
|
|
|
|
groupedCommands.SetItem(groupName, groupArray); |
|
|
|
|
_.memory.Free(groupArray); |
|
|
|
|
_.memory.Free(groupName); |
|
|
|
|
_.memory.Free(commandName); |
|
|
|
|
_.memory.Free(newCommandInstance); |
|
|
|
|
_.memory.Free4(groupArray, groupName, commandName, newCommandInstance); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Removes command of class `commandClass` from the list of |
|
|
|
|
* registered commands. |
|
|
|
|
* |
|
|
|
|
* WARNING: removing once registered commands is not an action that is expected |
|
|
|
|
* to be performed under normal circumstances and it is not efficient. |
|
|
|
|
* It is linear on the current amount of commands. |
|
|
|
|
* |
|
|
|
|
* @param commandClass Class of command to remove from being registered. |
|
|
|
|
*/ |
|
|
|
|
public final function RemoveCommand(class<Command> commandClass) |
|
|
|
|
{ |
|
|
|
|
/// Removes command of given class from the list of registered commands. |
|
|
|
|
/// |
|
|
|
|
/// Removing once registered commands is not an action that is expected to be performed under normal |
|
|
|
|
/// circumstances and it is not efficient. |
|
|
|
|
/// It is linear on the current amount of commands. |
|
|
|
|
public final function RemoveCommand(class<Command> commandClass) { |
|
|
|
|
local int i; |
|
|
|
|
local CollectionIterator iter; |
|
|
|
|
local Command nextCommand; |
|
|
|
@ -323,15 +351,11 @@ public final function RemoveCommand(class<Command> commandClass)
|
|
|
|
|
if (commandClass == none) return; |
|
|
|
|
if (registeredCommands == none) return; |
|
|
|
|
|
|
|
|
|
for (iter = registeredCommands.Iterate(); !iter.HasFinished(); iter.Next()) |
|
|
|
|
{ |
|
|
|
|
for (iter = registeredCommands.Iterate(); !iter.HasFinished(); iter.Next()) { |
|
|
|
|
nextCommand = Command(iter.Get()); |
|
|
|
|
nextCommandName = Text(iter.GetKey()); |
|
|
|
|
if ( nextCommand == none || nextCommandName == none |
|
|
|
|
|| nextCommand.class != commandClass) |
|
|
|
|
{ |
|
|
|
|
_.memory.Free(nextCommand); |
|
|
|
|
_.memory.Free(nextCommandName); |
|
|
|
|
if (nextCommand == none || nextCommandName == none || nextCommand.class != commandClass) { |
|
|
|
|
_.memory.Free2(nextCommand, nextCommandName); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
keysToRemove[keysToRemove.length] = nextCommandName; |
|
|
|
@ -339,57 +363,22 @@ public final function RemoveCommand(class<Command> commandClass)
|
|
|
|
|
_.memory.Free(nextCommand); |
|
|
|
|
} |
|
|
|
|
iter.FreeSelf(); |
|
|
|
|
for (i = 0; i < keysToRemove.length; i += 1) |
|
|
|
|
{ |
|
|
|
|
for (i = 0; i < keysToRemove.length; i += 1) { |
|
|
|
|
registeredCommands.RemoveItem(keysToRemove[i]); |
|
|
|
|
_.memory.Free(keysToRemove[i]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (i = 0; i < commandGroup.length; i += 1) { |
|
|
|
|
RemoveClassFromGroup(commandClass, commandGroup[i]); |
|
|
|
|
} |
|
|
|
|
_.memory.FreeMany(commandGroup); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private final function RemoveClassFromGroup( |
|
|
|
|
class<Command> commandClass, |
|
|
|
|
BaseText commandGroup) |
|
|
|
|
{ |
|
|
|
|
local int i; |
|
|
|
|
local ArrayList groupArray; |
|
|
|
|
local Command nextCommand; |
|
|
|
|
|
|
|
|
|
groupArray = groupedCommands.GetArrayList(commandGroup); |
|
|
|
|
if (groupArray == none) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
while (i < groupArray.GetLength()) |
|
|
|
|
{ |
|
|
|
|
nextCommand = Command(groupArray.GetItem(i)); |
|
|
|
|
if (nextCommand != none && nextCommand.class == commandClass) { |
|
|
|
|
groupArray.RemoveIndex(i); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
i += 1; |
|
|
|
|
} |
|
|
|
|
_.memory.Free(nextCommand); |
|
|
|
|
} |
|
|
|
|
if (groupArray.GetLength() == 0) { |
|
|
|
|
groupedCommands.RemoveItem(commandGroup); |
|
|
|
|
} |
|
|
|
|
_.memory.Free(groupArray); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns command based on a given name. |
|
|
|
|
* |
|
|
|
|
* @param commandName Name of the registered `Command` to return. |
|
|
|
|
* Case-insensitive. |
|
|
|
|
* @return Command, registered with a given name `commandName`. |
|
|
|
|
* If no command with such name was registered - returns `none`. |
|
|
|
|
*/ |
|
|
|
|
public final function Command GetCommand(BaseText commandName) |
|
|
|
|
{ |
|
|
|
|
/// Returns command based on a given name. |
|
|
|
|
/// |
|
|
|
|
/// Name of the registered `Command` to return is case-insensitive. |
|
|
|
|
/// |
|
|
|
|
/// If no command with such name was registered - returns `none`. |
|
|
|
|
public final function Command GetCommand(BaseText commandName) { |
|
|
|
|
local Text commandNameLowerCase; |
|
|
|
|
local Command commandInstance; |
|
|
|
|
|
|
|
|
@ -402,13 +391,8 @@ public final function Command GetCommand(BaseText commandName)
|
|
|
|
|
return commandInstance; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns array of names of all available commands. |
|
|
|
|
* |
|
|
|
|
* @return Array of names of all available (registered) commands. |
|
|
|
|
*/ |
|
|
|
|
public final function array<Text> GetCommandNames() |
|
|
|
|
{ |
|
|
|
|
/// Returns array of names of all available commands. |
|
|
|
|
public final function array<Text> GetCommandNames() { |
|
|
|
|
local array<Text> emptyResult; |
|
|
|
|
|
|
|
|
|
if (registeredCommands != none) { |
|
|
|
@ -417,15 +401,8 @@ public final function array<Text> GetCommandNames()
|
|
|
|
|
return emptyResult; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns array of names of all available commands belonging to the group |
|
|
|
|
* `groupName`. |
|
|
|
|
* |
|
|
|
|
* @return Array of names of all available (registered) commands, belonging to |
|
|
|
|
* the group `groupName`. |
|
|
|
|
*/ |
|
|
|
|
public final function array<Text> GetCommandNamesInGroup(BaseText groupName) |
|
|
|
|
{ |
|
|
|
|
/// Returns array of names of all available commands belonging to the group [`groupName`]. |
|
|
|
|
public final function array<Text> GetCommandNamesInGroup(BaseText groupName) { |
|
|
|
|
local int i; |
|
|
|
|
local ArrayList groupArray; |
|
|
|
|
local Command nextCommand; |
|
|
|
@ -435,8 +412,7 @@ public final function array<Text> GetCommandNamesInGroup(BaseText groupName)
|
|
|
|
|
groupArray = groupedCommands.GetArrayList(groupName); |
|
|
|
|
if (groupArray == none) return result; |
|
|
|
|
|
|
|
|
|
for (i = 0; i < groupArray.GetLength(); i += 1) |
|
|
|
|
{ |
|
|
|
|
for (i = 0; i < groupArray.GetLength(); i += 1) { |
|
|
|
|
nextCommand = Command(groupArray.GetItem(i)); |
|
|
|
|
if (nextCommand != none) { |
|
|
|
|
result[result.length] = nextCommand.GetName(); |
|
|
|
@ -446,13 +422,8 @@ public final function array<Text> GetCommandNamesInGroup(BaseText groupName)
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns all available command groups' names. |
|
|
|
|
* |
|
|
|
|
* @return Array of all available command groups' names. |
|
|
|
|
*/ |
|
|
|
|
public final function array<Text> GetGroupsNames() |
|
|
|
|
{ |
|
|
|
|
/// Returns all available command groups' names. |
|
|
|
|
public final function array<Text> GetGroupsNames() { |
|
|
|
|
local array<Text> emptyResult; |
|
|
|
|
|
|
|
|
|
if (groupedCommands != none) { |
|
|
|
@ -461,158 +432,75 @@ public final function array<Text> GetGroupsNames()
|
|
|
|
|
return emptyResult; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Handles user input: finds appropriate command and passes the rest of |
|
|
|
|
* the arguments to it for further processing. |
|
|
|
|
* |
|
|
|
|
* @param input Test that contains user's command input. |
|
|
|
|
* @param callerPlayer Player that caused this command call. |
|
|
|
|
*/ |
|
|
|
|
public final function HandleInput(BaseText input, EPlayer callerPlayer) |
|
|
|
|
{ |
|
|
|
|
/// Executes command based on the input. |
|
|
|
|
/// |
|
|
|
|
/// Takes [`commandLine`] as input with command's call, finds appropriate registered command |
|
|
|
|
/// instance and executes it with parameters specified in the [`commandLine`]. |
|
|
|
|
/// |
|
|
|
|
/// [`callerPlayer`] has to be specified and represents instigator of this command that will receive |
|
|
|
|
/// appropriate result/error messages. |
|
|
|
|
/// |
|
|
|
|
/// Returns `true` iff command was successfully executed. |
|
|
|
|
/// |
|
|
|
|
/// # Errors |
|
|
|
|
/// |
|
|
|
|
/// Doesn't log any errors, but can complain about errors in name or parameters to |
|
|
|
|
/// the [`callerPlayer`] |
|
|
|
|
public final function bool HandleInput(BaseText input, EPlayer callerPlayer) { |
|
|
|
|
local bool result; |
|
|
|
|
local Parser wrapper; |
|
|
|
|
|
|
|
|
|
if (input == none) { |
|
|
|
|
return; |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
wrapper = input.Parse(); |
|
|
|
|
HandleInputWith(wrapper, callerPlayer); |
|
|
|
|
result = HandleInputWith(wrapper, callerPlayer); |
|
|
|
|
wrapper.FreeSelf(); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Handles user input: finds appropriate command and passes the rest of |
|
|
|
|
* the arguments to it for further processing. |
|
|
|
|
* |
|
|
|
|
* @param parser Parser filled with user input that is expected to |
|
|
|
|
* contain command's name and it's parameters. |
|
|
|
|
* @param callerPlayer Player that caused this command call. |
|
|
|
|
*/ |
|
|
|
|
public final function HandleInputWith(Parser parser, EPlayer callerPlayer) |
|
|
|
|
{ |
|
|
|
|
local int i; |
|
|
|
|
local bool foundID; |
|
|
|
|
local string steamID; |
|
|
|
|
local PlayerController controller; |
|
|
|
|
/// Executes command based on the input. |
|
|
|
|
/// |
|
|
|
|
/// Takes [`commandLine`] as input with command's call, finds appropriate registered command |
|
|
|
|
/// instance and executes it with parameters specified in the [`commandLine`]. |
|
|
|
|
/// |
|
|
|
|
/// [`callerPlayer`] has to be specified and represents instigator of this command that will receive |
|
|
|
|
/// appropriate result/error messages. |
|
|
|
|
/// |
|
|
|
|
/// Returns `true` iff command was successfully executed. |
|
|
|
|
/// |
|
|
|
|
/// # Errors |
|
|
|
|
/// |
|
|
|
|
/// Doesn't log any errors, but can complain about errors in name or parameters to |
|
|
|
|
/// the [`callerPlayer`] |
|
|
|
|
public final function bool HandleInputWith(Parser parser, EPlayer callerPlayer) { |
|
|
|
|
local bool errorOccured; |
|
|
|
|
local Command commandInstance; |
|
|
|
|
local Command.CallData callData; |
|
|
|
|
local CommandCallPair callPair; |
|
|
|
|
|
|
|
|
|
if (parser == none) return; |
|
|
|
|
if (callerPlayer == none) return; |
|
|
|
|
if (!parser.Ok()) return; |
|
|
|
|
controller = callerPlayer.GetController(); |
|
|
|
|
if (controller == none) return; |
|
|
|
|
if (parser == none) return false; |
|
|
|
|
if (callerPlayer == none) return false; |
|
|
|
|
if (!parser.Ok()) return false; |
|
|
|
|
|
|
|
|
|
steamID = controller.GetPlayerIDHash(); |
|
|
|
|
for (i = 0; i < allowedPlayers.length; i += 1) |
|
|
|
|
{ |
|
|
|
|
if (allowedPlayers[i] == steamID) |
|
|
|
|
{ |
|
|
|
|
foundID = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (!foundID) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
callPair = ParseCommandCallPairWith(parser); |
|
|
|
|
commandInstance = GetCommand(callPair.commandName); |
|
|
|
|
if ( commandInstance == none |
|
|
|
|
&& callerPlayer != none && callerPlayer.IsExistent()) |
|
|
|
|
{ |
|
|
|
|
if (commandInstance == none && callerPlayer != none && callerPlayer.IsExistent()) { |
|
|
|
|
callerPlayer |
|
|
|
|
.BorrowConsole() |
|
|
|
|
.Flush() |
|
|
|
|
.Say(F("{$TextFailure Command not found!}")); |
|
|
|
|
} |
|
|
|
|
if (parser.Ok() && commandInstance != none) |
|
|
|
|
{ |
|
|
|
|
callData = commandInstance |
|
|
|
|
.ParseInputWith(parser, callerPlayer, callPair.subCommandName); |
|
|
|
|
commandInstance.Execute(callData, callerPlayer); |
|
|
|
|
if (parser.Ok() && commandInstance != none) { |
|
|
|
|
callData = commandInstance.ParseInputWith(parser, callerPlayer, callPair.subCommandName); |
|
|
|
|
errorOccured = 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function bool HandleCommands( |
|
|
|
|
EPlayer sender, |
|
|
|
|
MutableText message, |
|
|
|
|
bool teamMessage) |
|
|
|
|
{ |
|
|
|
|
local Parser parser; |
|
|
|
|
|
|
|
|
|
// We are only interested in messages that start with `chatCommandPrefix` |
|
|
|
|
parser = _.text.Parse(message); |
|
|
|
|
if (!parser.Match(chatCommandPrefix).Ok()) |
|
|
|
|
{ |
|
|
|
|
parser.FreeSelf(); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
// Pass input to command feature |
|
|
|
|
HandleInputWith(parser, sender); |
|
|
|
|
parser.FreeSelf(); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function HandleMutate(string command, PlayerController sendingPlayer) |
|
|
|
|
{ |
|
|
|
|
local Parser parser; |
|
|
|
|
local EPlayer sender; |
|
|
|
|
|
|
|
|
|
// A lot of other mutators use these commands |
|
|
|
|
if (command ~= "help") return; |
|
|
|
|
if (command ~= "version") return; |
|
|
|
|
if (command ~= "status") return; |
|
|
|
|
if (command ~= "credits") return; |
|
|
|
|
|
|
|
|
|
parser = _.text.ParseString(command); |
|
|
|
|
sender = _.players.FromController(sendingPlayer); |
|
|
|
|
HandleInputWith(parser, sender); |
|
|
|
|
sender.FreeSelf(); |
|
|
|
|
parser.FreeSelf(); |
|
|
|
|
_.memory.Free2(callPair.commandName, callPair.subCommandName); |
|
|
|
|
return errorOccured; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
defaultproperties |
|
|
|
|
{ |
|
|
|
|
defaultproperties { |
|
|
|
|
configClass = class'Commands' |
|
|
|
|
errCommandDuplicate = (l=LOG_Error,m="Command `%1` is already registered with name '%2'. Command `%3` with the same name will be ignored.") |
|
|
|
|
} |