Fix tracker crash and killcredit NPC linking

This commit is contained in:
Xurkon
2026-03-28 17:36:04 -05:00
parent edcd97a0ce
commit 75426c2bb1
4 changed files with 162 additions and 15 deletions
+38 -9
View File
@@ -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<NpcId, SpawnListNPC>?
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
+122 -4
View File
@@ -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
------------------------------------------------------------------------
+1 -1
View File
@@ -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()