306 lines
12 KiB
Ucode
306 lines
12 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 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.")
|
|
} |