fix: correct game-object quest objective handling and item count fallback

- 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)
This commit is contained in:
Kelsi 2026-03-11 00:13:09 -07:00
parent e64b566d72
commit 12aa5e01b6
3 changed files with 44 additions and 6 deletions

View file

@ -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<uint32_t>(
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<uint32_t>(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<uint32_t>(
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};

View file

@ -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),

View file

@ -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);
}