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.
This commit is contained in:
Kelsi 2026-03-18 04:25:37 -07:00
parent 09b0bea981
commit c1765b6b39
2 changed files with 42 additions and 15 deletions

View file

@ -2644,6 +2644,14 @@ private:
std::unordered_map<uint64_t, OnlineItemInfo> onlineItems_; std::unordered_map<uint64_t, OnlineItemInfo> onlineItems_;
std::unordered_map<uint32_t, ItemQueryResponseData> itemInfoCache_; std::unordered_map<uint32_t, ItemQueryResponseData> itemInfoCache_;
std::unordered_set<uint32_t> pendingItemQueries_; std::unordered_set<uint32_t> 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<PendingItemPushNotif> pendingItemPushNotifs_;
std::array<uint64_t, 23> equipSlotGuids_{}; std::array<uint64_t, 23> equipSlotGuids_{};
std::array<uint64_t, 16> backpackSlotGuids_{}; std::array<uint64_t, 16> backpackSlotGuids_{};
std::array<uint64_t, 32> keyringSlotGuids_{}; std::array<uint64_t, 32> keyringSlotGuids_{};

View file

@ -1956,22 +1956,22 @@ void GameHandler::handlePacket(network::Packet& packet) {
queryItemInfo(itemId, 0); queryItemInfo(itemId, 0);
if (showInChat) { if (showInChat) {
std::string itemName = "item #" + std::to_string(itemId);
uint32_t quality = 1; // white default
if (const ItemQueryResponseData* info = getItemInfo(itemId)) { if (const ItemQueryResponseData* info = getItemInfo(itemId)) {
if (!info->name.empty()) itemName = info->name; // Item info already cached — emit immediately.
quality = info->quality; 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 link = buildItemLink(itemId, quality, itemName);
std::string msg = "Received: " + link; std::string msg = "Received: " + link;
if (count > 1) msg += " x" + std::to_string(count); if (count > 1) msg += " x" + std::to_string(count);
addSystemChatMessage(msg); addSystemChatMessage(msg);
if (auto* renderer = core::Application::getInstance().getRenderer()) { if (auto* renderer = core::Application::getInstance().getRenderer()) {
if (auto* sfx = renderer->getUiSoundManager()) if (auto* sfx = renderer->getUiSoundManager())
sfx->playLootItem(); sfx->playLootItem();
} }
if (itemLootCallback_) { if (itemLootCallback_) itemLootCallback_(itemId, count, quality, itemName);
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, LOG_INFO("Item push: itemId=", itemId, " count=", count,
@ -14491,6 +14491,25 @@ void GameHandler::handleItemQueryResponse(network::Packet& packet) {
rebuildOnlineInventory(); rebuildOnlineInventory();
maybeDetectVisibleItemLayout(); 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 // Selectively re-emit only players whose equipment references this item entry
const uint32_t resolvedEntry = data.entry; const uint32_t resolvedEntry = data.entry;
for (const auto& [guid, entries] : otherPlayerVisibleItemEntries_) { for (const auto& [guid, entries] : otherPlayerVisibleItemEntries_) {