Browse Source

Refactor AliasesAPI to avoid using `Actor`s

Aliases were initially using `Actor`-base objects to manage themselves,
but that became a legacy approachin AcediaCore and they had to be
reimplemented without any `Actor`s (based on `Feature` instead),
allowing them mto be usable even when no `LevelCore` is provided.
pull/8/head
Anton Tarasenko 2 years ago
parent
commit
9394b82f51
  1. 135
      config/AcediaAliases.ini
  2. 2
      config/AcediaAliases_Colors.ini
  3. 2
      config/AcediaAliases_Entities.ini
  4. 119
      config/AcediaAliases_Features.ini
  5. 1
      config/AcediaAliases_Tests.ini
  6. 16
      config/AcediaAliases_Weapons.ini
  7. 11
      config/AcediaSystem.ini
  8. 139
      sources/Aliases/AliasService.uc
  9. 396
      sources/Aliases/AliasSource.uc
  10. 243
      sources/Aliases/Aliases.uc
  11. 323
      sources/Aliases/AliasesAPI.uc
  12. 205
      sources/Aliases/AliasesStorage.uc
  13. 249
      sources/Aliases/Aliases_Feature.uc
  14. 283
      sources/Aliases/BaseAliasSource.uc
  15. 2
      sources/Aliases/BuiltInSources/ColorAliases.uc
  16. 2
      sources/Aliases/BuiltInSources/EntityAliases.uc
  17. 2
      sources/Aliases/BuiltInSources/FeatureAliasSource.uc
  18. 4
      sources/Aliases/BuiltInSources/FeatureAliases.uc
  19. 2
      sources/Aliases/BuiltInSources/WeaponAliases.uc
  20. 2
      sources/Aliases/Tests/MockAliases.uc
  21. 16
      sources/Aliases/Tests/TEST_Aliases.uc
  22. 9
      sources/BaseRealm/AcediaEnvironment/AcediaEnvironment.uc
  23. 3
      sources/Commands/Aliases/CommandAliasSource.uc
  24. 5
      sources/Commands/Aliases/CommandAliases.uc
  25. 10
      sources/Manifest.uc

135
config/AcediaAliases.ini

@ -1,117 +1,18 @@
[AcediaCore.FeatureAliasSource]
[AcediaCore:Commands_Feature FeatureAliases]
Alias="commands"
Alias="command"
Alias="comands"
Alias="comand"
Alias="cmds"
Alias="cmd"
[AcediaCore:Avarice_Feature FeatureAliases]
Alias="Avarice"
[AcediaFixes:FixAmmoSelling_Feature FeatureAliases]
Alias="FixAmmoSelling"
Alias="FixSelling"
Alias="FixAmmo"
Alias="AmmoSellingFix"
Alias="SellingFix"
Alias="AmmoFix"
Alias="FixAmmoSell"
Alias="FixSell"
Alias="AmmoSellFix"
Alias="SellFix"
Alias="FixAmmoPrinting"
Alias="FixPrinting"
Alias="AmmoPrintingFix"
Alias="PrintingFix"
Alias="FixAmmoPrint"
Alias="FixPrint"
Alias="AmmoPrintFix"
Alias="PrintFix"
[AcediaFixes:FixDoshSpam_Feature FeatureAliases]
Alias="DoshSpamFix"
Alias="DoshFix"
Alias="CashSpamFix"
Alias="CashFix"
Alias="FixDoshSpamF"
Alias="FixDosh"
Alias="FixCashSpam"
Alias="FixCash"
[AcediaFixes:FixDualiesCost_Feature FeatureAliases]
Alias="FixDualiesCost"
Alias="FixDualies"
Alias="DualiesCostFix"
Alias="DualiesFix"
[AcediaFixes:FixFFHack_Feature FeatureAliases]
Alias="FixFFHack"
Alias="FixFriendlyFireHack"
Alias="FixFriendFireHack"
Alias="FFHackFix"
Alias="FriendlyFireHackFix"
Alias="FriendFireHackFix"
[AcediaFixes:FixInfiniteNades_Feature FeatureAliases]
Alias="FixInfiniteNades"
Alias="FixInfiniteNade"
Alias="FixInfNades"
Alias="FixInfNade"
Alias="FixNades"
Alias="FixNade"
Alias="InfiniteNadesFix"
Alias="InfiniteNadeFix"
Alias="InfNadesFix"
Alias="InfNadeFix"
Alias="NadesFix"
Alias="NadeFix"
[AcediaFixes:FixInventoryAbuse_Feature FeatureAliases]
Alias="FixInventoryAbuse"
Alias="FixInventory"
Alias="InventoryAbuseFix"
Alias="InventoryFix"
[AcediaFixes:FixLogSpam_Feature FeatureAliases]
Alias="FixLogSpam"
Alias="FixLog"
Alias="LogSpamFix"
Alias="LogFix"
[AcediaFixes:FixPipes_Feature FeatureAliases]
Alias="FixPipes"
Alias="FixPipe"
Alias="PipesFix"
Alias="PipeFix"
[AcediaFixes:FixProjectileFF_Feature FeatureAliases]
Alias="FixProjectileFriendlyFire"
Alias="FixProjectileFF"
Alias="FixProjFriendlyFire"
Alias="FixProjFF"
Alias="FixFriendlyFire"
Alias="FixFF"
Alias="ProjectileFriendlyFireFix"
Alias="ProjectileFFFix"
Alias="ProjFriendlyFireFix"
Alias="ProjFFFix"
Alias="FriendlyFireFix"
Alias="FFFix"
[AcediaFixes:FixSpectatorCrash_Feature FeatureAliases]
Alias="FixSpectatorCrash"
Alias="FixSpecCrash"
Alias="SpectatorCrashFix"
Alias="SpecCrashFix"
[AcediaFixes:FixZedTimeLags_Feature FeatureAliases]
Alias="FixZedTimeLags"
Alias="FixZedTime"
Alias="FixZTLags"
Alias="FixZT"
Alias="ZedTimeLagsFix"
Alias="ZedTimeFix"
Alias="ZTLagsFix"
Alias="ZTFix"
; NOTE: You most likely do not want to modify this file, unless you are really
; sure what you're doing or was told exactly what to do by someone.
[default Aliases]
; This feature allows you to configure what alias sources are used for resolving
; what alias types. Like all Acedia's features, it supports multiple named
; configs, allowing you to, say, replacve `weaponAliasSoruce` with a different
; one when running a mod that replaces stock weapons.
autoEnable=true
weaponAliasSource=Class'AcediaCore.WeaponAliasSource'
colorAliasSource=Class'AcediaCore.ColorAliasSource'
featureAliasSource=Class'AcediaCore.FeatureAliasSource'
entityAliasSource=Class'AcediaCore.EntityAliasSource'
; `customSource` records allow you to add custom alias sources into Acedia,
; that is sources that do not fit either of the standard groups. These sources
; can then be used by mods to resolve custom aliases using specified `name`.
; Their names, unlike aliases, are case-sensitive.
;customSource=(name="custom",source=Class'BlaBla.CustomSource')
customSource=(name="mock",source=Class'AcediaCore.MockAliasSource')

2
config/AcediaAliases_Colors.ini

@ -1,3 +1,5 @@
; This config file allows you to configure color aliases.
; Remember that aliases are case-insensitive.
[AcediaCore.ColorAliasSource]
; System colors
record=(alias="TextDefault",value="rgb(255,255,255)")

2
config/AcediaAliases_Entities.ini

@ -1,3 +1,5 @@
; This config file allows you to configure entity aliases.
; Remember that aliases are case-insensitive.
[AcediaCore.EntityAliasSource]
; Standard zeds
[KFChar:ZombieClot_STANDARD EntityAliases]

119
config/AcediaAliases_Features.ini

@ -0,0 +1,119 @@
; This config file allows you to configure feature aliases.
; Remember that aliases are case-insensitive.
[AcediaCore.FeatureAliasSource]
[AcediaCore:Commands_Feature FeatureAliases]
Alias="commands"
Alias="command"
Alias="comands"
Alias="comand"
Alias="cmds"
Alias="cmd"
[AcediaCore:Avarice_Feature FeatureAliases]
Alias="Avarice"
[AcediaFixes:FixAmmoSelling_Feature FeatureAliases]
Alias="FixAmmoSelling"
Alias="FixSelling"
Alias="FixAmmo"
Alias="AmmoSellingFix"
Alias="SellingFix"
Alias="AmmoFix"
Alias="FixAmmoSell"
Alias="FixSell"
Alias="AmmoSellFix"
Alias="SellFix"
Alias="FixAmmoPrinting"
Alias="FixPrinting"
Alias="AmmoPrintingFix"
Alias="PrintingFix"
Alias="FixAmmoPrint"
Alias="FixPrint"
Alias="AmmoPrintFix"
Alias="PrintFix"
[AcediaFixes:FixDoshSpam_Feature FeatureAliases]
Alias="DoshSpamFix"
Alias="DoshFix"
Alias="CashSpamFix"
Alias="CashFix"
Alias="FixDoshSpamF"
Alias="FixDosh"
Alias="FixCashSpam"
Alias="FixCash"
[AcediaFixes:FixDualiesCost_Feature FeatureAliases]
Alias="FixDualiesCost"
Alias="FixDualies"
Alias="DualiesCostFix"
Alias="DualiesFix"
[AcediaFixes:FixFFHack_Feature FeatureAliases]
Alias="FixFFHack"
Alias="FixFriendlyFireHack"
Alias="FixFriendFireHack"
Alias="FFHackFix"
Alias="FriendlyFireHackFix"
Alias="FriendFireHackFix"
[AcediaFixes:FixInfiniteNades_Feature FeatureAliases]
Alias="FixInfiniteNades"
Alias="FixInfiniteNade"
Alias="FixInfNades"
Alias="FixInfNade"
Alias="FixNades"
Alias="FixNade"
Alias="InfiniteNadesFix"
Alias="InfiniteNadeFix"
Alias="InfNadesFix"
Alias="InfNadeFix"
Alias="NadesFix"
Alias="NadeFix"
[AcediaFixes:FixInventoryAbuse_Feature FeatureAliases]
Alias="FixInventoryAbuse"
Alias="FixInventory"
Alias="InventoryAbuseFix"
Alias="InventoryFix"
[AcediaFixes:FixLogSpam_Feature FeatureAliases]
Alias="FixLogSpam"
Alias="FixLog"
Alias="LogSpamFix"
Alias="LogFix"
[AcediaFixes:FixPipes_Feature FeatureAliases]
Alias="FixPipes"
Alias="FixPipe"
Alias="PipesFix"
Alias="PipeFix"
[AcediaFixes:FixProjectileFF_Feature FeatureAliases]
Alias="FixProjectileFriendlyFire"
Alias="FixProjectileFF"
Alias="FixProjFriendlyFire"
Alias="FixProjFF"
Alias="FixFriendlyFire"
Alias="FixFF"
Alias="ProjectileFriendlyFireFix"
Alias="ProjectileFFFix"
Alias="ProjFriendlyFireFix"
Alias="ProjFFFix"
Alias="FriendlyFireFix"
Alias="FFFix"
[AcediaFixes:FixSpectatorCrash_Feature FeatureAliases]
Alias="FixSpectatorCrash"
Alias="FixSpecCrash"
Alias="SpectatorCrashFix"
Alias="SpecCrashFix"
[AcediaFixes:FixZedTimeLags_Feature FeatureAliases]
Alias="FixZedTimeLags"
Alias="FixZedTime"
Alias="FixZTLags"
Alias="FixZT"
Alias="ZedTimeLagsFix"
Alias="ZedTimeFix"
Alias="ZTLagsFix"
Alias="ZTFix"

