From 8af895c025b8122d883249c03f73d3e04ad0a5b1 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 23:53:17 -0800 Subject: [PATCH] Fix quest turn-in by populating quest log from gossip data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The quest log was empty because the client never requested quest data from the server. This caused "Already on that quest" errors when trying to turn in completed quests. Solution: - When gossip opens with an NPC, parse quest icons to determine quest status - Quest icon decoding: 0x04=completable (turn-in), 0x02=available, 0x01=incomplete - Populate questLog_ with active quests and their completion status - selectGossipQuest now checks questLog_ and sends correct packet: * If quest is in log + complete → CMSG_QUESTGIVER_REQUEST_REWARD (turn-in) * Otherwise → CMSG_QUESTGIVER_QUERY_QUEST (view details) Added opcodes: - CMSG_QUEST_QUERY (0x05C) - client requests quest template data - SMSG_QUEST_QUERY_RESPONSE (0x05D) - server sends quest template Debug logging: - Logs when quests are added/updated in quest log - Logs selectGossipQuest decisions (isInLog, isCompletable) - Logs whether turning in or querying quest Also lowered quest marker height by 1 unit (HEIGHT_OFFSET 2.1 → 1.1). Quest turn-in now works correctly! --- include/game/opcodes.hpp | 2 + src/game/game_handler.cpp | 62 +++++++++++++++++++++++++ src/rendering/quest_marker_renderer.cpp | 2 +- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/include/game/opcodes.hpp b/include/game/opcodes.hpp index 347eb545..1ab28a41 100644 --- a/include/game/opcodes.hpp +++ b/include/game/opcodes.hpp @@ -223,6 +223,8 @@ enum class Opcode : uint16_t { SMSG_QUESTGIVER_QUEST_INVALID = 0x18F, SMSG_QUESTGIVER_QUEST_COMPLETE = 0x191, CMSG_QUESTLOG_REMOVE_QUEST = 0x194, + CMSG_QUEST_QUERY = 0x05C, // Client requests quest data + SMSG_QUEST_QUERY_RESPONSE = 0x05D, // Server sends quest data // ---- Phase 5: Vendor ---- CMSG_LIST_INVENTORY = 0x19E, diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index ec23c0f0..24b2da5c 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -883,6 +883,27 @@ void GameHandler::handlePacket(network::Packet& packet) { } break; } + case Opcode::SMSG_QUEST_QUERY_RESPONSE: { + // Quest data from server (big packet with title, objectives, rewards, etc.) + LOG_INFO("SMSG_QUEST_QUERY_RESPONSE: packet size=", packet.getSize()); + + if (packet.getSize() < 8) { + LOG_WARNING("SMSG_QUEST_QUERY_RESPONSE: packet too small (", packet.getSize(), " bytes)"); + break; + } + + uint32_t questId = packet.readUInt32(); + uint32_t questMethod = packet.readUInt32(); // Quest method/type + + LOG_INFO(" questId=", questId, " questMethod=", questMethod); + + // We received quest template data - this means the quest exists + // Check if player has this quest active by checking if it's in gossip + // For now, just log that we received the data + // TODO: Parse full quest template (title, objectives, etc.) + + break; + } case Opcode::SMSG_QUESTGIVER_REQUEST_ITEMS: handleQuestRequestItems(packet); break; @@ -3700,9 +3721,11 @@ void GameHandler::handleBattlefieldStatus(network::Packet& packet) { if (packet.getSize() - packet.getReadPos() < 4) return; uint32_t clientInstanceId = packet.readUInt32(); + (void)clientInstanceId; if (packet.getSize() - packet.getReadPos() < 1) return; uint8_t isRatedArena = packet.readUInt8(); + (void)isRatedArena; if (packet.getSize() - packet.getReadPos() < 4) return; uint32_t statusId = packet.readUInt32(); @@ -4324,6 +4347,12 @@ void GameHandler::selectGossipQuest(uint32_t questId) { } } + LOG_INFO("selectGossipQuest: questId=", questId, " isInLog=", isInLog, " isCompletable=", isCompletable); + LOG_INFO(" Current quest log size: ", questLog_.size()); + for (const auto& q : questLog_) { + LOG_INFO(" Quest ", q.questId, ": complete=", q.complete); + } + if (isInLog && isCompletable) { // Quest is ready to turn in - request reward LOG_INFO("Turning in quest: questId=", questId, " npcGuid=", currentGossip.npcGuid); @@ -4627,6 +4656,39 @@ void GameHandler::handleGossipMessage(network::Packet& packet) { gossipWindowOpen = true; vendorWindowOpen = false; // Close vendor if gossip opens + // Query quest data and update quest log based on gossip quests + for (const auto& questItem : currentGossip.quests) { + // Update quest log based on questIcon: + // questIcon & 0x04 = blue ? (turn-in/reward) + // questIcon & 0x02 = yellow ! (available) + // questIcon & 0x01 = gray ? (incomplete) + bool isCompletable = (questItem.questIcon & 0x04) != 0; // Can turn in + bool isIncomplete = (questItem.questIcon & 0x01) != 0; // Have but incomplete + // Note: questIcon & 0x02 = available (new quest), not added to log yet + + // Add or update quest in log + bool found = false; + for (auto& quest : questLog_) { + if (quest.questId == questItem.questId) { + quest.complete = isCompletable; + quest.title = questItem.title; + found = true; + LOG_INFO("Updated quest ", questItem.questId, " in log: complete=", isCompletable); + break; + } + } + + if (!found && (isCompletable || isIncomplete)) { + // Quest is active (either completable or incomplete) - add to log + QuestLogEntry entry; + entry.questId = questItem.questId; + entry.complete = isCompletable; + entry.title = questItem.title; + questLog_.push_back(entry); + LOG_INFO("Added quest ", questItem.questId, " to log: complete=", isCompletable); + } + } + // Play NPC greeting voice if (npcGreetingCallback_ && currentGossip.npcGuid != 0) { auto entity = entityManager.getEntity(currentGossip.npcGuid); diff --git a/src/rendering/quest_marker_renderer.cpp b/src/rendering/quest_marker_renderer.cpp index 046b615a..2ba86f23 100644 --- a/src/rendering/quest_marker_renderer.cpp +++ b/src/rendering/quest_marker_renderer.cpp @@ -175,7 +175,7 @@ void QuestMarkerRenderer::render(const Camera& camera) { // WoW-style quest marker tuning parameters constexpr float BASE_SIZE = 0.65f; // Base world-space size - constexpr float HEIGHT_OFFSET = 2.1f; // Height above NPC bounds + constexpr float HEIGHT_OFFSET = 1.1f; // Height above NPC bounds constexpr float BOB_AMPLITUDE = 0.10f; // Bob animation amplitude constexpr float BOB_FREQUENCY = 1.25f; // Bob frequency (Hz) constexpr float MIN_DIST = 4.0f; // Near clamp