diff --git a/sources/BaseAPI/Global.uc b/sources/BaseAPI/Global.uc
index e87cb2f..f53553f 100644
--- a/sources/BaseAPI/Global.uc
+++ b/sources/BaseAPI/Global.uc
@@ -50,6 +50,7 @@ var public UserAPI users;
var public PlayersAPI players;
var public JsonAPI json;
var public SchedulerAPI scheduler;
+var public CommandAPI commands;
var public AvariceAPI avarice;
var public AcediaEnvironment environment;
@@ -92,6 +93,7 @@ protected function Initialize() {
players = PlayersAPI(memory.Allocate(class'PlayersAPI'));
scheduler = SchedulerAPI(memory.Allocate(class'SchedulerAPI'));
avarice = AvariceAPI(memory.Allocate(class'AvariceAPI'));
+ commands = CommandAPI(memory.Allocate(class'CommandAPI'));
environment = AcediaEnvironment(memory.Allocate(class'AcediaEnvironment'));
}
@@ -112,6 +114,7 @@ public function DropCoreAPI() {
players = none;
json = none;
scheduler = none;
+ commands = none;
avarice = none;
default.myself = none;
}
diff --git a/sources/LevelAPI/Features/Commands/CommandAPI.uc b/sources/LevelAPI/Features/Commands/CommandAPI.uc
new file mode 100644
index 0000000..8b46965
--- /dev/null
+++ b/sources/LevelAPI/Features/Commands/CommandAPI.uc
@@ -0,0 +1,114 @@
+/**
+ * Author: dkanus
+ * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
+ * License: GPL
+ * Copyright 2023 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class CommandAPI extends AcediaObject;
+
+var private Commands_Feature commandsFeature;
+
+// DO NOT CALL MANUALLY
+public final /*internal*/ function _reloadFeature()
+{
+ if (commandsFeature != none) {
+ commandsFeature.FreeSelf();
+ commandsFeature = none;
+ }
+ commandsFeature = Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
+}
+
+/// Checks if `Commands_Feature` is enabled, which is required for this API to be functional.
+public final function bool AreCommandsEnabled() {
+ // `Commands_Feature` is responsible for updating us with an actually enabled instance
+ return (commandsFeature != none);
+}
+
+/// Registers given command class, making it available via `Execute()`.
+///
+/// Returns `true` if command was successfully registered and `false` otherwise`.
+///
+/// # Errors
+///
+/// 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 commandClass) {
+ if (commandsFeature != none) {
+ return commandsFeature.RegisterCommand(commandClass);
+ }
+ return false;
+}
+
+/// 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 commandClass) {
+ if (commandsFeature != none) {
+ commandsFeature.RemoveCommand(commandClass);
+ }
+}
+
+/// 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 Execute(BaseText commandLine, EPlayer callerPlayer) {
+ if (commandsFeature != none) {
+ commandsFeature.HandleInput(commandLine, 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 Execute_S(string commandLine, EPlayer callerPlayer) {
+ local MutableText wrapper;
+
+ if (commandsFeature != none) {
+ wrapper = _.text.FromStringM(commandLine);
+ commandsFeature.HandleInput(wrapper, callerPlayer);
+ _.memory.Free(wrapper);
+ }
+}
+
+defaultproperties {
+}
\ No newline at end of file
diff --git a/sources/LevelAPI/Features/Commands/Commands.uc b/sources/LevelAPI/Features/Commands/Commands.uc
index 35138ca..82f5ba7 100644
--- a/sources/LevelAPI/Features/Commands/Commands.uc
+++ b/sources/LevelAPI/Features/Commands/Commands.uc
@@ -1,6 +1,8 @@
/**
- * Config object for `Commands_Feature`.
- * Copyright 2021-2022 Anton Tarasenko
+ * Author: dkanus
+ * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
+ * License: GPL
+ * Copyright 2021-2023 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@@ -21,64 +23,38 @@ class Commands extends FeatureConfig
perobjectconfig
config(AcediaSystem);
-var public config bool useChatInput;
-var public config bool useMutateInput;
-var public config string chatCommandPrefix;
-var public config array allowedPlayers;
+var public config bool useChatInput;
+var public config bool useMutateInput;
+var public config string chatCommandPrefix;
-protected function HashTable ToData()
-{
- local int i;
+protected function HashTable ToData() {
local HashTable data;
- local ArrayList playerList;
data = __().collections.EmptyHashTable();
data.SetBool(P("useChatInput"), useChatInput, true);
data.SetBool(P("useMutateInput"), useMutateInput, true);
data.SetString(P("chatCommandPrefix"), chatCommandPrefix);
- playerList = _.collections.EmptyArrayList();
- for (i = 0; i < allowedPlayers.length; i += 1) {
- playerList.AddString(allowedPlayers[i]);
- }
- data.SetItem(P("allowedPlayers"), playerList);
- playerList.FreeSelf();
return data;
}
-protected function FromData(HashTable source)
-{
- local int i;
- local ArrayList playerList;
-
+protected function FromData(HashTable source) {
if (source == none) {
return;
}
- useChatInput = source.GetBool(P("useChatInput"));
- useMutateInput = source.GetBool(P("useMutateInput"));
- chatCommandPrefix = source.GetString(P("chatCommandPrefix"), "!");
- playerList = source.GetArrayList(P("allowedPlayers"));
- allowedPlayers.length = 0;
- if (playerList == none) {
- return;
- }
- for (i = 0; i < playerList.GetLength(); i += 1) {
- allowedPlayers[allowedPlayers.length] = playerList.GetString(i);
- }
- playerList.FreeSelf();
+ useChatInput = source.GetBool(P("useChatInput"));
+ useMutateInput = source.GetBool(P("useMutateInput"));
+ chatCommandPrefix = source.GetString(P("chatCommandPrefix"), "!");
}
-protected function DefaultIt()
-{
- useChatInput = true;
- useMutateInput = true;
- chatCommandPrefix = "!";
- allowedPlayers.length = 0;
+protected function DefaultIt() {
+ useChatInput = true;
+ useMutateInput = true;
+ chatCommandPrefix = "!";
}
-defaultproperties
-{
+defaultproperties {
configName = "AcediaSystem"
- useChatInput = true
- useMutateInput = true
- chatCommandPrefix = "!"
+ useChatInput = true
+ useMutateInput = true
+ chatCommandPrefix = "!"
}
\ No newline at end of file
diff --git a/sources/LevelAPI/Features/Commands/Commands_Feature.uc b/sources/LevelAPI/Features/Commands/Commands_Feature.uc
index 3b41566..30c0ec3 100644
--- a/sources/LevelAPI/Features/Commands/Commands_Feature.uc
+++ b/sources/LevelAPI/Features/Commands/Commands_Feature.uc
@@ -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,100 +21,72 @@
*/
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
+// Delimiters that always separate command name from it's parameters
var private array commandDelimiters;
-// Registered commands, recorded as (, ) pairs.
-// Keys should be deallocated when their entry is removed.
+// Registered commands, recorded as (, ) pairs.
+// Keys should be deallocated when their entry is removed.
var private HashTable registeredCommands;
-// `HashTable` of "" <-> `ArrayList` of commands pairs
-// to allow quick fetch of commands belonging to a single group
+// `HashTable` of "" <-> `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`.
-// Default is `true`.
+// 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.
-// Default is `true`.
+// 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 "!".
+// 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 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()
-{
- registeredCommands = _.collections.EmptyHashTable();
- groupedCommands = _.collections.EmptyHashTable();
+protected function OnEnabled() {
+ registeredCommands = _.collections.EmptyHashTable();
+ groupedCommands = _.collections.EmptyHashTable();
RegisterCommand(class'ACommandHelp');
- // Macro selector
+ // Macro selector
commandDelimiters[0] = _.text.FromString("@");
- // Key selector
+ // Key selector
commandDelimiters[1] = _.text.FromString("#");
- // Player array (possibly JSON array)
+ // Player array (possibly JSON array)
commandDelimiters[2] = _.text.FromString("[");
- // Negation of the selector
+ // 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,23 +102,19 @@ protected function OnEnabled()
}
}
-protected function OnDisabled()
-{
+protected function OnDisabled() {
if (useChatInput) {
_.chat.OnMessage(self).Disconnect();
}
if (useMutateInput) {
_server.unreal.mutator.OnMutate(self).Disconnect();
}
- useChatInput = false;
- useMutateInput = false;
- _.memory.Free(registeredCommands);
- _.memory.Free(groupedCommands);
- _.memory.Free(chatCommandPrefix);
- _.memory.FreeMany(commandDelimiters);
- registeredCommands = none;
- groupedCommands = none;
- chatCommandPrefix = none;
+ useChatInput = false;
+ useMutateInput = false;
+ _.memory.Free3(registeredCommands, groupedCommands, chatCommandPrefix);
+ registeredCommands = none;
+ groupedCommands = none;
+ chatCommandPrefix = none;
commandDelimiters.length = 0;
}
@@ -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()
-{
- local Text autoConfig;
- local Commands_Feature feature;
+// 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;
+}
- if (!IsEnabled())
- {
+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 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()) {
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 commandClass)
-{
- local Text commandName, groupName;
+/// 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 commandClass) {
+ local Text commandName, groupName;
local ArrayList groupArray;
- local Command newCommandInstance, existingCommandInstance;
+ 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();
+ 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 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,43 +331,31 @@ public final function RegisterCommand(class 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 commandClass)
-{
- local int i;
- local CollectionIterator iter;
- local Command nextCommand;
- local Text nextCommandName;
- local array commandGroup;
- local array keysToRemove;
+/// 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 commandClass) {
+ local int i;
+ local CollectionIterator iter;
+ local Command nextCommand;
+ local Text nextCommandName;
+ local array commandGroup;
+ local array keysToRemove;
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 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 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 GetCommandNames()
-{
+/// Returns array of names of all available commands.
+public final function array GetCommandNames() {
local array emptyResult;
if (registeredCommands != none) {
@@ -417,26 +401,18 @@ public final function array 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 GetCommandNamesInGroup(BaseText groupName)
-{
- local int i;
- local ArrayList groupArray;
- local Command nextCommand;
- local array result;
+/// Returns array of names of all available commands belonging to the group [`groupName`].
+public final function array GetCommandNamesInGroup(BaseText groupName) {
+ local int i;
+ local ArrayList groupArray;
+ local Command nextCommand;
+ local array result;
if (groupedCommands == none) return result;
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 GetCommandNamesInGroup(BaseText groupName)
return result;
}
-/**
- * Returns all available command groups' names.
- *
- * @return Array of all available command groups' names.
- */
-public final function array GetGroupsNames()
-{
+/// Returns all available command groups' names.
+public final function array GetGroupsNames() {
local array emptyResult;
if (groupedCommands != none) {
@@ -461,158 +432,75 @@ public final function array 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;
- 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;
-
- steamID = controller.GetPlayerIDHash();
- for (i = 0; i < allowedPlayers.length; i += 1)
- {
- if (allowedPlayers[i] == steamID)
- {
- foundID = true;
- break;
- }
- }
- if (!foundID) {
- return;
- }
+/// 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 false;
+ if (callerPlayer == none) return false;
+ if (!parser.Ok()) return false;
+
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);
+ _.memory.Free2(callPair.commandName, callPair.subCommandName);
+ return errorOccured;
}
-// 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();
-}
-
-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.")
}
\ No newline at end of file