/**
* 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;
/// Possible statuses of command locks.
///
/// Command locks can prevent certain commands from being used.
/// A good example is having a "cheat" lock that commands can check to before
/// doing anything that can be considered cheating.
/// Their purpose is to track the "purity" of the game matches
/// (e.g. whether cheats were used).
enum LockStatus {
/// Lock is currently closed, preventing commands from doing actions that
/// might require it opened.
/// Lock can still be opened.
LS_Closed,
/// Lock is currently opened, allowing commands to perform actions that
/// might require it opened.
/// Lock can still be closed.
LS_Open,
/// Lock is currently closed, preventing commands from doing actions that
/// might require it opened.
/// Lock can no longer be opened during the game session.
LS_ClosedFixed,
/// Lock is currently opened, allowing commands to perform actions that
/// might require it opened.
/// Lock can no longer be closed during the game session.
/// For "cheat" lock, for example, it can happen if someone has already
/// used cheats.
LS_OpenFixed
};
/// Describes possible outcomes of starting a voting by its name
enum StartVotingResult {
/// Voting was successfully started.
SVR_Success,
/// There is no people that are allowed to vote.
SVR_NoVoters,
/// Voting itself decided to reject being started, most likely because of
/// the specified arguments.
SVR_Rejected,
/// Voting wasn't started because another one was still in progress.
SVR_AlreadyInProgress,
/// Voting wasn't started because voting with that name hasn't been
/// registered.
SVR_UnknownVoting,
/// `CommandAPI` isn't functioning properly.
SVR_InvalidState
};
/// Struct for storing the result of "resolving" [`Command`] access for
/// some user.
struct CommandConfigInfo {
/// Instance of the command. If it equals to `none`, then [`configName`]
/// will also equal `none`.
var public Command instance;
/// Config that determines permissions for the command [`instance`].
/// Can equal to `none` if:
///
/// * [`Command`] class didn't setup a custom permissions config.
/// * Using provided instance is forbidden
/// (in this case `usageForbidden` will be set to `true`).
var public CommandPermissions config;
/// Set to `true` in case using provided command if forbidden.
var public bool usageForbidden;
};
/// Struct for storing the result of "resolving" [`Voting`] access for
/// some user.
struct VotingConfigInfo {
/// [`Voting`]'s class. If it equals to `none`, then [`configName`]
/// will also equal `none`.
var public class votingClass;
/// Config that determines permissions for the [`votingClass`].
/// Can equal to `none` if using provided instance is forbidden
/// (in this case `usageForbidden` will be set to `true`).
var public VotingPermissions config;
/// Set to `true` in case using provided voting if forbidden.
var public bool usageForbidden;
};
/// Internal enum that describes all types of scheduled jobs that this API can
/// produce.
enum AsyncJobType {
CAJT_AddCommand,
CAJT_AuthorizeCommand,
CAJT_AddVoting,
CAJT_AuthorizeVoting
};
/// Internal struct that can describe data for any scheduled job that this API
/// can produce.
struct AsyncTask {
var public AsyncJobType type;
// For all jobs
var public Text entityName;
// For `CAJT_Add...`
var public class entityClass;
// For `CAJT_Authorize`
var public Text userGroup;
// For `CAJT_Authorize...`
var public Text configName;
};
/// Internal struct for storing all the utility objects that `Commands_Feature`
/// uses.
struct CommandFeatureTools {
var public CommandsTool commands;
var public VotingsTool votings;
};
/// Internal struct for representing the result of the resolve command by either
/// [`CommandsTool`] or [`VotingsTool`].
///
/// Defined in `CommandAPI` instead of their base [`CmdItemsTool`] to avoid
/// compiler issues with resolving `dependson`-graph.
struct ItemConfigInfo {
var public AcediaObject instance;
var public class class;
// Name of config that determines permissions for the item [`instance`]
var public Text configName;
};
/// Classes registered to be added in async way
var private array pendingAsyncJobs;
/// Job that is supposed to register pending commands, will be asking for new
/// jobs from [`pendingAsyncJobs`] once it completes its current task.
var private CommandRegistrationJob registeringJob;
/// Saves `HashTable` with command locks.
/// Locks are simply boolean switches that mark for commands whether they
/// can be executed.
///
/// Lock is considered "unlocked" if this `HashTable` stores `true` at the key
/// with its name and `false` otherwise.
var private HashTable commandLocks;
var private CommandsTool commandsTool;
var private VotingsTool votingsTool;
var private int commandsToolLifeVersion;
var private int votingsToolLifeVersion;
var protected CommandsAPI_OnCommandAdded_Signal onCommandAddedSignal;
var protected CommandsAPI_OnCommandRemoved_Signal onCommandRemovedSignal;
var protected CommandsAPI_OnVotingAdded_Signal onVotingAddedSignal;
var protected CommandsAPI_OnVotingRemoved_Signal onVotingRemovedSignal;
protected function Constructor() {
onCommandAddedSignal = CommandsAPI_OnCommandAdded_Signal(
_.memory.Allocate(class'CommandsAPI_OnCommandAdded_Signal'));
onCommandRemovedSignal = CommandsAPI_OnCommandRemoved_Signal(
_.memory.Allocate(class'CommandsAPI_OnCommandRemoved_Signal'));
onVotingAddedSignal = CommandsAPI_OnVotingAdded_Signal(
_.memory.Allocate(class'CommandsAPI_OnVotingAdded_Signal'));
onVotingRemovedSignal = CommandsAPI_OnVotingRemoved_Signal(
_.memory.Allocate(class'CommandsAPI_OnVotingRemoved_Signal'));
}
protected function Finalizer() {
_.memory.Free(onCommandAddedSignal);
_.memory.Free(onCommandRemovedSignal);
_.memory.Free(onVotingAddedSignal);
_.memory.Free(onVotingRemovedSignal);
onCommandAddedSignal = none;
onCommandRemovedSignal = none;
onVotingAddedSignal = none;
onVotingRemovedSignal = none;
}
/// Signal that will be emitted when a new [`Command`] class is successfully
/// added through this API.
///
/// # Slot description
///
/// bool (class addedClass, Text usedName)
///
/// ## Parameters
///
/// * [`addedClass`]: Class of the command that got added.
/// * [`usedName`]: Name, under which command class was added.
public /*signal*/ function CommandsAPI_OnCommandAdded_Slot OnCommandAdded(AcediaObject receiver) {
return CommandsAPI_OnCommandAdded_Slot(onCommandAddedSignal.NewSlot(receiver));
}
/// Signal that will be emitted when a [`Command`] class is removed through
/// this API.
///
/// # Slot description
///
/// bool (class removedClass)
///
/// ## Parameters
///
/// * [`removedClass`]: Class of the command that got removed.
public /*signal*/ function CommandsAPI_OnCommandRemoved_Slot OnCommandRemoved(
AcediaObject receiver
) {
return CommandsAPI_OnCommandRemoved_Slot(onCommandRemovedSignal.NewSlot(receiver));
}
/// Signal that will be emitted when a new [`Voting`] class is successfully
/// added through this API.
///
/// # Slot description
///
/// bool (class addedClass, Text usedName)
///
/// ## Parameters
///
/// * [`addedClass`]: Class of the voting that got added.
/// * [`usedName`]: Name, under which voting class was added.
public /*signal*/ function CommandsAPI_OnVotingAdded_Slot OnVotingAdded(AcediaObject receiver) {
return CommandsAPI_OnVotingAdded_Slot(onVotingAddedSignal.NewSlot(receiver));
}
/// Signal that will be emitted when a [`Voting`] class is removed through
/// this API.
///
/// # Slot description
///
/// bool (class removedClass)
///
/// ## Parameters
///
/// * [`removedClass`]: Class of the voting that got removed.
public /*signal*/ function CommandsAPI_OnVotingRemoved_Slot OnVotingRemoved(AcediaObject receiver) {
return CommandsAPI_OnVotingRemoved_Slot(onVotingRemovedSignal.NewSlot(receiver));
}
/// Checks if `Commands_Feature` is enabled, which is required for this API
/// to be functional.
public final function bool AreCommandsEnabled() {
local Commands_Feature feature;
feature = Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
_.memory.Free(feature);
// We can still compare to `none` even after deallocation to see if
// `GetEnabledInstance()` returned a valid instance.
return (feature != none);
}
/// Returns array of names of all available commands.
///
/// Resulting array cannot contain duplicates.
public final function array GetAllCommandNames() {
local array emptyResult;
if (VerifyCommandsTool()) {
return commandsTool.GetItemsNames();
}
return emptyResult;
}
/// Registers given command class, making it available via [`Execute()`].
///
/// Optionally a [`BaseText`] can be specified to be used as this command's name
/// (the main name, that command aliases are to be resolved into).
/// If name parameter is omitted (specified as `none`) or constitutes an empty
/// [`BaseText`], a command's preferred name will be used.
///
/// If command name is specified as `none`, then "default" will be
/// used instead. For non-`none` values, invalid name (according to
/// [`BaseText::IsValidName()`] method) will also prevent the group from being
/// authorized.
///
/// # 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 command class won't be registered.
///
/// If `commandClass` provides command with an empty name (and it isn't
/// overridden by the argument) - a warning will be logged and command class
/// won't be registered.
public final function bool AddCommand(
class commandClass,
optional BaseText commandName
) {
local bool result;
local Text immutableCommandName;
if (commandClass == none) {
return false;
}
if (VerifyCommandsTool()) {
if (commandName != none) {
immutableCommandName = commandName.Copy();
} else {
immutableCommandName = commandClass.static.GetPreferredName();
}
result = commandsTool.AddItemClass(commandClass, immutableCommandName);
if (result) {
onCommandAddedSignal.Emit(commandClass, immutableCommandName);
}
_.memory.Free(immutableCommandName);
}
return false;
}
/// Registers given command class, making it available via [`Execute()`].
///
/// Optionally a [`BaseText`] can be specified to be used as this command's name
/// (the main name, that command aliases are to be resolved into).
/// If name parameter is omitted (specified as empty [`string`]), a command's
/// default name (defined via its [`CommandDataBuilder`]) will be used.
///
/// Invalid name (according to [`BaseText::IsValidName()`] method) will prevent
/// the [`Command`] from being authorized.
///
/// # 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 command class won't be registered.
///
/// If `commandClass` provides command with an empty name (and it isn't
/// overridden by the argument) - a warning will be logged and command class
/// won't be registered.
public final function bool AddCommand_S(
class commandClass,
optional string commandName
) {
local bool result;
local MutableText wrapper;
if (commandName != "") {
wrapper = _.text.FromStringM(commandName);
}
result = AddCommand(commandClass, wrapper);
_.memory.Free(wrapper);
return result;
}
/// Registers given command class asynchronously, making it available
/// via [`Execute()`].
///
/// Doesn't register commands immediately, instead scheduling it to be done at
/// a later moment in time. This can help to reduce amount of work we do every
/// tick during server startup, therefore avoiding crashed due to the faulty
/// infinite loop detection. Different async calls from [`CommandAPI`] are
/// guaranteed to be handled in the order they were called.
///
/// Optionally a [`BaseText`] can be specified to be used as this command's name
/// (the main name, that command aliases are to be resolved into).
/// If name parameter is omitted (specified as `none`) or constitutes an empty
/// [`BaseText`], a command's default name (defined via its
/// [`CommandDataBuilder`]) will be used.
///
/// If command name is specified as `none`, then "default" will be
/// used instead. For non-`none` values, invalid name (according to
/// [`BaseText::IsValidName()`] method) will also prevent the group from being
/// authorized.
///
/// # 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 command class won't be registered.
///
/// If [`commandClass`] provides command with an empty name (and it isn't
/// overridden by the argument) - a warning will be logged and command class
/// won't be registered.
///
/// Warnings might be logged *after* the call itself, when the command class is
/// actually added.
public final function AddCommandAsync(
class commandClass,
optional BaseText commandName
) {
local AsyncTask newJob;
if (!VerifyCommandsTool()) {
return;
}
newJob.type = CAJT_AddCommand;
newJob.entityClass = commandClass;
if (commandName != none) {
newJob.entityName = commandName.Copy();
}
pendingAsyncJobs[pendingAsyncJobs.length] = newJob;
if (registeringJob == none || registeringJob.IsCompleted()) {
_.memory.Free(registeringJob);
registeringJob = CommandRegistrationJob(_.memory.Allocate(class'CommandRegistrationJob'));
_.scheduler.AddJob(registeringJob);
}
}
/// Registers given command class asynchronously, making it available
/// via [`Execute()`].
///
/// Doesn't register commands immediately, instead scheduling it to be done at
/// a later moment in time. This can help to reduce amount of work we do every
/// tick during server startup, therefore avoiding crashed due to the faulty
/// infinite loop detection. Different async calls from [`CommandAPI`] are
/// guaranteed to be handled in the order they were called.
///
/// Optionally a [`BaseText`] can be specified to be used as this command's name
/// (the main name, that command aliases are to be resolved into).
/// If name parameter is omitted (specified as empty [`string`]), a command's
/// default name (defined via its [`CommandDataBuilder`]) will be used.
///
/// Invalid name (according to [`BaseText::IsValidName()`] method) will prevent
/// the [`Command`] from being authorized.
///
/// # 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 command class won't be registered.
///
/// If [`commandClass`] provides command with an empty name (and it isn't
/// overridden by the argument) - a warning will be logged and command class
/// won't be registered.
///
/// Warnings might be logged *after* the call itself, when the command class is
/// actually added.
public final function AddCommandAsync_S(
class commandClass,
optional string commandName
) {
local MutableText wrapper;
if (commandName != "") {
wrapper = _.text.FromStringM(commandName);
}
AddCommandAsync(commandClass, wrapper);
_.memory.Free(wrapper);
}
/// Authorizes new user group to use the specified command, optionally
/// specifying name of the config (config's class is determined by the
/// [`Command`]'s class) that describes permissions of that group.
///
/// Method must be called after [`Command`] with a given name is added.
///
/// If config name is specified as `none`, then "default" will be
/// used instead. For non-`none` values, invalid name (according to
/// [`BaseText::IsValidName()`] method) will also prevent the group from being
/// authorized.
///
/// Function will return `true` if group was successfully authorized and
/// `false` otherwise (either group already authorized or no command with
/// specified name was added in the caller tool).
///
/// # Errors
///
/// If specified group was already authorized to use a command, then it
/// will log a warning message about it.
public function bool AuthorizeCommandUsage(
BaseText commandName,
BaseText groupName,
optional BaseText configName
) {
if (VerifyCommandsTool()) {
return commandsTool.AuthorizeUsage(commandName, groupName, configName);
}
return false;
}
/// Authorizes new user group to use the specified command, optionally
/// specifying name of the config (config's class is determined by the
/// [`Command`]'s class) that describes permissions of that group.
///
/// Method must be called after [`Command`] with a given name is added.
///
/// If this config name is specified as empty, then "default" will be
/// used instead. For non-empty values, an invalid name (according to
/// [`BaseText::IsValidName()`] method) will prevent the group from being
/// authorized.
///
/// # Errors
///
/// If specified group was already authorized to use a command, then it
/// will log a warning message about it.
public function bool AuthorizeCommandUsage_S(
string commandName,
string userGroupName,
optional string configName
) {
local bool result;
local MutableText wrapperVotingName, wrapperGroupName, wrapperConfigName;
wrapperVotingName = _.text.FromStringM(commandName);
wrapperGroupName = _.text.FromStringM(userGroupName);
wrapperConfigName = _.text.FromStringM(configName);
result = AuthorizeCommandUsage(wrapperVotingName, wrapperGroupName, wrapperConfigName);
_.memory.Free3(wrapperVotingName, wrapperGroupName, wrapperConfigName);
return result;
}
/// Authorizes new user group to use the specified command, optionally
/// specifying name of the config (config's class is determined by the
/// [`Command`]'s class) that describes permissions of that group.
///
/// Method must be called after [`Command`] with a given name is added.
///
/// Doesn't authorize commands immediately, instead scheduling it to be done at
/// a later moment in time. This can help to reduce amount of work we do every
/// tick during server startup, therefore avoiding crashed due to the faulty
/// infinite loop detection. Different async calls from [`CommandAPI`] are
/// guaranteed to be handled in the order they were called.
///
/// If config name is specified as `none`, then "default" will be
/// used instead. For non-`none` values, invalid name (according to
/// [`BaseText::IsValidName()`] method) will also prevent the group from being
/// authorized.
///
/// Function will return `true` if group was successfully authorized and
/// `false` otherwise (either group already authorized or no command with
/// specified name was added in the caller tool).
///
/// # Errors
///
/// If specified group was already authorized to use a command, then it
/// will log a warning message about it.
public function AuthorizeCommandUsageAsync(
BaseText commandName,
BaseText userGroupName,
optional BaseText configName
) {
local AsyncTask newTask;
if (!VerifyCommandsTool()) {
return;
}
newTask.type = CAJT_AuthorizeCommand;
if (commandName != none) {
newTask.entityName = commandName.Copy();
}
if (userGroupName != none) {
newTask.userGroup = userGroupName.Copy();
}
if (configName != none) {
newTask.configName = configName.Copy();
}
pendingAsyncJobs[pendingAsyncJobs.length] = newTask;
if (registeringJob == none || registeringJob.IsCompleted()) {
_.memory.Free(registeringJob);
registeringJob = CommandRegistrationJob(_.memory.Allocate(class'CommandRegistrationJob'));
_.scheduler.AddJob(registeringJob);
}
}
/// Authorizes new user group to use the specified command, optionally
/// specifying name of the config (config's class is determined by the
/// [`Command`]'s class) that describes permissions of that group.
///
/// Method must be called after [`Command`] with a given name is added.
///
/// Doesn't authorize commands immediately, instead scheduling it to be done at
/// a later moment in time. This can help to reduce amount of work we do every
/// tick during server startup, therefore avoiding crashed due to the faulty
/// infinite loop detection. Different async calls from [`CommandAPI`] are
/// guaranteed to be handled in the order they were called.
///
/// If this config name is specified as empty, then "default" will be
/// used instead. For non-empty values, an invalid name (according to
/// [`BaseText::IsValidName()`] method) will prevent the group from being
/// authorized.
///
/// Function will return `true` if group was successfully authorized and
/// `false` otherwise (either group already authorized or no command with
/// specified name was added in the caller tool).
///
/// # Errors
///
/// If specified group was already authorized to use a command, then it
/// will log a warning message about it.
public function AuthorizeCommandUsageAsync_S(
string commandName,
string userGroupName,
optional string configName
) {
local MutableText wrapperCommandName, wrapperGroupName, wrapperConfigName;
wrapperCommandName = _.text.FromStringM(commandName);
wrapperGroupName = _.text.FromStringM(userGroupName);
wrapperConfigName = _.text.FromStringM(configName);
AuthorizeCommandUsageAsync(wrapperCommandName, wrapperGroupName, wrapperConfigName);
_.memory.Free3(wrapperCommandName, wrapperGroupName, wrapperConfigName);
}
/// 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 does not have an efficient
/// implementation (it is linear on the current amount of commands).
///
/// Returns `true` if successfully removed registered [`Command`] class and
/// `false` otherwise (command wasn't registered).
public final function bool RemoveCommand(class commandClass) {
local bool result;
if (VerifyCommandsTool()) {
result = commandsTool.RemoveItemClass(commandClass);
if (result) {
onCommandRemovedSignal.Emit(commandClass);
}
}
return result;
}
/// Returns instance of the [`Command`] that was added under a specified name.
public final function Command GetCommand(BaseText commandName) {
local ItemConfigInfo intermediaryResult;
if (VerifyCommandsTool()) {
// `none` means we'll get instance + class, but without config,
// so nothing to deallocate in `intermediaryResult`
intermediaryResult = commandsTool.ResolveItem(commandName, none);
return Command(intermediaryResult.instance);
}
return none;
}
/// Returns instance of the [`Command`] that was added under a specified name.
public final function Command GetCommand_S(string commandName) {
local Command result;
local MutableText wrapper;
wrapper = _.text.FromStringM(commandName);
result = GetCommand(wrapper);
_.memory.Free(wrapper);
return result;
}
/// Returns pair of [`Command`] and config name based on a given
/// case-insensitive name and text ID of the caller player.
///
/// Function only returns `none` for [`Command`] instance if [`Command`] with
/// a given name wasn't found.
public final function CommandConfigInfo ResolveCommandForTextID(BaseText itemName, BaseText id) {
local ItemConfigInfo intermediaryResult;
local CommandConfigInfo result;
if (VerifyCommandsTool()) {
intermediaryResult = commandsTool.ResolveItem(itemName, id);
result.instance = Command(intermediaryResult.instance);
if (result.instance == none) {
return result;
}
if (intermediaryResult.configName == none) {
result.usageForbidden = true;
} else {
result.config = result.instance.LoadConfig(intermediaryResult.configName);
_.memory.Free(intermediaryResult.configName);
}
}
return result;
}
/// Returns pair of [`Command`] and config name based on a given
/// case-insensitive name and text ID of the caller player.
///
/// Function only returns `none` for [`Command`] instance if [`Command`] with
/// a given name wasn't found.
public final function CommandConfigInfo ResolveCommandForTextID_S(string itemName, string id) {
local CommandConfigInfo result;
local MutableText wrapperItemname, wrapperID;
wrapperItemname = _.text.FromStringM(itemName);
wrapperID = _.text.FromStringM(id);
result = ResolveCommandForTextID(wrapperItemname, wrapperID);
_.memory.Free2(wrapperItemname, wrapperID);
return result;
}
/// Returns pair of [`Command`] and config name based on a given
/// case-insensitive name and [`UserID`] of the caller player.
///
/// Function only returns `none` for [`Command`] instance if [`Command`] with
/// a given name wasn't found or provided id was `none`.
public final function CommandConfigInfo ResolveCommandForUserID(BaseText itemName, UserID id) {
local CommandConfigInfo result;
local Text textID;
if (itemName == none) return result;
if (id == none) return result;
textID = id.GetUniqueID();
if (textID == none) return result;
result = ResolveCommandForTextID(itemName, textID);
textID.FreeSelf();
return result;
}
/// Returns pair of [`Command`] and config name based on a given
/// case-insensitive name and [`UserID`] of the caller player.
///
/// Function only returns `none` for [`Command`] instance if [`Command`] with
/// a given name wasn't found or provided id was `none`.
public final function CommandConfigInfo ResolveCommandForUserID_S(string itemName, UserID id) {
local CommandConfigInfo result;
local MutableText wrapper;
wrapper = _.text.FromStringM(itemName);
result = ResolveCommandForUserID(wrapper, id);
_.memory.Free(wrapper);
return result;
}
/// Returns pair of [`Command`] and config name based on a given
/// case-insensitive name and [`User`] of the caller player.
///
/// Function only returns `none` for [`Command`] instance if [`Command`] with
/// a given name wasn't found or provided id was `none`.
public final function CommandConfigInfo ResolveCommandForUser(BaseText itemName, User user) {
local CommandConfigInfo result;
local UserID id;
if (itemName == none) return result;
if (user == none) return result;
id = user.GetID();
result = ResolveCommandForUserID(itemName, id);
_.memory.Free(id);
return result;
}
/// Returns pair of [`Command`] and config name based on a given
/// case-insensitive name and [`User`] of the caller player.
///
/// Function only returns `none` for [`Command`] instance if [`Command`] with
/// a given name wasn't found or provided id was `none`.
public final function CommandConfigInfo ResolveCommandForUser_S(string itemName, User user) {
local CommandConfigInfo result;
local MutableText wrapper;
wrapper = _.text.FromStringM(itemName);
result = ResolveCommandForUser(wrapper, user);
_.memory.Free(wrapper);
return result;
}
/// Returns all available command groups' names.
public final function array GetGroupsNames() {
local array emptyResult;
if (VerifyCommandsTool()) {
return commandsTool.GetGroupsNames();
}
return emptyResult;
}
/// Returns all available command groups' names.
public final function array GetGroupsNames_S() {
local array emptyResult;
if (VerifyCommandsTool()) {
return _.text.IntoStrings(commandsTool.GetGroupsNames());
}
return emptyResult;
}
/// Returns array of names of all available commands belonging to the specified
/// command group.
public final function array GetCommandNamesInGroup(BaseText groupName) {
local array emptyResult;
if (VerifyCommandsTool()) {
return commandsTool.GetCommandNamesInGroup(groupName);
}
return emptyResult;
}
/// Returns array of names of all available commands belonging to the specifie
/// command group.
public final function array GetCommandNamesInGroup_S(string groupName) {
local array result;
local MutableText wrapper;
if (VerifyCommandsTool()) {
wrapper = _.text.FromStringM(groupName);
result = _.text.IntoStrings(commandsTool.GetCommandNamesInGroup(wrapper));
_.memory.Free(wrapper);
}
return result;
}
/// Executes command based on the textual input with a given instigator.
///
/// Input should be provided in a form that players are expected to use.
/// For example, "mutate inventory @all" or "say !inventory @all" will both
/// translate into calling this method with "inventory @all" argument.
///
/// Command's instigator will receive appropriate result/error messages.
///
/// # Errors
///
/// Doesn't log any errors, but can complain about errors in name or parameters
/// to the [`instigator`].
public final function Execute(BaseText commandLine, EPlayer instigator) {
local Commands_Feature feature;
feature = Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
if (feature != none) {
feature.HandleInput(commandLine, instigator);
}
_.memory.Free(feature);
}
/// Executes command based on the textual input with a given instigator.
///
/// Input should be provided in a form that players are expected to use.
/// For example, "mutate inventory @all" or "say !inventory @all" will both
/// translate into calling this method with "inventory @all" argument.
///
/// Command's instigator will receive appropriate result/error messages.
///
/// # Errors
///
/// Doesn't log any errors, but can complain about errors in name or parameters
/// to the [`instigator`].
public final function Execute_S(string commandLine, EPlayer instigator) {
local MutableText wrapper;
local Commands_Feature feature;
feature = Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
if (feature != none) {
wrapper = _.text.FromStringM(commandLine);
feature.HandleInput(wrapper, instigator);
_.memory.Free(wrapper);
}
_.memory.Free(feature);
}
/// Returns current status of a lock with a given name.
public final function LockStatus GetLockStatus(BaseText lockName) {
local LockStatus result;
local Text lowerCaseName;
if (lockName == none) {
return LS_ClosedFixed;
}
if (commandLocks == none) {
commandLocks = _.collections.EmptyHashTable();
}
lowerCaseName = lockName.LowerCopy();
result = LockStatus(commandLocks.GetInt(lowerCaseName));
lowerCaseName.FreeSelf();
return result;
}
/// Returns current status of a lock with a given name.
public final function LockStatus GetLockStatus_S(string lockName) {
local LockStatus result;
local MutableText wrapper;
wrapper = _.text.FromStringM(lockName);
result = GetLockStatus(wrapper);
_.memory.Free(wrapper);
return result;
}
/// Sets new status for a lock with a given name.
///
/// Can fail and return `false` in case lock was already fixed in the opposite
/// state. Otherwise returns `true`.
public final function bool SetLockStatus(BaseText lockName, LockStatus newStatus) {
local LockStatus previousStatus;
local Text lowerCaseName;
if (lockName == none) {
return false;
}
if (commandLocks == none) {
commandLocks = _.collections.EmptyHashTable();
}
lowerCaseName = lockName.LowerCopy();
previousStatus = LockStatus(commandLocks.GetInt(lowerCaseName));
if (previousStatus != LS_OpenFixed) {
commandLocks.SetInt(lowerCaseName, int(newStatus));
lowerCaseName.FreeSelf();
return true;
}
lowerCaseName.FreeSelf();
return false;
}
/// Sets new status for a lock with a given name.
///
/// Can fail and return `false` in case lock was already fixed in the opposite
/// state. Otherwise returns `true`.
public final function bool SetLockStatus_S(string lockName, LockStatus newStatus) {
local bool result;
local MutableText wrapper;
wrapper = _.text.FromStringM(lockName);
result = SetLockStatus(wrapper, newStatus);
_.memory.Free(wrapper);
return result;
}
/// Closes a command lock with a given case-insensitive name, preventing it from
/// being opened again.
///
/// Can fail and return `false` in case lock was already fixed in the opened
/// state. Otherwise returns `true`.
public final function bool Lock(BaseText lockName) {
return SetLockStatus(lockName, LS_ClosedFixed);
}
/// Closes a command lock with a given case-insensitive name, preventing it from
/// being opened again.
///
/// Can fail and return `false` in case lock was already fixed in the opened
/// state. Otherwise returns `true`.
public final function bool Lock_S(string lockName) {
return SetLockStatus_S(lockName, LS_ClosedFixed);
}
/// Opens a command lock with a given case-insensitive name.
///
/// Lock can still be closed after successful execution of this command.
///
/// Can fail and return `false` in case lock was already fixed in the closed
/// state. Otherwise returns `true`.
public final function bool Unlock(BaseText lockName) {
return SetLockStatus(lockName, LS_Open);
}
/// Opens a command lock with a given case-insensitive name.
///
/// Lock can still be closed after successful execution of this command.
///
/// Can fail and return `false` in case lock was already fixed in the closed
/// state. Otherwise returns `true`.
public final function bool Unlock_S(string lockName) {
return SetLockStatus_S(lockName, LS_Open);
}
/// Opens a command lock with a given case-insensitive name, preventing it from
/// being closed again.
///
/// Can fail and return `false` in case lock was already fixed in the closed
/// state. Otherwise returns `true`.
public final function bool BreakOpenLock(BaseText lockName) {
return SetLockStatus(lockName, LS_OpenFixed);
}
/// Opens a command lock with a given case-insensitive name, preventing it from
/// being closed again.
///
/// Can fail and return `false` in case lock was already fixed in the closed
/// state. Otherwise returns `true`.
public final function bool BreakOpenLock_S(string lockName) {
return SetLockStatus_S(lockName, LS_OpenFixed);
}
/// Checks if a command lock with a given case-insensitive name is closed.
///
/// If lock is closed, method returns `true` (regardless of whether or not it is
/// fixed in closed state) and `false` if its currently open.
public final function bool IsLocked(BaseText lockName) {
local LockStatus currentStatus;
currentStatus = GetLockStatus(lockName);
return (currentStatus == LS_ClosedFixed || currentStatus == LS_Closed);
}
/// Checks if a command lock with a given case-insensitive name is closed.
///
/// If lock is closed, method returns `true` (regardless of whether or not it is
/// fixed in closed state) and `false` if its currently open.
public final function bool IsLocked_S(string lockName) {
local LockStatus currentStatus;
currentStatus = GetLockStatus_S(lockName);
return (currentStatus == LS_ClosedFixed || currentStatus == LS_Closed);
}
/// Returns array of names of all available commands.
///
/// Resulting array can contain duplicates, but only if the same voting class
/// was registered by several different names.
public final function array< class > GetAllVotingClasses() {
local int i;
local array< class > intermediaryResult;
local array< class > result;
if (VerifyVotingsTool()) {
intermediaryResult = votingsTool.GetAllItemClasses();
for (i = 0; i < intermediaryResult.length; i += 1) {
result[result.length] = class(intermediaryResult[i]);
}
}
return result;
}
/// Returns array of names of all available votings.
///
/// Resulting array cannot contain duplicates.
public final function array GetAllVotingsNames() {
local array emptyResult;
if (VerifyVotingsTool()) {
return votingsTool.GetItemsNames();
}
return emptyResult;
}
/// Registers given voting class, making it available via [`StartVoting()`].
///
/// Optionally a [`BaseText`] can be specified to be used as this voting's name.
/// If name parameter is omitted (specified as `none`) or constitutes an empty
/// [`BaseText`], a voting's preferred name will be used.
///
/// If voting name is specified as `none`, then "default" will be
/// used instead. For non-`none` values, invalid name (according to
/// [`BaseText::IsValidName()`] method) will also prevent the group from being
/// authorized.
///
/// # Errors
///
/// Returns `true` if voting was successfully registered and `false`
/// otherwise`.
///
/// If [`votingClass`] provides voting with a name that is already taken
/// (comparison is case-insensitive) by a different voting - a warning will be
/// logged and voting class won't be registered.
///
/// If votingClass` provides voting with an empty name (and it isn't
/// overridden by the argument) - a warning will be logged and voting class
/// won't be registered.
public final function bool AddVoting(
class votingClass,
optional BaseText votingName
) {
local bool result;
local Text immutableVotingName;
if (votingClass == none) {
return false;
}
if (VerifyVotingsTool()) {
if (votingName != none) {
immutableVotingName = votingName.Copy();
} else {
immutableVotingName = votingClass.static.GetPreferredName();
}
result = votingsTool.AddItemClass(votingClass, immutableVotingName);
if (result) {
onVotingAddedSignal.Emit(votingClass, immutableVotingName);
}
_.memory.Free(immutableVotingName);
}
return false;
}
/// Registers given voting class, making it available via [`StartVoting()`].
///
/// Optionally a [`BaseText`] can be specified to be used as this voting's name.
/// If name parameter is omitted (specified as `none`) or constitutes an empty
/// [`BaseText`], a voting's preferred name will be used.
///
/// Invalid name (according to [`BaseText::IsValidName()`] method) will prevent
/// the [`Voting`] from being authorized.
///
/// # Errors
///
/// Returns `true` if voting was successfully registered and `false`
/// otherwise`.
///
/// If [`votingClass`] provides voting with a name that is already taken
/// (comparison is case-insensitive) by a different voting - a warning will be
/// logged and voting class won't be registered.
///
/// If votingClass` provides voting with an empty name (and it isn't
/// overridden by the argument) - a warning will be logged and voting class
/// won't be registered.
public final function bool AddVoting_S(
class votingClass,
optional string votingName
) {
local bool result;
local MutableText wrapper;
if (votingName != "") {
wrapper = _.text.FromStringM(votingName);
}
result = AddVoting(votingClass, wrapper);
_.memory.Free(wrapper);
return result;
}
/// Registers given voting class, making it available via [`StartVoting()`].
///
/// Doesn't register voting immediately, instead scheduling it to be done at
/// a later moment in time. This can help to reduce amount of work we do every
/// tick during server startup, therefore avoiding crashed due to the faulty
/// infinite loop detection.
///
/// Optionally a [`BaseText`] can be specified to be used as this voting's name.
/// If name parameter is omitted (specified as `none`) or constitutes an empty
/// [`BaseText`], a voting's preferred name will be used.
///
/// If voting name is specified as `none`, then "default" will be
/// used instead. For non-`none` values, invalid name (according to
/// [`BaseText::IsValidName()`] method) will also prevent the group from being
/// authorized.
///
/// # Errors
///
/// Returns `true` if voting was successfully registered and `false`
/// otherwise`.
///
/// If [`votingClass`] provides voting with a name that is already taken
/// (comparison is case-insensitive) by a different voting - a warning will be
/// logged and voting class won't be registered.
///
/// If votingClass` provides voting with an empty name (and it isn't
/// overridden by the argument) - a warning will be logged and voting class
/// won't be registered.
public final function AddVotingAsync(
class votingClass,
optional BaseText votingName
) {
local AsyncTask newTask;
if (!VerifyVotingsTool()) {
return;
}
newTask.type = CAJT_AddVoting;
newTask.entityClass = votingClass;
if (votingName != none) {
newTask.entityName = votingName.Copy();
}
pendingAsyncJobs[pendingAsyncJobs.length] = newTask;
if (registeringJob == none || registeringJob.IsCompleted()) {
_.memory.Free(registeringJob);
registeringJob = CommandRegistrationJob(_.memory.Allocate(class'CommandRegistrationJob'));
_.scheduler.AddJob(registeringJob);
}
}
/// Registers given voting class, making it available via [`StartVoting()`].
///
/// Doesn't register votings immediately, instead scheduling it to be done at
/// a later moment in time. This can help to reduce amount of work we do every
/// tick during server startup, therefore avoiding crashed due to the faulty
/// infinite loop detection. Different async calls from [`CommandAPI`] are
/// guaranteed to be handled in the order they were called.
///
/// Optionally a [`BaseText`] can be specified to be used as this voting's name.
/// If name parameter is omitted (specified as `none`) or constitutes an empty
/// [`BaseText`], a voting's preferred name will be used.
///
/// Invalid name (according to [`BaseText::IsValidName()`] method) will prevent
/// the [`Voting`] from being authorized.
///
/// # Errors
///
/// Returns `true` if voting was successfully registered and `false`
/// otherwise`.
///
/// If [`votingClass`] provides voting with a name that is already taken
/// (comparison is case-insensitive) by a different voting - a warning will be
/// logged and voting class won't be registered.
///
/// If votingClass` provides voting with an empty name (and it isn't
/// overridden by the argument) - a warning will be logged and voting class
/// won't be registered.
public final function AddVotingAsync_S(
class votingClass,
optional string votingName
) {
local MutableText wrapper;
if (votingName != "") {
wrapper = _.text.FromStringM(votingName);
}
AddVotingAsync(votingClass, wrapper);
_.memory.Free(wrapper);
}
/// Authorizes new user group to use the specified voting, optionally
/// specifying name of the config (config's class is determined by the
/// [`Voting`]'s class) that describes permissions of that group.
///
/// Method must be called after [`Voting`] with a given name is added.
///
/// If config name is specified as `none`, then "default" will be
/// used instead. For non-`none` values, invalid name (according to
/// [`BaseText::IsValidName()`] method) will also prevent the group from being
/// authorized.
///
/// Function will return `true` if group was successfully authorized and
/// `false` otherwise (either group already authorized or no voting with
/// specified name was added in the caller tool).
///
/// # Errors
///
/// If specified group was already authorized to use a voting, then it
/// will log a warning message about it.
public function bool AuthorizeVotingUsage(
BaseText votingName,
BaseText groupName,
optional BaseText configName
) {
if (VerifyVotingsTool()) {
return votingsTool.AuthorizeUsage(votingName, groupName, configName);
}
return false;
}
/// Authorizes new user group to use the specified voting, optionally
/// specifying name of the config (config's class is determined by the
/// [`Voting`]'s class) that describes permissions of that group.
///
/// Method must be called after [`Voting`] with a given name is added.
///
/// If this config name is specified as empty, then "default" will be
/// used instead. For non-empty values, an invalid name (according to
/// [`BaseText::IsValidName()`] method) will prevent the group from being
/// authorized.
///
/// Function will return `true` if group was successfully authorized and
/// `false` otherwise (either group already authorized or no voting with
/// specified name was added in the caller tool).
///
/// # Errors
///
/// If specified group was already authorized to use a voting, then it
/// will log a warning message about it.
public function bool AuthorizeVotingUsage_S(
string votingName,
string userGroupName,
optional string configName
) {
local bool result;
local MutableText wrapperVotingName, wrapperGroupName, wrapperConfigName;
wrapperVotingName = _.text.FromStringM(votingName);
wrapperGroupName = _.text.FromStringM(userGroupName);
wrapperConfigName = _.text.FromStringM(configName);
result = AuthorizeVotingUsage(wrapperVotingName, wrapperGroupName, wrapperConfigName);
_.memory.Free3(wrapperVotingName, wrapperGroupName, wrapperConfigName);
return result;
}
/// Authorizes new user group to use the specified voting, optionally
/// specifying name of the config (config's class is determined by the
/// [`Voting`]'s class) that describes permissions of that group.
///
/// Method must be called after [`Voting`] with a given name is added.
///
/// Doesn't authorize votings immediately, instead scheduling it to be done at
/// a later moment in time. This can help to reduce amount of work we do every
/// tick during server startup, therefore avoiding crashed due to the faulty
/// infinite loop detection. Different async calls from [`CommandAPI`] are
/// guaranteed to be handled in the order they were called.
///
/// If config name is specified as `none`, then "default" will be
/// used instead. For non-`none` values, invalid name (according to
/// [`BaseText::IsValidName()`] method) will also prevent the group from being
/// authorized.
///
/// Function will return `true` if group was successfully authorized and
/// `false` otherwise (either group already authorized or no voting with
/// specified name was added in the caller tool).
///
/// # Errors
///
/// If specified group was already authorized to use a voting, then it
/// will log a warning message about it.
public function AuthorizeVotingUsageAsync(
BaseText votingName,
BaseText userGroupName,
optional BaseText configName
) {
local AsyncTask newTask;
if (!VerifyVotingsTool()) {
return;
}
newTask.type = CAJT_AuthorizeVoting;
if (votingName != none) {
newTask.entityName = votingName.Copy();
}
if (userGroupName != none) {
newTask.userGroup = userGroupName.Copy();
}
if (configName != none) {
newTask.configName = configName.Copy();
}
pendingAsyncJobs[pendingAsyncJobs.length] = newTask;
if (registeringJob == none || registeringJob.IsCompleted()) {
_.memory.Free(registeringJob);
registeringJob = CommandRegistrationJob(_.memory.Allocate(class'CommandRegistrationJob'));
_.scheduler.AddJob(registeringJob);
}
}
/// Authorizes new user group to use the specified voting, optionally
/// specifying name of the config (config's class is determined by the
/// [`Voting`]'s class) that describes permissions of that group.
///
/// Method must be called after [`Voting`] with a given name is added.
///
/// Doesn't authorize votings immediately, instead scheduling it to be done at
/// a later moment in time. This can help to reduce amount of work we do every
/// tick during server startup, therefore avoiding crashed due to the faulty
/// infinite loop detection. Different async calls from [`CommandAPI`] are
/// guaranteed to be handled in the order they were called.
///
/// If this config name is specified as empty, then "default" will be
/// used instead. For non-empty values, an invalid name (according to
/// [`BaseText::IsValidName()`] method) will prevent the group from being
/// authorized.
///
/// Function will return `true` if group was successfully authorized and
/// `false` otherwise (either group already authorized or no voting with
/// specified name was added in the caller tool).
///
/// # Errors
///
/// If specified group was already authorized to use a voting, then it
/// will log a warning message about it.
public function AuthorizeVotingUsageAsync_S(
string votingName,
string userGroupName,
optional string configName
) {
local MutableText wrapperVotingName, wrapperGroupName, wrapperConfigName;
wrapperVotingName = _.text.FromStringM(votingName);
wrapperGroupName = _.text.FromStringM(userGroupName);
wrapperConfigName = _.text.FromStringM(configName);
AuthorizeVotingUsageAsync(wrapperVotingName, wrapperGroupName, wrapperConfigName);
_.memory.Free3(wrapperVotingName, wrapperGroupName, wrapperConfigName);
}
/// Removes voting of given class from the list of registered votings.
///
/// Removing once registered votings is not an action that is expected to
/// be performed under normal circumstances and does not have an efficient
/// implementation (it is linear on the current amount of votings).
///
/// Returns `true` if successfully removed registered [`Voting`] class and
/// `false` otherwise (voting wasn't registered).
public final function bool RemoveVoting(class votingClass) {
local bool result;
if (VerifyVotingsTool()) {
result = votingsTool.RemoveItemClass(votingClass);
if (result) {
onVotingRemovedSignal.Emit(votingClass);
}
}
return result;
}
/// Returns class of the [`Voting`] that was added under a specified name.
public final function class GetVotingClass(BaseText itemName) {
local ItemConfigInfo intermediaryResult;
if (VerifyVotingsTool()) {
intermediaryResult = votingsTool.ResolveItem(itemName, none);
return class(intermediaryResult.class);
}
return none;
}
/// Returns pair of [`Voting`] and config name based on a given
/// case-insensitive name and text ID of the caller player.
///
/// Function only returns `none` for [`Voting`] instance if [`Voting`] with
/// a given name wasn't found.
public final function VotingConfigInfo ResolveVotingForTextID(BaseText itemName, BaseText id) {
local ItemConfigInfo intermediaryResult;
local VotingConfigInfo result;
if (VerifyVotingsTool()) {
intermediaryResult = votingsTool.ResolveItem(itemName, id);
result.votingClass = class(intermediaryResult.class);
if (result.votingClass == none) {
return result;
}
if (intermediaryResult.configName == none) {
result.usageForbidden = true;
} else {
result.config = result.votingClass.static.LoadConfig(intermediaryResult.configName);
_.memory.Free(intermediaryResult.configName);
}
}
return result;
}
/// Returns pair of [`Voting`] and config name based on a given
/// case-insensitive name and text ID of the caller player.
///
/// Function only returns `none` for [`Voting`] instance if [`Voting`] with
/// a given name wasn't found.
public final function VotingConfigInfo ResolveVotingForTextID_S(string itemName, string id) {
local VotingConfigInfo result;
local MutableText wrapperItemname, wrapperID;
wrapperItemname = _.text.FromStringM(itemName);
wrapperID = _.text.FromStringM(id);
result = ResolveVotingForTextID(wrapperItemname, wrapperID);
_.memory.Free2(wrapperItemname, wrapperID);
return result;
}
/// Returns pair of [`Voting`] and config name based on a given
/// case-insensitive name and [`UserID`] of the caller player.
///
/// Function only returns `none` for [`Voting`] instance if [`Voting`] with
/// a given name wasn't found or provided id was `none`.
public final function VotingConfigInfo ResolveVotingForUserID(BaseText itemName, UserID id) {
local VotingConfigInfo result;
local Text textID;
if (itemName == none) return result;
if (id == none) return result;
textID = id.GetUniqueID();
if (textID == none) return result;
result = ResolveVotingForTextID(itemName, textID);
textID.FreeSelf();
return result;
}
/// Returns pair of [`Voting`] and config name based on a given
/// case-insensitive name and [`UserID`] of the caller player.
///
/// Function only returns `none` for [`Voting`] instance if [`Voting`] with
/// a given name wasn't found or provided id was `none`.
public final function VotingConfigInfo ResolveVotingForUserID_S(string itemName, UserID id) {
local VotingConfigInfo result;
local MutableText wrapper;
wrapper = _.text.FromStringM(itemName);
result = ResolveVotingForUserID(wrapper, id);
_.memory.Free(wrapper);
return result;
}
/// Returns pair of [`Voting`] and config name based on a given
/// case-insensitive name and [`User`] of the caller player.
///
/// Function only returns `none` for [`Voting`] instance if [`Voting`] with
/// a given name wasn't found or provided id was `none`.
public final function VotingConfigInfo ResolveVotingForUser(BaseText itemName, User user) {
local VotingConfigInfo result;
local UserID id;
if (itemName == none) return result;
if (user == none) return result;
id = user.GetID();
result = ResolveVotingForUserID(itemName, id);
_.memory.Free(id);
return result;
}
/// Returns pair of [`Voting`] and config name based on a given
/// case-insensitive name and [`User`] of the caller player.
///
/// Function only returns `none` for [`Voting`] instance if [`Voting`] with
/// a given name wasn't found or provided id was `none`.
public final function VotingConfigInfo ResolveVotingForUser_S(string itemName, User user) {
local VotingConfigInfo result;
local MutableText wrapper;
wrapper = _.text.FromStringM(itemName);
result = ResolveVotingForUser(wrapper, user);
_.memory.Free(wrapper);
return result;
}
/// Starts a voting process with a given name and arguments.
public final function StartVotingResult StartVoting(
VotingConfigInfo votingData,
optional HashTable arguments) {
if (VerifyVotingsTool()) {
return votingsTool.StartVoting(votingData, arguments);
}
return SVR_InvalidState;
}
/// Returns instance of the active voting.
///
/// `none` iff no voting is currently active.
public final function Voting GetCurrentVoting() {
if (VerifyVotingsTool()) {
return votingsTool.GetCurrentVoting();
}
return none;
}
// DO NOT CALL MANUALLY
public final /*internal*/ function AsyncTask _popPending() {
local AsyncTask result;
if (pendingAsyncJobs.length == 0) {
return result;
}
result = pendingAsyncJobs[0];
pendingAsyncJobs.Remove(0, 1);
return result;
}
// DO NOT CALL MANUALLY
public final /*internal*/ function _reloadFeature() {
local Commands_Feature commandsFeature;
local CommandFeatureTools toolsBundle;
commandsToolLifeVersion = -1;
votingsToolLifeVersion = -1;
commandsFeature = Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
if (commandsFeature != none) {
toolsBundle = commandsFeature._borrowTools();
commandsTool = toolsBundle.commands;
if (commandsTool != none) {
commandsToolLifeVersion = commandsTool.GetLifeVersion();
}
votingsTool = toolsBundle.votings;
if (votingsTool != none) {
votingsToolLifeVersion = votingsTool.GetLifeVersion();
}
}
_.memory.Free(commandsFeature);
}
private final function bool VerifyCommandsTool() {
if (commandsTool == none) {
return false;
}
if (!commandsTool.IsAllocated() || commandsTool.GetLifeVersion() != commandsToolLifeVersion) {
commandsTool = none;
return false;
}
return true;
}
private final function bool VerifyVotingsTool() {
if (votingsTool == none) {
return false;
}
if (!votingsTool.IsAllocated() || votingsTool.GetLifeVersion() != votingsToolLifeVersion) {
votingsTool = none;
return false;
}
return true;
}
defaultproperties {
}