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