Files
DarkflameServer/dGame/dGameMessages/InventoryMessages.h
Aaron Kimbrell 20f70ac0de refactor: inventory and item game messages as structs
Converts AddItemToInventoryClientSync, SetInventorySize,
RemoveItemFromInventory, ConsumeClientItem, UseItemResult,
UseItemRequirementsResponse, ResponseMoveItemBetweenInventoryTypes,
NotifyNotEnoughInvSpace, UpdateInventoryUi, MarkInventoryItemAsActive and
MoveInventoryBatch (11 old Send functions) and the received EquipInventory,
UnEquipInventory, RemoveItemFromInventory, MoveItemInInventory,
MoveItemBetweenInventoryTypes, RequestMoveItemBetweenInventoryTypes,
PushEquippedItemsState, PopEquippedItemsState, ClientItemConsumed,
UseNonEquipmentItem, SetConsumableItem, UpdateInventoryGroup and
UpdateInventoryGroupContents (13 handlers) to NetGameMsgs in
InventoryMessages.{h,cpp}. The received messages are registered in
GameMessageHandler's map and handled by InventoryComponent (new On* methods
holding the old handler logic verbatim); the old functions and switch cases
are deleted. AddItemToInventoryClientSync::SetItem fills the fields that
come from the Item.

No wire change and no change in recipients. Kept as DLU has always sent them
and documented: NotifyNotEnoughInvSpace goes out with the message ID of
VehicleNotifyFinishedRace, and RemoveItemFromInventory always sets the flag
of the fields DLU fills. The old SendMoveInventoryBatch was never called
and wrote one flag bit fewer than the client reads (it had no moveSubkey);
the struct follows the client's layout (1.10.64,
LWOInventoryComponent_Common::msgMoveInventoryBatch at 00ce1310) and has no
oracle. UnEquipInventory still ignores the trailing replacementObjectID the
client sends, as before.

Verified byte for byte against a frozen verbatim copy of the old functions
over an input grid (AddItemToInventoryClientSync with real Items, extra info
and bind flags), to one client and broadcast; received messages compared
with the old handlers' read sequences and every truncated payload rejected;
hand computed golden bytes; round trips; a deliberate width mutation made
the tests fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:51 -05:00

315 lines
13 KiB
C++