1
config/AcediaAliases_Tests.ini

@ -6,7 +6,6 @@
[AcediaCore.MockAliasSource]
record=(alias="global",value="value")
record=(alias="question",value="response")
record=(alias="",value="empty")
record=(alias="also",value="")
[car MockAliases]
Alias="Ford"

16
config/AcediaAliases_Weapons.ini

@ -1,3 +1,5 @@
; This config file allows you to configure weapon aliases.
; Remember that aliases are case-insensitive.
[AcediaCore.WeaponAliasSource]
; Field Medic weapons
[KFMod:MP7MMedicGun WeaponAliases]
@ -133,44 +135,34 @@ Alias="HuntingShotgun"
Alias="BoomStick"
Alias="Hunting"
[KFMod:KSGShotgun WeaponAliases]
Alias="HSG-1Shotgun"
Alias="HSG1Shotgun"
Alias="HSGShotgun"
Alias="HSG"
Alias="KSG-1Shotgun"
Alias="KSG1Shotgun"
Alias="KSGShotgun"
Alias="KSG"
Alias="HSG-1Shotgun0"
Alias="HSG1Shotgun0"
Alias="HSGShotgun0"
Alias="HSG0"
Alias="KSG-1Shotgun0"
Alias="KSG1Shotgun0"
Alias="KSGShotgun0"
Alias="KSG0"
[KFMod:NeonKSGShotgun WeaponAliases]
Alias="HSG-1Shotgun1"
Alias="HSG1Shotgun1"
Alias="HSGShotgun1"
Alias="HSG1"
Alias="KSG-1Shotgun1"
Alias="KSG1Shotgun1"
Alias="KSGShotgun1"
Alias="KSG1"
Alias="NeonHSG-1Shotgun"
Alias="NeonHSG1Shotgun"
Alias="NeonHSGShotgun"
Alias="NeonHSG"
Alias="NeonKSG-1Shotgun"
Alias="NeonKSG1Shotgun"
Alias="NeonKSGShotgun"
Alias="NeonKSG"
Alias="HSG-1ShotgunNeon"
Alias="HSG1ShotgunNeon"
Alias="HSGShotgunNeon"
Alias="HSGNeon"
Alias="KSG-1ShotgunNeon"
Alias="KSG1ShotgunNeon"
Alias="KSGShotgunNeon"
Alias="KSGNeon"
@ -593,13 +585,9 @@ Alias="SteamTomySMG"
Alias="SteamTomy"
Alias="TomySMGSteam"
Alias="TomySteam"
Alias="Dr.T'sLeadDeliverySystem"
Alias="Dr.TsLeadDeliverySystem"
Alias="DrT'sLeadDeliverySystem"
Alias="DrTsLeadDeliverySystem"
Alias="Dr.T'LeadDeliverySystem"
Alias="Dr.TLeadDeliverySystem"
Alias="DrT'LeadDeliverySystem"
Alias="DrTLeadDeliverySystem"
Alias="DrTDeliverySystem"
Alias="DrTLeadSystem"

11
config/AcediaSystem.ini

@ -119,17 +119,6 @@ levelStamp=true
; centralized manner.
autoEnable=true
[AcediaCore.AliasService]
; Changing these allows you to change in what sources `AliasesAPI`
; looks for weapon and color aliases.
weaponAliasesSource=Class'WeaponAliasSource'
colorAliasesSource=Class'ColorAliasSource'
featureAliasesSource=Class'FeatureAliasSource'
; How often are different alias-storing objects are allowed to record
; their updated data into a config.
; Negative or zero values would be reset to `0.05`.
saveInterval=0.05
[AcediaCore.TestingService]
; Allows you to run tests on server's start up. This option is to help run
; tests quicker during development and should not be used for servers that are

139
sources/Aliases/AliasService.uc

@ -1,139 +0,0 @@
/**
* Service that handles pending saving of aliases data into configs.
* Adding aliases into `AliasSource`s causes corresponding configs to update.
* This service allows to delay and spread config rewrites over time,
* which should help in case someone dynamically adds a lot of
* different aliases.
* Copyright 2020 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 AliasService extends Service
config(AcediaSystem);
// Objects for which we are yet to write configs
var private array<AliasSource> sourcesPendingToSave;
var private array<Aliases> aliasesPendingToSave;
// How often should we do it.
// Negative or zero values would be reset to `0.05`.
var public config const float saveInterval;
// To avoid creating yet another object for aliases system we will
// keep config variable pointing to weapon, color, etc. `AliasSource`
// subclasses here. It's not the best regarding separation of responsibility,
// but should make config files less fragmented.
// Changing these allows you to change in what sources `AliasesAPI`
// looks for weapon and color aliases.
var public config const class<AliasSource> weaponAliasesSource;
var public config const class<AliasSource> colorAliasesSource;
var public config const class<AliasSource> featureAliasesSource;
var public config const class<AliasSource> entityAliasesSource;
protected simulated function OnLaunch()
{
local float actualInterval;
actualInterval = saveInterval;
if (actualInterval <= 0)
{
actualInterval = 0.05;
}
SetTimer(actualInterval, true);
}
protected simulated function OnShutdown()
{
SaveAllPendingObjects();
}
public simulated final function PendingSaveSource(AliasSource sourceToSave)
{
local int i;
if (sourceToSave == none) return;
// Starting searching from the end of an array will make situations when
// we add several aliases to a single source in a row more efficient.
for (i = sourcesPendingToSave.length - 1;i >= 0; i -= 1) {
if (sourcesPendingToSave[i] == sourceToSave) return;
}
sourcesPendingToSave[sourcesPendingToSave.length] = sourceToSave;
}
public simulated final function PendingSaveObject(Aliases objectToSave)
{
local int i;
if (objectToSave == none) return;
// Starting searching from the end of an array will make situations when
// we add several aliases to a single `Aliases` object in a row
// more efficient.
for (i = aliasesPendingToSave.length - 1;i >= 0; i -= 1) {
if (aliasesPendingToSave[i] == objectToSave) return;
}
aliasesPendingToSave[aliasesPendingToSave.length] = objectToSave;
}
/**
* Forces saving of the next object (either `AliasSource` or `Aliases`)
* in queue to the config file.
*
* Does not reset the timer until next saving.
*/
private simulated final function DoSaveNextPendingObject()
{
if (sourcesPendingToSave.length > 0)
{
if (sourcesPendingToSave[0] != none) {
sourcesPendingToSave[0].SaveConfig();
}
sourcesPendingToSave.Remove(0, 1);
return;
}
if (aliasesPendingToSave.length > 0)
{
aliasesPendingToSave[0].SaveOrClear();
aliasesPendingToSave.Remove(0, 1);
}
}
/**
* Forces saving of all objects (both `AliasSource`s or `Aliases`s) in queue
* to their config files.
*/
private simulated final function SaveAllPendingObjects()
{
local int i;
for (i = 0; i < sourcesPendingToSave.length; i += 1) {
if (sourcesPendingToSave[i] == none) continue;
sourcesPendingToSave[i].SaveConfig();
}
for (i = 0; i < aliasesPendingToSave.length; i += 1) {
aliasesPendingToSave[i].SaveOrClear();
}
sourcesPendingToSave.length = 0;
aliasesPendingToSave.length = 0;
}
public simulated function Timer()
{
DoSaveNextPendingObject();
}
defaultproperties
{
saveInterval = 0.05
weaponAliasesSource = class'WeaponAliasSource'
colorAliasesSource = class'ColorAliasSource'
featureAliasesSource = class'FeatureAliasSource'
entityAliasesSource = class'EntityAliasSource'
}

396
sources/Aliases/AliasSource.uc

