Huge dump of refactored code. Still in the middle of the changes that are to be squashed later in a one huge monster commit, because there is no value in anything atomic here.
1578 lines
58 KiB
Ucode
1578 lines
58 KiB
Ucode
/**
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
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<Voting> 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<AcediaObject> 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<AcediaObject> 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<AsyncTask> 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 <slot>(class<Command> 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 <slot>(class<Command> 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 <slot>(class<Voting> 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 <slot>(class<Voting> 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<Text> GetAllCommandNames() {
|
|
local array<Text> 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<Command> 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<Command> 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<Command> 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<Command> 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<Command> 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<Text> GetGroupsNames() {
|
|
local array<Text> emptyResult;
|
|
|
|
if (VerifyCommandsTool()) {
|
|
return commandsTool.GetGroupsNames();
|
|
}
|
|
return emptyResult;
|
|
}
|
|
|
|
/// Returns all available command groups' names.
|
|
public final function array<string> GetGroupsNames_S() {
|
|
local array<string> 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<Text> GetCommandNamesInGroup(BaseText groupName) {
|
|
local array<Text> 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<string> GetCommandNamesInGroup_S(string groupName) {
|
|
local array<string> 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<Voting> > GetAllVotingClasses() {
|
|
local int i;
|
|
local array< class<AcediaObject> > intermediaryResult;
|
|
local array< class<Voting> > result;
|
|
|
|
if (VerifyVotingsTool()) {
|
|
intermediaryResult = votingsTool.GetAllItemClasses();
|
|
for (i = 0; i < intermediaryResult.length; i += 1) {
|
|
result[result.length] = class<Voting>(intermediaryResult[i]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Returns array of names of all available votings.
|
|
///
|
|
/// Resulting array cannot contain duplicates.
|
|
public final function array<Text> GetAllVotingsNames() {
|
|
local array<Text> 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<Voting> 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<Voting> 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<Voting> 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<Voting> 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<Voting> 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<Voting> GetVotingClass(BaseText itemName) {
|
|
local ItemConfigInfo intermediaryResult;
|
|
|
|
if (VerifyVotingsTool()) {
|
|
intermediaryResult = votingsTool.ResolveItem(itemName, none);
|
|
return class<Voting>(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<Voting>(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 {
|
|
} |