From 4fbebf7135c1e1be171efe360014cf1a054bc82c Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 2 Oct 2026 09:46:38 -0500 Subject: [PATCH] fix(inventory): an opened package reports success in UseItemResult, as live Item::UseNonEquip declared a second `success` inside the package branch, so the UseItemResult it sent (and its log line) was always false, even when the package opened and its contents were given. The client (LWOInventoryComponent_Client::sub_00c1bb20 (0x00c1bb20)) plays the item's audioEventUse on true and a failure sound on false; live sent true for 183 of the 187 UseItemResult in the captures. Test: UseItemResultTests.AnOpenedPackageReportsSuccess. Check in game: opening a package (e.g. a starter pack or a reward bag) plays its open sound rather than the failure sound. Co-Authored-By: Claude Opus 5.5 --- dGame/dInventory/Item.cpp | 2 +- .../dComponentsTests/CMakeLists.txt | 1 + .../dComponentsTests/UseItemResultTests.cpp | 67 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/dGameTests/dComponentsTests/UseItemResultTests.cpp diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index eeb75ef47..ef03b62cb 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -384,7 +384,7 @@ void Item::UseNonEquip(Item* item) { auto* packCompTable = CDClientManager::GetTable(); auto packages = packCompTable->Query([=](const CDPackageComponent entry) {return entry.id == static_cast(packageComponentId); }); - auto success = !packages.empty(); + success = !packages.empty(); if (success) { if (this->GetPreconditionExpression()->Check(playerInventoryComponent->GetParent())) { auto* entityParent = playerInventoryComponent->GetParent(); diff --git a/tests/dGameTests/dComponentsTests/CMakeLists.txt b/tests/dGameTests/dComponentsTests/CMakeLists.txt index c74ede502..57665c224 100644 --- a/tests/dGameTests/dComponentsTests/CMakeLists.txt +++ b/tests/dGameTests/dComponentsTests/CMakeLists.txt @@ -22,6 +22,7 @@ set(DCOMPONENTS_TESTS "ReplicaDecoderTests.cpp" "CapturePropertyTests.cpp" "ItemRemovalTests.cpp" + "UseItemResultTests.cpp" "LootMetricsTests.cpp" "SpawnerComponentTests.cpp" ) diff --git a/tests/dGameTests/dComponentsTests/UseItemResultTests.cpp b/tests/dGameTests/dComponentsTests/UseItemResultTests.cpp new file mode 100644 index 000000000..8f3af50c3 --- /dev/null +++ b/tests/dGameTests/dComponentsTests/UseItemResultTests.cpp @@ -0,0 +1,67 @@ +#include "GameDependencies.h" + +#include "CDClientManager.h" +#include "CDComponentsRegistryTable.h" +#include "CDItemComponentTable.h" +#include "CDLootMatrixTable.h" +#include "CDPackageComponentTable.h" +#include "Entity.h" +#include "InventoryComponent.h" +#include "InventoryMessages.h" +#include "Item.h" +#include "eReplicaComponentType.h" + +#include "dGameMessagesTests/GameMessageTestUtils.h" + +#include + +using namespace GameMessageTestUtils; + +// Opening a package: the client plays the item's use sound on a successful UseItemResult and a failure sound +// otherwise; live sent true for packages that opened (183 of the 187 in the captures). +class UseItemResultTests : public GameDependenciesTest { +protected: + static constexpr LOT PACKAGE = 2998; + static constexpr uint32_t PACKAGE_COMPONENT = 97000; + static constexpr uint32_t LOOT_MATRIX = 97001; + + std::unique_ptr entity; + InventoryComponent* inventoryComponent{}; + + void SetUp() override { + SetUpDependencies(); + const auto itemComponent = static_cast(96000 + PACKAGE); + auto& registry = CDClientManager::GetEntriesMutable(); + registry.insert_or_assign(static_cast(info.lot), 0); + registry.insert_or_assign(static_cast(PACKAGE), itemComponent); + registry.insert_or_assign(static_cast(eReplicaComponentType::ITEM) << 32 | static_cast(PACKAGE), itemComponent); + registry.insert_or_assign(static_cast(eReplicaComponentType::PACKAGE) << 32 | static_cast(PACKAGE), PACKAGE_COMPONENT); + CDItemComponent component{}; + component.id = itemComponent; + CDClientManager::GetEntriesMutable().insert_or_assign(itemComponent, component); + CDClientManager::GetEntriesMutable().push_back({ PACKAGE_COMPONENT, LOOT_MATRIX, 0 }); + CDClientManager::GetEntriesMutable()[LOOT_MATRIX] = {}; // an empty package + + entity = std::make_unique(1, info); + inventoryComponent = entity->AddComponent(-1); + } + + void TearDown() override { + entity.reset(); + CDClientManager::GetEntriesMutable().clear(); + CDClientManager::GetEntriesMutable().erase(LOOT_MATRIX); + TearDownDependencies(); + } +}; + +TEST_F(UseItemResultTests, AnOpenedPackageReportsSuccess) { + auto* const items = inventoryComponent->GetInventory(eInventoryType::ITEMS); + auto* const package = new Item(0x7000, PACKAGE, items, 0, 2, false, {}, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eLootSourceType::NONE); + + const auto results = SentGameMessages(Capture([&] { package->UseNonEquip(package); })); + + ASSERT_EQ(results.size(), 1u); + EXPECT_EQ(results[0].itemTemplateID, PACKAGE); + EXPECT_TRUE(results[0].useItemResult); + EXPECT_EQ(package->GetCount(), 1u); +}