From c1765b6b39d8e3ddb6e7031d0ed8fada724df0bc Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 18 Mar 2026 04:25:37 -0700 Subject: [PATCH] fix: defer loot item notification until item name is known from server query When SMSG_ITEM_PUSH_RESULT arrives for an item not yet in the cache, store a PendingItemPushNotif and fire the 'Received: [item]' chat message only after SMSG_ITEM_QUERY_SINGLE_RESPONSE resolves the name and quality, so the notification always shows a proper item link instead of 'item #12345'. Notifications that are already cached emit immediately as before; multiple pending notifs for the same item are all flushed on the single response. --- include/game/game_handler.hpp | 8 ++++++ src/game/game_handler.cpp | 49 ++++++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index a9f69c94..38579ff5 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -2644,6 +2644,14 @@ private: std::unordered_map onlineItems_; std::unordered_map itemInfoCache_; std::unordered_set pendingItemQueries_; + + // Deferred SMSG_ITEM_PUSH_RESULT notifications for items whose info wasn't + // cached at arrival time; emitted once the query response arrives. + struct PendingItemPushNotif { + uint32_t itemId = 0; + uint32_t count = 1; + }; + std::vector pendingItemPushNotifs_; std::array equipSlotGuids_{}; std::array backpackSlotGuids_{}; std::array keyringSlotGuids_{}; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 16393cb0..80a50af0 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1956,22 +1956,22 @@ void GameHandler::handlePacket(network::Packet& packet) { queryItemInfo(itemId, 0); if (showInChat) { - std::string itemName = "item #" + std::to_string(itemId); - uint32_t quality = 1; // white default if (const ItemQueryResponseData* info = getItemInfo(itemId)) { - if (!info->name.empty()) itemName = info->name; - quality = info->quality; - } - std::string link = buildItemLink(itemId, quality, itemName); - std::string msg = "Received: " + link; - if (count > 1) msg += " x" + std::to_string(count); - addSystemChatMessage(msg); - if (auto* renderer = core::Application::getInstance().getRenderer()) { - if (auto* sfx = renderer->getUiSoundManager()) - sfx->playLootItem(); - } - if (itemLootCallback_) { - itemLootCallback_(itemId, count, quality, itemName); + // Item info already cached — emit immediately. + std::string itemName = info->name.empty() ? ("item #" + std::to_string(itemId)) : info->name; + uint32_t quality = info->quality; + std::string link = buildItemLink(itemId, quality, itemName); + std::string msg = "Received: " + link; + if (count > 1) msg += " x" + std::to_string(count); + addSystemChatMessage(msg); + if (auto* renderer = core::Application::getInstance().getRenderer()) { + if (auto* sfx = renderer->getUiSoundManager()) + sfx->playLootItem(); + } + if (itemLootCallback_) itemLootCallback_(itemId, count, quality, itemName); + } else { + // Item info not yet cached; defer until SMSG_ITEM_QUERY_SINGLE_RESPONSE. + pendingItemPushNotifs_.push_back({itemId, count}); } } LOG_INFO("Item push: itemId=", itemId, " count=", count, @@ -14491,6 +14491,25 @@ void GameHandler::handleItemQueryResponse(network::Packet& packet) { rebuildOnlineInventory(); maybeDetectVisibleItemLayout(); + // Flush any deferred loot notifications waiting on this item's name/quality. + for (auto it = pendingItemPushNotifs_.begin(); it != pendingItemPushNotifs_.end(); ) { + if (it->itemId == data.entry) { + std::string itemName = data.name.empty() ? ("item #" + std::to_string(data.entry)) : data.name; + std::string link = buildItemLink(data.entry, data.quality, itemName); + std::string msg = "Received: " + link; + if (it->count > 1) msg += " x" + std::to_string(it->count); + addSystemChatMessage(msg); + if (auto* renderer = core::Application::getInstance().getRenderer()) { + if (auto* sfx = renderer->getUiSoundManager()) + sfx->playLootItem(); + } + if (itemLootCallback_) itemLootCallback_(data.entry, it->count, data.quality, itemName); + it = pendingItemPushNotifs_.erase(it); + } else { + ++it; + } + } + // Selectively re-emit only players whose equipment references this item entry const uint32_t resolvedEntry = data.entry; for (const auto& [guid, entries] : otherPlayerVisibleItemEntries_) {