Files
DarkflameServer/dGame/dGameMessages/InventoryMessages.h
Aaron Kimbrell 51981b7bee feat(property): resend the equipment on ResyncEquipment
The property editor sends ResyncEquipment (1238, no payload)
after the player picks up or sets down a model they carry: 59 live
packets, every one right after PlaceModelResponse. Live answered with no
game message, only replica serializations (packet 0x1b), i.e. the
player's equipment sent again. DLU dropped it.

The handler marks the inventory's equipped items dirty and serializes the
player, so everyone gets the equipment again. That live re-sent the
inventory part of the replica is inferred: the captures show a
serialization but it was not decoded.

Check in game: on your property, in build mode, pick up a model from the
world and place it again a few times, then leave build mode: your hat,
shirt, weapon etc. are all shown on you (and to a second player
watching), none invisible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 03:44:06 -05:00

375 lines
15 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{};
};
// Both directions: the client asks to unequip; the server tells the client an item was unequipped when it took
// it away (live: bIgnoreCooldown set, after RemoveItemFromInventory).
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{};
LWOOBJID replacementObjectID{ LWOOBJID_EMPTY }; // optional
};
// 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;
// Client -> server: brick by brick moving bricks between BRICKS and BRICKS_IN_BBB. The client has already taken
// them out of srcBag (LWOInventoryComponent_Common::msgMoveInventoryBatch, 0x00ce1310) and waits for
// AddItemToInventoryClientSync for dstBag.
void Handle(Entity& entity, const SystemAddress& sysAddr) 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. WIRE FIX: DLU used to send this with the ID of VehicleNotifyFinishedRace (1396); the client
// registers it as NOTIFY_NOT_ENOUGH_INV_SPACE (1516, msgNotifyNotEnoughInvSpace::GetMsgID @ 00545c90 in 1.10.64)
// and reads freeSlotsNeeded then the optional inventoryType (00d8b850).
struct NotifyNotEnoughInvSpace : public NetGameMsg {
NotifyNotEnoughInvSpace() : NetGameMsg(MessageType::Game::NOTIFY_NOT_ENOUGH_INV_SPACE) {}
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. No payload. The property editor sends it after the player picks up or sets down a model
// they carry (live: right after PlaceModelResponse). Live answered with a serialization of the player, no message.
struct ResyncEquipment : public NetGameMsg {
ResyncEquipment() : NetGameMsg(MessageType::Game::RESYNC_EQUIPMENT) {}
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{};
};
struct UseItemOnClient : public NetGameMsg {
UseItemOnClient() : NetGameMsg(MessageType::Game::USE_ITEM_ON_CLIENT) {}
LWOOBJID playerId{};
LWOOBJID itemToUse{};
uint32_t itemType{};
LOT itemLOT{};
NiPoint3 targetPosition{};
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
struct DropClientLoot : public NetGameMsg {
DropClientLoot() : NetGameMsg(MessageType::Game::DROP_CLIENT_LOOT) {}
void Serialize(RakNet::BitStream& stream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
LWOOBJID sourceID{ LWOOBJID_EMPTY };
LOT item{ LOT_NULL };
int32_t currency{};
NiPoint3 spawnPos{};
NiPoint3 finalPosition{};
int32_t count{};
bool bUsePosition{};
LWOOBJID lootID{ LWOOBJID_EMPTY };
LWOOBJID ownerID{ LWOOBJID_EMPTY };
};
using DropClientLootEvent = NetGameMsgEvent<DropClientLoot>;
struct PickupItem : public NetGameMsg {
PickupItem() : NetGameMsg(MessageType::Game::PICKUP_ITEM) {}
void Serialize(RakNet::BitStream& bitStream) const override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool Deserialize(RakNet::BitStream& stream) override;
LWOOBJID lootID{};
LWOOBJID lootOwnerID{};
};
using PickupItemEvent = NetGameMsgEvent<PickupItem>;
struct TeamPickupItem : public NetGameMsg {
TeamPickupItem() : NetGameMsg(MessageType::Game::TEAM_PICKUP_ITEM) {}
void Serialize(RakNet::BitStream& stream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
LWOOBJID lootID{};
LWOOBJID lootOwnerID{};
};
};
#endif // INVENTORYMESSAGES_H