Files
DarkflameServer/dGame/dComponents/InventoryComponent.h
Aaron Kimbrell 107437e72f fix(inventory): a save in build mode keeps the equipment from before it
Entering build mode pushes the equipped items (PushEquippedItems); the
thinking hat and a model picked up off the property are then equipped
until PopEquippedItems puts the old equipment back. A character save in
between (the world's periodic save, a disconnect, a zone change) wrote
what was equipped at that moment, so a carried brick built model (LOT
6662) was saved in MODELS with eq="true" and equipped again on every
load. While the equipment is pushed, the save now writes the pushed
equipment as equipped instead. Nothing is dropped from the save.

In game: on a property, pick up a brick built model and carry it, wait
for a save (or log out) while carrying it, log back in: the model is in
the Models bag, not equipped, and your normal gear is worn. Characters
already saved with an equipped model keep it until it is put away or
the save is cleaned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:33:58 -05:00

599 lines
19 KiB
C++

#pragma once
#ifndef INVENTORYCOMPONENT_H
#define INVENTORYCOMPONENT_H
#include <map>
#include <set>
#include <stack>
#include "BehaviorSlot.h"
#include "tinyxml2.h"
#include "dCommonVars.h"
#include "EquippedItem.h"
#include "Inventory.h"
#include "LDFFormat.h"
#include "DatabasePet.h"
#include "Component.h"
#include "ItemSetPassiveAbility.h"
#include "PossessorComponent.h"
#include "eInventoryType.h"
#include "eReplicaComponentType.h"
#include "eLootSourceType.h"
#include "Loot.h"
class Entity;
class ItemSet;
typedef std::map<std::string, EquippedItem> EquipmentMap;
enum class eItemType : int32_t;
namespace GameMessages {
struct GetObjectReportInfo;
struct EquipInventory;
struct UnEquipInventory;
struct RemoveItemFromInventory;
struct MoveItemInInventory;
struct MoveItemBetweenInventoryTypes;
struct RequestMoveItemBetweenInventoryTypes;
struct ClientItemConsumed;
struct UseNonEquipmentItem;
struct UpdateInventoryGroup;
struct UpdateInventoryGroupContents;
}
/**
* Handles the inventory of entity, including the items they possess and have equipped. An entity can have inventories
* of different types, each type representing a different group of items, see `eInventoryType` for a list of
* inventories.
*/
// Where and how ReceiveItem puts items; the defaults suit items arriving from someone else
struct ReceiveItemOptions {
eInventoryType inventory = INVALID; // INVALID: the item's usual inventory
bool showFlyingLoot = true;
bool equip = false;
int32_t preferredSlot = -1;
eInventoryType sourceInventory = INVALID; // set when moving between the player's own inventories
};
// Which item or stack ReceiveItem put the items in
struct ReceivedItem {
LWOOBJID id = LWOOBJID_EMPTY; // LWOOBJID_EMPTY if nothing could be added
bool merged = false; // joined a stack that was already there (and has a history of its own)
};
class InventoryComponent final : public Component {
public:
struct Group {
// Generated ID for the group. The ID is sent by the client and has the format user_group + Math.random() * UINT_MAX.
std::string groupId;
// Custom name assigned by the user.
std::string groupName;
// All the lots the user has in the group.
std::set<LOT> lots;
};
enum class GroupUpdateCommand {
ADD,
ADD_LOT,
MODIFY,
REMOVE,
REMOVE_LOT,
};
// Based on the command, certain fields will be used or not used.
// for example, ADD_LOT wont use groupName, MODIFY wont use lots, etc.
struct GroupUpdate {
std::string groupId;
std::string groupName;
LOT lot;
eInventoryType inventory;
GroupUpdateCommand command;
};
static constexpr uint32_t MaximumGroupCount = 50;
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY;
InventoryComponent(Entity* parent, const int32_t componentID);
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void LoadXml(const tinyxml2::XMLDocument& document);
void UpdateXml(tinyxml2::XMLDocument& document) override;
/**
* Returns an inventory of the specified type, if it exists
* @param type the inventory type to find an inventory for
* @return the inventory of the specified type
*/
Inventory* GetInventory(eInventoryType type);
/**
* Returns all the inventories this entity has, indexed by type
* @return all the inventories this entity has, indexed by type
*/
const std::map<eInventoryType, Inventory*>& GetInventories() const;
/**
* Returns the amount of items this entity possesses of a certain LOT
* @param lot the lot to search for
* @return the amount of items this entity possesses the specified LOT
*/
uint32_t GetLotCount(LOT lot) const;
/**
* Returns the amount of items this entity possesses of a LOT, given that they're not in a temporary inventory
* (vendor buyback, vault, etc).
* @param lot the lot to search for
* @return the amount of items this entity possesses of the specified lot
*/
uint32_t GetLotCountNonTransfer(LOT lot, bool includeVault = true) const;
/**
* Returns the items that are currently equipped by this entity
* @return the items that are currently equipped by this entity
*/
const EquipmentMap& GetEquippedItems() const;
/**
* Adds an item to the inventory of the entity
* @param lot the lot to add
* @param count the amount of items to add
* @param inventoryType the inventory to add the item to
* @param config optional config for this item, used for example for rockets
* @param parent optional parent of this item, used for proxy items
* @param showFlyingLoot show a client animation if the item is added
* @param isModMoveAndEquip equips the item
* @param subKey optional sub ID of a related object, used by pets
* @param inventorySourceType if the inventory was moved, the source inventory
* @param sourceType the source of the item, used to determine if the item is dropped or mailed if the inventory is full
* @param bound whether this item is bound
* @param preferredSlot the preferred slot to store this item
* @param lootSourceType The source of the loot. Defaults to none.
*/
void AddItem(
LOT lot,
uint32_t count,
eLootSourceType lootSourceType = eLootSourceType::NONE,
eInventoryType inventoryType = INVALID,
const LwoNameValue& config = {},
LWOOBJID parent = LWOOBJID_EMPTY,
bool showFlyingLoot = true,
bool isModMoveAndEquip = false,
LWOOBJID subKey = LWOOBJID_EMPTY,
eInventoryType inventorySourceType = INVALID,
int32_t sourceType = 0,
bool bound = false,
int32_t preferredSlot = -1
);
/**
* Receive items that already exist elsewhere (another player's inventory, the mail), keeping them traceable:
* - a unique item (does not stack, or carries config/subkey) keeps its object id
* - stackable items merge into this inventory's stack of the same LOT if there is one, taking that stack's id;
* with nothing to merge into, a whole stack keeps its id and a partial one gets a new id
* @param id the items' current object id, or LWOOBJID_EMPTY for part of a stack
* @return the object id of the item or stack that received them, and whether that stack was already there
*/
ReceivedItem ReceiveItem(LWOOBJID id, LOT lot, uint32_t count, eLootSourceType lootSourceType, const LwoNameValue& config = {}, LWOOBJID subKey = LWOOBJID_EMPTY, bool bound = false, const ReceiveItemOptions& options = {});
// Whether items of this LOT are one of a kind rather than stacking
static bool IsUniqueLot(LOT lot);
/**
* Removes a LOT from the inventory
* @param lot the lot to remove
* @param count the number of items to remove
* @param inventoryType optional inventory type to remove the item from
* @param ignoreBound ignores bound items
* @param silent silently remove the item
*/
bool RemoveItem(LOT lot, uint32_t count, eInventoryType inventoryType = INVALID, bool ignoreBound = false, bool silent = false);
/**
* Moves an existing item to an inventory of the entity
* @param item the item to add
* @param inventory the inventory to add the item to
* @param count the number of items to add
* @param showFlyingLot displays UI animation to the user
* @param isModMoveAndEquip equips the item
* @param ignoreEquipped does not stack on equipped items
* @param preferredSlot the preferred slot to store the item in
*/
void MoveItemToInventory(Item* item, eInventoryType inventory, uint32_t count, bool showFlyingLot = true, bool isModMoveAndEquip = false, bool ignoreEquipped = false, int32_t preferredSlot = -1);
/**
* Moves a stack of items to an inventory
* @param item the item to move
* @param inventory the inventory to move the item to
* @param slot the slot in the inventory to move the item to
*/
void MoveStack(Item* item, eInventoryType inventory, uint32_t slot = 0);
/**
* Returns an item in the inventory by object ID
* @param id the id of the item to find
* @return item in the inventory by object ID
*/
Item* FindItemById(LWOOBJID id) const;
/**
* Returns an item in the inventory that matches the specified LOT
* @param lot the lot of the item to find
* @param inventoryType optional inventory to search in
* @param ignoreEquipped ignores items that are equipped
* @param ignoreBound ignores items that are bound
* @return item in the inventory that matches the specified LOT
*/
Item* FindItemByLot(LOT lot, eInventoryType inventoryType = INVALID, bool ignoreEquipped = false, bool ignoreBound = false);
/**
* Finds an item in the inventory that has the specified subkey, useful for pets
* @param id the subkey to look for
* @param inventoryType optional inventory type to search in
* @return item in the inventory that has the specified subkey
*/
Item* FindItemBySubKey(LWOOBJID id, eInventoryType inventoryType = INVALID);
/**
* Checks if the entity has enough space for a batch of loot
* @param loot a map of items to add and how many to add
* @return whether the entity has enough space for all the items
*/
bool HasSpaceForLoot(const Loot::Return& loot);
/**
* Equips an item in the specified slot
* @param location the location to store the item (e.g. chest, left hand, etc.)
* @param item the item to place
* @param keepCurrent stores the item in an additional temp slot if there's already an item equipped
*/
void UpdateSlot(const std::string& location, const EquippedItem& item, bool keepCurrent = false);
/**
* Removes a slot from the inventory
* @param location the slot to remove
*/
void RemoveSlot(const std::string& location);
/**
* Equips the given item, guesses the slot to equip it in
* @param item the item to equip
* @param skipChecks skips checks for equipping cars and rockets (e.g. no special behavior follows)
*/
void EquipItem(Item* item, bool skipChecks = false);
/**
* Unequips an item from the inventory
* @param item the item to unequip
*/
void UnEquipItem(Item* item);
/**
* Unequips an Item from the inventory
* @param item the Item to unequip
* @return if we were successful
*/
void HandlePossession(Item* item);
/**
* Adds a buff related to equipping a lot to the entity
* @param item the item to find buffs for
*/
void ApplyBuff(Item* item) const;
/**
* Removes buffs related to equipping a lot from the entity
* @param item the item to find buffs for
*/
void RemoveBuff(Item* item) const;
/**
* Saves the equipped items into a temp state
*/
void PushEquippedItems();
/**
* Unequips all the temporary items and equips the previous item state
*/
void PopEquippedItems();
/**
* Returns if the entity has an item equipped of the given lot
* @param lot to lot to search for
* @return if the entity has an item equipped of the given lot
*/
bool IsEquipped(LOT lot) const;
/**
* Checks and ensures that we have loaded the item set that might be related to this item
* @param lot the lot to check the item set for
*/
void CheckItemSet(LOT lot);
/**
* Sets the current consumable lot
* @param lot the lot to set as consumable
*/
void SetConsumable(LOT lot);
/**
* Returns the current consumable lot
* @return the current consumable lot
*/
LOT GetConsumable() const;
/**
* Finds all the buffs related to a lot
* @param item the item to get the buffs for
* @param castOnEquip if true, the skill missions for these buffs will be progressed
* @return the buffs related to the specified lot
*/
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
*/
void SetNPCItems(const std::vector<LOT>& items);
/**
* Adds a skill related to a passed item to the currently equipped skills
* @param lot the lot to add a skill for
*/
void AddItemSkills(LOT lot);
/**
* Removes the skills related to the passed LOT from the currently equipped skills
* @param lot the lot to remove
*/
void RemoveItemSkills(LOT lot);
/**
* Triggers one of the passive abilities from the equipped item set
* @param trigger the trigger to fire
*/
void TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target = nullptr);
/**
* Despawns the currently active pet, if any
*/
void DespawnPet();
/**
* Spawns the item as a pet (if it is one)
* @param item the pet to spawn
*/
void SpawnPet(Item* item);
/**
* Updates the database pet data for an item (e.g. moderation status)
* @param id the id of the pet to find
* @param data the data to store on the pet
*/
void SetDatabasePet(LWOOBJID id, const DatabasePet& data);
/**
* Returns the database pet information for an object
* @param id the object ID to search for
* @return the database pet information for the object that belongs to the passed id
*/
const DatabasePet& GetDatabasePet(LWOOBJID id) const;
/**
* Checks if the provided object ID is in this inventory and is a pet
* @param id the id of the object to check for
* @return if the provided object ID is in this inventory and is a pet
*/
bool IsPet(LWOOBJID id) const;
/**
* Removes pet database information from the item with the specified object id
* @param id the object id to remove pet info for
*/
void RemoveDatabasePet(LWOOBJID id);
/**
* Returns the behavior slot for the given equipLocation string.
* This is the authoritative mapping used for skill slot assignment.
*/
static BehaviorSlot FindBehaviorSlot(const std::string& equipLocation);
/**
* Checks if the inventory type is a temp inventory
* @param type the inventory type to check
* @return if the inventory type is a temp inventory
*/
static bool IsTransferInventory(eInventoryType type, bool includeVault = true);
/**
* Finds the skill related to the passed LOT from the ObjectSkills table
* @param lot the lot to find
* @return the skill related to the passed LOT
*/
static uint32_t FindSkill(LOT lot);
/**
* Call this when you equip an item. This calls OnFactionTriggerItemEquipped for any scripts found on the items.
*
* @param equippedItem The item script to lookup and call equip on
*/
void EquipScripts(Item* equippedItem);
/**
* Call this when you unequip an item. This calls OnFactionTriggerItemUnequipped for any scripts found on the items.
*
* @param unequippedItem The item script to lookup and call unequip on
*/
void UnequipScripts(Item* unequippedItem);
std::map<BehaviorSlot, uint32_t> GetSkills() { return m_Skills; };
void ClearSkills() { m_Skills.clear(); };
bool SetSkill(int slot, uint32_t skillId);
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
void UpdateGroup(const GroupUpdate& groupUpdate);
std::unordered_map<LWOOBJID, DatabasePet>& GetPetsMut() { return m_Pets; };
void FixInvisibleItems();
// Used to migrate a character version, no need to call outside of that context
void RegenerateItemIDs();
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
// Game messages received from the client (see InventoryMessages.h).
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.
void OnRequestMoveItemBetweenInventoryTypes(const GameMessages::RequestMoveItemBetweenInventoryTypes& msg, const SystemAddress& sysAddr);
void OnClientItemConsumed(const GameMessages::ClientItemConsumed& msg);
void OnUseNonEquipmentItem(const GameMessages::UseNonEquipmentItem& msg);
void OnUpdateInventoryGroup(const GameMessages::UpdateInventoryGroup& msg);
void OnUpdateInventoryGroupContents(const GameMessages::UpdateInventoryGroupContents& msg);
~InventoryComponent() override;
private:
/**
* The key is the inventory the group belongs to, the value maps' key is the id for the group.
* This is only used for bricks and model inventories.
*/
std::map<eInventoryType, std::vector<Group>> m_Groups{ { eInventoryType::BRICKS, {} }, { eInventoryType::MODELS, {} } };
/**
* All the inventory this entity possesses
*/
std::map<eInventoryType, Inventory*> m_Inventories;
/**
* The skills that this entity currently has active
*/
std::map<BehaviorSlot, uint32_t> m_Skills;
/**
* The pets this entity has, mapped by object ID and pet info
*/
std::unordered_map<LWOOBJID, DatabasePet> m_Pets;
/**
* Cache of item sets this entity has encountered
*/
std::vector<ItemSet*> m_Itemsets;
/**
* The LOTs we've checked all the item sets for (for cache reasons)
*/
std::vector<LOT> m_ItemSetsChecked;
/**
* all the equipped items
*/
EquipmentMap m_Equipped;
/**
* Clone of the equipped items before unequipping all of them
*/
EquipmentMap m_Pushed;
/**
* Whether the equipped items are pushed (between PushEquippedItems and PopEquippedItems, e.g. in build mode).
* What is equipped then is temporary (the thinking hat, a carried model) and m_Pushed is what gets saved.
*/
bool m_HasPushed{};
/**
* If the inventory has changed
*/
bool m_Dirty;
/**
* The currently active consumable
*/
LOT m_Consumable;
/**
* Currently has a car equipped
*/
bool hasCarEquipped = false;
Entity* equippedCarEntity = nullptr;
LWOOBJID previousPossessableID = LWOOBJID_EMPTY;
LWOOBJID previousPossessorID = LWOOBJID_EMPTY;
/**
* Creates all the proxy items (subitems) for a parent item
* @param parent the parent item to generate all the subitems for
* @return the proxy items (subitems) for a parent item
*/
std::vector<Item*> GenerateProxies(Item* parent);
/**
* Finds all the proxy items in this inventory for a given parent item
* @param parent the parent to find proxy items for
* @return the proxy items for the parent
*/
std::vector<Item*> FindProxies(LWOOBJID parent);
/**
* Returns true if the provided LWOOBJID is the parent of this Item.
* @param parent the parent item to check for proxies
* @return if the provided ID is a valid proxy item
*/
bool IsValidProxy(LWOOBJID parent);
/**
* Returns if the provided ID is a valid proxy item (e.g. we have children for it)
* @param parent the parent item to check for
* @return if the provided ID is a valid proxy item
*/
bool IsParentValid(Item* root);
/**
* Removes all the proxy items that have a dangling parent
*/
void CheckProxyIntegrity();
/**
* Removes all the proxy items for a given parent from the inventory
* @param item the item to remove proxy items for
*/
void PurgeProxies(Item* item);
/**
* Saves all the pet information stored in inventory items to the database
* @param document the xml doc to save to
*/
void LoadPetXml(const tinyxml2::XMLDocument& document);
/**
* Loads all the pet information from an xml doc into items
* @param document the xml doc to load from
*/
void UpdatePetXml(tinyxml2::XMLDocument& document);
void LoadGroupXml(const tinyxml2::XMLElement& groups);
void UpdateGroupXml(tinyxml2::XMLElement& groups) const;
};
#endif