Administration tools: commands and non gameplay server configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

796 lines
28 KiB

/**
* Auxiliary object for working with player's inventory and making reports
* about it. Simplifies code for inventory commands themselves by
* taking care of actual item addition/removal and reporting about successes,
* failures and inventory status.
* This tool is supposed to be created for one player and provides wrapper
* methods for his usual inventory methods that take care of information
* collection about outcome of operations and then reporting on them.
* 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 InventoryTool extends AcediaObject;
enum InventoryReportTarget
{
IRT_Instigator,
IRT_Target,
IRT_Others
};
/**
* Every instance of this class is created for a particular player and that
* player cannot be changed. It allows:
* 1. This object allows for editing player's inventory in a way that allows
* it to produce a human-readable report about the changes.
* Call `AddItem()`, `RemoveItem()` or `RemoveAllItems()` and then call
* `ReportChanges()` to write changes made into the `ConsoleWriter`.
* 2. `ReportInventory()` summarizes player's inventory.
*/
// References to player (for whom this tool was created)...
var private EPlayer targetPlayer;
// ...and his inventory (for easy access)
var private EInventory targetInventory;
/**
* `ListBuilder`s for 6 different cases:
* ~ two of "...Verbose" and "...Failed" ones make reports about
* successes and failures of adding and removals to the instigator of
* these changes;
* ~ two other ones (`itemsAdded` and `itemsRemoved`) make reports about
* successful changes to everybody else present on the server.
* Supposed to be created via `CreateFor()` method.
*/
var public ListBuilder itemsAdded;
var public ListBuilder itemsRemoved;
var public ListBuilder itemsAddedPrivate;
var public ListBuilder itemsRemovedPrivate;
var public ListBuilder itemsAdditionFailed;
var public ListBuilder itemsRemovalFailed;
var private TextTemplate templateItemsAdded, templateItemsRemoved;
var private TextTemplate templateItemsAddedVerbose, templateItemsRemovedVerbose;
var private TextTemplate templateAdditionFailed, templateRemovalFailed;
var const int TINSTIGATOR, TTARGET, TRESOLVED_INTO, TTILDE_QUOTE;
var const int TITEM_MISSING, TITEM_NOT_REMOVABLE, TUNKNOWN, TVISIBLE;
var const int TDISPLAYING_INVENTORY, THEADER_COLON, TDOT_SPACE, TCOLON_SPACE;
var const int TCOMMA_SPACE, TSPACE, TOUT_OF, THIDDEN_ITEMS, TDOLLAR, TYOU;
var const int TTHEMSELVES, TFAULTY_INVENTORY_IMPLEMENTATION;
public static function StaticConstructor()
{
if (StaticConstructorGuard()) {
return;
}
default.templateItemsAdded = __().text.MakeTemplate_S(
"%%instigator%% {$TextPositive added} following weapons to"
@ "%%target%%: ");
default.templateItemsRemoved = __().text.MakeTemplate_S(
"%%instigator%% {$TextNegative removed} following weapons from"
@ "%%target%%: ");
default.templateItemsAddedVerbose = __().text.MakeTemplate_S(
"Weapons {$TextPositive added} to %%target%%: ");
default.templateItemsRemovedVerbose = __().text.MakeTemplate_S(
"Weapons {$TextNegative removed} from %%target%%: ");
default.templateAdditionFailed = __().text.MakeTemplate_S(
"Weapons we've {$TextFailure failed} to add to %%target%%: ");
default.templateRemovalFailed = __().text.MakeTemplate_S(
"Weapons we've {$TextFailure failed} to remove from %%target%%: " );
}
protected function Constructor()
{
itemsAdded = ListBuilder(_.memory.Allocate(class'ListBuilder'));
itemsRemoved = ListBuilder(_.memory.Allocate(class'ListBuilder'));
itemsAddedPrivate = ListBuilder(_.memory.Allocate(class'ListBuilder'));
itemsRemovedPrivate = ListBuilder(_.memory.Allocate(class'ListBuilder'));
itemsAdditionFailed = ListBuilder(_.memory.Allocate(class'ListBuilder'));
itemsRemovalFailed = ListBuilder(_.memory.Allocate(class'ListBuilder'));
}
protected function Finalizer()
{
// Deallocate report tools
_.memory.Free(itemsAdded);
_.memory.Free(itemsRemoved);
_.memory.Free(itemsAddedPrivate);
_.memory.Free(itemsRemovedPrivate);
_.memory.Free(itemsAdditionFailed);
_.memory.Free(itemsRemovalFailed);
itemsAdded = none;
itemsRemoved = none;
itemsAddedPrivate = none;
itemsRemovedPrivate = none;
itemsAdditionFailed = none;
itemsRemovalFailed = none;
// Deallocate player references
_.memory.Free(targetPlayer);
_.memory.Free(targetInventory);
targetPlayer = none;
targetInventory = none;
}
/**
* Creates new `InventoryTool` instance for a given player `target`.
*
* @param target Player for which to create new `InventoryTool`.
* @return `InventoryTool` created for the given player - not a copy of any
* preexisting instance. `none` iff `target == none` or refers to
* a non-existent player.
*/
public static final function InventoryTool CreateFor(EPlayer target)
{
local InventoryTool newInventoryTool;
if (target == none) return none;
if (!target.IsExistent()) return none;
newInventoryTool =
InventoryTool(__().memory.Allocate(class'InventoryTool'));
newInventoryTool.targetPlayer = EPlayer(target.Copy());
newInventoryTool.targetInventory = target.GetInventory();
return newInventoryTool;
}
// Checks whether reference to the `EPlayer` that caller `InventoryTool` was
// created for is still valid.
private final function bool TargetPlayerIsInvalid()
{
if (targetPlayer == none) return true;
if (!targetPlayer.IsExistent()) return true;
return false;
}
/**
* Resets `InventoryTool`, forgetting about changes made with it so far.
*/
public final function Reset()
{
itemsAddedPrivate.Reset();
itemsRemovedPrivate.Reset();
itemsAdded.Reset();
itemsRemoved.Reset();
itemsAdditionFailed.Reset();
itemsRemovalFailed.Reset();
}
// Makes "`resolvedWhat` resolved into `intoWhat`" line
// In case `resolvedWhat == intoWhat` just returns copy of
// original `resolvedWhat`
private function MutableText MakeResolvedIntoLine(
BaseText resolvedWhat,
BaseText intoWhat)
{
if (resolvedWhat == none) {
return none;
}
if (_.text.IsEmpty(intoWhat) || resolvedWhat.Compare(intoWhat)) {
return resolvedWhat.MutableCopy();
}
return _.text.Empty()
.Append(T(TTILDE_QUOTE))
.Append(resolvedWhat)
.Append(T(TRESOLVED_INTO))
.Append(intoWhat)
.Append(T(TTILDE_QUOTE));
}
// Tries to fill ammo for the `item` in case it is a weapon
private function TryFillAmmo(EItem item)
{
local EWeapon itemAsWeapon;
if (item == none) {
return;
}
itemAsWeapon = EWeapon(item.As(class'EWeapon'));
if (itemAsWeapon != none)
{
itemAsWeapon.FillAmmo();
_.memory.Free(itemAsWeapon);
}
}
/**
* Adds a new item, based on user provided name `userProvidedName`.
*
* @param userProvidedName Name of the inventory, provided by the user.
* If it is started with "$", then tool tried to treat it as
* an alias first. If it either does not start with "$" or does not
* correspond to a valid alias - it is treated as a template.
* @param doForce Set to `true` if we must try to add an item
* even if it normally cannot be added.
* @param doFillAmmo Set to `true` if we must also fill ammo reserves
* of weapons we have added to the full.
*/
public function AddItem(
BaseText userProvidedName,
bool doForce,
bool doFillAmmo)
{
local EItem addedItem;
local MutableText resolvedLine;
local Text realItemName, itemTemplate, failureReason;
if (TargetPlayerIsInvalid()) return;
if (userProvidedName == none) return;
// Get template in case alias was specified
// (`itemTemplate` cannot be `none`, since `userProvidedName != none`)
if (userProvidedName.StartsWith(T(TDOLLAR))) {
itemTemplate = _.alias.ResolveWeapon(userProvidedName, true);
}
else {
itemTemplate = userProvidedName.Copy();
}
// The only way we can fail in a valid way is when API says we will
// via `CanAddTemplateExplain()`
failureReason = targetInventory
.CanAddTemplateExplain(itemTemplate, doForce);
if (failureReason != none)
{
itemsAdditionFailed.Item(userProvidedName).Comment(failureReason);
_.memory.Free(failureReason);
_.memory.Free(itemTemplate);
return;
}
// Actually try to add specified item
addedItem = targetInventory.AddTemplate(itemTemplate, doForce);
if (addedItem != none)
{
if (doFillAmmo) {
TryFillAmmo(addedItem);
}
realItemName = addedItem.GetName();
resolvedLine = MakeResolvedIntoLine(userProvidedName, itemTemplate);
itemsAdded.Item(realItemName);
itemsAddedPrivate.Item(realItemName).Comment(resolvedLine);
_.memory.Free(realItemName);
_.memory.Free(resolvedLine);
_.memory.Free(addedItem);
}
else
{
// `CanAddTemplateExplain()` told us that we should not have failed,
// so complain about bad API
itemsAdditionFailed.Item(userProvidedName)
.Comment(T(TFAULTY_INVENTORY_IMPLEMENTATION));
}
_.memory.Free(itemTemplate);
}
/**
* Removes a specified item, based on user provided name `userProvidedName`.
*
* @param userProvidedName Name of inventory, provided by the user.
* If it is started with "$", then tool tried to treat it as
* an alias first. If it either does not start with "$" or does not
* correspond to a valid alias - it is treated as a template.
* @param doKeep Set to `true` if item should be preserved
* (or, at least, attempted to be preserved) and not simply destroyed.
* @param doForce Set to `true` if we must try to remove an item
* even if it normally cannot be removed.
* @param doRemoveAll Set to `true` to remove all instances of given
* template and `false` to only remove one.
*/
public function RemoveItem(
BaseText userProvidedName,
bool doKeep,
bool doForce,
bool doRemoveAll)
{
local bool itemWasMissing;
local Text realItemName, itemTemplate;
local MutableText resolvedLine;
local EItem storedItem;
if (TargetPlayerIsInvalid()) return;
if (userProvidedName == none) return;
// Get template in case alias was specified
// (`itemTemplate` cannot be `none`, since `userProvidedName != none`)
if (userProvidedName.StartsWith(T(TDOLLAR))) {
itemTemplate = _.alias.ResolveWeapon(userProvidedName, true);
}
else {
itemTemplate = userProvidedName.Copy();
}
// Check if item is even in the inventory
storedItem = targetInventory.GetTemplateItem(itemTemplate);
if (storedItem == none)
{
// If not, we still need to attempt to remove it, as it can be
// "merged" into another item
itemWasMissing = true;
realItemName = P("").Copy();
}
else {
// Need to remember the name before removing the item
realItemName = storedItem.GetName();
}
if (targetInventory
.RemoveTemplate(itemTemplate, doKeep, doForce, doRemoveAll))
{
resolvedLine = MakeResolvedIntoLine(userProvidedName, itemTemplate);
itemsRemoved.Item(realItemName);
itemsRemovedPrivate.Item(realItemName).Comment(resolvedLine);
_.memory.Free(resolvedLine);
}
// Try to guess why operation failed
// (no special explanation method is present in the API)
else if (itemWasMissing) { // likely because it was missing
itemsRemovalFailed.Item(userProvidedName).Comment(T(TITEM_MISSING));
}
else if (!doForce && !storedItem.IsRemovable()) // simply was not removable
{
itemsRemovalFailed.Item(userProvidedName)
.Comment(T(TITEM_NOT_REMOVABLE));
}
else { // no idea about the reason
itemsRemovalFailed.Item(userProvidedName).Comment(T(TUNKNOWN));
}
_.memory.Free(storedItem);
_.memory.Free(realItemName);
_.memory.Free(itemTemplate);
}
// Auxiliary method for detecting and reporting about removed items by
/// comparing lists of `EItem` interfeaces created beofer and after removal
private function DetectAndReportRemovedItems(
out array<EItem> itemsAfterRemoval,
array<EItem> itemsBeforeRemoval,
array<BaseText> itemNames,
bool doForce)
{
local int i, j;
local bool itemWasRemoved;
for (i = 0; i < itemsBeforeRemoval.length; i += 1)
{
itemWasRemoved = true;
// If item was not destroyed - double check whether it got removed
if (itemsBeforeRemoval[i].IsExistent())
{
for (j = 0; j < itemsAfterRemoval.length; j += 1)
{
if (itemsBeforeRemoval[i].SameAs(itemsAfterRemoval[j]))
{
_.memory.Free(itemsAfterRemoval[j]);
itemsAfterRemoval.Remove(j, 1);
itemWasRemoved = false;
break;
}
}
}
if (itemWasRemoved)
{
itemsRemoved.Item(itemNames[i]);
itemsRemovedPrivate.Item(itemNames[i]);
}
else if (doForce || itemsBeforeRemoval[i].IsRemovable()) {
itemsRemovalFailed.Item(itemNames[i]).Comment(T(TUNKNOWN));
}
}
}
/**
* Removes all items from the player's inventory.
*
* @param doKeep Set to `true` if items should be preserved
* (or, at least, attempted to be preserved) and not simply destroyed.
* @param doForce Set to `true` if we must try to remove an item
* even if it normally cannot be removed.
* @param includeHidden Set to `true` if "hidden" items should also be
* targeted by this method. These are items player cannot directly see in
* their inventory, usually serving some sort of technical role.
*/
public function RemoveAllItems(bool doKeep, bool doForce, bool includeHidden)
{
local int i;
local array<Text> itemNames;
local array<EItem> itemsBeforeRemoval, itemsAfterRemoval;
if (TargetPlayerIsInvalid()) {
return;
}
// Remove all items!
// Remember what items we have had before to output them and
// what items we have after removal to detect what we have actually
// removed.
// This is necessary, since (to an extent depending on flags)
// some items might not be removable.
if (includeHidden) {
itemsBeforeRemoval = targetInventory.GetAllItems();
}
else {
itemsBeforeRemoval = targetInventory.GetTagItems(T(TVISIBLE));
}
for (i = 0; i < itemsBeforeRemoval.length; i += 1) {
itemNames[i] = itemsBeforeRemoval[i].GetName();
}
targetInventory.RemoveAll(doKeep, doForce, includeHidden);
itemsAfterRemoval = targetInventory.GetAllItems();
// Figure out what items are actually gone and report about them
DetectAndReportRemovedItems( itemsAfterRemoval,
itemsBeforeRemoval, itemNames,
doForce);
_.memory.FreeMany(itemNames);
_.memory.FreeMany(itemsBeforeRemoval);
_.memory.FreeMany(itemsAfterRemoval);
}
/**
* Removes all equipped items from the player's inventory.
*
* @param doKeep Set to `true` if items should be preserved
* (or, at least, attempted to be preserved) and not simply destroyed.
* @param doForce Set to `true` if we must try to remove an item
* even if it normally cannot be removed.
* @param includeHidden Set to `true` if "hidden" items should also be
* targeted by this method. These are items player cannot directly see in
* their inventory, usually serving some sort of technical role.
*/
public function RemoveEquippedItems(
bool doKeep,
bool doForce,
bool includeHidden)
{
local int i;
local EItem nextItem;
local Text nextItemName;
local array<EItem> equippedItems;
if (TargetPlayerIsInvalid()) {
return;
}
equippedItems = targetInventory.GetEquippedItems();
for (i = 0; i < equippedItems.length; i += 1)
{
nextItem = equippedItems[i];
if (!nextItem.IsExistent()) continue;
if (!includeHidden && !nextItem.HasTag(T(TVISIBLE))) continue;
nextItemName = nextItem.GetName();
// Try to guess the reason we cannot remove the item
if (!doForce && !nextItem.IsRemovable())
{
itemsRemovalFailed
.Item(nextItemName)
.Comment(T(TITEM_NOT_REMOVABLE));
}
else if (!targetInventory.Remove(nextItem, doKeep, doForce))
{
itemsRemovalFailed
.Item(nextItemName)
.Comment(T(TUNKNOWN));
}
_.memory.Free(nextItemName);
nextItemName = none;
}
_.memory.FreeMany(equippedItems);
}
/**
* Tells `InventoryTool` which player is responsible for the changes it is
* reporting. This information is used to choose the phrasing of the reported
* messages.
*
* @param instigator Player that supposedly requested all the changes done by
* the calller `InventoryTool`.
*/
public final function SetupReportInstigator(EPlayer instigator)
{
local MutableText instigatorName, targetName;
if (TargetPlayerIsInvalid()) return;
if (instigator == none) return;
instigatorName = ColorNickname(instigator.GetName());
if (!targetPlayer.SameAs(instigator)) {
targetName = ColorNickname(targetPlayer.GetName());
}
else {
targetName = T(TYOU).MutableCopy();
}
// For instigator
default.templateItemsAdded.Reset().TextArg(T(TINSTIGATOR), instigatorName);
default.templateItemsRemoved
.Reset()
.TextArg(T(TINSTIGATOR), instigatorName);
// For everybody else
default.templateAdditionFailed.Reset().TextArg(T(TTARGET), targetName);
default.templateRemovalFailed.Reset().TextArg(T(TTARGET), targetName);
default.templateItemsAddedVerbose.Reset().TextArg(T(TTARGET), targetName);
default.templateItemsRemovedVerbose.Reset().TextArg(T(TTARGET), targetName);
_.memory.Free(instigatorName);
_.memory.Free(targetName);
}
private final function MutableText ColorNickname(/* take */ BaseText nickname)
{
if (nickname == none) {
return none;
}
return nickname
.IntoMutableText()
.ChangeDefaultColor(_.color.LightGray);
}
/**
* Reports changes made to the player's inventory so far.
*
* Ability to provide this reports is pretty much the main reason for
* using `InventoryTool`
* @param blamedPlayer Player that should be listed as the one who caused
* the changes.
* @param writer `ConsoleWriter` that will be used to output report.
* Method does nothing if given `writer` is `none`.
* @param reportTarget For who is this report meant to? For general public
* and target only actually occured changes are reported (with different
* phrasing), but for instigator changes that tool failed to do will also
* be reported.
*/
public final function ReportChanges(
EPlayer instigator,
ConsoleWriter writer,
InventoryReportTarget reportTarget)
{
if (TargetPlayerIsInvalid()) {
return;
}
if (reportTarget != IRT_Instigator)
{
SwapTargetNameInTemplates(instigator, reportTarget);
ReportWeaponList(writer, default.templateItemsRemoved, itemsRemoved);
ReportWeaponList(writer, default.templateItemsAdded, itemsAdded);
return;
}
ReportWeaponList(
writer,
default.templateItemsRemovedVerbose,
itemsRemovedPrivate);
ReportWeaponList(
writer,
default.templateRemovalFailed,
itemsRemovalFailed);
ReportWeaponList(
writer,
default.templateItemsAddedVerbose,
itemsAddedPrivate);
ReportWeaponList(
writer,
default.templateAdditionFailed,
itemsAdditionFailed);
}
private final function SwapTargetNameInTemplates(
EPlayer instigator,
InventoryReportTarget reportTarget)
{
local MutableText targetName;
if (TargetPlayerIsInvalid()) {
return;
}
if (!targetPlayer.SameAs(instigator)) {
targetName = ColorNickname(targetPlayer.GetName());
}
else if (reportTarget == IRT_Target) {
targetName = T(TYOU).MutableCopy();
}
else {
targetName = T(TTHEMSELVES).MutableCopy();
}
default.templateItemsAdded.TextArg(T(TTARGET), targetName);
default.templateItemsRemoved.TextArg(T(TTARGET), targetName);
_.memory.Free(targetName);
}
private final function ReportWeaponList(
ConsoleWriter writer,
TextTemplate header,
ListBuilder builder)
{
local MutableText output;
if (writer == none) return;
if (builder == none) return;
if (builder.IsEmpty()) return;
if (header != none)
{
output = header.CollectFormattedM();
writer.Write(output);
_.memory.Free(output);
output = none;
}
output = builder.GetMutable();
writer.WriteLine(output);
_.memory.Free(output);
}
// TODO: Use `ListBuilder` for the below method?
/**
* Command that outputs summary of the player's inventory.
*
* @param writer `ConsoleWriter` into which to output information.
* Method does nothing if given `writer` is `none`.
* @param includeHidden Set to `true` if "hidden" items should also be
* targeted by this method. These are items player cannot directly see in
* their inventory, usually serving some sort of technical role.
*/
public final function ReportInventory(ConsoleWriter writer, bool includeHidden)
{
local int i;
local int lineCounter;
local array<EItem> availableItems;
local Text playerName;
if (writer == none) return;
if (TargetPlayerIsInvalid()) return;
playerName = targetPlayer.GetName();
writer.Flush()
.Write(T(TDISPLAYING_INVENTORY))
.UseColorOnce(_.color.White).Write(playerName)
.Write(T(THEADER_COLON)).Flush();
lineCounter = 1;
availableItems = targetInventory.GetAllItems();
// First show visible items
for (i = 0; i < availableItems.length; i += 1)
{
if (availableItems[i].HasTag(T(TVISIBLE)))
{
AppendItemInfo(writer, availableItems[i], lineCounter);
lineCounter += 1;
}
}
// Once more pass for non-visible items, to display them at the end
if (includeHidden)
{
writer.Write(T(THIDDEN_ITEMS)).Flush();
for (i = 0; i < availableItems.length; i += 1)
{
if (!availableItems[i].HasTag(T(TVISIBLE)))
{
AppendItemInfo(writer, availableItems[i], lineCounter);
lineCounter += 1;
}
}
}
_.memory.Free(playerName);
_.memory.FreeMany(availableItems);
}
private final function AppendItemInfo(
ConsoleWriter writer,
EItem item,
int lineNumber)
{
local Text itemName;
local Text lineNumberAsText;
local EWeapon itemAsWeapon;
local Mutabletext allAmmoInfo;
if (writer == none) return;
if (item == none) return;
itemName = item.GetName();
lineNumberAsText = _.text.FromInt(lineNumber);
writer.Write(lineNumberAsText)
.Write(T(TDOT_SPACE))
.UseColorOnce(_.color.TextEmphasis).Write(itemName);
// Try to display additional ammo info if this is a weapon
itemAsWeapon = EWeapon(item.As(class'EWeapon'));
if (itemAsWeapon != none)
{
allAmmoInfo = DisplayAllAmmoInfo(itemAsWeapon);
if (allAmmoInfo != none) {
writer.Write(T(TCOLON_SPACE)).Write(allAmmoInfo);
}
_.memory.Free(itemAsWeapon);
_.memory.Free(allAmmoInfo);
}
writer.Flush();
_.memory.Free(itemName);
_.memory.Free(lineNumberAsText);
}
private final function MutableText DisplayAllAmmoInfo(EWeapon weapon)
{
local int i;
local array<EAmmo> allAmmo;
local MutableText builder;
allAmmo = weapon.GetAvailableAmmo();
if (allAmmo.length == 0) {
return none;
}
builder = _.text.Empty();
for (i = 0; i < allAmmo.length; i += 1)
{
if (i > 0) {
builder.Append(T(TCOMMA_SPACE));
}
AppendAmmoInstanceInfo(builder, allAmmo[i]);
}
_.memory.FreeMany(allAmmo);
return builder;
}
private final function AppendAmmoInstanceInfo(MutableText builder, EAmmo ammo)
{
local Text ammoName;
if (ammo == none) {
return;
}
ammoName = ammo.GetName();
builder.AppendString( string(ammo.GetTotalAmount()),
_.text.FormattingFromColor(_.color.TypeNumber))
.Append(T(TSPACE)).Append(ammoName).Append(T(TOUT_OF))
.AppendString( string(ammo.GetMaxTotalAmount()),
_.text.FormattingFromColor(_.color.TypeNumber));
_.memory.Free(ammoName);
}
defaultproperties
{
TINSTIGATOR = 0
stringConstants(0) = "instigator"
TTARGET = 1
stringConstants(1) = "target"
TRESOLVED_INTO = 2
stringConstants(2) = "` resolved into `"
TTILDE_QUOTE = 3
stringConstants(3) = "`"
TFAULTY_INVENTORY_IMPLEMENTATION = 4
stringConstants(4) = "faulty inventory implementation"
TITEM_MISSING = 5
stringConstants(5) = "item missing"
TITEM_NOT_REMOVABLE = 6
stringConstants(6) = "item not removable"
TUNKNOWN = 7
stringConstants(7) = "unknown"
TVISIBLE = 8
stringConstants(8) = "visible"
TDISPLAYING_INVENTORY = 9
stringConstants(9) = "{$TextHeader Displaying inventory for player }"
THEADER_COLON = 10
stringConstants(10) = "{$TextHeader :}"
TDOT_SPACE = 11
stringConstants(11) = ". "
TCOLON_SPACE = 12
stringConstants(12) = ": "
TCOMMA_SPACE = 13
stringConstants(13) = ", "
TSPACE = 14
stringConstants(14) = " "
TOUT_OF = 15
stringConstants(15) = " out of "
THIDDEN_ITEMS = 16
stringConstants(16) = "{$TextSubHeader Hidden items:}"
TDOLLAR = 17
stringConstants(17) = "$"
TYOU = 18
stringConstants(18) = "you"
TTHEMSELVES = 19
stringConstants(19) = "themselves"
}