From 506e841ce060a4859c8a51fb5df04d42a09f46f1 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 19 Feb 2026 02:53:44 -0800 Subject: [PATCH] Handle SMSG_QUESTGIVER_QUEST_LIST (0x185) for questgiver flows --- include/game/game_handler.hpp | 1 + include/game/opcode_table.hpp | 1 + src/game/game_handler.cpp | 91 +++++++++++++++++++++++++++++++++++ src/game/opcode_table.cpp | 2 + 4 files changed, 95 insertions(+) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index b64e9478..652a38b1 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1086,6 +1086,7 @@ private: void handleLootReleaseResponse(network::Packet& packet); void handleLootRemoved(network::Packet& packet); void handleGossipMessage(network::Packet& packet); + void handleQuestgiverQuestList(network::Packet& packet); void handleGossipComplete(network::Packet& packet); void handleQuestDetails(network::Packet& packet); void handleQuestRequestItems(network::Packet& packet); diff --git a/include/game/opcode_table.hpp b/include/game/opcode_table.hpp index 7b11516b..8ba54c6d 100644 --- a/include/game/opcode_table.hpp +++ b/include/game/opcode_table.hpp @@ -256,6 +256,7 @@ enum class LogicalOpcode : uint16_t { SMSG_QUESTGIVER_STATUS, SMSG_QUESTGIVER_STATUS_MULTIPLE, CMSG_QUESTGIVER_HELLO, + SMSG_QUESTGIVER_QUEST_LIST, CMSG_QUESTGIVER_QUERY_QUEST, SMSG_QUESTGIVER_QUEST_DETAILS, CMSG_QUESTGIVER_ACCEPT_QUEST, diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 474e1806..4b97e5f0 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1406,6 +1406,9 @@ void GameHandler::handlePacket(network::Packet& packet) { case Opcode::SMSG_GOSSIP_MESSAGE: handleGossipMessage(packet); break; + case Opcode::SMSG_QUESTGIVER_QUEST_LIST: + handleQuestgiverQuestList(packet); + break; case Opcode::SMSG_BINDPOINTUPDATE: { BindPointUpdateData data; if (BindPointUpdateParser::parse(packet, data)) { @@ -9300,6 +9303,94 @@ void GameHandler::handleGossipMessage(network::Packet& packet) { } } +void GameHandler::handleQuestgiverQuestList(network::Packet& packet) { + if (packet.getSize() - packet.getReadPos() < 8) return; + + GossipMessageData data; + data.npcGuid = packet.readUInt64(); + data.menuId = 0; + data.titleTextId = 0; + + // Server text (header/greeting) and optional emote fields. + std::string header = packet.readString(); + if (packet.getSize() - packet.getReadPos() >= 8) { + (void)packet.readUInt32(); // emoteDelay / unk + (void)packet.readUInt32(); // emote / unk + } + (void)header; + + auto readQuestCount = [&](network::Packet& pkt) -> uint32_t { + size_t rem = pkt.getSize() - pkt.getReadPos(); + if (rem >= 4) { + size_t p = pkt.getReadPos(); + uint32_t c = pkt.readUInt32(); + if (c <= 64) return c; + pkt.setReadPos(p); + } + if (rem >= 1) { + return static_cast(pkt.readUInt8()); + } + return 0; + }; + + uint32_t questCount = readQuestCount(packet); + data.quests.reserve(questCount); + for (uint32_t i = 0; i < questCount; ++i) { + if (packet.getSize() - packet.getReadPos() < 12) break; + GossipQuestItem q; + q.questId = packet.readUInt32(); + q.questIcon = packet.readUInt32(); + q.questLevel = static_cast(packet.readUInt32()); + + // WotLK includes questFlags + isRepeatable; Classic variants may omit. + size_t titlePos = packet.getReadPos(); + if (packet.getSize() - packet.getReadPos() >= 5) { + q.questFlags = packet.readUInt32(); + q.isRepeatable = packet.readUInt8(); + q.title = packet.readString(); + if (q.title.empty()) { + packet.setReadPos(titlePos); + q.questFlags = 0; + q.isRepeatable = 0; + q.title = packet.readString(); + } + } else { + q.questFlags = 0; + q.isRepeatable = 0; + q.title = packet.readString(); + } + if (q.questId != 0) { + data.quests.push_back(std::move(q)); + } + } + + currentGossip = std::move(data); + gossipWindowOpen = true; + vendorWindowOpen = false; + + bool hasAvailableQuest = false; + bool hasRewardQuest = false; + bool hasIncompleteQuest = false; + for (const auto& questItem : currentGossip.quests) { + bool isCompletable = (questItem.questIcon == 5 || questItem.questIcon == 10); + bool isIncomplete = (questItem.questIcon == 3 || questItem.questIcon == 4); + bool isAvailable = (questItem.questIcon == 2 || questItem.questIcon == 7 || questItem.questIcon == 8); + hasAvailableQuest |= isAvailable; + hasRewardQuest |= isCompletable; + hasIncompleteQuest |= isIncomplete; + } + if (currentGossip.npcGuid != 0) { + QuestGiverStatus derivedStatus = QuestGiverStatus::NONE; + if (hasRewardQuest) derivedStatus = QuestGiverStatus::REWARD; + else if (hasAvailableQuest) derivedStatus = QuestGiverStatus::AVAILABLE; + else if (hasIncompleteQuest) derivedStatus = QuestGiverStatus::INCOMPLETE; + npcQuestStatus_[currentGossip.npcGuid] = derivedStatus; + } + + LOG_INFO("Questgiver quest list: npc=0x", std::hex, currentGossip.npcGuid, std::dec, + " quests=", currentGossip.quests.size()); +} + void GameHandler::handleGossipComplete(network::Packet& packet) { (void)packet; diff --git a/src/game/opcode_table.cpp b/src/game/opcode_table.cpp index 8e1208d7..abda728f 100644 --- a/src/game/opcode_table.cpp +++ b/src/game/opcode_table.cpp @@ -207,6 +207,7 @@ static const OpcodeNameEntry kOpcodeNames[] = { {"SMSG_QUESTGIVER_STATUS", LogicalOpcode::SMSG_QUESTGIVER_STATUS}, {"SMSG_QUESTGIVER_STATUS_MULTIPLE", LogicalOpcode::SMSG_QUESTGIVER_STATUS_MULTIPLE}, {"CMSG_QUESTGIVER_HELLO", LogicalOpcode::CMSG_QUESTGIVER_HELLO}, + {"SMSG_QUESTGIVER_QUEST_LIST", LogicalOpcode::SMSG_QUESTGIVER_QUEST_LIST}, {"CMSG_QUESTGIVER_QUERY_QUEST", LogicalOpcode::CMSG_QUESTGIVER_QUERY_QUEST}, {"SMSG_QUESTGIVER_QUEST_DETAILS", LogicalOpcode::SMSG_QUESTGIVER_QUEST_DETAILS}, {"CMSG_QUESTGIVER_ACCEPT_QUEST", LogicalOpcode::CMSG_QUESTGIVER_ACCEPT_QUEST}, @@ -568,6 +569,7 @@ void OpcodeTable::loadWotlkDefaults() { {LogicalOpcode::SMSG_QUESTGIVER_STATUS, 0x183}, {LogicalOpcode::SMSG_QUESTGIVER_STATUS_MULTIPLE, 0x198}, {LogicalOpcode::CMSG_QUESTGIVER_HELLO, 0x184}, + {LogicalOpcode::SMSG_QUESTGIVER_QUEST_LIST, 0x185}, {LogicalOpcode::CMSG_QUESTGIVER_QUERY_QUEST, 0x186}, {LogicalOpcode::SMSG_QUESTGIVER_QUEST_DETAILS, 0x188}, {LogicalOpcode::CMSG_QUESTGIVER_ACCEPT_QUEST, 0x189},