#ifndef INVENTORYMESSAGES_H
#define INVENTORYMESSAGES_H
#include "GameMessages.h"
#include "eInventoryType.h"
#include "eLootSourceType.h"
#include "eReponseMoveItemBetweenInventoryTypeCode.h"
#include "eUnequippableActiveType.h"
#include "eUseItemResponse.h"
#include "NiPoint3.h"
#include <string>
class Item;
// Game messages for inventories and items: adding, removing, moving, equipping and using items, inventory sizes
// and groups.
// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order.
// Received messages are handled by the target's InventoryComponent.
namespace GameMessages {
// Server -> client.
struct AddItemToInventoryClientSync : public NetGameMsg {
AddItemToInventoryClientSync() : NetGameMsg(MessageType::Game::ADD_ITEM_TO_INVENTORY_CLIENT_SYNC) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
// Fills the fields that describe the item itself (bound flags, extra info, LOT, inventory, stack total and
// slot). The caller sets the rest.
void SetItem(const Item& item);
bool bBound{};
bool bIsBOE{};
bool bIsBOP{};
eLootSourceType eLootTypeSource{ eLootSourceType::NONE }; // optional
// Name value text: u32 length and the characters, then a null character if it is not empty.
std::u16string extraInfo{};
LOT iObjTemplate{};
LWOOBJID iSubkey{ LWOOBJID_EMPTY }; // optional
eInventoryType invType{ eInventoryType::ITEMS }; // optional
int32_t itemCount{ 1 }; // optional
uint32_t itemsTotal{ 0 }; // optional
LWOOBJID newObjID{};
NiPoint3 ni3FlyingLootPosit{};
bool showFlyingLoot{};
uint32_t slotID{};
};
// Server -> client.
struct SetInventorySize : public NetGameMsg {
SetInventorySize() : NetGameMsg(MessageType::Game::SET_INVENTORY_SIZE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
eInventoryType inventoryType{};
int32_t size{};
};
// Both directions: the client asks to delete (trash) an item, and the server tells the client an item left
// its inventory. DLU always sends the flag of the fields marked "always sent" (they are still read as optional).
struct RemoveItemFromInventory : public NetGameMsg {
RemoveItemFromInventory() : NetGameMsg(MessageType::Game::REMOVE_ITEM_FROM_INVENTORY) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool bConfirmed{};
bool bDeleteItem{ true };
bool bOutSuccess{};
int32_t eInvType{ INVENTORY_MAX }; // optional, always sent
int32_t eLootTypeSource{ LOOTTYPE_NONE }; // optional, always sent
// Name value text: u32 length and the characters, then a null character if it is not empty.
std::u16string extraInfo{};
bool forceDeletion{ true };
LWOOBJID iLootTypeSource{ LWOOBJID_EMPTY }; // optional
LWOOBJID iObjID{ LWOOBJID_EMPTY }; // optional, always sent
LOT iObjTemplate{ LOT_NULL }; // optional, always sent
LWOOBJID iRequestingObjID{ LWOOBJID_EMPTY }; // optional
uint32_t iStackCount{ 1 }; // optional, always sent
uint32_t iStackRemaining{ 0 }; // optional, always sent
LWOOBJID iSubkey{ LWOOBJID_EMPTY }; // optional
LWOOBJID iTradeID{ LWOOBJID_EMPTY }; // optional
};
// Client -> server.
struct EquipInventory : public NetGameMsg {
EquipInventory() : NetGameMsg(MessageType::Game::EQUIP_INVENTORY) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool bIgnoreCooldown{};
bool bOutSuccess{};
LWOOBJID itemToEquip{};
};
// Client -> server. The client also sends an optional replacementObjectID after itemToUnequip, which DLU
// has never read (and still does not).
struct UnEquipInventory : public NetGameMsg {
UnEquipInventory() : NetGameMsg(MessageType::Game::UN_EQUIP_INVENTORY) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool bEvenIfDead{};
bool bIgnoreCooldown{};
bool bOutSuccess{};
LWOOBJID itemToUnequip{};
};
// Client -> server.
struct MoveItemInInventory : public NetGameMsg {
MoveItemInInventory() : NetGameMsg(MessageType::Game::MOVE_ITEM_IN_INVENTORY) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
int32_t destInvType{ eInventoryType::INVALID }; // optional
LWOOBJID iObjID{};
int32_t inventoryType{};
int32_t responseCode{};
int32_t slot{};
};
// Client -> server.
struct MoveItemBetweenInventoryTypes : public NetGameMsg {
MoveItemBetweenInventoryTypes() : NetGameMsg(MessageType::Game::MOVE_ITEM_BETWEEN_INVENTORY_TYPES) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
eInventoryType inventoryTypeA{};
eInventoryType inventoryTypeB{};
LWOOBJID objectID{};
bool showFlyingLoot{ true };
uint32_t stackCount{ 1 }; // optional
LOT templateID{ LOT_NULL }; // optional
};
// Client -> server. Answered with ResponseMoveItemBetweenInventoryTypes.
struct RequestMoveItemBetweenInventoryTypes : public NetGameMsg {
RequestMoveItemBetweenInventoryTypes() : NetGameMsg(MessageType::Game::REQUEST_MOVE_ITEM_BETWEEN_INVENTORY_TYPES) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool bAllowPartial{};
int32_t destSlot{ -1 }; // optional
int32_t iStackCount{ 1 }; // optional
eInventoryType invTypeDst{ eInventoryType::ITEMS }; // optional
eInventoryType invTypeSrc{ eInventoryType::ITEMS }; // optional
LWOOBJID itemID{ LWOOBJID_EMPTY }; // optional
bool showFlyingLoot{};
LWOOBJID subkey{ LWOOBJID_EMPTY }; // optional
LOT itemLOT{ LOT_NULL }; // optional
};
// Server -> client.
struct ResponseMoveItemBetweenInventoryTypes : public NetGameMsg {
ResponseMoveItemBetweenInventoryTypes() : NetGameMsg(MessageType::Game::RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
eInventoryType inventoryTypeDestination{ eInventoryType::ITEMS }; // optional
eInventoryType inventoryTypeSource{ eInventoryType::ITEMS }; // optional
eReponseMoveItemBetweenInventoryTypeCode response{ eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC }; // optional
};
// Server -> client. Moves count items out of srcBag (the client fills dstBag from a following
// AddItemToInventoryClientSync). Laid out as the client reads it; DLU does not send it.
struct MoveInventoryBatch : public NetGameMsg {
MoveInventoryBatch() : NetGameMsg(MessageType::Game::MOVE_INVENTORY_BATCH) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
bool bAllowPartial{};
bool bOutSuccess{};
uint32_t count{ 1 }; // optional
eInventoryType dstBag{ eInventoryType::ITEMS }; // optional
LOT moveLOT{ LOT_NULL }; // optional
LWOOBJID moveSubkey{ LWOOBJID_EMPTY }; // optional
bool showFlyingLoot{};
eInventoryType srcBag{ eInventoryType::ITEMS }; // optional
LWOOBJID startObjectID{ LWOOBJID_EMPTY }; // optional
};
// Server -> client. DLU sends this with the message ID of VehicleNotifyFinishedRace, not
// NOTIFY_NOT_ENOUGH_INV_SPACE; kept as it has always been sent (see docs/PacketArchitecture.md: wire fixes
// are separate changes).
struct NotifyNotEnoughInvSpace : public NetGameMsg {
NotifyNotEnoughInvSpace() : NetGameMsg(MessageType::Game::VEHICLE_NOTIFY_FINISHED_RACE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
uint32_t freeSlotsNeeded{};
eInventoryType inventoryType{ eInventoryType::ITEMS }; // optional
};
// Server -> client. No payload. The client sends it to itself normally; DLU sends it to refresh the inventory
// UI (its defaults get around items that would otherwise stay invisible).
struct UpdateInventoryUi : public NetGameMsg {
UpdateInventoryUi() : NetGameMsg(MessageType::Game::UPDATE_INVENTORY_UI) {}
};
// Server -> client. Marks a pet or mount item as in use (or no longer).
struct MarkInventoryItemAsActive : public NetGameMsg {
MarkInventoryItemAsActive() : NetGameMsg(MessageType::Game::MARK_INVENTORY_ITEM_AS_ACTIVE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
bool bActive{};
eUnequippableActiveType iType{ eUnequippableActiveType::INVALID }; // optional
LWOOBJID itemID{ LWOOBJID_EMPTY }; // optional
};
// Server -> client.
struct ConsumeClientItem : public NetGameMsg {
ConsumeClientItem() : NetGameMsg(MessageType::Game::CONSUME_CLIENT_ITEM) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
bool bSuccess{};
LWOOBJID item{};
};
// Client -> server.
struct ClientItemConsumed : public NetGameMsg {
ClientItemConsumed() : NetGameMsg(MessageType::Game::CLIENT_ITEM_CONSUMED) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
LWOOBJID item{};
};
// Client -> server.
struct UseNonEquipmentItem : public NetGameMsg {
UseNonEquipmentItem() : NetGameMsg(MessageType::Game::USE_NON_EQUIPMENT_ITEM) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
LWOOBJID itemToUse{};
};
// Server -> client.
struct UseItemResult : public NetGameMsg {
UseItemResult() : NetGameMsg(MessageType::Game::USE_ITEM_RESULT) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
LOT itemTemplateID{};
bool useItemResult{};
};
// Server -> client.
struct UseItemRequirementsResponse : public NetGameMsg {
UseItemRequirementsResponse() : NetGameMsg(MessageType::Game::USE_ITEM_REQUIREMENTS_RESPONSE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
eUseItemResponse eUseResponse{};
};
// Client -> server.
struct SetConsumableItem : public NetGameMsg {
SetConsumableItem() : NetGameMsg(MessageType::Game::SET_CONSUMABLE_ITEM) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
LOT itemTemplateID{};
};
// Client -> server. No payload.
struct PushEquippedItemsState : public NetGameMsg {
PushEquippedItemsState() : NetGameMsg(MessageType::Game::PUSH_EQUIPPED_ITEMS_STATE) {}
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
// Client -> server. No payload.
struct PopEquippedItemsState : public NetGameMsg {
PopEquippedItemsState() : NetGameMsg(MessageType::Game::POP_EQUIPPED_ITEMS_STATE) {}
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
// Client -> server. Adds, modifies or removes an inventory group.
struct UpdateInventoryGroup : public NetGameMsg {
UpdateInventoryGroup() : NetGameMsg(MessageType::Game::UPDATE_INVENTORY_GROUP) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
std::string action{}; // ADD, MODIFY or REMOVE
std::string groupID{};
std::u16string groupName{};
eInventoryType inventoryType{};
bool locked{};
};
// Client -> server. Adds a LOT to or removes one from an inventory group.
struct UpdateInventoryGroupContents : public NetGameMsg {
UpdateInventoryGroupContents() : NetGameMsg(MessageType::Game::UPDATE_INVENTORY_GROUP_CONTENTS) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
std::string action{}; // ADD or REMOVE
std::string groupID{};
eInventoryType inventoryType{};
LOT lot{};
};
};
#endif // INVENTORYMESSAGES_H