Anton Tarasenko
2 years ago
3 changed files with 1101 additions and 0 deletions
@ -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" |
||||
} |
@ -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) = "$" |
||||
} |
@ -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…
Reference in new issue