/** * Author: dkanus * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore * License: GPL * Copyright 2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * * Acedia is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, or * (at your option) any later version. * * Acedia is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ class 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 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 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 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 itemClass) { local int i; local CollectionIterator iter; local ItemCard nextCard; local array 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 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 > GetAllItemClasses() { local array< class > 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 GetItemsNames() { local array 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 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.") }