diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index ff253b96..e75fedb5 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1460,6 +1460,9 @@ public: void acceptQuest(); void declineQuest(); void closeGossip(); + // Quest-starting items: right-click triggers quest offer dialog via questgiver protocol + void offerQuestFromItem(uint64_t itemGuid, uint32_t questId); + uint64_t getBagItemGuid(int bagIndex, int slotIndex) const; bool isGossipWindowOpen() const { return gossipWindowOpen; } const GossipMessageData& getCurrentGossip() const { return currentGossip; } bool isQuestDetailsOpen() { diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 40c861d3..c5fe5c5e 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -20552,6 +20552,34 @@ void GameHandler::closeGossip() { currentGossip = GossipMessageData{}; } +void GameHandler::offerQuestFromItem(uint64_t itemGuid, uint32_t questId) { + if (state != WorldState::IN_WORLD || !socket) return; + if (itemGuid == 0 || questId == 0) { + addSystemChatMessage("Cannot start quest right now."); + return; + } + // Send CMSG_QUESTGIVER_QUERY_QUEST with the item GUID as the "questgiver." + // The server responds with SMSG_QUESTGIVER_QUEST_DETAILS which handleQuestDetails() + // picks up and opens the Accept/Decline dialog. + auto queryPkt = packetParsers_ + ? packetParsers_->buildQueryQuestPacket(itemGuid, questId) + : QuestgiverQueryQuestPacket::build(itemGuid, questId); + socket->send(queryPkt); + LOG_INFO("offerQuestFromItem: itemGuid=0x", std::hex, itemGuid, std::dec, + " questId=", questId); +} + +uint64_t GameHandler::getBagItemGuid(int bagIndex, int slotIndex) const { + if (bagIndex < 0 || bagIndex >= inventory.NUM_BAG_SLOTS) return 0; + if (slotIndex < 0) return 0; + uint64_t bagGuid = equipSlotGuids_[19 + bagIndex]; + if (bagGuid == 0) return 0; + auto it = containerContents_.find(bagGuid); + if (it == containerContents_.end()) return 0; + if (slotIndex >= static_cast(it->second.numSlots)) return 0; + return it->second.slotGuids[slotIndex]; +} + void GameHandler::openVendor(uint64_t npcGuid) { if (state != WorldState::IN_WORLD || !socket) return; buybackItems_.clear(); diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index 42c03e8e..083096a7 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -2335,8 +2335,12 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite } else if (kind == SlotKind::BACKPACK && backpackIndex >= 0) { LOG_INFO("Right-click backpack item: name='", item.name, "' inventoryType=", (int)item.inventoryType, - " itemId=", item.itemId); - if (item.inventoryType > 0) { + " itemId=", item.itemId, + " startQuestId=", item.startQuestId); + if (item.startQuestId != 0) { + uint64_t iGuid = gameHandler_->getBackpackItemGuid(backpackIndex); + gameHandler_->offerQuestFromItem(iGuid, item.startQuestId); + } else if (item.inventoryType > 0) { gameHandler_->autoEquipItemBySlot(backpackIndex); } else { gameHandler_->useItemBySlot(backpackIndex); @@ -2344,8 +2348,12 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite } else if (kind == SlotKind::BACKPACK && isBagSlot) { LOG_INFO("Right-click bag item: name='", item.name, "' inventoryType=", (int)item.inventoryType, - " bagIndex=", bagIndex, " slotIndex=", bagSlotIndex); - if (item.inventoryType > 0) { + " bagIndex=", bagIndex, " slotIndex=", bagSlotIndex, + " startQuestId=", item.startQuestId); + if (item.startQuestId != 0) { + uint64_t iGuid = gameHandler_->getBagItemGuid(bagIndex, bagSlotIndex); + gameHandler_->offerQuestFromItem(iGuid, item.startQuestId); + } else if (item.inventoryType > 0) { gameHandler_->autoEquipItemInBag(bagIndex, bagSlotIndex); } else { gameHandler_->useItemInBag(bagIndex, bagSlotIndex);