From 0bc1a88e7534d0829a16f5c062dc97feaf4d6d71 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 2 Oct 2026 10:30:05 -0500 Subject: [PATCH] fix(inventory): adding an inventory group that exists does nothing, as the client LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) ignores an ADD whose group id is already in the inventory. The server added a second group with the same id, saved to the charxml. Co-Authored-By: Claude Opus 5.5 --- dGame/dComponents/InventoryComponent.cpp | 3 + dGame/dComponents/InventoryComponent.h | 1 + .../dComponentsTests/CMakeLists.txt | 1 + .../dComponentsTests/InventoryGroupTests.cpp | 62 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 tests/dGameTests/dComponentsTests/InventoryGroupTests.cpp diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index cf6a3d917..645f63816 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -1998,6 +1998,9 @@ void InventoryComponent::UpdateGroup(const GroupUpdate& groupUpdate) { return group.groupId == groupUpdate.groupId; }); + // As LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0): adding a group that exists does nothing + if (groupUpdate.command == GroupUpdateCommand::ADD && groupItr != groups.end()) return; + if (groupUpdate.command != GroupUpdateCommand::ADD && groupItr == groups.end()) { LOG("Group %s not found in inventory %s. Cannot process command.", groupUpdate.groupId.c_str(), StringifiedEnum::ToString(groupUpdate.inventory).data()); return; diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 07543c2fa..f071407b7 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -453,6 +453,7 @@ public: bool SetSkill(BehaviorSlot slot, uint32_t skillId); void UpdateGroup(const GroupUpdate& groupUpdate); + const std::map>& GetGroups() const { return m_Groups; } std::unordered_map& GetPetsMut() { return m_Pets; }; diff --git a/tests/dGameTests/dComponentsTests/CMakeLists.txt b/tests/dGameTests/dComponentsTests/CMakeLists.txt index 2045ca40e..f1307c24f 100644 --- a/tests/dGameTests/dComponentsTests/CMakeLists.txt +++ b/tests/dGameTests/dComponentsTests/CMakeLists.txt @@ -21,6 +21,7 @@ set(DCOMPONENTS_TESTS "ReplicaConstructionTests.cpp" "ReplicaDecoderTests.cpp" "CapturePropertyTests.cpp" + "InventoryGroupTests.cpp" "InventoryMoveTests.cpp" "ItemRemovalTests.cpp" "UseItemResultTests.cpp" diff --git a/tests/dGameTests/dComponentsTests/InventoryGroupTests.cpp b/tests/dGameTests/dComponentsTests/InventoryGroupTests.cpp new file mode 100644 index 000000000..ddbe8dfa7 --- /dev/null +++ b/tests/dGameTests/dComponentsTests/InventoryGroupTests.cpp @@ -0,0 +1,62 @@ +#include "GameDependencies.h" + +#include "CDClientManager.h" +#include "CDComponentsRegistryTable.h" +#include "Entity.h" +#include "InventoryComponent.h" +#include "InventoryMessages.h" + +#include + +// Inventory groups as LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) and +// msgUpdateInventoryGroupContents (0x00ce1f50) keep them. +class InventoryGroupTests : public GameDependenciesTest { +protected: + std::unique_ptr entity; + InventoryComponent* inventory{}; + + void SetUp() override { + SetUpDependencies(); + CDClientManager::GetEntriesMutable().insert_or_assign(static_cast(info.lot), 0); + entity = std::make_unique(1, info); + inventory = entity->AddComponent(-1); + } + + void TearDown() override { + entity.reset(); + TearDownDependencies(); + } + + void Group(const std::string& action, const std::string& id, const std::u16string& name, const eInventoryType type = eInventoryType::BRICKS, const bool locked = false) { + GameMessages::UpdateInventoryGroup msg; + msg.action = action; + msg.groupID = id; + msg.groupName = name; + msg.inventoryType = type; + msg.locked = locked; + inventory->OnUpdateInventoryGroup(msg); + } + + void Contents(const std::string& action, const std::string& id, const LOT lot, const eInventoryType type = eInventoryType::BRICKS) { + GameMessages::UpdateInventoryGroupContents msg; + msg.action = action; + msg.groupID = id; + msg.lot = lot; + msg.inventoryType = type; + inventory->OnUpdateInventoryGroupContents(msg); + } + + const std::vector& Groups(const eInventoryType type = eInventoryType::BRICKS) { + return inventory->GetGroups().at(type); + } +}; + +TEST_F(InventoryGroupTests, AddingAGroupThatExistsDoesNothing) { + Group("ADD", "user_group1", u"first"); + Contents("ADD", "user_group1", 3001); + Group("ADD", "user_group1", u"second"); + + ASSERT_EQ(Groups().size(), 1u); + EXPECT_EQ(Groups()[0].groupName, "first"); + EXPECT_EQ(Groups()[0].lots.size(), 1u); +}