Browse Source
To avoid bloating out `Commands_Feature`, we'll re-implement out its functionality into auxiliary tool classes.develop
Anton Tarasenko
1 year ago
4 changed files with 744 additions and 0 deletions
@ -0,0 +1,306 @@
|
||||
/** |
||||
* 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 CmdItemsTool extends AcediaObject |
||||
dependson(CommandAPI) |
||||
abstract; |
||||
|
||||
//! This is a base class for auxiliary objects that will be used for storing |
||||
//! named [`Command`] instances and [`Voting`] classes: they both have in common |
||||
//! the need to remember who was authorized to use them (i.e. which user group) |
||||
//! and with what permissions (i.e. name of the config that contains appropriate |
||||
//! permissions). |
||||
//! |
||||
//! Aside from trivial accessors to its data, it also provides a way to resolve |
||||
//! the best permissions available to the user by finding the most priviledged |
||||
//! group he belongs to. |
||||
//! |
||||
//! NOTE: child classes must implement `MakeCard()` method and can override |
||||
//! `DiscardCard()` method to catch events of removing items from storage. |
||||
|
||||
/// Allows to specify a base class requirement for this tool - only classes |
||||
/// that were derived from it can be stored inside. |
||||
var protected const class<AcediaObject> ruleBaseClass; |
||||
|
||||
/// Names of user groups that can decide permissions for items, |
||||
/// in order of importance: from most significant to the least significant. |
||||
/// This is used for resolving the best permissions for each user. |
||||
var private array<Text> permissionGroupOrder; |
||||
|
||||
/// Maps item names to their [`ItemCards`] with information about which groups |
||||
/// are authorized to use this particular item. |
||||
var private HashTable registeredCards; |
||||
|
||||
var LoggerAPI.Definition errItemInvalidName; |
||||
var LoggerAPI.Definition errItemDuplicate; |
||||
|
||||
protected function Constructor() { |
||||
registeredCards = _.collections.EmptyHashTable(); |
||||
} |
||||
|
||||
protected function Finalizer() { |
||||
_.memory.Free(registeredCards); |
||||
_.memory.FreeMany(permissionGroupOrder); |
||||
registeredCards = none; |
||||
permissionGroupOrder.length = 0; |
||||
} |
||||
|
||||
/// Registers given item class under the specified (case-insensitive) name. |
||||
/// |
||||
/// If name parameter is omitted (specified as `none`) or is an invalid name |
||||
/// (according to [`BaseText::IsValidName()`] method), then item class will not |
||||
/// be registered. |
||||
/// |
||||
/// Returns `true` if item was successfully registered and `false` otherwise`. |
||||
/// |
||||
/// # Errors |
||||
/// |
||||
/// If provided name that is invalid or already taken by a different item - |
||||
/// a warning will be logged and item class won't be registered. |
||||
public function bool AddItemClass(class<AcediaObject> itemClass, BaseText itemName) { |
||||
local Text itemKey; |
||||
local ItemCard newCard, existingCard; |
||||
|
||||
if (itemClass == none) return false; |
||||
if (itemName == none) return false; |
||||
if (registeredCards == none) return false; |
||||
|
||||
if (ruleBaseClass == none || !ClassIsChildOf(itemClass, ruleBaseClass)) { |
||||
return false; |
||||
} |
||||
// The item name is transformed into lowercase, immutable value. |
||||
// This facilitates the use of item names as keys in a [`HashTable`], |
||||
// enabling case-insensitive matching. |
||||
itemKey = itemName.LowerCopy(); |
||||
if (itemKey == none || !itemKey.IsValidName()) { |
||||
_.logger.Auto(errItemInvalidName).ArgClass(itemClass).Arg(itemKey); |
||||
return false; |
||||
} |
||||
// Guaranteed to only store cards |
||||
existingCard = ItemCard(registeredCards.GetItem(itemName)); |
||||
if (existingCard != none) { |
||||
_.logger.Auto(errItemDuplicate) |
||||
.ArgClass(existingCard.GetItemClass()) |
||||
.Arg(itemKey) |
||||
.ArgClass(itemClass); |
||||
_.memory.Free(existingCard); |
||||
return false; |
||||
} |
||||
newCard = MakeCard(itemClass, itemName); |
||||
registeredCards.SetItem(itemKey, newCard); |
||||
_.memory.Free2(itemKey, newCard); |
||||
return true; |
||||
} |
||||
|
||||
/// Removes item of given class from the list of registered items. |
||||
/// |
||||
/// Removing once registered item 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 items). |
||||
/// |
||||
/// Returns `true` if successfully removed registered item class and |
||||
/// `false` otherwise (either item wasn't registered or caller tool |
||||
/// initialized). |
||||
public function bool RemoveItemClass(class<AcediaObject> itemClass) { |
||||
local int i; |
||||
local CollectionIterator iter; |
||||
local ItemCard nextCard; |
||||
local array<Text> keysToRemove; |
||||
|
||||
if (itemClass == none) return false; |
||||
if (registeredCards == none) return false; |
||||
|
||||
// Removing items during iterator breaks an iterator, so first we find |
||||
// all the keys to remove |
||||
iter = registeredCards.Iterate(); |
||||
iter.LeaveOnlyNotNone(); |
||||
while (!iter.HasFinished()) { |
||||
// Guaranteed to only be `ItemCard` |
||||
nextCard = ItemCard(iter.Get()); |
||||
if (nextCard.GetItemClass() == itemClass) { |
||||
keysToRemove[keysToRemove.length] = Text(iter.GetKey()); |
||||
DiscardCard(nextCard); |
||||
} |
||||
_.memory.Free(nextCard); |
||||
iter.Next(); |
||||
} |
||||
iter.FreeSelf(); |
||||
// Actual clean up everything in `keysToRemove` |
||||
for (i = 0; i < keysToRemove.length; i += 1) { |
||||
registeredCards.RemoveItem(keysToRemove[i]); |
||||
} |
||||
_.memory.FreeMany(keysToRemove); |
||||
return (keysToRemove.length > 0); |
||||
} |
||||
|
||||
/// Allows to specify the order of the user group in terms of privilege for |
||||
/// accessing stored items. Only specified groups will be used when resolving |
||||
/// appropriate permissions config name for a user. |
||||
public final function SetPermissionGroupOrder(array<Text> groupOrder) { |
||||
local int i; |
||||
|
||||
_.memory.FreeMany(permissionGroupOrder); |
||||
permissionGroupOrder.length = 0; |
||||
for (i = 0; i < groupOrder.length; i += 1) { |
||||
if (groupOrder[i] != none) { |
||||
permissionGroupOrder[permissionGroupOrder.length] = groupOrder[i].Copy(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Specifies what permissions (given by the config name) given user group has |
||||
/// when using an item with a specified name. |
||||
/// |
||||
/// Method must be called after item with a given name is added. |
||||
/// |
||||
/// If this config name is specified as `none`, then "default" will be |
||||
/// used instead. For non-`none` values, only an invalid name (according to |
||||
/// [`BaseText::IsValidName()`] method) will prevent the group from being |
||||
/// registered. |
||||
/// |
||||
/// Method will return `true` if group was successfully authorized and `false` |
||||
/// otherwise (either group already authorized or no item with specified name |
||||
/// was added in the caller tool so far). |
||||
/// |
||||
/// # Errors |
||||
/// |
||||
/// If specified group was already authorized to use card's item, then it |
||||
/// will log a warning message about it. |
||||
public function bool AuthorizeUsage(BaseText itemName, BaseText groupName, BaseText configName) { |
||||
local bool result; |
||||
local ItemCard relevantCard; |
||||
|
||||
if (configName != none && !configName.IsValidName()) { |
||||
return false; |
||||
} |
||||
relevantCard = GetCard(itemName); |
||||
if (relevantCard != none) { |
||||
result = relevantCard.AuthorizeGroupWithConfig(groupName, configName); |
||||
_.memory.Free(relevantCard); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/// Returns struct with item class (+ instance, if one was stored) for a given |
||||
/// case in-sensitive item name and name of the config with best permissions |
||||
/// available to the player with provided ID. |
||||
/// |
||||
/// Function only returns `none` for item class if item with a given name |
||||
/// wasn't found. |
||||
/// Config name being `none` with non-`none` item class in the result means |
||||
/// that user with provided ID doesn't have permissions for using the item at |
||||
/// all. |
||||
public final function CommandAPI.ItemConfigInfo ResolveItem(BaseText itemName, BaseText textID) { |
||||
local int i; |
||||
local ItemCard relevantCard; |
||||
local CommandAPI.ItemConfigInfo result; |
||||
|
||||
relevantCard = GetCard(itemName); |
||||
if (relevantCard == none) { |
||||
// At this point contains `none` for all values -> indicates a failure |
||||
// to find item in storage |
||||
return result; |
||||
} |
||||
result.instance = relevantCard.GetItem(); |
||||
result.class = relevantCard.GetItemClass(); |
||||
if (textID == none) { |
||||
return result; |
||||
} |
||||
// Look through all `permissionGroupOrder` in order to find most priviledged |
||||
// group that user with `textID` belongs to |
||||
for (i = 0; i < permissionGroupOrder.length && result.configName == none; i += 1) { |
||||
if (_.users.IsSteamIDInGroup(textID, permissionGroupOrder[i])) { |
||||
result.configName = relevantCard.GetConfigNameForGroup(permissionGroupOrder[i]); |
||||
} |
||||
} |
||||
_.memory.Free(relevantCard); |
||||
return result; |
||||
} |
||||
|
||||
/// Returns all item classes that are stored inside caller tool. |
||||
/// |
||||
/// Doesn't check for duplicates (although with a normal usage, there shouldn't |
||||
/// be any). |
||||
public final function array< class<AcediaObject> > GetAllItemClasses() { |
||||
local array< class<AcediaObject> > result; |
||||
local ItemCard value; |
||||
local CollectionIterator iter; |
||||
|
||||
for (iter = registeredCards.Iterate(); !iter.HasFinished(); iter.Next()) { |
||||
value = ItemCard(iter.Get()); |
||||
if (value != none) { |
||||
result[result.length] = value.GetItemClass(); |
||||
} |
||||
_.memory.Free(value); |
||||
} |
||||
iter.FreeSelf(); |
||||
return result; |
||||
} |
||||
|
||||
/// Returns array of names of all available items. |
||||
public final function array<Text> GetItemsNames() { |
||||
local array<Text> emptyResult; |
||||
|
||||
if (registeredCards != none) { |
||||
return registeredCards.GetTextKeys(); |
||||
} |
||||
return emptyResult; |
||||
} |
||||
|
||||
/// Called each time a new card is to be created and stored. |
||||
/// |
||||
/// Must be reimplemented by child classes. |
||||
protected function ItemCard MakeCard(class<AcediaObject> itemClass, BaseText itemName) { |
||||
return none; |
||||
} |
||||
|
||||
/// Called each time a certain card is to be removed from storage. |
||||
/// |
||||
/// Must be reimplemented by child classes |
||||
/// (reimplementations SHOULD NOT DEALLOCATE `toDiscard`). |
||||
protected function DiscardCard(ItemCard toDiscard) { |
||||
} |
||||
|
||||
/// Find item card for the item that was stored with a specified |
||||
/// case-insensitive name |
||||
/// |
||||
/// Function only returns `none` if item with a given name wasn't found |
||||
/// (or `none` was provided as an argument). |
||||
protected final function ItemCard GetCard(BaseText itemName) { |
||||
local Text itemKey; |
||||
local ItemCard relevantCard; |
||||
|
||||
if (itemName == none) return none; |
||||
if (registeredCards == none) return none; |
||||
|
||||
/// The item name is transformed into lowercase, immutable value. |
||||
/// This facilitates the use of item names as keys in a [`HashTable`], |
||||
/// enabling case-insensitive matching. |
||||
itemKey = itemName.LowerCopy(); |
||||
relevantCard = ItemCard(registeredCards.GetItem(itemKey)); |
||||
_.memory.Free(itemKey); |
||||
return relevantCard; |
||||
} |
||||
|
||||
defaultproperties { |
||||
errItemInvalidName = (l=LOG_Error,m="Attempt at registering item with class `%1` under an invalid name \"%2\" will be ignored.") |
||||
errItemDuplicate = (l=LOG_Error,m="Command `%1` is already registered with name '%2'. Attempt at registering command `%3` with the same name will be ignored.") |
||||
} |
@ -0,0 +1,142 @@
|
||||
/** |
||||
* 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 CommandsTool extends CmdItemsTool; |
||||
|
||||
//! This is a base class for auxiliary objects that will be used for storing |
||||
//! named [`Command`] instances. |
||||
//! |
||||
//! This storage class allows for efficient manipulation and retrieval of |
||||
//! [`Command`]s, along with information about what use groups were authorized |
||||
//! to use them. |
||||
//! |
||||
//! Additionally, this tool allows for efficient fetching of commands that |
||||
//! belong to a particular *command group*. |
||||
|
||||
/// [`HashTable`] that maps a command group name to a set of command names that |
||||
/// belong to it. |
||||
var private HashTable groupedCommands; |
||||
|
||||
protected function Constructor() { |
||||
super.Constructor(); |
||||
groupedCommands = _.collections.EmptyHashTable(); |
||||
} |
||||
|
||||
protected function Finalizer() { |
||||
super.Finalizer(); |
||||
_.memory.Free(groupedCommands); |
||||
groupedCommands = none; |
||||
} |
||||
|
||||
/// Returns all known command groups' names. |
||||
public final function array<Text> GetGroupsNames() { |
||||
local array<Text> emptyResult; |
||||
|
||||
if (groupedCommands != none) { |
||||
return groupedCommands.GetTextKeys(); |
||||
} |
||||
return emptyResult; |
||||
} |
||||
|
||||
/// Returns array of names of all available commands belonging to the specified |
||||
/// group. |
||||
public final function array<Text> GetCommandNamesInGroup(BaseText groupName) { |
||||
local int i; |
||||
local ArrayList commandNamesArray; |
||||
local array<Text> result; |
||||
|
||||
if (groupedCommands == none) return result; |
||||
commandNamesArray = groupedCommands.GetArrayList(groupName); |
||||
if (commandNamesArray == none) return result; |
||||
|
||||
for (i = 0; i < commandNamesArray.GetLength(); i += 1) { |
||||
result[result.length] = commandNamesArray.GetText(i); |
||||
} |
||||
_.memory.Free(commandNamesArray); |
||||
return result; |
||||
} |
||||
|
||||
protected function ItemCard MakeCard(class<AcediaObject> commandClass, BaseText itemName) { |
||||
local Command newCommandInstance; |
||||
local ItemCard newCard; |
||||
local Text commandGroup; |
||||
|
||||
if (class<Command>(commandClass) != none) { |
||||
newCommandInstance = Command(_.memory.Allocate(commandClass, true)); |
||||
newCommandInstance.Initialize(itemName); |
||||
newCard = ItemCard(_.memory.Allocate(class'ItemCard')); |
||||
newCard.InitializeWithInstance(newCommandInstance); |
||||
|
||||
// Guaranteed to be lower case (keys of [`HashTable`]) |
||||
if (itemName != none) { |
||||
itemName = itemName.LowerCopy(); |
||||
} else { |
||||
itemName = newCommandInstance.GetPreferredName(); |
||||
} |
||||
commandGroup = newCommandInstance.GetGroupName(); |
||||
AssociateGroupAndName(commandGroup, itemName); |
||||
_.memory.Free3(newCommandInstance, itemName, commandGroup); |
||||
} |
||||
return newCard; |
||||
} |
||||
|
||||
protected function DiscardCard(ItemCard toDiscard) { |
||||
local Text groupKey, commandName; |
||||
local Command storedCommand; |
||||
local ArrayList listOfCommands; |
||||
|
||||
if (toDiscard == none) return; |
||||
// Guaranteed to store a [`Command`] |
||||
storedCommand = Command(toDiscard.GetItem()); |
||||
if (storedCommand == none) return; |
||||
|
||||
// Guaranteed to be stored in a lower case |
||||
commandName = storedCommand.GetName(); |
||||
listOfCommands = groupedCommands.GetArrayList(groupKey); |
||||
if (listOfCommands != none && commandName != none) { |
||||
listOfCommands.RemoveItem(commandName); |
||||
} |
||||
_.memory.Free2(commandName, storedCommand); |
||||
} |
||||
|
||||
// Expect both arguments to be not `none`. |
||||
// Expect both arguments to be lower-case. |
||||
private final function AssociateGroupAndName(BaseText groupKey, BaseText commandName) { |
||||
local ArrayList listOfCommands; |
||||
|
||||
if (groupedCommands != none) { |
||||
listOfCommands = groupedCommands.GetArrayList(groupKey); |
||||
if (listOfCommands == none) { |
||||
listOfCommands = _.collections.EmptyArrayList(); |
||||
} |
||||
if (listOfCommands.Find(commandName) < 0) { |
||||
// `< 0` means not found |
||||
listOfCommands.AddItem(commandName); |
||||
} |
||||
// Set `listOfCommands` in case we've just created that array. |
||||
// Won't do anything if it is already recorded there. |
||||
groupedCommands.SetItem(groupKey, listOfCommands); |
||||
} |
||||
} |
||||
|
||||
defaultproperties { |
||||
ruleBaseClass = class'Command'; |
||||
} |
@ -0,0 +1,177 @@
|
||||
/** |
||||
* 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 ItemCard extends AcediaObject; |
||||
|
||||
//! Utility class designed for storing either class of an object |
||||
//! (possibly also a specific instance) along with authorization information: |
||||
//! which user groups are allowed to use stored entity and with what level of |
||||
//! permissions (defined by the name of a config with permissions). |
||||
//! |
||||
//! [`ItemCard`] has to be initialized with either [`InitializeWithClass()`] or |
||||
//! [`InitializeWithInstance()`] before it can be used. |
||||
|
||||
/// Class of object that this card describes. |
||||
var private class<AcediaObject> storedClass; |
||||
/// Instance of an object (can also *optionally* be stored in this card) |
||||
var private AcediaObject storedInstance; |
||||
|
||||
/// This [`HashTable`] maps authorized groups to their respective config names. |
||||
/// |
||||
/// Each key represents an authorized group, and its corresponding value |
||||
/// indicates the associated config name. If a key has a value of `none`, |
||||
/// the default config (named "default") should be used for that group. |
||||
var private HashTable groupToConfig; |
||||
|
||||
var LoggerAPI.Definition errGroupAlreadyHasConfig; |
||||
|
||||
protected function Finalizer() { |
||||
_.memory.Free2(storedInstance, groupToConfig); |
||||
storedInstance = none; |
||||
storedClass = none; |
||||
groupToConfig = none; |
||||
} |
||||
|
||||
/// Initializes the caller [`ItemCard`] object with class to be stored. |
||||
/// |
||||
/// Initialization can only be done once: once method returned `true`, |
||||
/// all future calls will fail. |
||||
/// |
||||
/// Returns `false` if caller was already initialized or `none` is provided as |
||||
/// an argument. Otherwise succeeds and returns `true`. |
||||
public function bool InitializeWithClass(class<AcediaObject> toStore) { |
||||
if (storedClass != none) return false; |
||||
if (toStore == none) return false; |
||||
|
||||
storedClass = toStore; |
||||
groupToConfig = _.collections.EmptyHashTable(); |
||||
return true; |
||||
} |
||||
|
||||
/// Initializes the caller [`ItemCard`] object with an object to be stored. |
||||
/// |
||||
/// Initialization can only be done once: once method returned `true`, |
||||
/// all future calls will fail. |
||||
/// |
||||
/// Returns `false` caller was already initialized or `none` is provided as |
||||
/// an argument. Otherwise succeeds and returns `true`. |
||||
public function bool InitializeWithInstance(AcediaObject toStore) { |
||||
if (storedClass != none) return false; |
||||
if (toStore == none) return false; |
||||
|
||||
storedClass = toStore.class; |
||||
storedInstance = toStore; |
||||
storedInstance.NewRef(); |
||||
groupToConfig = _.collections.EmptyHashTable(); |
||||
return true; |
||||
} |
||||
|
||||
/// Authorizes a new group to use the this card's item. |
||||
/// |
||||
/// This function allows to specify the config name for a particular user group. |
||||
/// If this config name is skipped (specified as `none`), then "default" will be |
||||
/// used instead. |
||||
/// |
||||
/// Function will return `true` if group was successfully authorized and |
||||
/// `false` otherwise (either group already authorized or caller [`ItemCard`] |
||||
/// isn't initialized). |
||||
/// |
||||
/// # Errors |
||||
/// |
||||
/// If specified group was already authorized to use card's item, then it |
||||
/// will log an error message about it. |
||||
public function bool AuthorizeGroupWithConfig(BaseText groupName, optional BaseText configName) { |
||||
local Text itemKey; |
||||
local Text storedConfigName; |
||||
|
||||
if (storedClass == none) return false; |
||||
if (groupToConfig == none) return false; |
||||
if (groupName == none) return false; |
||||
if (groupName.IsEmpty()) return false; |
||||
|
||||
/// Make group name immutable and have its characters have a uniform case to |
||||
/// be usable as case-insensitive keys for [`HashTable`]. |
||||
itemKey = groupName.LowerCopy(); |
||||
storedConfigName = groupToConfig.GetText(itemKey); |
||||
if (storedConfigName != none) { |
||||
_.logger.Auto(errGroupAlreadyHasConfig) |
||||
.ArgClass(storedClass) |
||||
.Arg(groupName.Copy()) |
||||
.Arg(storedConfigName) |
||||
.Arg(configName.Copy()); |
||||
_.memory.Free(itemKey); |
||||
return false; |
||||
} |
||||
// We don't actually record "default" value at this point, instead opting |
||||
// to return "default" in getter functions in case stored `configName` |
||||
// is `none`. |
||||
groupToConfig.SetItem(itemKey, configName); |
||||
_.memory.Free(itemKey); |
||||
return true; |
||||
} |
||||
|
||||
/// Returns item instance for the caller [`ItemCard`]. |
||||
/// |
||||
/// Returns `none` iff this card wasn't initialized with an instance. |
||||
public function AcediaObject GetItem() { |
||||
if (storedInstance != none) { |
||||
storedInstance.NewRef(); |
||||
} |
||||
return storedInstance; |
||||
} |
||||
|
||||
/// Returns item class for the caller [`ItemCard`]. |
||||
/// |
||||
/// Returns `none` iff this card wasn't initialized. |
||||
public function class<AcediaObject> GetItemClass() { |
||||
return storedClass; |
||||
} |
||||
|
||||
/// Returns the name of config that was authorized for the specified group. |
||||
/// |
||||
/// Returns `none` if group wasn't authorized, otherwise guaranteed to |
||||
/// return non-`none` and non-empty `Text` value. |
||||
public function Text GetConfigNameForGroup(BaseText groupName) { |
||||
local Text groupNameAsKey, result; |
||||
|
||||
if (storedClass == none) return none; |
||||
if (groupToConfig == none) return none; |
||||
if (groupName == none) return none; |
||||
|
||||
/// Make group name immutable and have its characters a uniform case to |
||||
/// be usable as case-insensitive keys for [`HashTable`] |
||||
groupNameAsKey = groupName.LowerCopy(); |
||||
if (groupToConfig.HasKey(groupNameAsKey)) { |
||||
result = groupToConfig.GetText(groupNameAsKey); |
||||
if (result == none) { |
||||
// If we do have specified group recorded as a key, then we must |
||||
// return non-`none` config name, defaulting to "default" value |
||||
// if none was provided |
||||
result = P("default").Copy(); |
||||
} |
||||
} |
||||
_.memory.Free(groupNameAsKey); |
||||
return result; |
||||
} |
||||
|
||||
defaultproperties { |
||||
errGroupAlreadyHasConfig = (l=LOG_Error,m="Item `%1` is already added to group '%2' with config '%3'. Attempt to add it with config '%4' is ignored.") |
||||
} |
@ -0,0 +1,119 @@
|
||||
/** |
||||
* 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 VotingsTool extends CmdItemsTool |
||||
dependson(CommandAPI); |
||||
|
||||
//! This is a base class for auxiliary objects that will be used for storing |
||||
//! named [`Voting`] classes. |
||||
//! |
||||
//! This storage class allows for efficient manipulation and retrieval of |
||||
//! [`Voting`] classes, along with information about what use groups were |
||||
//! authorized to use them. |
||||
//! |
||||
//! Additionally this tool is used to keep track of the currently ongoing |
||||
//! voting, preventing [`CommandsAPI`] from starting several votings at once. |
||||
|
||||
/// Currently running voting process. |
||||
/// This tool doesn't actively track when voting ends, so reference can be |
||||
/// non-`none` even if voting has already ended. Instead `DropFinishedVoting()` |
||||
/// method is used as needed to figure out whether that voting has ended and |
||||
/// should be deallocated. |
||||
var private Voting currentVoting; |
||||
|
||||
protected function Finalizer() { |
||||
super.Finalizer(); |
||||
_.memory.Free(currentVoting); |
||||
currentVoting = none; |
||||
} |
||||
|
||||
/// Starts a voting process with a given name, returning its result. |
||||
public final function CommandAPI.StartVotingResult StartVoting( |
||||
CommandAPI.VotingConfigInfo votingData, |
||||
HashTable arguments |
||||
) { |
||||
local CommandAPI.StartVotingResult result; |
||||
DropFinishedVoting(); |
||||
if (currentVoting != none) { |
||||
return SVR_AlreadyInProgress; |
||||
} |
||||
if (votingData.votingClass == none) { |
||||
return SVR_UnknownVoting; |
||||
} |
||||
currentVoting = Voting(_.memory.Allocate(votingData.votingClass)); |
||||
result = currentVoting.Start(votingData.config, arguments); |
||||
if (result != SVR_Success) { |
||||
_.memory.Free(currentVoting); |
||||
currentVoting = none; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/// Returns `true` iff some voting is currently active. |
||||
public final function bool IsVotingRunning() { |
||||
DropFinishedVoting(); |
||||
return (currentVoting != none); |
||||
} |
||||
|
||||
/// Returns instance of the active voting. |
||||
/// |
||||
/// `none` iff no voting is currently active. |
||||
public final function Voting GetCurrentVoting() { |
||||
DropFinishedVoting(); |
||||
if (currentVoting != none) { |
||||
currentVoting.NewRef(); |
||||
} |
||||
return currentVoting; |
||||
} |
||||
|
||||
protected function ItemCard MakeCard(class<AcediaObject> votingClass, BaseText itemName) { |
||||
local ItemCard newCard; |
||||
|
||||
if (class<Voting>(votingClass) != none) { |
||||
newCard = ItemCard(_.memory.Allocate(class'ItemCard')); |
||||
newCard.InitializeWithClass(votingClass); |
||||
} |
||||
return newCard; |
||||
} |
||||
|
||||
private final function class<Voting> GetVoting(BaseText itemName) { |
||||
local ItemCard relevantCard; |
||||
local class<Voting> result; |
||||
|
||||
relevantCard = GetCard(itemName); |
||||
if (relevantCard != none) { |
||||
result = class<Voting>(relevantCard.GetItemClass()); |
||||
} |
||||
_.memory.Free(relevantCard); |
||||
return result; |
||||
} |
||||
|
||||
// Clears `currentVoting` if it has already finished |
||||
private final function DropFinishedVoting() { |
||||
if (currentVoting != none && currentVoting.HasEnded()) { |
||||
_.memory.Free(currentVoting); |
||||
currentVoting = none; |
||||
} |
||||
} |
||||
|
||||
defaultproperties { |
||||
ruleBaseClass = class'Voting' |
||||
} |
Loading…
Reference in new issue