From 75426c2bb163bc20f99d031a2f2994a1b43e9286 Mon Sep 17 00:00:00 2001 From: Xurkon Date: Sat, 28 Mar 2026 17:36:04 -0500 Subject: [PATCH] Fix tracker crash and killcredit NPC linking --- Libs/AceComm-3.0/AceComm-3.0.lua | 2 +- Modules/Quest/QuestieQuestPrivates.lua | 47 +++++++-- Modules/QuestieLearner.lua | 126 ++++++++++++++++++++++++- Modules/Tracker/QuestieTracker.lua | 2 +- 4 files changed, 162 insertions(+), 15 deletions(-) diff --git a/Libs/AceComm-3.0/AceComm-3.0.lua b/Libs/AceComm-3.0/AceComm-3.0.lua index 3f21f5b..3fe4a61 100644 --- a/Libs/AceComm-3.0/AceComm-3.0.lua +++ b/Libs/AceComm-3.0/AceComm-3.0.lua @@ -63,7 +63,7 @@ function AceComm:RegisterComm(prefix, method) end if C_ChatInfo then C_ChatInfo.RegisterAddonMessagePrefix(prefix) - else + elseif RegisterAddonMessagePrefix then RegisterAddonMessagePrefix(prefix) end diff --git a/Modules/Quest/QuestieQuestPrivates.lua b/Modules/Quest/QuestieQuestPrivates.lua index ee0451e..183b6f8 100644 --- a/Modules/Quest/QuestieQuestPrivates.lua +++ b/Modules/Quest/QuestieQuestPrivates.lua @@ -65,13 +65,46 @@ _QuestieQuest.objectiveSpawnListCallTable = {} killcredit = function(npcId, objective, objectiveData) ---@type SpawnListNPC[] local ret = {} + local foundValid = false + + -- First pass: try all IDs in IdList for npcIdIndex = 1, #objectiveData.IdList do local killCreditNpcId = objectiveData.IdList[npcIdIndex] - local monsterResult = monster(killCreditNpcId, objective) - if monsterResult then - ret[killCreditNpcId] = monsterResult[killCreditNpcId] + if killCreditNpcId and killCreditNpcId > 0 then + local monsterResult = monster(killCreditNpcId, objective) + if monsterResult then + ret[killCreditNpcId] = monsterResult[killCreditNpcId] + foundValid = true + end end end + + -- Second pass: if no valid NPCs found, try name-based lookup from objective description + -- This helps custom server quests where IdList may have 0 or wrong IDs + if not foundValid and objective and (objective.Description or objective.text) then + local desc = objective.Description or objective.text + local targetName = desc:match("^%d+/%d+%s+(.+)$") or desc:match("^(.-):%s*%d+/%d+$") + if not targetName then + targetName = desc:gsub("%d+/%d+", ""):gsub("%d+", ""):gsub("[:!?,.%(%)]", ""):gsub("^%s+", ""):gsub("%s+$", "") + end + + if targetName and targetName ~= "" then + -- Search for NPC by name using the npcData table + local npcData = QuestieDB.npcData or {} + for searchId, npcRecord in pairs(npcData) do + if npcRecord and npcRecord[1] and string.lower(npcRecord[1]) == string.lower(targetName) then + local monsterResult = monster(searchId, objective) + if monsterResult then + ret[searchId] = monsterResult[searchId] + foundValid = true + Questie:Debug(Questie.DEBUG_DEVELOP, "[killcredit] Found NPC by name fallback:", searchId, targetName) + break + end + end + end + end + end + return ret end @@ -79,12 +112,8 @@ end ---@param objective any ---@return table? monster = function(npcId, objective) - if (not npcId) then - Questie:Error( - "Corrupted objective data handed to objectiveSpawnListCallTable['monster']:", - "'" .. objective.Description .. "' -", - "Please report this error on Discord or GitHub." - ) + if (not npcId) or npcId <= 0 then + Questie:Debug(Questie.DEBUG_CRITICAL, "Invalid NPC ID passed to monster function:", npcId) return nil end diff --git a/Modules/QuestieLearner.lua b/Modules/QuestieLearner.lua index b971d78..b927661 100644 --- a/Modules/QuestieLearner.lua +++ b/Modules/QuestieLearner.lua @@ -1546,16 +1546,48 @@ function QuestieLearner:OnQuestAccepted(firstArg, secondArg) local numObj = GetNumQuestLeaderBoards and GetNumQuestLeaderBoards(logIdx) or 0 for j = 1, numObj do local objText, objType, finished = GetQuestLogLeaderBoard(j, logIdx) - if objText and not finished and objType == "monster" then - -- Try to extract "Boar" from "0/10 Boar Slain" + if objText and not finished and (objType == "monster" or objType == "killcredit") then local targetName = objText:match("^%d+/%d+%s+(.+)%s*") or objText:match("^(.+):%s*%d+/%d+") if not targetName then - -- Fallback: strip everything that looks like a count or punctuation targetName = objText:gsub("%d+/%d+", ""):gsub("%d+", ""):gsub("[:!?,.%(%)]", ""):gsub("^%s+", ""):gsub("%s+$", "") end if targetName and targetName ~= "" then - local npcId = self:GetNPCIdByName(targetName) + local npcId = nil + -- For killcredit, try ID-based lookup first using quest objectives data + if objType == "killcredit" then + local quest = QuestieDB and QuestieDB.GetQuest and QuestieDB.GetQuest(questId) + if quest and quest.ObjectiveData and quest.ObjectiveData[j] then + local objData = quest.ObjectiveData[j] + if objData.IdList then + for _, possibleId in ipairs(objData.IdList) do + if possibleId and possibleId > 0 then + local npc = QuestieDB:GetNPC(possibleId) + if npc and npc.name and string.lower(npc.name) == string.lower(targetName) then + npcId = possibleId + break + end + end + end + -- Fallback: try first valid ID in the list even if name doesn't match + if not npcId then + for _, possibleId in ipairs(objData.IdList) do + if possibleId and possibleId > 0 then + local npc = QuestieDB:GetNPC(possibleId) + if npc then + npcId = possibleId + break + end + end + end + end + end + end + end + -- Fallback to name-based lookup + if not npcId then + npcId = self:GetNPCIdByName(targetName) + end if npcId then Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] Proactively mapped objective", j, "to NPC", npcId, "(" .. targetName .. ")") self:LearnQuestObjectiveNPC(questId, npcId, objText, j) @@ -1953,9 +1985,95 @@ function QuestieLearner:Initialize() self:PruneGuidNpcCache() end) + -- Scan existing quests in log after initialization (deferred to ensure DB is ready) + QuestieCompat.C_Timer.After(1, function() + self:ScanExistingQuestLog() + end) + Questie:Debug(Questie.DEBUG_INFO, "[QuestieLearner] Initialized") end +function QuestieLearner:ScanExistingQuestLog() + if not self:IsEnabled() then return end + if not Questie.dbLearner.global.settings.learnQuests then return end + if not GetNumQuestLogEntries then return end + + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieLearner] Scanning existing quest log...") + local count = 0 + + for i = 1, GetNumQuestLogEntries() do + local title, level, _, isHeader, _, _, _, questId = QuestieCompat.GetQuestLogTitle(i) + if not isHeader and questId and questId > 0 then + -- Check if this quest needs objective mapping + local existingData = Questie.dbLearner.global.quests[questId] + local needsMapping = not existingData or not existingData.objIndex or not next(existingData.objIndex) + + if needsMapping then + -- Use the existing OnQuestAccepted logic by manually triggering objective mapping + local logIdx = i + local numObj = GetNumQuestLeaderBoards and GetNumQuestLeaderBoards(logIdx) or 0 + for j = 1, numObj do + local objText, objType, finished = GetQuestLogLeaderBoard(j, logIdx) + if objText and not finished and (objType == "monster" or objType == "killcredit") then + local targetName = objText:match("^%d+/%d+%s+(.+)%s*") or objText:match("^(.+):%s*%d+/%d+") + if not targetName then + targetName = objText:gsub("%d+/%d+", ""):gsub("%d+", ""):gsub("[:!?,.%(%)]", ""):gsub("^%s+", ""):gsub("%s+$", "") + end + + if targetName and targetName ~= "" then + local npcId = nil + -- For killcredit, try ID-based lookup first + if objType == "killcredit" then + local quest = QuestieDB and QuestieDB.GetQuest and QuestieDB.GetQuest(questId) + if quest and quest.ObjectiveData and quest.ObjectiveData[j] then + local objData = quest.ObjectiveData[j] + if objData.IdList then + for _, possibleId in ipairs(objData.IdList) do + if possibleId and possibleId > 0 then + local npc = QuestieDB:GetNPC(possibleId) + if npc and npc.name and string.lower(npc.name) == string.lower(targetName) then + npcId = possibleId + break + end + end + end + -- Fallback: try first valid ID in the list + if not npcId then + for _, possibleId in ipairs(objData.IdList) do + if possibleId and possibleId > 0 then + local npc = QuestieDB:GetNPC(possibleId) + if npc then + npcId = possibleId + break + end + end + end + end + end + end + end + -- Fallback to name-based lookup + if not npcId then + npcId = self:GetNPCIdByName(targetName) + end + if npcId then + Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] Scanned existing quest", questId, "objective", j, "to NPC", npcId, "(" .. targetName .. ")") + self:LearnQuestObjectiveNPC(questId, npcId, objText, j) + count = count + 1 + end + end + end + end + end + end + end + + if count > 0 then + self:InjectLearnedData() + Questie:Debug(Questie.DEBUG_INFO, "[QuestieLearner] Scanned existing quest log, mapped", count, "objectives") + end +end + ------------------------------------------------------------------------ -- Network bridge ------------------------------------------------------------------------ diff --git a/Modules/Tracker/QuestieTracker.lua b/Modules/Tracker/QuestieTracker.lua index 3684251..6c60c43 100644 --- a/Modules/Tracker/QuestieTracker.lua +++ b/Modules/Tracker/QuestieTracker.lua @@ -1230,7 +1230,7 @@ function QuestieTracker:Update() -- Add incomplete Quest Objectives if complete == 0 and quest.isComplete ~= true then for _, objective in pairs(quest.Objectives) do - if (not Questie.db.profile.hideCompletedQuestObjectives or (Questie.db.profile.hideCompletedQuestObjectives and objective.Needed ~= objective.Collected)) then + if objective and (not Questie.db.profile.hideCompletedQuestObjectives or (Questie.db.profile.hideCompletedQuestObjectives and objective.Needed ~= objective.Collected)) then -- Get next line in linePool line = TrackerLinePool.GetNextLine()