feat(inventory): enforce DeletionRestrictions when deleting items

Deleting an item now follows its ItemComponent delResIndex row in the
DeletionRestrictions table, the way the client's shared inventory code
decides it (LWOInventoryComponent_Common::CanRemoveFromInventory @
0x00ce0d20, CheckDeletionRestrictionIndex @ 0x00c94c20):
- missing, unrestricted, unknown-type or empty rows allow it;
- LOTS_INCLUDED: another item of any listed LOT must remain;
- LOTS_EXCLUDED: other items of every listed LOT must remain;
- ANY_RESTRICTION / ALL_RESTRICTIONS: any / all listed rows allow it;
- ZONE: only in the listed maps; ALWAYS_RESTRICTED: never.
Operators (GM level 9) may delete anything, as in the client. A refused
delete is logged and the item stays.

ItemComponent.minNumRequired is not used: the client never reads it, so
its meaning can't be verified.

Issue 960: the rocket (6416, row 8) and the classic rocket parts (rows 1-3)
have rows that keep at least one rocket or part, so the last rocket can
no longer be deleted and strand the player.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 08:49:03 -05:00
parent 4399daad4c
commit 02108055e7
9 changed files with 276 additions and 1 deletions

View File

@@ -4,6 +4,7 @@
#define INVENTORYCOMPONENT_H
#include <map>
#include <set>
#include <stack>
@@ -333,6 +334,12 @@ public:
*/
std::vector<uint32_t> FindBuffs(Item* item, bool castOnEquip) const;
/**
* One DeletionRestrictions row (CheckDeletionRestrictionIndex @ 00c94c20). visited stops rows that refer back to
* themselves; the client lets such rows through.
*/
bool CheckDeletionRestriction(LWOOBJID itemId, int32_t row, std::set<int32_t>& visited) const;
/**
* Initializes the equipped items with a list of items
* @param items the items to equip
@@ -451,6 +458,12 @@ public:
void OnEquipInventory(const GameMessages::EquipInventory& msg);
void OnUnEquipInventory(const GameMessages::UnEquipInventory& msg);
void OnRemoveItemFromInventory(const GameMessages::RemoveItemFromInventory& msg);
/**
* Whether the player may delete this item: its ItemComponent's DeletionRestrictions row, as the client checks it
* (LWOInventoryComponent_Common::CanRemoveFromInventory @ 00ce0d20 in 1.10.64). Operators may delete anything.
*/
bool CanDelete(const Item& item) const;
void OnMoveItemInInventory(const GameMessages::MoveItemInInventory& msg);
void OnMoveItemBetweenInventoryTypes(const GameMessages::MoveItemBetweenInventoryTypes& msg);
// Answers the client at sysAddr with ResponseMoveItemBetweenInventoryTypes.