Browse Source

Add `ACommandInventory`

feature_improvement
Anton Tarasenko 2 years ago
parent
commit
6116b4ec5a
  1. 220
      sources/Commands/ACommandInventory.uc
  2. 657
      sources/Tools/InventoryTool.uc
  3. 224
      sources/Tools/ReportTool.uc

220
sources/Commands/ACommandInventory.uc

@ -0,0 +1,220 @@
/**
* Command for managing (displaying + adding and removing to/items from it)
* player's inventory.
* Copyright 2021 - 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 ACommandInventory extends Command;
var protected const int TINVENTORY, TADD, TREMOVE, TITEMS, TEQUIP, TALL, TKEEP;
var protected const int THIDDEN, TFORCE, TAMMO, TALL_WEAPONS;
protected function BuildData(CommandDataBuilder builder)
{
builder.Name(T(TINVENTORY))
.Summary(P("Manages player's inventory."))
.Describe(P("Command for displaying and editing players' inventories."
@ "If called without specifying subcommand - simply displays"
@ "targeted player's inventory."));
builder.RequireTarget();
builder.SubCommand(T(TADD))
.OptionalParams()
.ParamTextList(T(TITEMS))
.Describe(P("This command adds items (based on listed templates) to"
@ "the targeted player's inventory."
@ "Instead of templates item aliases can be specified."));
builder.SubCommand(T(TREMOVE))
.OptionalParams()
.ParamTextList(T(TITEMS))
.Describe(P("This command removes items (based on listed templates)"
@ "from the targeted player's inventory."
@ "Instead of templates item aliases can be specified."));
builder.Option(T(TEQUIP))
.Describe(F("Affect items currently equipped by the targeted player."
@ "Releveant for a {$TextEmphasis remove} subcommand."));
builder.Option(T(TALL))
.Describe(F("This flag tells editing commands to affect all items."
@ "When adding items it means \"all available weapons in the game\""
@ "and when removing it means \"all weapons in"
@ "the player's inventory\"."));
builder.Option(T(TKEEP))
.Describe(F("Removing items by default means simply destroying them."
@ "This flag makes command to try and keep them in some form."
@ "Success for all items is not guaranteed."));
builder.Option(T(THIDDEN))
.Describe(F("Some of the items in the inventory are"
@ "{$TextEmphasis hidden} and are not supposed to be seem by"
@ "the player. To avoid weird behavior, {$TextEmphasis inventory}"
@ "command by default ignores them when affecting groups of items"
@ "(like when removing all items) unless they're directly"
@ "specified. This flag tells it to also affect hidden items."));
builder.Option(T(TFORCE))
.Describe(P("Sometimes adding and removing items is impossible due to"
@ "the limitations imposed by the game. This option allows to"
@ "ignore some of those limitation."));
builder.Option(T(TAMMO), P("A"))
.Describe(P("When adding weapons - signals that their"
@ "ammo / charge / whatever has to be filled after addition."));
}
protected function ExecutedFor(
EPlayer player,
CallData result,
EPlayer callerPlayer)
{
local ConsoleWriter publicWriter;
local InventoryTool tool;
tool = class'InventoryTool'.static.CreateFor(player);
if (tool == none) {
return;
}
if (result.subCommandName.IsEmpty())
{
tool.ReportInventory( callerPlayer.BorrowConsole(),
result.options.HasKey(T(THIDDEN)));
}
else if (result.subCommandName.Compare(T(TADD)))
{
SubCommandAdd( tool, result.parameters.GetDynamicArray(T(TITEMS)),
result.options.HasKey(T(TALL)),
result.options.HasKey(T(TFORCE)),
result.options.HasKey(T(TAMMO)));
}
else if (result.subCommandName.Compare(T(TREMOVE)))
{
SubCommandRemove( tool,
result.parameters.GetDynamicArray(T(TITEMS)),
result.options.HasKey(T(TALL)),
result.options.HasKey(T(TFORCE)),
result.options.HasKey(T(TKEEP)),
result.options.HasKey(T(TEQUIP)),
result.options.HasKey(T(THIDDEN)));
}
tool.ReportChanges(callerPlayer, player.BorrowConsole(), false);
publicWriter = _.console.ForAll().ButPlayer(callerPlayer);
tool.ReportChanges(callerPlayer, publicWriter, true);
_.memory.Free(tool);
_.memory.Free(publicWriter);
}
protected function SubCommandAdd(
InventoryTool tool,
DynamicArray templateList,
bool flagAll,
bool doForce,
bool doFillAmmo)
{
if (flagAll) {
AddAllItems(tool, doForce, doFillAmmo);
}
else {
AddGivenTemplates(tool, templateList, doForce, doFillAmmo);
}
}
protected function SubCommandRemove(
InventoryTool tool,
DynamicArray templateList,
bool flagAll,
bool doForce,
bool doKeep,
bool flagEquip,
bool flagHidden)
{
if (flagAll)
{
tool.RemoveAllItems(doKeep, doForce, flagHidden);
return;
}
if (flagEquip) {
tool.RemoveEquippedItems(doKeep, doForce, flagHidden);
}
RemoveGivenTemplates(tool, templateList, doForce, doKeep);
}
protected function AddAllItems(
InventoryTool tool,
bool doForce,
bool doFillAmmo)
{
local int i;
local array<Text> allTempaltes;
if (tool == none) {
return;
}
allTempaltes = _.kf.templates.GetItemList(T(TALL_WEAPONS));
for (i = 0; i < allTempaltes.length; i += 1) {
tool.AddItem(allTempaltes[i], doForce, doFillAmmo);
}
_.memory.FreeMany(allTempaltes);
}
protected function AddGivenTemplates(
InventoryTool tool,
DynamicArray templateList,
bool doForce,
bool doFillAmmo)
{
local int i;
if (tool == none) return;
if (templateList == none) return;
for (i = 0; i < templateList.GetLength(); i += 1) {
tool.AddItem(templateList.GetText(i), doForce, doFillAmmo);
}
}
protected function RemoveGivenTemplates(
InventoryTool tool,
DynamicArray templateList,
bool doForce,
bool doKeep)
{
local int i;
if (tool == none) return;
if (templateList == none) return;
for (i = 0; i < templateList.GetLength(); i += 1) {
tool.RemoveItem(templateList.GetText(i), doKeep, doForce);
}
}
defaultproperties
{
TINVENTORY = 0
stringConstants(0) = "inventory"
TADD = 1
stringConstants(1) = "add"
TREMOVE = 2
stringConstants(2) = "remove"
TITEMS = 3
stringConstants(3) = "items"
TEQUIP = 4
stringConstants(4) = "equip"
TALL = 5
stringConstants(5) = "all"
TKEEP = 6
stringConstants(6) = "keep"
THIDDEN = 7
stringConstants(7) = "hidden"
TFORCE = 8
stringConstants(8) = "force"
TAMMO = 9
stringConstants(9) = "ammo"
TALL_WEAPONS = 10
stringConstants(10) = "all weapons"
}

657
sources/Tools/InventoryTool.uc

@ -0,0 +1,657 @@
/**
* 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;
/**
* 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;
/**
* `ReportTool`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 private ReportTool itemsAdded;
var private ReportTool itemsRemoved;
var private ReportTool itemsAddedPrivate;
var private ReportTool itemsRemovedPrivate;
var private ReportTool itemsAdditionFailed;
var private ReportTool itemsRemovalFailed;
var const int TITEMS_ADDED_MESSAGE, TITEMS_ADDED_VEROBSE_MESSAGE;
var const int TITEMS_REMOVED_MESSAGE, TITEMS_REMOVED_VERBOSE_MESSAGE;
var const int TITEMS_ADDITION_FAILED_MESSAGE, TITEMS_REMOVAL_FAILED_MESSAGE;
var const int TRESOLVED_INTO, TTILDE_QUOTE, TFAULTY_INVENTORY_IMPLEMENTATION;
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;
protected function Constructor()
{
itemsAdded = ReportTool(_.memory.Allocate(class'ReportTool'));
itemsRemoved = ReportTool(_.memory.Allocate(class'ReportTool'));
itemsAddedPrivate = ReportTool(_.memory.Allocate(class'ReportTool'));
itemsRemovedPrivate = ReportTool(_.memory.Allocate(class'ReportTool'));
itemsAdditionFailed = ReportTool(_.memory.Allocate(class'ReportTool'));
itemsRemovalFailed = ReportTool(_.memory.Allocate(class'ReportTool'));
itemsAdded.Initialize(T(TITEMS_ADDED_MESSAGE));
itemsRemoved.Initialize(T(TITEMS_REMOVED_MESSAGE));
itemsAddedPrivate.Initialize(T(TITEMS_ADDED_VEROBSE_MESSAGE));
itemsRemovedPrivate.Initialize(T(TITEMS_REMOVED_VERBOSE_MESSAGE));
itemsAdditionFailed.Initialize(T(TITEMS_ADDITION_FAILED_MESSAGE));
itemsRemovalFailed.Initialize(T(TITEMS_REMOVAL_FAILED_MESSAGE));
}
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(
Text resolvedWhat,
Text 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(Text 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).Detail(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).Detail(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)
.Detail(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.
*/
public function RemoveItem(Text userProvidedName, bool doKeep, bool doForce)
{
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))
{
resolvedLine = MakeResolvedIntoLine(userProvidedName, itemTemplate);
itemsRemoved.Item(realItemName);
itemsRemovedPrivate.Item(realItemName).Detail(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).Detail(T(TITEM_MISSING));
}
else if (!doForce && !storedItem.IsRemovable()) // simply was not removable
{
itemsRemovalFailed.Item(userProvidedName)
.Detail(T(TITEM_NOT_REMOVABLE));
}
else { // no idea about the reason
itemsRemovalFailed.Item(userProvidedName).Detail(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<Text> 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]).Detail(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)
.Detail(T(TITEM_NOT_REMOVABLE));
}
else if (!targetInventory.Remove(nextItem, doKeep, doForce))
{
itemsRemovalFailed
.Item(nextItemName)
.Detail(T(TUNKNOWN));
}
_.memory.Free(nextItemName);
nextItemName = none;
}
_.memory.FreeMany(equippedItems);
}
/**
* 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 publicReport Is this report meant for the public or for
* the player that caused the changes? Former only (briefly) lists
* successful changes, while latter also provides report about failed
* changes.
*/
public final function ReportChanges(
EPlayer blamedPlayer,
ConsoleWriter writer,
bool publicReport)
{
local Text blamedName, targetName;
if (TargetPlayerIsInvalid()) {
return;
}
targetName = targetPlayer.GetName();
if (blamedPlayer != none) {
blamedName = blamedPlayer.GetName();
}
if (publicReport)
{
itemsAdded.Report(writer, blamedName, targetName);
itemsRemoved.Report(writer, blamedName, targetName);
}
else
{
itemsAddedPrivate.Report(writer, blamedName, targetName);
itemsRemovedPrivate.Report(writer, blamedName, targetName);
itemsAdditionFailed.Report(writer, blamedName, targetName);
itemsRemovalFailed.Report(writer, blamedName, targetName);
}
_.memory.Free(blamedName);
_.memory.Free(targetName);
}
/**
* 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
{
TITEMS_ADDED_MESSAGE = 0
stringConstants(0) = "%cause% has {$TextPositive added} following weapons to %target%:"
TITEMS_ADDED_VEROBSE_MESSAGE = 1
stringConstants(1) = "Weapons {$TextPositive added} to %target%:"
TITEMS_REMOVED_MESSAGE = 2
stringConstants(2) = "%cause% has {$TextNegative removed} following weapons from %target%:"
TITEMS_REMOVED_VERBOSE_MESSAGE = 3
stringConstants(3) = "Weapons {$TextNegative removed} from %target%:"
TITEMS_ADDITION_FAILED_MESSAGE = 4
stringConstants(4) = "Weapons we've {$TextFailure failed} to add to %target%:"
TITEMS_REMOVAL_FAILED_MESSAGE = 5
stringConstants(5) = "Weapons we've {$TextFailure failed} to remove from %target%:"
TRESOLVED_INTO = 6
stringConstants(6) = "` resolved into `"
TTILDE_QUOTE = 7
stringConstants(7) = "`"
TFAULTY_INVENTORY_IMPLEMENTATION = 8
stringConstants(8) = "faulty inventory implementation"
TITEM_MISSING = 9
stringConstants(9) = "item missing"
TITEM_NOT_REMOVABLE = 10
stringConstants(10) = "item not removable"
TUNKNOWN = 11
stringConstants(11) = "unknown"
TVISIBLE = 12
stringConstants(12) = "visible"
TDISPLAYING_INVENTORY = 13
stringConstants(13) = "{$TextHeader Displaying inventory for player }"
THEADER_COLON = 14
stringConstants(14) = "{$TextHeader :}"
TDOT_SPACE = 15
stringConstants(15) = ". "
TCOLON_SPACE = 16
stringConstants(16) = ": "
TCOMMA_SPACE = 17
stringConstants(17) = ", "
TSPACE = 18
stringConstants(18) = " "
TOUT_OF = 19
stringConstants(19) = " out of "
THIDDEN_ITEMS = 20
stringConstants(20) = "{$TextSubHeader Hidden items:}"
TDOLLAR = 21
stringConstants(21) = "$"
}

224
sources/Tools/ReportTool.uc

@ -0,0 +1,224 @@
/**
* Auxiliary object for outputting lists of values (with optional comments)
* for Futility's commands. Some of the commands need to report that one of
* the player did something to affect the other and then list the changes.
* This tool is made to simplify forming such reports.
* Produced reports have a form of "<list header, noting who affected who>:
* item1 (detail), item2, item3 (detail1, detail2)".
* 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 ReportTool extends AcediaObject;
/**
* How to use:
* 1. Specify "list header" via `Initialize()` method right after creating
* a new instance of `ReportTool`. It can contain "%cause%" and
* "%target%" substrings, that will be replaces with approprtiate
* parameters of `Report()` method when it is invoked;
* 2. Use `Item()` method to add new items (they will be listed after
* list header + whitespace, separated by commas and whitespaces ", ");
* 3. Use `Detail()` method to specify details for the item (they will be
* listed between the paranthesisasd after the corresponding item).
* Details will be added to the last item, added via `Item()` call.
* If no items were added, specified details will be discarded.
* 4. Use `Report()` method to feed the `ConsoleWriter` with report that
* has been assebled so far.
* 5. Use `Reset()` to forget all the items and details
* (but not list header), allowing to start forming a new report.
*/
// Header template (with possible "%cause%" and "%target%" placeholders)
// for the lists this `ReportTool` will generate.
// Doubles as a way to remember whether `ReportTool` was already
// initialized (iff `headerTemplate != none`).
var private Text headerTemplate;
// Represents one item + all of its details.
struct ReportItem
{
var Text itemTitle;
var array<Text> details;
};
// All items recorded reported thus far
var private array<ReportItem> itemsToReport;
var const int TCAUSE, TTARGET, TCOMMA, TSPACE, TSPACE_OPEN_PARANSIS;
var const int TCLOSE_PARANSIS;
protected function Finalizer()
{
Reset();
_.memory.Free(headerTemplate);
headerTemplate = none;
}
/**
* Initialized a new `ReportTool` with appropriate template to serve as
* a header.
*
* Template (`template`) is allowed to contain "%cause%" and "%target%"
* placeholder substrings that will be replaced with corresponding names of the
* player that caused a change we are reporting and player affefcted by
* that change.
*
* @param template Template for the header of the reports made by
* the caller `ReportTool`.
* Method does nothing (initialization fails) iff `template == none`.
*/
public final function Initialize(Text template)
{
if (template == none) {
return;
}
headerTemplate = template.Copy();
}
/**
* Adds new `item` to the current report.
*
* @param item Text to be included into the report as an item.
* One should avoid using commas or parantheses inside an `item`, but
* this limitation is not checked or prevented by `Item()` method.
* Does nothing if `item == none` (`Detail()` will continue adding details
* to the previously added item).
* @return Reference to the caller `ReportTool` to allow for method chaining.
*/
public final function ReportTool Item(Text item)
{
local ReportItem newItem;
if (headerTemplate == none) return self;
if (item == none) return self;
newItem.itemTitle = item.Copy();
itemsToReport[itemsToReport.length] = newItem;
return self;
}
/**
* Adds new `detail` to the last added `item` in the current report.
*
* @param detail Text to be included into the report as a detail to
* the last added item. One should avoid using commas or parantheses inside
* a `detail`, but this limitation is not checked or prevented by
* `Detail()` method.
* Does nothing if `detail == none` or no items were added thuis far.
* @return Reference to the caller `ReportTool` to allow for method chaining.
*/
public final function ReportTool Detail(Text detail)
{
local array<Text> detailToReport;
if (headerTemplate == none) return self;
if (detail == none) return self;
if (itemsToReport.length == 0) return self;
detailToReport = itemsToReport[itemsToReport.length - 1].details;
detailToReport[detailToReport.length] = detail.Copy();
itemsToReport[itemsToReport.length - 1].details = detailToReport;
return self;
}
/**
* Outputs report assembled thus far into the provided `ConsoleWriter`.
*
* @param writer `ConsoleWriter` to output report into.
* @param cause Player that caused the change this report is about.
* Their name will replace "%cause%" substring in the header template
* (if it is contained there).
* @param target Player that was affected by the change this report is about.
* Their name will replace "%target%" substring in the header template
* (if it is contained there).
* @return Reference to the caller `ReportTool` to allow for method chaining.
*/
public final function ReportTool Report(
ConsoleWriter writer,
optional Text cause,
optional Text target)
{
local int i, j;
local MutableText intro;
local array<Text> detailToReport;
if (headerTemplate == none) return self;
if (itemsToReport.length == 0) return self;
if (writer == none) return self;
intro = headerTemplate.MutableCopy()
.Replace(T(TCAUSE), cause)
.Replace(T(TTARGET), target);
writer.Flush().Write(intro);
_.memory.Free(intro);
for (i = 0; i < itemsToReport.length; i += 1)
{
if (i > 0) {
writer.Write(T(TCOMMA));
}
writer.Write(T(TSPACE)).Write(itemsToReport[i].itemTitle);
detailToReport = itemsToReport[i].details;
if (detailToReport.length > 0) {
writer.Write(T(TSPACE_OPEN_PARANSIS));
}
for (j = 0; j < detailToReport.length; j += 1)
{
if (j > 0) {
writer.Write(P(", "));
}
writer.Write(detailToReport[j]);
}
if (detailToReport.length > 0) {
writer.Write(T(TCLOSE_PARANSIS));
}
}
writer.Flush();
return self;
}
/**
* Forgets all items or details specified for the caller `ReportTool` so far,
* allowing to start forming a new report. Does not reset template header,
* specified in the `Initialize()` method.
*
* @return Reference to the caller `ReportTool` to allow for method chaining.
*/
public final function ReportTool Reset()
{
local int i;
for (i = 0; i < itemsToReport.length; i += 1)
{
_.memory.Free(itemsToReport[i].itemTitle);
_.memory.FreeMany(itemsToReport[i].details);
}
if (itemsToReport.length > 0) {
itemsToReport.length = 0;
}
return self;
}
defaultproperties
{
TCAUSE = 0
stringConstants(0) = "%cause%"
TTARGET = 1
stringConstants(1) = "%target%"
TCOMMA = 2
stringConstants(2) = ","
TSPACE = 3
stringConstants(3) = " "
TSPACE_OPEN_PARANSIS = 4
stringConstants(4) = " ("
TCLOSE_PARANSIS = 5
stringConstants(5) = ")"
}
Loading…
Cancel
Save