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