@ -1,11 +1,7 @@
/**
* Aliases allow users to define human-readable and easier to use
* "synonyms" to some symbol sequences (mainly names of UnrealScript classes).
* This class implements an alias database that stores aliases inside
* standard config ini-files.
* Several `AliasSource`s are supposed to exist separately, each storing
* aliases of particular kind: for weapon, zeds, colors, etc..
* Copyright 2020 - 2021 Anton Tarasenko
* Copyright 2020-2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -22,18 +18,9 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class AliasSource extends Singleton
class AliasSource extends BaseAliasSource
dependson(HashTable)
config(AcediaAliases);
// (Sub-)class of `Aliases` objects that this `AliasSource` uses to store
// aliases in per-object-config manner.
// Leaving this variable `none` will produce an `AliasSource` that can
// only store aliases in form of `record=(alias="...",value="...")`.
var public const class<Aliases> aliasesClass;
// Storage for all objects of `aliasesClass` class in the config.
// Exists after `OnCreated()` event and is maintained up-to-date at all times.
var private array<Aliases> loadedAliasObjects;
abstract;
// Links alias to a value.
// An array of these structures (without duplicate `alias` records) defines
@ -46,153 +33,222 @@ struct AliasValuePair
// Aliases data for saving and loading on a disk (ini-file).
// Name is chosen to make configurational files more readable.
var private config array<AliasValuePair> record;
// (Sub-)class of `Aliases` objects that this `AliasSource` uses to store
// aliases in per-object-config manner.
// Leaving this variable `none` will produce an `AliasSource` that can
// only store aliases in form of `record=(alias="...",value="...")`.
var public const class<AliasesStorage> aliasesClass;
// Storage for all objects of `aliasesClass` class in the config.
// Exists after `OnCreated()` event and is maintained up-to-date at all times.
var private array<AliasesStorage> loadedAliasObjects;
// Faster access to value by alias' name.
// It contains same records as `record` array + aliases from
// `loadedAliasObjects` objects when there are no duplicate aliases.
// Otherwise only stores first loaded alias.
var private HashTable aliasHash;
// Faster access to all aliases, corresponding to a certain value.
// This `HashTable` stores same data as `aliasHash`, but "in reverse":
// for each value as a "key" it stored `ArrayList` of corresponding aliases.
var private HashTable valueHash;
// `true` means that this `AliasSource` is awaiting saving into its config
var private bool pendingSaveToConfig;
var private LoggerAPI.Definition errIncorrectAliasPair, warnDuplicateAlias;
var private LoggerAPI.Definition warnInvalidAlias;
// Load and hash all the data `AliasSource` creation.
protected simulated function OnCreated()
protected function Constructor()
{
if (!AssertAliasesClassIsOwnedByThisSource()) {
Destroy();
// If this check fails - caller alias source is fundamentally broken
// and requires mod to be fixed
if (!ASSERT_AliasesClassIsOwnedByThisSource()) {
return;
}
// Load and hash
loadedAliasObjects = aliasesClass.static.LoadAllObjects();
aliasHash = _.collections.EmptyHashTable();
valueHash = _.collections.EmptyHashTable();
HashValidAliasesFromRecord();
HashValidAliasesFromPerObjectConfig();
}
protected simulated function OnDestroyed()
protected function Finalizer()
{
loadedAliasObjects.length = 0;
_.memory.Free(aliasHash);
aliasHash = none;
if (pendingSaveToConfig) {
SaveConfig();
}
}
// Ensures that our `Aliases` class is properly linked with this
// source's class. Logs failure otherwise.
private simulated final function bool AssertAliasesClassIsOwnedByThisSource()
private function bool ASSERT_AliasesClassIsOwnedByThisSource()
{
if (aliasesClass == none) return true;
if (aliasesClass.default.sourceClass == class) return true;
_.logger.Auto(errIncorrectAliasPair).ArgClass(class);
Destroy();
return false;
}
// Load hashes from `AliasSource`'s config (`record` array)
private simulated final function HashValidAliasesFromRecord()
private function HashValidAliasesFromRecord()
{
local int i;
local Text aliasAsText, valueAsText;
for (i = 0; i < record.length; i += 1)
{
aliasAsText = _.text.FromString(record[i].alias);
valueAsText = _.text.FromString(record[i].value);
InsertAlias(aliasAsText, valueAsText);
InsertAliasIntoHash(aliasAsText, valueAsText);
aliasAsText.FreeSelf();
valueAsText.FreeSelf();
}
}
// Load hashes from `Aliases` objects' config
private simulated final function HashValidAliasesFromPerObjectConfig()
private function HashValidAliasesFromPerObjectConfig()
{
local int i, j;
local Text nextValue;
local array<Text> valueAliases;
for (i = 0; i < loadedAliasObjects.length; i += 1)
{
nextValue = loadedAliasObjects[i].GetValue();
valueAliases = loadedAliasObjects[i].GetAliases();
for (j = 0; j < valueAliases.length; j += 1) {
InsertAlias(valueAliases[j], nextValue);
InsertAliasIntoHash(valueAliases[j], nextValue);
}
nextValue.FreeSelf();
_.memory.FreeMany(valueAliases);
}
}
public static function bool AreValuesCaseSensitive()
{
// Almost all built-in aliases are aliases to class names (or templates)
// and the rest are colors. Both are case-insensitive, so returning `false`
// is a good default implementation. Child classes can just change this
// value, if they need.
return false;
}
public function array<Text> GetAliases(BaseText value)
{
local int i;
local Text storedValue;
local ArrayList aliasesArray;
local array<Text> result;
storedValue = NormalizeValue(value);
aliasesArray = valueHash.GetArrayList(storedValue);
storedValue.FreeSelf();
if (aliasesArray == none) {
return result;
}
for (i = 0; i < aliasesArray.GetLength(); i += 1) {
result[result.length] = aliasesArray.GetText(i);
}
return result;
}
// "Normalizes" value:
// 1. Converts it into lower case if `AreValuesCaseSensitive()` returns
// `true`;
// 2. Converts in into `Text` in case passed value is `MutableText`, so
// that hash table is actually usable.
private function Text NormalizeValue(BaseText value)
{
if (value == none) {
return none;
}
if (AreValuesCaseSensitive()) {
return value.Copy();
}
return value.LowerCopy();
}
// Inserts alias into `aliasHash`, cleaning previous keys/values in case
// they already exist.
// Takes care of lower case conversion to store aliases in `aliasHash`
// in a case-insensitive way.
private simulated final function InsertAlias(BaseText alias, BaseText value)
// in a case-insensitive way. Depending on `AreValuesCaseSensitive()`, can also
// convert values to lower case.
private function InsertAliasIntoHash(BaseText alias, BaseText value)
{
local Text aliasLowerCaseCopy;
local HashTable.Entry hashEntry;
local Text storedAlias;
local Text storedValue;
local Text existingValue;
local ArrayList valueAliases;
if (alias == none) return;
if (value == none) return;
aliasLowerCaseCopy = alias.LowerCopy();
hashEntry = aliasHash.TakeEntry(aliasLowerCaseCopy);
if (hashEntry.value != none) {
LogDuplicateAliasWarning(alias, Text(hashEntry.value));
if (!alias.IsValidName())
{
_.logger.Auto(warnInvalidAlias)
.ArgClass(class)
.Arg(alias.Copy());
return;
}
storedAlias = alias.LowerCopy();
existingValue = aliasHash.GetText(storedAlias);
if (aliasHash.HasKey(storedAlias))
{
_.logger.Auto(warnDuplicateAlias)
.ArgClass(class)
.Arg(alias.Copy())
.Arg(existingValue);
}
_.memory.Free(hashEntry.key);
_.memory.Free(hashEntry.value);
aliasHash.SetItem(aliasLowerCaseCopy, value);
aliasLowerCaseCopy.FreeSelf();
_.memory.Free(existingValue);
storedValue = NormalizeValue(value);
// Add to `aliasHash`: alias -> value
aliasHash.SetItem(storedAlias, storedValue);
// Add to `valueHash`: value -> alias
valueAliases = valueHash.GetArrayList(storedValue);
if (valueAliases == none) {
valueAliases = _.collections.EmptyArrayList();
}
valueAliases.AddItem(storedAlias);
valueHash.SetItem(storedValue, valueAliases);
// Clean up
storedAlias.FreeSelf();
storedValue.FreeSelf();
}
/**
* Checks if given alias is present in caller `AliasSource`.
*
* @param alias Alias to check, case-insensitive.
* @return `true` if present, `false` otherwise.
*/
public simulated function bool HasAlias(BaseText alias)
public function bool HasAlias(BaseText alias)
{
local bool result;
local Text lowerCaseAlias;
local Text storedAlias;
if (alias == none) {
return false;
}
lowerCaseAlias = alias.LowerCopy();
result = aliasHash.HasKey(lowerCaseAlias);
lowerCaseAlias.FreeSelf();
storedAlias = alias.LowerCopy();
result = aliasHash.HasKey(storedAlias);
storedAlias.FreeSelf();
return result;
}
/**
* Return value stored for the given alias in caller `AliasSource`
* (as well as it's `Aliases` objects).
*
* @param alias Alias, for which method will attempt to return
* a value. Case-insensitive. If given `alias` starts with "$" character -
* that character will be removed before resolving that alias.
* @param copyOnFailure Whether method should return copy of original
* `alias` value in case caller source did not have any records
* corresponding to `alias`.
* @return If look up was successful - value, associated with the given
* alias `alias`. If lookup was unsuccessful, it depends on `copyOnFailure`
* flag: `copyOnFailure == false` means method will return `none`
* and `copyOnFailure == true` means method will return `alias.Copy()`.
* If `alias == none` method always returns `none`.
*/
public simulated function Text Resolve(
public function Text Resolve(
BaseText alias,
optional bool copyOnFailure)
{
local Text result;
local Text lowerCaseAlias;
local Text storedAlias;
if (alias == none) {
return none;
}
// Automatically get rid of "$" prefix, if present
if (alias.StartsWith(P("$"))) {
lowerCaseAlias = alias.LowerCopy(1);
}
else {
lowerCaseAlias = alias.LowerCopy();
}
result = Text(aliasHash.GetItem(lowerCaseAlias));
lowerCaseAlias.FreeSelf();
storedAlias = alias.LowerCopy();
result = aliasHash.GetText(storedAlias);
storedAlias.FreeSelf();
if (result != none) {
return result;
}
@ -202,99 +258,90 @@ public simulated function Text Resolve(
return none;
}
/**
* Adds another alias to the caller `AliasSource`.
* If alias with the same name as `aliasToAdd` already exists, -
* method overwrites it.
*
* Can fail iff `aliasToAdd` is an invalid alias or `aliasValue == none`.
*
* When adding alias to an object (`saveInObject == true`) alias `aliasToAdd`
* will be altered by changing any ':' inside it into a '.'.
* This is a necessary measure to allow storing class names in
* config files via per-object-config.
*
* NOTE: This call will cause update of an ini-file. That update can be
* slightly delayed, so do not make assumptions about it's immediacy.
*
* NOTE #2: Removing alias would require this method to go through the
* whole `AliasSource` to remove possible duplicates.
* This means that unless you can guarantee that there is no duplicates, -
* performing a lot of alias additions during run-time can be costly.
*
* @param aliasToAdd Alias that you want to add to caller source.
* Alias names are case-insensitive.
* @param aliasValue Intended value of this alias.
* @param saveInObject Setting this to `true` will make `AliasSource` save
* given alias in per-object-config storage, while keeping it at default
* `false` will just add alias to the `record=` storage.
* If caller `AliasSource` does not support per-object-config storage, -
* this flag will be ignores.
* @return `true` if alias was added and `false` otherwise (alias was invalid).
*/
public simulated final function bool AddAlias(
Text aliasToAdd,
Text aliasValue,
optional bool saveInObject)
public function bool AddAlias(BaseText aliasToAdd, BaseText aliasValue)
{
local Text lowerCaseAlias;
local AliasValuePair newPair;
local Text storedAlias;
if (aliasToAdd == none) return false;
if (aliasValue == none) return false;
if (!aliasToAdd.IsValidName()) return false;
lowerCaseAlias = aliasToAdd.LowerCopy();
if (aliasHash.HasKey(lowerCaseAlias)) {
// Check if alias already exists and if yes - remove it
storedAlias = aliasToAdd.LowerCopy();
if (aliasHash.HasKey(storedAlias)) {
RemoveAlias(aliasToAdd);
}
// Save
if (saveInObject) {
GetAliasesObjectWithValue(aliasValue).AddAlias(aliasToAdd);
storedAlias.FreeSelf();
// Add alias-value pair
AddToConfigRecords(aliasToAdd.ToString(), aliasValue.ToString());
InsertAliasIntoHash(aliasToAdd, aliasValue);
return true;
}
public function bool RemoveAlias(BaseText aliasToRemove)
{
local Text storedAlias, storedValue;
local ArrayList valueAliases;
if (aliasToRemove == none) return false;
if (!aliasToRemove.IsValidName()) return false;
storedAlias = aliasToRemove.LowerCopy();
storedValue = aliasHash.GetText(storedAlias);
if (storedValue == none)
{
storedAlias.FreeSelf();
return false;
}
aliasHash.RemoveItem(aliasToRemove);
// Since we've found `storedValue`, this couldn't possibly be `none` if
// "same data invariant" is preserved (see their declaration)
valueAliases = valueHash.GetArrayList(storedValue);
if (valueAliases != none) {
valueAliases.RemoveItem(storedAlias, true);
}
else
if (valueAliases != none && valueAliases.GetLength() <= 0)
{
newPair.alias = aliasToAdd.ToString();
newPair.value = aliasValue.ToString();
record[record.length] = newPair;
valueHash.SetItem(storedValue, none);
valueAliases = none;
}
aliasHash.SetItem(lowerCaseAlias, aliasValue);
_.memory.Free(lowerCaseAlias);
_.memory.Free(aliasValue);
AliasService(class'AliasService'.static.Require()).PendingSaveSource(self);
_.memory.Free(valueAliases);
RemoveFromConfigRecords(aliasToRemove.ToString());
return true;
}
/**
* Removes alias (all records with it, in case of duplicates) from
* the caller `AliasSource`.
*
* Cannot fail.
*
* NOTE: This call will cause update of an ini-file. That update can be
* slightly delayed, so do not make assumptions about it's immediacy.
*
* NOTE #2: removing alias requires this method to go through the
* whole `AliasSource` to remove possible duplicates, which can make
* performing a lot of alias removal during run-time costly.
*
* @param aliasToRemove Alias that you want to remove from caller source.
*/
public simulated final function RemoveAlias(BaseText aliasToRemove)
// Takes `string`s that represents alias to remove in proper case (lower for
// aliases and for values it depends on the caller source's settings):
// aliases are supposed to be ASCII, so `string` should handle it and its
// comparison just fine
private function AddToConfigRecords(string alias, string value)
{
local AliasValuePair newPair;
newPair.alias = alias;
newPair.value = value;
record[record.length] = newPair;
// Request saving
if (!pendingSaveToConfig)
{
pendingSaveToConfig = true;
_.scheduler.RequestDiskAccess(self).connect = SaveSelf;
}
}
// Takes `string` that represents alias to remove in lower case: aliases are
// supposed to be ASCII, so `string` should handle it and its comparison just
// fine
private function RemoveFromConfigRecords(string aliasToRemove)
{
local int i;
local bool isMatchingRecord;
local bool removedAliasFromRecord;
local HashTable.Entry hashEntry;
if (aliasToRemove == none) {
return;
}
hashEntry = aliasHash.TakeEntry(aliasToRemove);
_.memory.Free(hashEntry.key);
_.memory.Free(hashEntry.value);
// Aliases are supposed to be ASCII, so `string` should handle it and its
// comparison just fine
while (i < record.length)
{
isMatchingRecord = aliasToRemove
.CompareToString(record[i].alias, SCASE_INSENSITIVE);
if (isMatchingRecord)
if (aliasToRemove ~= record[i].alias)
{
record.Remove(i, 1);
removedAliasFromRecord = true;
@ -303,46 +350,24 @@ public simulated final function RemoveAlias(BaseText aliasToRemove)
i += 1;
}
}
// Since admins can fuck up and add duplicate aliases, we need to
// thoroughly check every alias object
for (i = 0; i < loadedAliasObjects.length; i += 1) {
loadedAliasObjects[i].RemoveAlias(aliasToRemove);
loadedAliasObjects[i].RemoveAlias_S(aliasToRemove);
}
if (removedAliasFromRecord)
// Alias objects can request disk access themselves, so only record if
// needed for the record
if (removedAliasFromRecord && !pendingSaveToConfig)
{
AliasService(class'AliasService'.static.Require())
.PendingSaveSource(self);
pendingSaveToConfig = true;
_.scheduler.RequestDiskAccess(self).connect = SaveSelf;
}
}
private simulated final function LogDuplicateAliasWarning(
BaseText alias,
BaseText existingValue)
private function SaveSelf()
{
_.logger.Auto(warnDuplicateAlias)
.ArgClass(class)
.Arg(alias.Copy())
.Arg(existingValue.Copy());
}
// Tries to find a loaded `Aliases` config object that stores aliases for
// the given value. If such object does not exists - creates a new one.
// Assumes `value != none`.
private simulated final function Aliases GetAliasesObjectWithValue(
BaseText value)
{
local int i;
local Text nextValue;
local Aliases newAliasesObject;
for (i = 0; i < loadedAliasObjects.length; i += 1)
{
nextValue = loadedAliasObjects[i].GetValue();
if (value.Compare(nextValue)) {
return loadedAliasObjects[i];
}
_.memory.Free(nextValue);
}
newAliasesObject = aliasesClass.static.LoadObject(value);
loadedAliasObjects[loadedAliasObjects.length] = newAliasesObject;
return newAliasesObject;
pendingSaveToConfig = false;
SaveConfig();
}
defaultproperties
@ -351,4 +376,5 @@ defaultproperties
aliasesClass = class'Aliases'
errIncorrectAliasPair = (l=LOG_Error,m="`AliasSource`-`Aliases` class pair is incorrectly setup for source `%1`. Omitting it.")
warnDuplicateAlias = (l=LOG_Warning,m="Alias source `%1` has duplicate record for alias \"%2\". This is likely due to an erroneous config. \"%3\" value will be used.")
warnInvalidAlias = (l=LOG_Warning,m="Alias source `%1` has record with invalid alias \"%2\". This is likely due to an erroneous config. This alias will be discarded.")
}

243
sources/Aliases/Aliases.uc

@ -1,15 +1,6 @@
/**
* This is a simple helper object for `AliasSource` that can store
* an array of aliases in config files in a per-object-config manner.
* One `Aliases` object can store several aliases for a single value.
* It is recommended that you do not try to access these objects directly.
* Class name `Aliases` is chosen to make configuration files
* more readable.
* It's only interesting function is storing '.'s as ':' in it's config,
* which is necessary to allow storing aliases for class names via
* these objects (since UnrealScript's cannot handle '.'s in object's names
* in it's configs).
* Copyright 2019 - 2021 Anton Tarasenko
* Config object for `Aliases_Feature`.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -26,184 +17,118 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class Aliases extends AcediaObject
perObjectConfig
class Aliases extends FeatureConfig
perobjectconfig
config(AcediaAliases);
// Name of the configurational file (without extension) where
// this `AliasSource`'s data will be stored.
var protected const string configName;
// Link to the `AliasSource` that uses `Aliases` objects of this class.
// To ensure that any `Aliases` sub-class only belongs to one `AliasSource`.
var public const class<AliasSource> sourceClass;
// Aliases, recorded by this `Aliases` object that all mean the same value,
// defined by this object's name `string(self.name)`.
var protected config array<string> alias;
// Since:
// 1. '.'s in values are converted into ':' for storage purposes;
// 2. We have to store values in `string` to make use of config files.
// we need methods to convert between "storage" (`string`)
// and "actual" (`Text`) value version.
// `ToStorageVersion()` and `ToActualVersion()` do that.
private final static function string ToStorageVersion(BaseText actualValue)
struct CustomSourceRecord
{
return Repl(actualValue.ToString(), ".", ":");
}
var string name;
var class<AliasSource> source;
};
// See comment to `ToStorageVersion()`.
private final static function Text ToActualVersion(string storageValue)
{
return __().text.FromString(Repl(storageValue, ":", "."));
}
var public config class<AliasSource> weaponAliasSource;
var public config class<AliasSource> colorAliasSource;
var public config class<AliasSource> featureAliasSource;
var public config class<AliasSource> entityAliasSource;
var public config array<CustomSourceRecord> customSource;
/**
* Loads all `Aliases` objects from their config file
* (defined in paired `AliasSource` class).
*
* @return Array of all `Aliases` objects, loaded from their config file.
*/
public static final function array<Aliases> LoadAllObjects()
protected function HashTable ToData()
{
local int i;
local array<string> objectNames;
local array<Aliases> loadedAliasObjects;
objectNames = GetPerObjectNames(default.configName,
string(default.class.name), MaxInt);
for (i = 0; i < objectNames.length; i += 1) {
loadedAliasObjects[i] = LoadObjectByName(objectNames[i]);
}
return loadedAliasObjects;
}
// Loads a new `Aliases` object by it's given name (`objectName`).
private static final function Aliases LoadObjectByName(string objectName)
{
local Aliases result;
// Since `MemoryAPI` for now does not support specifying names
// to created objects - do some manual dark magic and
// initialize this shit ourselves
result = new(none, objectName) default.class;
result._constructor();
return result;
}
local Text nextKey;
local HashTable data, otherSourcesData;
/**
* Loads a new `Aliases` object based on the value (`aliasesValue`)
* of it's aliases.
*
* @param aliasesValue Value that aliases in this `Aliases` object will
* correspond to.
* @return Instance of `Aliases` object with a given name.
*/
public static final function Aliases LoadObject(BaseText aliasesValue)
{
if (aliasesValue != none) {
return LoadObjectByName(ToStorageVersion(aliasesValue));
data = __().collections.EmptyHashTable();
// Add named aliases
data.SetString(P("weapon"), string(weaponAliasSource));
data.SetString(P("color"), string(colorAliasSource));
data.SetString(P("feature"), string(featureAliasSource));
data.SetString(P("entity"), string(entityAliasSource));
// Add the rest
otherSourcesData = __().collections.EmptyHashTable();
for (i = 0; i < customSource.length; i += 1)
{
nextKey = _.text.FromString(customSource[i].name);
otherSourcesData.SetString(nextKey, string(customSource[i].source));
nextKey.FreeSelf();
}
return none;
data.SetItem(P("other"), otherSourcesData);
otherSourcesData.FreeSelf();
return data;
}
/**
* Returns value that caller's `Aliases` object's aliases point to.
*
* @return Value, stored by this object.
*/
public final function Text GetValue()
{
return ToActualVersion(string(self.name));
}
/**
* Returns array of aliases that caller `Aliases` tells us point to it's value.
*
* @return Array of all aliases, stored by caller `Aliases` object.
*/
public final function array<Text> GetAliases()
protected function FromData(HashTable source)
{
local int i;
local array<Text> textAliases;
for (i = 0; i < alias.length; i += 1) {
textAliases[i] = _.text.FromString(alias[i]);
}
return textAliases;
}
local HashTable otherSourcesData;
/**
* [For inner use by `AliasSource`] Adds new alias to this object.
*
* Does no duplicates checks through for it's `AliasSource` and
* neither does it update relevant `AliasHash`,
* but will prevent adding duplicate records inside it's own storage.
*
* @param aliasToAdd Alias to add to caller `Aliases` object.
* If `none`, method will do nothing.
*/
public final function AddAlias(BaseText aliasToAdd)
{
local int i;
if (aliasToAdd == none) return;
for (i = 0; i < alias.length; i += 1)
{
if (aliasToAdd.CompareToString(alias[i], SCASE_INSENSITIVE)) {
if (source == none) {
return;
}
// We cast `class` into `string`
// (e.g. `string(class'AcediaAliases_Weapons')`)
// instead of writing full name of the class so that code is independent
// from this package's name, making it easier to change later
weaponAliasSource = class<AliasSource>(_.memory.LoadClass_S(
source.GetString(P("weapon"), string(class'WeaponAliasSource'))));
colorAliasSource = class<AliasSource>(_.memory.LoadClass_S(
source.GetString(P("color"), string(class'ColorAliasSource'))));
featureAliasSource = class<AliasSource>(_.memory.LoadClass_S(
source.GetString(P("feature"), string(class'FeatureAliasSource'))));
entityAliasSource = class<AliasSource>(_.memory.LoadClass_S(
source.GetString(P("entity"), string(class'EntityAliasSource'))));
otherSourcesData = source.GetHashTable(P("other"));
if (otherSourcesData != none)
{
ReadOtherSources(otherSourcesData);
otherSourcesData.FreeSelf();
}
alias[alias.length] = aliasToAdd.ToString();
AliasService(class'AliasService'.static.Require())
.PendingSaveObject(self);
}
/**
* [For inner use by `AliasSource`] Removes alias from this object.
*
* Does not update relevant `AliasHash`.
*
* Will prevent adding duplicate records inside it's own storage.
*
* @param aliasToRemove Alias to remove from caller `Aliases` object.
*/
public final function RemoveAlias(BaseText aliasToRemove)
// Doesn't check whether `otherSources` is `none`
protected function ReadOtherSources(HashTable otherSourcesData)
{
local int i;
local bool removedAlias;
if (aliasToRemove == none) return;
while (i < alias.length)
local CustomSourceRecord newRecord;
local BaseText keyAsText, valueAsText;
local AcediaObject key, value;
local HashTableIterator iter;
customSource.length = 0;
iter = HashTableIterator(otherSourcesData.Iterate().LeaveOnlyNotNone());
for (iter = iter; !iter.HasFinished(); iter.Next())
{
if (aliasToRemove.CompareToString(alias[i], SCASE_INSENSITIVE))
key = iter.GetKey();
value = iter.GetKey();
keyAsText = BaseText(key);
valueAsText = BaseText(value);
if (keyAsText != none && valueAsText != none)
{
alias.Remove(i, 1);
removedAlias = true;
newRecord.name = keyAsText.ToString();
newRecord.source = class<AliasSource>(
_.memory.LoadClass_S(valueAsText.ToString()));
if (newRecord.source != none) {
customSource[customSource.length] = newRecord;
}
else {
i += 1;
}
}
if (removedAlias)
{
AliasService(class'AliasService'.static.Require())
.PendingSaveObject(self);
_.memory.Free(key);
_.memory.Free(value);
}
}
/**
* If this object still has any alias records, - forces a rewrite of it's data
* into the config file, otherwise - removes it's record entirely.
*/
public final function SaveOrClear()
protected function DefaultIt()
{
if (alias.length <= 0) {
ClearConfig();
}
else {
SaveConfig();
}
customSource.length = 0;
weaponAliasSource = class'WeaponAliasSource';
colorAliasSource = class'ColorAliasSource';
featureAliasSource = class'FeatureAliasSource';
entityAliasSource = class'EntityAliasSource';
}
defaultproperties
{
sourceClass = class'AliasSource'
configName = "AcediaAliases"
weaponAliasSource = class'WeaponAliasSource'
colorAliasSource = class'ColorAliasSource'
featureAliasSource = class'FeatureAliasSource'
entityAliasSource = class'EntityAliasSource'
}

323
sources/Aliases/AliasesAPI.uc

@ -1,6 +1,6 @@
/**
* Provides convenient access to Aliases-related functions.
* Copyright 2020 - 2021 Anton Tarasenko
* Copyright 2020-2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -20,154 +20,169 @@
class AliasesAPI extends AcediaObject
dependson(LoggerAPI);
var private LoggerAPI.Definition noWeaponAliasSource, invalidWeaponAliasSource;
var private LoggerAPI.Definition noColorAliasSource, invalidColorAliasSource;
var private LoggerAPI.Definition noFeatureAliasSource, invalidFeatureAliasSource;
var private LoggerAPI.Definition noEntityAliasSource, invalidEntityAliasSource;
// To avoid bothering with fetching `Aliases_Feature` each time we need to
// access an alias source, we save all the basic ones separately and
// `Aliases_Feature` can simply trigger their updates whenever necessary via
// `AliasesAPI._reloadSources()` function.
var private BaseAliasSource weaponAliasSource;
var private BaseAliasSource colorAliasSource;
var private BaseAliasSource featureAliasSource;
var private BaseAliasSource entityAliasSource;
public function _reloadSources()
{
local Aliases_Feature feature;
_.memory.Free(weaponAliasSource);
_.memory.Free(colorAliasSource);
_.memory.Free(featureAliasSource);
_.memory.Free(entityAliasSource);
weaponAliasSource = none;
colorAliasSource = none;
featureAliasSource = none;
entityAliasSource = none;
feature = Aliases_Feature(
class'Aliases_Feature'.static.GetEnabledInstance());
if (feature == none) {
return;
}
weaponAliasSource = feature.GetWeaponSource();
colorAliasSource = feature.GetColorSource();
featureAliasSource = feature.GetFeatureSource();
entityAliasSource = feature.GetEntitySource();
_.memory.Free(feature);
}
/**
* Provides an easier access to the instance of the `AliasSource` of
* the given class.
* Provides an easier access to the instance of the custom `BaseAliasSource`
* with a given name `sourceName`.
*
* Can fail if `customSourceClass` is incorrectly defined.
* Custom alias sources can be added manually by the admin through the
* `Aliases_Feature` config file..
*
* @param customSourceClass Class of the source we want.
* @return Instance of the requested `AliasSource`,
* `none` if `customSourceClass` is incorrectly defined.
* @param sourceName Name that alias source was added as to
* `Aliases_Feature`.
* @return Instance of the requested `BaseAliasSource`,
* `none` if `sourceName` is `none`, does not refer to any alias source
* or `Aliases_Feature` is disabled.
*/
public final function AliasSource GetCustomSource(
class<AliasSource> customSourceClass)
public final function BaseAliasSource GetCustomSource(BaseText sourceName)
{
return AliasSource(customSourceClass.static.GetInstance(true));
local Aliases_Feature feature;
if (sourceName == none) {
return none;
}
feature =
Aliases_Feature(class'Aliases_Feature'.static.GetEnabledInstance());
if (feature != none) {
return feature.GetCustomSource(sourceName);
}
return none;
}
/**
* Returns `AliasSource` that is designated in configuration files as
* Returns `BaseAliasSource` that is designated in configuration files as
* a source for weapon aliases.
*
* NOTE: while by default weapon aliases source will contain only weapon
* aliases, you should not assume that. Acedia allows admins to store all
* the aliases in the same config.
*
* @return Reference to the `AliasSource` that contains weapon aliases.
* @return Reference to the `BaseAliasSource` that contains weapon aliases.
* Can return `none` if no source for weapons was configured or
* the configured source is incorrectly defined.
*/
public final function AliasSource GetWeaponSource()
public final function BaseAliasSource GetWeaponSource()
{
local AliasSource weaponSource;
local class<AliasSource> sourceClass;
sourceClass = class'AliasService'.default.weaponAliasesSource;
if (sourceClass == none)
{
_.logger.Auto(noWeaponAliasSource);
return none;
if (weaponAliasSource != none) {
weaponAliasSource.NewRef();
}
weaponSource = AliasSource(sourceClass.static.GetInstance(true));
if (weaponSource == none)
{
_.logger.Auto(invalidWeaponAliasSource).ArgClass(sourceClass);
return none;
}
return weaponSource;
return weaponAliasSource;
}
/**
* Returns `AliasSource` that is designated in configuration files as
* Returns `BaseAliasSource` that is designated in configuration files as
* a source for color aliases.
*
* NOTE: while by default color aliases source will contain only color aliases,
* you should not assume that. Acedia allows admins to store all the aliases
* in the same config.
*
* @return Reference to the `AliasSource` that contains color aliases.
* @return Reference to the `BaseAliasSource` that contains color aliases.
* Can return `none` if no source for colors was configured or
* the configured source is incorrectly defined.
*/
public final function AliasSource GetColorSource()
public final function BaseAliasSource GetColorSource()
{
local AliasSource colorSource;
local class<AliasSource> sourceClass;
sourceClass = class'AliasService'.default.colorAliasesSource;
if (sourceClass == none)
{
_.logger.Auto(noColorAliasSource);
return none;
if (colorAliasSource != none) {
colorAliasSource.NewRef();
}
colorSource = AliasSource(sourceClass.static.GetInstance(true));
if (colorSource == none)
{
_.logger.Auto(invalidColorAliasSource).ArgClass(sourceClass);
return none;
}
return colorSource;
return colorAliasSource;
}
/**
* Returns `AliasSource` that is designated in configuration files as
* Returns `BaseAliasSource` that is designated in configuration files as
* a source for feature aliases.
*
* NOTE: while by default feature aliases source will contain only feature
* aliases, you should not assume that. Acedia allows admins to store all the
* aliases in the same config.
*
* @return Reference to the `AliasSource` that contains feature aliases.
* @return Reference to the `BaseAliasSource` that contains feature aliases.
* Can return `none` if no source for features was configured or
* the configured source is incorrectly defined.
*/
public final function AliasSource GetFeatureSource()
public final function BaseAliasSource GetFeatureSource()
{
local AliasSource featureSource;
local class<AliasSource> sourceClass;
sourceClass = class'AliasService'.default.featureAliasesSource;
if (sourceClass == none)
{
_.logger.Auto(noFeatureAliasSource);
return none;
if (featureAliasSource != none) {
featureAliasSource.NewRef();
}
featureSource = AliasSource(sourceClass.static.GetInstance(true));
if (featureSource == none)
{
_.logger.Auto(invalidFeatureAliasSource).ArgClass(sourceClass);
return none;
}
return featureSource;
return featureAliasSource;
}
/**
* Returns `AliasSource` that is designated in configuration files as
* Returns `BaseAliasSource` that is designated in configuration files as
* a source for entity aliases.
*
* NOTE: while by default entity aliases source will contain only entity
* aliases, you should not assume that. Acedia allows admins to store all the
* aliases in the same config.
*
* @return Reference to the `AliasSource` that contains entity aliases.
* @return Reference to the `BaseAliasSource` that contains entity aliases.
* Can return `none` if no source for entities was configured or
* the configured source is incorrectly defined.
*/
public final function AliasSource GetEntitySource()
public final function BaseAliasSource GetEntitySource()
{
local AliasSource entitySource;
local class<AliasSource> sourceClass;
sourceClass = class'AliasService'.default.entityAliasesSource;
if (sourceClass == none)
{
_.logger.Auto(noEntityAliasSource);
return none;
if (entityAliasSource != none) {
entityAliasSource.NewRef();
}
entitySource = AliasSource(sourceClass.static.GetInstance(true));
if (entitySource == none)
{
_.logger.Auto(invalidEntityAliasSource).ArgClass(sourceClass);
return entityAliasSource;
}
private final function Text ResolveWithSource(
BaseText alias,
BaseAliasSource source,
optional bool copyOnFailure)
{
local Text result;
local Text trimmedAlias;
if (alias == none) {
return none;
}
return entitySource;
if (alias.StartsWith(P("$"))) {
trimmedAlias = alias.Copy(1);
}
else {
trimmedAlias = alias.Copy();
}
if (source != none) {
result = source.Resolve(trimmedAlias, copyOnFailure);
}
else if (copyOnFailure) {
result = trimmedAlias.Copy();
}
trimmedAlias.FreeSelf();
return result;
}
/**
* Tries to look up a value stored for given alias in an `AliasSource`
* configured to store weapon aliases. Returns `none` on failure.
* Tries to look up a value stored for given alias in an `BaseAliasSource`
* configured to store weapon aliases.
*
* In Acedia aliases are typically prefixed with '$' to indicate that user
* means to enter alias. This method is able to handle both aliases with and
* without that prefix. This does not lead to conflicts, because '$' is cannot
* be a valid part of any alias.
*
* Lookup of alias can fail if either alias does not exist in weapon alias
* source or weapon alias source itself does not exist
@ -179,7 +194,8 @@ public final function AliasSource GetEntitySource()
* look up a value. Case-insensitive.
* @param copyOnFailure Whether method should return copy of original
* `alias` value in case caller source did not have any records
* corresponding to `alias`.
* corresponding to `alias`. If `alias` was specified with '$' prefix -
* it will be discarded.
* @return If look up was successful - value, associated with the given
* alias `alias`. If lookup was unsuccessful, it depends on `copyOnFailure`
* flag: `copyOnFailure == false` means method will return `none`
@ -190,17 +206,17 @@ public final function Text ResolveWeapon(
BaseText alias,
optional bool copyOnFailure)
{
local AliasSource source;
source = GetWeaponSource();
if (source != none) {
return source.Resolve(alias, copyOnFailure);
}
return none;
return ResolveWithSource(alias, weaponAliasSource, copyOnFailure);
}
/**
* Tries to look up a value stored for given alias in an `AliasSource`
* configured to store color aliases. Reports error on failure.
* Tries to look up a value stored for given alias in an `BaseAliasSource`
* configured to store color aliases.
*
* In Acedia aliases are typically prefixed with '$' to indicate that user
* means to enter alias. This method is able to handle both aliases with and
* without that prefix. This does not lead to conflicts, because '$' is cannot
* be a valid part of any alias.
*
* Lookup of alias can fail if either alias does not exist in color alias
* source or color alias source itself does not exist
@ -212,7 +228,8 @@ public final function Text ResolveWeapon(
* look up a value. Case-insensitive.
* @param copyOnFailure Whether method should return copy of original
* `alias` value in case caller source did not have any records
* corresponding to `alias`.
* corresponding to `alias`. If `alias` was specified with '$' prefix -
* it will be discarded.
* @return If look up was successful - value, associated with the given
* alias `alias`. If lookup was unsuccessful, it depends on `copyOnFailure`
* flag: `copyOnFailure == false` means method will return `none`
@ -223,17 +240,17 @@ public final function Text ResolveColor(
BaseText alias,
optional bool copyOnFailure)
{
local AliasSource source;
source = GetColorSource();
if (source != none) {
return source.Resolve(alias, copyOnFailure);
}
return none;
return ResolveWithSource(alias, colorAliasSource, copyOnFailure);
}
/**
* Tries to look up a value stored for given alias in an `AliasSource`
* configured to store feature aliases. Reports error on failure.
* Tries to look up a value stored for given alias in an `BaseAliasSource`
* configured to store feature aliases.
*
* In Acedia aliases are typically prefixed with '$' to indicate that user
* means to enter alias. This method is able to handle both aliases with and
* without that prefix. This does not lead to conflicts, because '$' is cannot
* be a valid part of any alias.
*
* Lookup of alias can fail if either alias does not exist in feature alias
* source or feature alias source itself does not exist
@ -245,7 +262,8 @@ public final function Text ResolveColor(
* look up a value. Case-insensitive.
* @param copyOnFailure Whether method should return copy of original
* `alias` value in case caller source did not have any records
* corresponding to `alias`.
* corresponding to `alias`. If `alias` was specified with '$' prefix -
* it will be discarded.
* @return If look up was successful - value, associated with the given
* alias `alias`. If lookup was unsuccessful, it depends on `copyOnFailure`
* flag: `copyOnFailure == false` means method will return `none`
@ -256,17 +274,17 @@ public final function Text ResolveFeature(
BaseText alias,
optional bool copyOnFailure)
{
local AliasSource source;
source = GetFeatureSource();
if (source != none) {
return source.Resolve(alias, copyOnFailure);
}
return none;
return ResolveWithSource(alias, featureAliasSource, copyOnFailure);
}
/**
* Tries to look up a value stored for given alias in an `AliasSource`
* configured to store entity aliases. Reports error on failure.
* Tries to look up a value stored for given alias in an `BaseAliasSource`
* configured to store entity aliases.
*
* In Acedia aliases are typically prefixed with '$' to indicate that user
* means to enter alias. This method is able to handle both aliases with and
* without that prefix. This does not lead to conflicts, because '$' is cannot
* be a valid part of any alias.
*
* Lookup of alias can fail if either alias does not exist in entity alias
* source or entity alias source itself does not exist
@ -278,7 +296,8 @@ public final function Text ResolveFeature(
* look up a value. Case-insensitive.
* @param copyOnFailure Whether method should return copy of original
* `alias` value in case caller source did not have any records
* corresponding to `alias`.
* corresponding to `alias`. If `alias` was specified with '$' prefix -
* it will be discarded.
* @return If look up was successful - value, associated with the given
* alias `alias`. If lookup was unsuccessful, it depends on `copyOnFailure`
* flag: `copyOnFailure == false` means method will return `none`
@ -289,23 +308,53 @@ public final function Text ResolveEntity(
BaseText alias,
optional bool copyOnFailure)
{
local AliasSource source;
source = GetEntitySource();
if (source != none) {
return source.Resolve(alias, copyOnFailure);
}
return ResolveWithSource(alias, entityAliasSource, copyOnFailure);
}
/**
* Tries to look up a value stored for given alias in a custom alias source
* with a given name `sourceName`.
*
* In Acedia aliases are typically prefixed with '$' to indicate that user
* means to enter alias. This method is able to handle both aliases with and
* without that prefix. This does not lead to conflicts, because '$' is cannot
* be a valid part of any alias.
*
* Custom alias sources are any type of alias source that isn't built-in into
* Acedia. They can either be added manually by the admin through config file.
*
* Lookup of alias can fail if either alias does not exist in entity alias
* source or entity alias source itself does not exist
* (due to either faulty configuration or incorrect definition).
* To determine if entity alias source exists you can check
* `_.alias.GetCustomSource()` value.
*
* @param alias Alias, for which method will attempt to
* look up a value. Case-insensitive.
* @param copyOnFailure Whether method should return copy of original
* `alias` value in case caller source did not have any records
* corresponding to `alias`. If `alias` was specified with '$' prefix -
* it will be discarded.
* @return If look up was successful - value, associated with the given
* alias `alias`. If lookup was unsuccessful, it depends on `copyOnFailure`
* flag: `copyOnFailure == false` means method will return `none`
* and `copyOnFailure == true` means method will return `alias.Copy()`.
* If `alias == none` method always returns `none`.
*/
public final function Text ResolveCustom(
BaseText sourceName,
BaseText alias,
optional bool copyOnFailure)
{
local BaseAliasSource customSource;
customSource = GetCustomSource(sourceName);
if (customSource == none) {
return none;
}
return ResolveWithSource(alias, customSource, copyOnFailure);
}
defaultproperties
{
// TODO: all this shit below can be done as two messages
noWeaponAliasSource = (l=LOG_Error,m="No weapon aliases source configured for Acedia's alias API. Error is most likely cause by erroneous config.")
invalidWeaponAliasSource = (l=LOG_Error,m="`AliasSource` class `%1` is configured to store weapon aliases, but it seems to be invalid. This is a bug and not configuration file problem, but issue might be avoided by using a different `AliasSource`.")
noColorAliasSource = (l=LOG_Error,m="No color aliases source configured for Acedia's alias API. Error is most likely cause by erroneous config.")
invalidColorAliasSource = (l=LOG_Error,m="`AliasSource` class `%1` is configured to store color aliases, but it seems to be invalid. This is a bug and not configuration file problem, but issue might be avoided by using a different `AliasSource`.")
noFeatureAliasSource = (l=LOG_Error,m="No feature aliases source configured for Acedia's alias API. Error is most likely cause by erroneous config.")
invalidFeatureAliasSource = (l=LOG_Error,m="`AliasSource` class `%1` is configured to store feature aliases, but it seems to be invalid. This is a bug and not configuration file problem, but issue might be avoided by using a different `AliasSource`.")
noEntityAliasSource = (l=LOG_Error,m="No entity aliases source configured for Acedia's alias API. Error is most likely cause by erroneous config.")
invalidEntityAliasSource = (l=LOG_Error,m="`AliasSource` class `%1` is configured to store entity aliases, but it seems to be invalid. This is a bug and not configuration file problem, but issue might be avoided by using a different `AliasSource`.")
}

205
sources/Aliases/AliasesStorage.uc

@ -0,0 +1,205 @@
/**
* This is a simple helper object for `AliasSource` that can store
* an array of aliases in config files in a per-object-config manner.
* One `AliasesStorage` object can store several aliases for a single
* value.
* It is recommended that you do not try to access these objects directly.
* `AliasesStorage` is abstract, so it can never be created, for any
* actual use create a child class. Suggested name scheme is
* "<something>Aliases", like "ColorAliases" or "WeaponAliases".
* It's only interesting function is storing '.'s as ':' in it's config,
* which is necessary to allow storing aliases for class names via
* these objects (since UnrealScript's cannot handle '.'s in object's names
* in it's configs).
* [INTERNAL] This is an internal object and should not be directly modified,
* its only allowed use is documented way to create new alias sources.
* Copyright 2019-2022 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 AliasesStorage extends AcediaObject
perObjectConfig
config(AcediaAliases)
abstract;
// Aliases, recorded by this `AliasesStorage` object that all refer to
// the same value, defined by this object's name `string(self.name)`
var protected config array<string> alias;
// Name of the configurational file (without extension) where
// this `AliasSource`'s data will be stored
var protected const string configName;
// Link to the `AliasSource` that uses `AliasesStorage` objects of this class.
// To ensure that any `AliasesStorage` sub-class only belongs to one
// `AliasSource`.
var public const class<AliasSource> sourceClass;
// `true` means that this `AliasesStorage` object is awaiting saving into its
// config
var private bool pendingSaveToConfig;
// Since:
// 1. '.'s in values are converted into ':' for storage purposes;
// 2. We have to store values in `string` to make use of config files.
// we need methods to convert between "storage" (`string`)
// and "actual" (`Text`) value version.
// `ToStorageVersion()` and `ToActualVersion()` do that.
private final static function string ToStorageVersion(BaseText actualValue)
{
return Repl(actualValue.ToString(), ".", ":");
}
// See comment to `ToStorageVersion()`
private final static function Text ToActualVersion(string storageValue)
{
return __().text.FromString(Repl(storageValue, ":", "."));
}
/**
* Loads all `AliasesStorage` objects from their config file
* (defined in paired `AliasSource` class).
*
* This is an internal method and returned `AliasesStorage` objects require
* a special treatment: they aren't meant to be used with Acedia's reference
* counting - never release their references.
*
* @return Array of all `Aliases` objects, loaded from their config file.
*/
public static final function array<AliasesStorage> LoadAllObjects()
{
local int i;
local array<string> objectNames;
local array<AliasesStorage> loadedAliasObjects;
objectNames = GetPerObjectNames(default.configName,
string(default.class.name), MaxInt);
for (i = 0; i < objectNames.length; i += 1) {
loadedAliasObjects[i] = LoadObjectByName(objectNames[i]);
}
return loadedAliasObjects;
}
// Loads a new `AliasesStorage` object by it's given name (`objectName`).
private static final function AliasesStorage LoadObjectByName(
string objectName)
{
local AliasesStorage result;
// Since `MemoryAPI` for now does not support specifying names
// to created objects - do some manual dark magic and
// initialize this shit ourselves. Don't repeat this at home, kids.
result = new(none, objectName) default.class;
result._constructor();
return result;
}
/**
* Loads a new `AliasesStorage` object based on the value (`aliasesValue`)
* of it's aliases.
*
* @param aliasesValue Value that aliases in this `Aliases` object will
* correspond to.
* @return Instance of `AliasesStorage` object with a given name.
*/
public static final function AliasesStorage LoadObject(BaseText aliasesValue)
{
if (aliasesValue != none) {
return LoadObjectByName(ToStorageVersion(aliasesValue));
}
return none;
}
/**
* Returns value that caller's `Aliases` object's aliases point to.
*
* @return Value, stored by this object.
*/
public final function Text GetValue()
{
return ToActualVersion(string(self.name));
}
/**
* Returns array of aliases that caller `AliasesStorage` tells us point to
* it's value.
*
* @return Array of all aliases, stored by caller `AliasesStorage` object.
*/
public final function array<Text> GetAliases()
{
local int i;
local array<Text> textAliases;
for (i = 0; i < alias.length; i += 1) {
textAliases[i] = _.text.FromString(alias[i]);
}
return textAliases;
}
/**
* [For inner use by `AliasSource`] Removes alias from this object.
*
* Does not update relevant `AliasHash`.
*
* Will prevent adding duplicate records inside it's own storage.
*
* @param aliasToRemove Alias to remove from caller `AliasesStorage` object.
* Expected to be in lower case.
*/
public final function RemoveAlias_S(string aliasToRemove)
{
local int i;
local bool removedAlias;
while (i < alias.length)
{
if (aliasToRemove ~= alias[i])
{
alias.Remove(i, 1);
removedAlias = true;
}
else {
i += 1;
}
}
if (!pendingSaveToConfig)
{
pendingSaveToConfig = true;
_.scheduler.RequestDiskAccess(self).connect = SaveOrClear;
}
}
/**
* If this object still has any alias records, - forces a rewrite of it's data
* into the config file, otherwise - removes it's record entirely.
*/
private final function SaveOrClear()
{
if (alias.length <= 0) {
ClearConfig();
}
else {
SaveConfig();
}
pendingSaveToConfig = false;
}
defaultproperties
{
sourceClass = class'AliasSource'
configName = "AcediaAliases"
}

249
sources/Aliases/Aliases_Feature.uc

@ -0,0 +1,249 @@
/**
* This feature provides a mechanism to define commands that automatically
* parse their arguments into standard Acedia collection. It also allows to
* manage them (and specify limitation on how they can be called) in a
* centralized manner.
* Copyright 2022 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 Aliases_Feature extends Feature
dependson(Aliases);
struct ClassSourcePair
{
var class<AliasSource> class;
var AliasSource source;
};
// We don't want to reload `AliasSource`s several times when this feature is
// disabled and re-enabled or its config is swapped, so we keep all loaded
// sources here at all times and look them up from here
var private array<ClassSourcePair> loadedSources;
// Loaded `AliasSource`s
var private AliasSource weaponAliasSource;
var private AliasSource colorAliasSource;
var private AliasSource featureAliasSource;
var private AliasSource entityAliasSource;
// Everything else
var private HashTable customSources;
var private LoggerAPI.Definition errCannotLoadAliasSource, errEmptyName;
var private LoggerAPI.Definition errDuplicateCustomSource;
protected function OnEnabled()
{
_.alias._reloadSources();
}
protected function OnDisabled()
{
DropSources();
_.alias._reloadSources();
}
private function DropSources()
{
_.memory.Free(weaponAliasSource);
weaponAliasSource = none;
_.memory.Free(colorAliasSource);
colorAliasSource = none;
_.memory.Free(featureAliasSource);
featureAliasSource = none;
_.memory.Free(entityAliasSource);
entityAliasSource = none;
_.memory.Free(customSources);
customSources = none;
}
// Create each `AliasSource` instance only once to avoid any possible
// nonsense with loading named objects several times: alias sources don't use
// `AcediaConfig`s, so they don't automatically avoid named config reloading
private function AliasSource GetSource(class<AliasSource> sourceClass)
{
local int i;
local AliasSource newSource;
local ClassSourcePair newPair;
if (sourceClass == none) {
return none;
}
for (i = 0; i < loadedSources.length; i += 1)
{
if (loadedSources[i].class == sourceClass) {
return loadedSources[i].source;
}
}
newSource = AliasSource(_.memory.Allocate(sourceClass));
if (newSource != none)
{
newPair.class = sourceClass;
newPair.source = newSource;
// One reference we store, one we return
newSource.NewRef();
}
else {
_.logger.Auto(errCannotLoadAliasSource).ArgClass(sourceClass);
}
return newSource;
}
protected function SwapConfig(FeatureConfig config)
{
local Aliases newConfig;
newConfig = Aliases(config);
if (newConfig == none) {
return;
}
_.memory.Free(weaponAliasSource);
DropSources();
weaponAliasSource = GetSource(newConfig.weaponAliasSource);
colorAliasSource = GetSource(newConfig.colorAliasSource);
featureAliasSource = GetSource(newConfig.featureAliasSource);
entityAliasSource = GetSource(newConfig.entityAliasSource);
LoadCustomSources(newConfig.customSource);
_.alias._reloadSources();
}
private function LoadCustomSources(
array<Aliases.CustomSourceRecord> configCustomSources)
{
local int i;
local bool reportedEmptyName;
local Text nextKey;
local AliasSource nextSource, conflictingSource;
_.memory.Free(customSources);
customSources = _.collections.EmptyHashTable();
for (i = 0; i < configCustomSources.length; i += 1)
{
if (configCustomSources[i].name == "" && !reportedEmptyName)
{
reportedEmptyName = true;
_.logger.Auto(errEmptyName);
}
nextKey = _.text.FromString(configCustomSources[i].name);
// We only store `AliasSource`s
conflictingSource = AliasSource(customSources.GetItem(nextKey));
if (conflictingSource != none)
{
_.logger.Auto(errDuplicateCustomSource)
.ArgClass(conflictingSource.class)
.Arg(nextKey) // Releases `nextKey`
.ArgClass(configCustomSources[i].source);
conflictingSource.FreeSelf();
continue;
}
nextSource = GetSource(configCustomSources[i].source);
if (nextSource != none)
{
customSources.SetItem(nextKey, nextSource);
nextSource.FreeSelf();
}
nextKey.FreeSelf();
}
}
/**
* Returns `AliasSource` for weapon aliases.
*
* @return `AliasSource`, configured to store weapon aliases.
*/
public function AliasSource GetWeaponSource()
{
if (weaponAliasSource != none) {
weaponAliasSource.Newref();
}
return weaponAliasSource;
}
/**
* Returns `AliasSource` for color aliases.
*
* @return `AliasSource`, configured to store color aliases.
*/
public function AliasSource GetColorSource()
{
if (colorAliasSource != none) {
colorAliasSource.Newref();
}
return colorAliasSource;
}
/**
* Returns `AliasSource` for feature aliases.
*
* @return `AliasSource`, configured to store feature aliases.
*/
public function AliasSource GetFeatureSource()
{
if (featureAliasSource != none) {
featureAliasSource.Newref();
}
return featureAliasSource;
}
/**
* Returns `AliasSource` for entity aliases.
*
* @return `AliasSource`, configured to store entity aliases.
*/
public function AliasSource GetEntitySource()
{
if (entityAliasSource != none) {
entityAliasSource.Newref();
}
return entityAliasSource;
}
/**
* Returns custom `AliasSource` with a given name.
*
* @return Custom `AliasSource`, configured with a given name `sourceName`.
*/
public function AliasSource GetCustomSource(BaseText sourceName)
{
if (sourceName == none) {
return none;
}
// We only store `AliasSource`s
return AliasSource(customSources.GetItem(sourceName));
}
/**
* Returns custom `AliasSource` with a given name `sourceName.
*
* @return Custom `AliasSource`, configured with a given name `sourceName`.
*/
public function AliasSource GetCustomSource_S(string sourceName)
{
local Text wrapper;
local AliasSource result;
wrapper = _.text.FromString(sourceName);
result = GetCustomSource(wrapper);
wrapper.FreeSelf();
return result;
}
defaultproperties
{
configClass = class'Aliases'
errEmptyName = (l=LOG_Error,m="Empty name provided for the custom alias source. This is likely due to an erroneous config.")
errCannotLoadAliasSource = (l=LOG_Error,m="Failed to load alias source class `%1`.")
errDuplicateCustomSource = (l=LOG_Error,m="Custom alias source `%1` is already registered with name '%2'. Alias source `%3` with the same name will be ignored.")
}

283
sources/Aliases/BaseAliasSource.uc

@ -0,0 +1,283 @@
/**
* Aliases allow users to define human-readable and easier to use
* "synonyms" to some symbol sequences (mainly names of UnrealScript classes).
* This is an interface class that can be implemented in various different
* ways.
* Several `AliasSource`s are supposed to exist separately, each storing
* aliases of particular kind: for weapon, zeds, colors, etc..
* Copyright 2022 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 BaseAliasSource extends AcediaObject
abstract;
/**
* # `AliasSource`
*
* Interface for any alias source instance that includes basic methods for
* alias lookup and adding/removal.
*
* ## Aliases
*
* Aliases in Acedia are usually defined as either `$<alias_name>`.
* `<alias_name>` can only contain ASCII latin letters and digits.
* `<alias_name>` is meant to define a human-readable name for something
* (e.g. "m14" for `KFMod.M14EBRBattleRifle`).
* Aliases are *case-insensitive*.
* '$' prefix is used to emphasize that what user is specifying is,
* in fact, an alias and it *is not* actually important for aliases feature
* and API: only `<alias_name>` is used. However, for convenience's sake,
* `AliasesAPI` usually recognizes aliases both with and without '$' prefix.
* Alias sources (classes derived from `BaseAliasSource`), however should only
* handle resolving `<alias_name>`, treating '$' prefix as a mistake in alias
* parameter.
*
* ## Implementation
*
* As far as implementation goes, it's up to you how your own child alias
* source class is configured to obtain its aliases, but `Aliases_Feature`
* should only create a single instance for every source class (although
* nothing can prevent other mods from creating more instances, so we cannot
* give any guarantees).
* Methods that add or remove aliases are allowed to fail for whatever
* reason is valid for your source's case (it might forbid adding aliases
* at all), as long as they return `false`.
* Although all built-in aliases are storing case-insensitive values,
* `BaseAliasSource` does not demand that and allows to configure this behavior
* via `AreValuesCaseSensitive()`. This is important for `GetAliases()` method
* that returns all aliases referring to a given value.
*/
/**
* Returns whether caller alias source class stores case-sensitive values.
*
* This information is necessary for organizing lookup of aliases by
* a given value. Aliases themselves are always case-insensitive.
*
* This method should not change returned value for any fixed class.
*
* @return `true` if stored values are case-sensitive and `false` if they are
* case-insensitive.
*/
public static function bool AreValuesCaseSensitive();
/**
* Returns all aliases that represent given value `value`.
*
* @param value Value for which to return all its aliases.
* Whether it is treated as case-sensitive is decided by
* `AreValuesCaseSensitive()` method, but all default alias sources are
* case-insensitive.
* @return Array of all aliases that refer to the given `value` inside
* the caller alias source. All `Text` references are guaranteed to not be
* `none` or duplicated.
*/
public function array<Text> GetAliases(BaseText value);
/**
* Returns all aliases that represent given value `value`.
*
* @param value Value for which to return all its aliases.
* Whether it is treated as case-sensitive is decided by
* `AreValuesCaseSensitive()` method, but all default alias sources are
* case-insensitive.
* @return Array of all aliases that refer to the given `value` inside
* the caller alias source. All `string` references are guaranteed to not
* be duplicated.
*/
public function array<string> GetAliases_S(string value)
{
local int i;
local Text valueAsText;
local array<Text> resultWithTexts;
local array<string> result;
valueAsText = _.text.FromString(value);
resultWithTexts = GetAliases(valueAsText);
_.memory.Free(valueAsText);
for (i = 0; i < resultWithTexts.length; i += 1) {
result[result.length] = resultWithTexts[i].ToString();
}
_.memory.FreeMany(resultWithTexts);
return result;
}
/**
* Checks if given alias is present in caller `AliasSource`.
*
* NOTE: having '$' prefix is considered to be invalid for `alias` by this
* method.
*
* @param alias Alias to check, case-insensitive.
* @return `true` if present, `false` otherwise.
*/
public function bool HasAlias(BaseText alias);
/**
* Checks if given alias is present in caller `AliasSource`.
*
* NOTE: having '$' prefix is considered to be invalid for `alias` by this
* method.
*
* @param alias Alias to check, case-insensitive.
* @return `true` if present, `false` otherwise.
*/
public function bool HasAlias_S(string alias)
{
local bool result;
local Text aliasAsText;
aliasAsText = _.text.FromString(alias);
result = HasAlias(aliasAsText);
_.memory.Free(aliasAsText);
return result;
}
/**
* Returns value stored for the given alias in caller `AliasSource`
* (as well as it's `Aliases` objects).
*
* NOTE: having '$' prefix is considered to be invalid for `alias` by this
* method.
*
* @param alias Alias, for which method will attempt to return
* a value. Case-insensitive. If given `alias` starts with "$" character -
* that character will be removed before resolving that alias.
* @param copyOnFailure Whether method should return copy of original
* `alias` value in case caller source did not have any records
* corresponding to `alias`.
* @return If look up was successful - value, associated with the given
* alias `alias`. If lookup was unsuccessful, it depends on `copyOnFailure`
* flag: `copyOnFailure == false` means method will return `none`
* and `copyOnFailure == true` means method will return `alias.Copy()`.
* If `alias == none` method always returns `none`.
*/
public function Text Resolve(BaseText alias, optional bool copyOnFailure);
/**
* Returns value stored for the given alias in caller `AliasSource`
* (as well as it's `Aliases` objects).
*
* NOTE: having '$' prefix is considered to be invalid for `alias` by this
* method.
*
* @param alias Alias, for which method will attempt to return
* a value. Case-insensitive. If given `alias` starts with "$" character -
* that character will be removed before resolving that alias.
* @param copyOnFailure Whether method should return copy of original
* `alias` value in case caller source did not have any records
* corresponding to `alias`.
* @return If look up was successful - value, associated with the given
* alias `alias`. If lookup was unsuccessful, it depends on `copyOnFailure`
* flag: `copyOnFailure == false` means method will return empty `string`
* and `copyOnFailure == true` means method will return `alias`.
*/
public function string Resolve_S(
string alias,
optional bool copyOnFailure)
{
local Text resultAsText;
local Text aliasAsText;
aliasAsText = _.text.FromString(alias);
resultAsText = Resolve(aliasAsText, copyOnFailure);
return _.text.IntoString(resultAsText);
}
/**
* Adds another alias to the caller `AliasSource`.
* If alias with the same name as `aliasToAdd` already exists - method
* overwrites it.
*
* Can fail iff `aliasToAdd` is an invalid alias or `aliasValue == none`.
*
* NOTE: having '$' prefix is considered to be invalid for `alias` by this
* method.
*
* @param aliasToAdd Alias that you want to add to caller source.
* Alias names are case-insensitive.
* @param aliasValue Intended value of this alias.
* @return `true` if alias was added and `false` otherwise (alias was invalid).
*/
public function bool AddAlias(BaseText aliasToAdd, BaseText aliasValue);
/**
* Adds another alias to the caller `AliasSource`.
* If alias with the same name as `aliasToAdd` already exists, -
* method overwrites it.
*
* Can fail iff `aliasToAdd` is an invalid alias.
*
* NOTE: having '$' prefix is considered to be invalid for `alias` by this
* method.
*
* @param aliasToAdd Alias that you want to add to caller source.
* Alias names are case-insensitive.
* @param aliasValue Intended value of this alias.
* @return `true` if alias was added and `false` otherwise (alias was invalid).
*/
public function bool AddAlias_S(string aliasToAdd, string aliasValue)
{
local bool result;
local Text aliasAsText, valueAsText;
aliasAsText = _.text.FromString(aliasToAdd);
valueAsText = _.text.FromString(aliasValue);
result = AddAlias(aliasAsText, valueAsText);
_.memory.Free(aliasAsText);
_.memory.Free(valueAsText);
return result;
}
/**
* Removes alias (all records with it, in case of duplicates) from
* the caller `AliasSource`.
*
* NOTE: having '$' prefix is considered to be invalid for `alias` by this
* method.
*
* @param aliasToRemove Alias that you want to remove from caller source.
* @return `true` if an alias was present in the source and was deleted and
* `false` if there was no specified alias in the first place.
*/
public function bool RemoveAlias(BaseText aliasToRemove);
/**
* Removes alias (all records with it, in case of duplicates) from
* the caller `AliasSource`.
*
* NOTE: having '$' prefix is considered to be invalid for `alias` by this
* method.
*
* @param aliasToRemove Alias that you want to remove from caller source.
* @return `true` if an alias was present in the source and was deleted and
* `false` if there was no specified alias in the first place.
*/
public function bool RemoveAlias_S(string aliasToRemove)
{
local bool result;
local Text aliasAsText;
aliasAsText = _.text.FromString(aliasToRemove);
result = RemoveAlias(aliasAsText);
_.memory.Free(aliasAsText);
return result;
}
defaultproperties
{
}

2
sources/Aliases/BuiltInSources/ColorAliases.uc

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class ColorAliases extends Aliases
class ColorAliases extends AliasesStorage
perObjectConfig
config(AcediaAliases_Colors);

2
sources/Aliases/BuiltInSources/EntityAliases.uc

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class EntityAliases extends Aliases
class EntityAliases extends AliasesStorage
perObjectConfig
config(AcediaAliases_Entities);

2
sources/Aliases/BuiltInSources/FeatureAliasSource.uc

@ -18,7 +18,7 @@
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class FeatureAliasSource extends AliasSource
config(AcediaAliases);
config(AcediaAliases_Features);
defaultproperties
{

4
sources/Aliases/BuiltInSources/FeatureAliases.uc

@ -17,9 +17,9 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class FeatureAliases extends Aliases
class FeatureAliases extends AliasesStorage
perObjectConfig
config(AcediaAliases);
config(AcediaAliases_Features);
defaultproperties
{

2
sources/Aliases/BuiltInSources/WeaponAliases.uc

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class WeaponAliases extends Aliases
class WeaponAliases extends AliasesStorage
perObjectConfig
config(AcediaAliases_Weapons);

2
sources/Aliases/Tests/MockAliases.uc

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class MockAliases extends Aliases
class MockAliases extends AliasesStorage
perObjectConfig
config(AcediaAliases_Tests);

16
sources/Aliases/Tests/TEST_Aliases.uc

@ -23,15 +23,17 @@ class TEST_Aliases extends TestCase
protected static function TESTS()
{
Context("Testing loading aliases from a mock object `MockAliasSource`.");
Issue("`GetCustomSource()` fails to return alias source.");
TEST_ExpectNotNone(__().alias.GetCustomSource(P("mock")));
SubTest_AliasLoadingCorrect();
SubTest_AliasLoadingIncorrect();
}
protected static function SubTest_AliasLoadingCorrect()
{
local AliasSource source;
local BaseAliasSource source;
Issue("`Resolve()` fails to return alias value that should be loaded.");
source = __().alias.GetCustomSource(class'MockAliasSource');
source = __().alias.GetCustomSource(P("mock"));
TEST_ExpectTrue(source.Resolve(P("Global")).ToString() == "value");
TEST_ExpectTrue(source.Resolve(P("ford")).ToString() == "car");
@ -51,21 +53,19 @@ protected static function SubTest_AliasLoadingCorrect()
TEST_ExpectTrue( source.Resolve(P("HardToBeAGod")).ToString()
== "sci.fi");
Issue("Aliases with empty values in alias name or their value are handled"
@ "incorrectly.");
TEST_ExpectTrue(source.Resolve(P("")).ToString() == "empty");
Issue("Aliases corresponding to empty values are handled incorrectly.");
TEST_ExpectTrue(source.Resolve(P("also")).ToString() == "");
}
protected static function SubTest_AliasLoadingIncorrect()
{
local AliasSource source;
local BaseAliasSource source;
Issue("`AliasAPI` cannot return value custom source.");
source = __().alias.GetCustomSource(class'MockAliasSource');
source = __().alias.GetCustomSource(P("mock"));
TEST_ExpectNotNone(source);
Issue("`Resolve()` reports success of finding inexistent alias.");
source = __().alias.GetCustomSource(class'MockAliasSource');
source = __().alias.GetCustomSource(P("mock"));
TEST_ExpectNone(source.Resolve(P("noSuchThing")));
Issue("`HasAlias()` reports inexistent alias as present.");

9
sources/BaseRealm/AcediaEnvironment/AcediaEnvironment.uc

@ -23,7 +23,7 @@ class AcediaEnvironment extends AcediaObject;
* # `AcediaEnvironment`
*
* Instance of this class will be used by Acedia to manage resources available
* from different packages like `Feature`s, aliases, etc..
* from different packages like `Feature`s and such other etc..
* This is mostly necessary to implement Acedia loader (and, possibly,
* its alternatives) that would load available packages and enable `Feature`s
* admin wants to be enabled.
@ -244,13 +244,6 @@ private final function ReadManifest(class<_manifest> manifestClass)
{
local int i;
for (i = 0; i < manifestClass.default.aliasSources.length; i += 1)
{
if (manifestClass.default.aliasSources[i] == none) {
continue;
}
_.memory.Allocate(manifestClass.default.aliasSources[i]);
}
for (i = 0; i < manifestClass.default.features.length; i += 1)
{
if (manifestClass.default.features[i] == none) {

3
sources/Commands/Aliases/CommandAliasSource.uc

@ -17,7 +17,8 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class CommandAliasSource extends AliasSource;
class CommandAliasSource extends AliasSource
config(AcediaAliases_Commands);
defaultproperties
{

5
sources/Commands/Aliases/CommandAliases.uc

@ -17,8 +17,9 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class CommandAliases extends Aliases
perObjectConfig;
class CommandAliases extends AliasesStorage
perObjectConfig
config(AcediaAliases_Commands);
defaultproperties
{

10
sources/Manifest.uc

@ -22,13 +22,9 @@
defaultproperties
{
features(0) = class'Commands_Feature'
features(1) = class'Avarice_Feature'
aliasSources(0) = class'AliasSource'
aliasSources(1) = class'WeaponAliasSource'
aliasSources(2) = class'ColorAliasSource'
aliasSources(3) = class'FeatureAliasSource'
aliasSources(4) = class'EntityAliasSource'
features(0) = class'Aliases_Feature'
features(1) = class'Commands_Feature'
features(2) = class'Avarice_Feature'
testCases(0) = class'TEST_Base'
testCases(1) = class'TEST_ActorService'
testCases(2) = class'TEST_Boxes'

Loading…
Cancel
Save