From 12aa5e01b6933e7b330ba4536694345932d4eb4c Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 11 Mar 2026 00:13:09 -0700 Subject: [PATCH] fix: correct game-object quest objective handling and item count fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SMSG_QUESTUPDATE_ADD_KILL: use absolute value of npcOrGoId when looking up required count from killObjectives (negative values = game objects) - applyPackedKillCountsFromFields: same fix — use abs(npcOrGoId) as map key so GO objective counts are stored with the correct entry key - SMSG_QUESTUPDATE_ADD_ITEM: also match quests via itemObjectives when requiredItemCounts is not yet populated (race at quest accept time) - Quest log and minimap sidebar: fall back to GO name cache for entries that return empty from getCachedCreatureName (interact/loot objectives) --- src/game/game_handler.cpp | 34 +++++++++++++++++++++++++++++++--- src/ui/game_screen.cpp | 11 ++++++++--- src/ui/quest_log_screen.cpp | 5 +++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 5f4663e5..ce8eb976 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4157,8 +4157,22 @@ void GameHandler::handlePacket(network::Packet& packet) { if (reqCount == 0) { auto it = quest.killCounts.find(entry); if (it != quest.killCounts.end()) reqCount = it->second.second; - if (reqCount == 0) reqCount = count; } + // Fall back to killObjectives (parsed from SMSG_QUEST_QUERY_RESPONSE). + // Note: npcOrGoId < 0 means game object; server always sends entry as uint32 + // in QUESTUPDATE_ADD_KILL regardless of type, so match by absolute value. + if (reqCount == 0) { + for (const auto& obj : quest.killObjectives) { + if (obj.npcOrGoId == 0 || obj.required == 0) continue; + uint32_t objEntry = static_cast( + obj.npcOrGoId > 0 ? obj.npcOrGoId : -obj.npcOrGoId); + if (objEntry == entry) { + reqCount = obj.required; + break; + } + } + } + if (reqCount == 0) reqCount = count; // last-resort: avoid 0/0 display quest.killCounts[entry] = {count, reqCount}; std::string creatureName = getCachedCreatureName(entry); @@ -4204,9 +4218,20 @@ void GameHandler::handlePacket(network::Packet& packet) { bool updatedAny = false; for (auto& quest : questLog_) { if (quest.complete) continue; - const bool tracksItem = + bool tracksItem = quest.requiredItemCounts.count(itemId) > 0 || quest.itemCounts.count(itemId) > 0; + // Also check itemObjectives parsed from SMSG_QUEST_QUERY_RESPONSE in case + // requiredItemCounts hasn't been populated yet (race during quest accept). + if (!tracksItem) { + for (const auto& obj : quest.itemObjectives) { + if (obj.itemId == itemId && obj.required > 0) { + quest.requiredItemCounts.emplace(itemId, obj.required); + tracksItem = true; + break; + } + } + } if (!tracksItem) continue; quest.itemCounts[itemId] = count; updatedAny = true; @@ -15076,7 +15101,10 @@ void GameHandler::applyPackedKillCountsFromFields(QuestLogEntry& quest) { for (int i = 0; i < 4; ++i) { const auto& obj = quest.killObjectives[i]; if (obj.npcOrGoId == 0 || obj.required == 0) continue; - const uint32_t entryKey = static_cast(obj.npcOrGoId); + // Negative npcOrGoId means game object; use absolute value as the map key + // (SMSG_QUESTUPDATE_ADD_KILL always sends a positive entry regardless of type). + const uint32_t entryKey = static_cast( + obj.npcOrGoId > 0 ? obj.npcOrGoId : -obj.npcOrGoId); // Don't overwrite live kill count with stale packed data if already non-zero. if (counts[i] == 0 && quest.killCounts.count(entryKey)) continue; quest.killCounts[entryKey] = {counts[i], obj.required}; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 94b4deaa..663a031a 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -5048,10 +5048,15 @@ void GameScreen::renderQuestObjectiveTracker(game::GameHandler& gameHandler) { } else { // Kill counts for (const auto& [entry, progress] : q.killCounts) { - std::string creatureName = gameHandler.getCachedCreatureName(entry); - if (!creatureName.empty()) { + std::string name = gameHandler.getCachedCreatureName(entry); + if (name.empty()) { + // May be a game object objective; fall back to GO name cache. + const auto* goInfo = gameHandler.getCachedGameObjectInfo(entry); + if (goInfo && !goInfo->name.empty()) name = goInfo->name; + } + if (!name.empty()) { ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.75f, 1.0f), - " %s: %u/%u", creatureName.c_str(), + " %s: %u/%u", name.c_str(), progress.first, progress.second); } else { ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.75f, 1.0f), diff --git a/src/ui/quest_log_screen.cpp b/src/ui/quest_log_screen.cpp index d524d0c1..8a9ddd55 100644 --- a/src/ui/quest_log_screen.cpp +++ b/src/ui/quest_log_screen.cpp @@ -380,6 +380,11 @@ void QuestLogScreen::render(game::GameHandler& gameHandler) { ImGui::TextColored(ImVec4(0.8f, 0.9f, 1.0f, 1.0f), "Tracked Progress"); for (const auto& [entry, progress] : sel.killCounts) { std::string name = gameHandler.getCachedCreatureName(entry); + if (name.empty()) { + // Game object objective: fall back to GO name cache. + const auto* goInfo = gameHandler.getCachedGameObjectInfo(entry); + if (goInfo && !goInfo->name.empty()) name = goInfo->name; + } if (name.empty()) name = "Unknown (" + std::to_string(entry) + ")"; ImGui::BulletText("%s: %u/%u", name.c_str(), progress.first, progress.second); }