fix: correct learner npc and item learning ids
This commit is contained in:
+60
-21
@@ -2770,10 +2770,31 @@ local HEX_PREFIXES = {
|
||||
|
||||
local CREATURE_HEX_PREFIXES = { ["F130"]=true, ["F131"]=true, ["F110"]=true, ["F111"]=true }
|
||||
|
||||
local function _GetDashGuidField(guid, index)
|
||||
if not guid or type(guid) ~= "string" or index <= 0 then return nil end
|
||||
local field = 1
|
||||
local startPos = 1
|
||||
while true do
|
||||
local sepStart, sepEnd = string.find(guid, "-", startPos, true)
|
||||
if not sepStart then
|
||||
if field == index then
|
||||
return string.sub(guid, startPos)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
if field == index then
|
||||
return string.sub(guid, startPos, sepStart - 1)
|
||||
end
|
||||
field = field + 1
|
||||
startPos = sepEnd + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function GetIdAndTypeFromGUID(guid)
|
||||
if not guid then return nil, nil end
|
||||
-- Modern dash-separated GUID (e.g. "Creature-0-3726-0-189-5638296-...")
|
||||
local unitType, _, _, _, _, parsedId = strsplit("-", guid)
|
||||
local unitType = _GetDashGuidField(guid, 1)
|
||||
local parsedId = _GetDashGuidField(guid, 6)
|
||||
local id = tonumber(parsedId)
|
||||
if id and id > 0 and unitType then
|
||||
return id, unitType
|
||||
@@ -2901,8 +2922,8 @@ function QuestieLearner:OnMouseoverUnit()
|
||||
if guid == _Learner._lastMouseoverGuid then return end
|
||||
_Learner._lastMouseoverGuid = guid
|
||||
|
||||
local entityId, unitType = GetIdAndTypeFromGUID(guid)
|
||||
local name = UnitName("mouseover")
|
||||
local entityId, unitType = self:ResolveNpcIdFromGuidAndName(guid, name)
|
||||
TraceLearnerEntity("mouseover", guid, unitType, entityId, name)
|
||||
|
||||
if not entityId or entityId <= 0 then return end
|
||||
@@ -2969,7 +2990,7 @@ function QuestieLearner:OnTargetChanged()
|
||||
_Learner._lastTargetGuid = guid
|
||||
|
||||
local name = UnitName("target")
|
||||
local entityId, unitType = GetIdAndTypeFromGUID(guid)
|
||||
local entityId, unitType = self:ResolveNpcIdFromGuidAndName(guid, name)
|
||||
TraceLearnerEntity("target", guid, unitType, entityId, name)
|
||||
|
||||
if not entityId or entityId <= 0 then return end
|
||||
@@ -3011,7 +3032,7 @@ function QuestieLearner:OnQuestDetail()
|
||||
-- Identify the quest giver NPC or object
|
||||
local npcGuid = UnitGUID("npc")
|
||||
if npcGuid then
|
||||
local entityId, unitType = GetIdAndTypeFromGUID(npcGuid)
|
||||
local entityId, unitType = self:ResolveNpcIdFromGuidAndName(npcGuid, UnitName("npc"))
|
||||
TraceLearnerEntity("quest_detail", npcGuid, unitType, entityId, UnitName("npc"))
|
||||
if entityId and entityId > 0 then
|
||||
local entityName = UnitName("npc")
|
||||
@@ -3044,7 +3065,7 @@ function QuestieLearner:OnQuestComplete()
|
||||
-- Identify the quest turn-in NPC or object
|
||||
local npcGuid = UnitGUID("npc")
|
||||
if npcGuid then
|
||||
local entityId, unitType = GetIdAndTypeFromGUID(npcGuid)
|
||||
local entityId, unitType = self:ResolveNpcIdFromGuidAndName(npcGuid, UnitName("npc"))
|
||||
TraceLearnerEntity("quest_complete", npcGuid, unitType, entityId, UnitName("npc"))
|
||||
if entityId and entityId > 0 then
|
||||
local entityName = UnitName("npc")
|
||||
@@ -3072,6 +3093,30 @@ function QuestieLearner:GetNPCIdByName(npcName)
|
||||
return index.base[lowerName]
|
||||
end
|
||||
|
||||
function QuestieLearner:ResolveNpcIdFromGuidAndName(guid, npcName)
|
||||
local entityId, unitType = GetIdAndTypeFromGUID(guid)
|
||||
if not npcName or npcName == "" then
|
||||
return entityId, unitType
|
||||
end
|
||||
|
||||
local namedId = self:GetNPCIdByName(npcName)
|
||||
if not namedId or namedId <= 0 then
|
||||
return entityId, unitType
|
||||
end
|
||||
|
||||
if entityId and entityId > 0 and entityId ~= namedId then
|
||||
local parsedNpc = QuestieDB and QuestieDB.GetNPC and QuestieDB:GetNPC(entityId)
|
||||
local parsedName = parsedNpc and parsedNpc.name
|
||||
if not parsedName or string.lower(parsedName) ~= string.lower(npcName) then
|
||||
return namedId, unitType
|
||||
end
|
||||
elseif not entityId or entityId <= 0 then
|
||||
return namedId, unitType
|
||||
end
|
||||
|
||||
return entityId, unitType
|
||||
end
|
||||
|
||||
local function ResolveAcceptedQuestId(firstArg, secondArg)
|
||||
local maxLog = GetNumQuestLogEntries and GetNumQuestLogEntries() or 25
|
||||
|
||||
@@ -3244,7 +3289,7 @@ function QuestieLearner:OnQuestAccepted(firstArg, secondArg)
|
||||
local npcGuid = UnitGUID("npc")
|
||||
local giverEntity = nil
|
||||
if npcGuid then
|
||||
local entityId, unitType = GetIdAndTypeFromGUID(npcGuid)
|
||||
local entityId, unitType = self:ResolveNpcIdFromGuidAndName(npcGuid, UnitName("npc"))
|
||||
if entityId and entityId > 0 then
|
||||
giverEntity = { id = entityId, name = UnitName("npc"), unitType = unitType }
|
||||
end
|
||||
@@ -3275,7 +3320,7 @@ function QuestieLearner:OnQuestTurnedIn(questId, xpReward, moneyReward)
|
||||
-- Capture turn-in NPC/object while the gossip unit is still set
|
||||
local npcGuid = UnitGUID("npc")
|
||||
if npcGuid then
|
||||
local entityId, unitType = GetIdAndTypeFromGUID(npcGuid)
|
||||
local entityId, unitType = self:ResolveNpcIdFromGuidAndName(npcGuid, UnitName("npc"))
|
||||
if entityId and entityId > 0 then
|
||||
local entityName = UnitName("npc")
|
||||
if unitType == "GameObject" then
|
||||
@@ -3300,7 +3345,7 @@ function QuestieLearner:OnLootOpened()
|
||||
local targetId, targetType = nil, nil
|
||||
local npcId = nil
|
||||
if targetGuid then
|
||||
targetId, targetType = GetIdAndTypeFromGUID(targetGuid)
|
||||
targetId, targetType = self:ResolveNpcIdFromGuidAndName(targetGuid, UnitName("target"))
|
||||
TraceLearnerEntity("loot_target", targetGuid, targetType, targetId, UnitName("target"))
|
||||
if targetType == "GameObject" and targetId and targetId > 0 then
|
||||
self:LearnObject(targetId, UnitName("target"))
|
||||
@@ -3351,11 +3396,8 @@ function QuestieLearner:OnLootOpened()
|
||||
if itemId and itemId > 0 then
|
||||
local itemName, _, _, itemLevel, requiredLevel, _, _, _, _, _, _, itemClassId, itemSubClassId = GetItemInfo(link)
|
||||
if itemName then
|
||||
-- Only record quest items (class 12)
|
||||
if itemClassId == 12 then
|
||||
self:LearnItem(itemId, itemName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
|
||||
if npcId then self:LearnItemDrop(itemId, npcId) end
|
||||
end
|
||||
self:LearnItem(itemId, itemName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
|
||||
if itemClassId == 12 and npcId then self:LearnItemDrop(itemId, npcId) end
|
||||
else
|
||||
-- GetItemInfo returned nil; queue for retry (class check happens on retry)
|
||||
table.insert(_Learner.pendingItemLinks, { link = link, itemId = itemId, npcId = npcId })
|
||||
@@ -3395,7 +3437,7 @@ function QuestieLearner:OnGossipShow()
|
||||
local npcGuid = UnitGUID("npc")
|
||||
if not npcGuid then return end
|
||||
|
||||
local id, unitType = GetIdAndTypeFromGUID(npcGuid)
|
||||
local id, unitType = self:ResolveNpcIdFromGuidAndName(npcGuid, UnitName("npc"))
|
||||
if not id or id <= 0 then return end
|
||||
|
||||
local name = UnitName("npc")
|
||||
@@ -3414,7 +3456,7 @@ end
|
||||
function QuestieLearner:LearnSpellCast(spellId, spellName, dstGUID, dstName)
|
||||
if not spellId or not spellName then return end
|
||||
|
||||
local npcId = dstGUID and GetNpcIdFromGUID(dstGUID)
|
||||
local npcId = dstGUID and self:ResolveNpcIdFromGuidAndName(dstGUID, dstName)
|
||||
local objId = dstGUID and GetObjectIdFromGUID(dstGUID)
|
||||
|
||||
-- Check if this spell is a quest objective
|
||||
@@ -3451,11 +3493,8 @@ function QuestieLearner:OnGetItemInfoReceived(itemId)
|
||||
if entry.itemId == itemId then
|
||||
local itemName, _, _, itemLevel, requiredLevel, _, _, _, _, _, _, itemClassId, itemSubClassId = GetItemInfo(entry.link)
|
||||
if itemName then
|
||||
-- Only record quest items (class 12)
|
||||
if itemClassId == 12 then
|
||||
self:LearnItem(itemId, itemName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
|
||||
if entry.npcId then self:LearnItemDrop(itemId, entry.npcId) end
|
||||
end
|
||||
self:LearnItem(itemId, itemName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
|
||||
if itemClassId == 12 and entry.npcId then self:LearnItemDrop(itemId, entry.npcId) end
|
||||
else
|
||||
table.insert(remaining, entry) -- still not cached, keep
|
||||
end
|
||||
@@ -3600,7 +3639,7 @@ function QuestieLearner:OnCombatLogEvent(timestamp, eventType, srcGUID, srcName,
|
||||
end
|
||||
end
|
||||
|
||||
local npcId = GetNpcIdFromGUID(dstGUID)
|
||||
local npcId = self:ResolveNpcIdFromGuidAndName(dstGUID, dstName)
|
||||
local name = dstName
|
||||
|
||||
-- Fallback chain for mob name: combat-log dstName → cached target/mouseover → current target unit
|
||||
|
||||
@@ -212,6 +212,93 @@ describe("QuestieLearner quest accept resolution", function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("QuestieLearner GUID and loot learning", function()
|
||||
local QuestieLearner
|
||||
local originalGetNPC
|
||||
local originalGetItemInfo
|
||||
local originalLearnItem
|
||||
local originalLearnItemDrop
|
||||
|
||||
before_each(function()
|
||||
dofile("Tests/wow_api_mock.lua")
|
||||
Questie.dbLearner.global.settings.enabled = true
|
||||
Questie.dbLearner.global.settings.dataSourceMode = "learner"
|
||||
Questie.dbLearner.global.settings.learnItems = true
|
||||
Questie.dbLearner.global.settings.learnNpcs = true
|
||||
|
||||
QuestieDB.npcData = {
|
||||
[15297] = { [1] = "Arcanist Helion" },
|
||||
}
|
||||
originalGetNPC = QuestieDB.GetNPC
|
||||
QuestieDB.GetNPC = function(self, id)
|
||||
if id == 168 then
|
||||
return { name = "Something Else" }
|
||||
end
|
||||
return originalGetNPC and originalGetNPC(self, id) or nil
|
||||
end
|
||||
|
||||
originalGetItemInfo = _G.GetItemInfo
|
||||
_G.GetItemInfo = function(link)
|
||||
if link == "item:20470" then
|
||||
return "Quest Token", nil, nil, 1, 1, 3, 1, nil, nil, nil, nil, 3, 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
dofile("Modules/QuestieLearner.lua")
|
||||
QuestieLearner = _G.QuestieLearner
|
||||
originalLearnItem = QuestieLearner.LearnItem
|
||||
originalLearnItemDrop = QuestieLearner.LearnItemDrop
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
QuestieDB.GetNPC = originalGetNPC
|
||||
_G.GetItemInfo = originalGetItemInfo
|
||||
if QuestieLearner then
|
||||
QuestieLearner.LearnItem = originalLearnItem
|
||||
QuestieLearner.LearnItemDrop = originalLearnItemDrop
|
||||
end
|
||||
end)
|
||||
|
||||
it("prefers the exact NPC name over a mismatched GUID entry id", function()
|
||||
local resolvedId, unitType = QuestieLearner:ResolveNpcIdFromGuidAndName("Creature-0-0-0-0-168-0000000000", "Arcanist Helion")
|
||||
assert.equals(15297, resolvedId)
|
||||
assert.equals("Creature", unitType)
|
||||
end)
|
||||
|
||||
it("learns pending loot items once item info arrives even when class metadata is not quest-item shaped", function()
|
||||
local learnedItemId = nil
|
||||
local dropItemId = nil
|
||||
local dropNpcId = nil
|
||||
|
||||
QuestieLearner.LearnItem = function(self, itemId, name, itemLevel, requiredLevel, itemClassId, itemSubClassId)
|
||||
learnedItemId = itemId
|
||||
end
|
||||
QuestieLearner.LearnItemDrop = function(self, itemId, npcId)
|
||||
dropItemId = itemId
|
||||
dropNpcId = npcId
|
||||
end
|
||||
|
||||
_G.GetNumLootItems = function()
|
||||
return 1
|
||||
end
|
||||
_G.GetLootSlotInfo = function(slot)
|
||||
return nil, "Quest Token", nil, nil, 1
|
||||
end
|
||||
_G.GetLootSlotLink = function(slot)
|
||||
return "item:20470"
|
||||
end
|
||||
_G.GetLootSourceInfo = function(slot)
|
||||
return "Creature-0-0-0-0-15297-0000000000", 1
|
||||
end
|
||||
|
||||
QuestieLearner:OnLootOpened()
|
||||
|
||||
assert.equals(20470, learnedItemId)
|
||||
assert.is_nil(dropItemId)
|
||||
assert.is_nil(dropNpcId)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("QuestieDB learner source fallback", function()
|
||||
before_each(function()
|
||||
dofile("Tests/wow_api_mock.lua")
|
||||
|
||||
Reference in New Issue
Block a user