From 62b7622f751ed3cb6a11182464a8683ebad94685 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 17:05:04 -0700 Subject: [PATCH] feat: parse and display StartQuest field from item query response Items that begin a quest (like quest starter drop items) now show "Begins a Quest" in the tooltip. All three expansion parsers (WotLK/TBC/Classic) now read the PageText/LanguageID/PageMaterial/StartQuest fields after Description. startQuestId is propagated through all 5 inventory rebuild paths and stored in ItemDef. --- include/game/inventory.hpp | 1 + include/game/world_packets.hpp | 1 + src/game/game_handler.cpp | 5 +++++ src/game/packet_parsers_classic.cpp | 8 ++++++++ src/game/packet_parsers_tbc.cpp | 8 ++++++++ src/game/world_packets.cpp | 8 ++++++++ src/ui/inventory_screen.cpp | 5 +++++ 7 files changed, 36 insertions(+) diff --git a/include/game/inventory.hpp b/include/game/inventory.hpp index fcd50c3f..7a3bcb8c 100644 --- a/include/game/inventory.hpp +++ b/include/game/inventory.hpp @@ -56,6 +56,7 @@ struct ItemDef { // Generic stat pairs for non-primary stats (hit, crit, haste, AP, SP, etc.) struct ExtraStat { uint32_t statType = 0; int32_t statValue = 0; }; std::vector extraStats; + uint32_t startQuestId = 0; // Non-zero: item begins a quest }; struct ItemSlot { diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index eef6dba5..786cab8b 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1566,6 +1566,7 @@ struct ItemQueryResponseData { // Generic stat pairs for non-primary stats (hit, crit, haste, AP, SP, etc.) struct ExtraStat { uint32_t statType = 0; int32_t statValue = 0; }; std::vector extraStats; + uint32_t startQuestId = 0; // Non-zero: item begins a quest bool valid = false; }; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 8677e9b5..a8841074 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -10763,6 +10763,7 @@ void GameHandler::rebuildOnlineInventory() { def.requiredLevel = infoIt->second.requiredLevel; def.bindType = infoIt->second.bindType; def.description = infoIt->second.description; + def.startQuestId = infoIt->second.startQuestId; def.extraStats.clear(); for (const auto& es : infoIt->second.extraStats) def.extraStats.push_back({es.statType, es.statValue}); @@ -10811,6 +10812,7 @@ void GameHandler::rebuildOnlineInventory() { def.requiredLevel = infoIt->second.requiredLevel; def.bindType = infoIt->second.bindType; def.description = infoIt->second.description; + def.startQuestId = infoIt->second.startQuestId; def.extraStats.clear(); for (const auto& es : infoIt->second.extraStats) def.extraStats.push_back({es.statType, es.statValue}); @@ -10894,6 +10896,7 @@ void GameHandler::rebuildOnlineInventory() { def.requiredLevel = infoIt->second.requiredLevel; def.bindType = infoIt->second.bindType; def.description = infoIt->second.description; + def.startQuestId = infoIt->second.startQuestId; def.extraStats.clear(); for (const auto& es : infoIt->second.extraStats) def.extraStats.push_back({es.statType, es.statValue}); @@ -10943,6 +10946,7 @@ void GameHandler::rebuildOnlineInventory() { def.requiredLevel = infoIt->second.requiredLevel; def.bindType = infoIt->second.bindType; def.description = infoIt->second.description; + def.startQuestId = infoIt->second.startQuestId; def.extraStats.clear(); for (const auto& es : infoIt->second.extraStats) def.extraStats.push_back({es.statType, es.statValue}); @@ -11034,6 +11038,7 @@ void GameHandler::rebuildOnlineInventory() { def.sellPrice = infoIt->second.sellPrice; def.bindType = infoIt->second.bindType; def.description = infoIt->second.description; + def.startQuestId = infoIt->second.startQuestId; def.extraStats.clear(); for (const auto& es : infoIt->second.extraStats) def.extraStats.push_back({es.statType, es.statValue}); diff --git a/src/game/packet_parsers_classic.cpp b/src/game/packet_parsers_classic.cpp index 9cf0d570..35dc54f4 100644 --- a/src/game/packet_parsers_classic.cpp +++ b/src/game/packet_parsers_classic.cpp @@ -1331,6 +1331,14 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ if (packet.getReadPos() < packet.getSize()) data.description = packet.readString(); + // Post-description: PageText, LanguageID, PageMaterial, StartQuest + if (packet.getReadPos() + 16 <= packet.getSize()) { + packet.readUInt32(); // PageText + packet.readUInt32(); // LanguageID + packet.readUInt32(); // PageMaterial + data.startQuestId = packet.readUInt32(); // StartQuest + } + data.valid = !data.name.empty(); LOG_DEBUG("[Classic] Item query response: ", data.name, " (quality=", data.quality, " invType=", data.inventoryType, " stack=", data.maxStack, ")"); diff --git a/src/game/packet_parsers_tbc.cpp b/src/game/packet_parsers_tbc.cpp index 44626ce2..ebe467ea 100644 --- a/src/game/packet_parsers_tbc.cpp +++ b/src/game/packet_parsers_tbc.cpp @@ -991,6 +991,14 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery if (packet.getReadPos() < packet.getSize()) data.description = packet.readString(); + // Post-description: PageText, LanguageID, PageMaterial, StartQuest + if (packet.getReadPos() + 16 <= packet.getSize()) { + packet.readUInt32(); // PageText + packet.readUInt32(); // LanguageID + packet.readUInt32(); // PageMaterial + data.startQuestId = packet.readUInt32(); // StartQuest + } + data.valid = !data.name.empty(); LOG_DEBUG("[TBC] Item query: ", data.name, " quality=", data.quality, " invType=", data.inventoryType, " armor=", data.armor); diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 4668d821..710ad501 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2529,6 +2529,14 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa if (packet.getReadPos() < packet.getSize()) data.description = packet.readString(); + // Post-description fields: PageText, LanguageID, PageMaterial, StartQuest + if (packet.getReadPos() + 16 <= packet.getSize()) { + packet.readUInt32(); // PageText + packet.readUInt32(); // LanguageID + packet.readUInt32(); // PageMaterial + data.startQuestId = packet.readUInt32(); // StartQuest + } + data.valid = !data.name.empty(); return true; } diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index 66614e6d..8567c3ce 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -1918,6 +1918,11 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I } } + // "Begins a Quest" line (shown in yellow-green like the game) + if (item.startQuestId != 0) { + ImGui::TextColored(ImVec4(1.0f, 0.82f, 0.0f, 1.0f), "Begins a Quest"); + } + // Flavor / lore text (italic yellow in WoW, just yellow here) if (!item.description.empty()) { ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.5f, 0.9f), "\"%s\"", item.description.c_str());