fix: restore learner questgiver and item drop learning

This commit is contained in:
Xurkon
2026-06-06 07:56:29 -05:00
parent cccfa1e81b
commit 36668338f5
3 changed files with 48 additions and 39 deletions
+14 -22
View File
@@ -1396,11 +1396,17 @@ function QuestieLearner:LearnNPC(npcId, name, level, subName, npcFlags, factionS
if zoneId and zoneId > 0 and not existing[9] then existing[9] = zoneId end
if factionString and not existing[13] then existing[13] = factionString end
if subName and not existing[14] then existing[14] = subName end
if spawnX and spawnY and x and y and zoneId and zoneId > 0 then
if x and y and zoneId and zoneId > 0 then
existing[7] = existing[7] or {}
existing[7][zoneId] = existing[7][zoneId] or {}
InsertIfNewBucket(existing[7][zoneId], x, y, GetCoordGridForZone(zoneId))
existing.spawnSource = "explicit"
if spawnX and spawnY then
existing.spawnSource = "explicit"
elseif existing.spawnSource ~= "learned" then
-- Quest-giver/turn-in fallback learning uses the player's position as a
-- proxy when the entity is opened from gossip without a reliable spawn.
existing.spawnSource = "fallback"
end
elseif existing.spawnSource ~= "explicit" and existing.spawnSource ~= "learned" then
-- Quest-giver/turn-in fallback learning uses the player's position as a proxy.
-- Keep that separate from actual learned spawn evidence so we can safely
@@ -2483,24 +2489,10 @@ function QuestieLearner:InjectLearnedData()
Questie:Debug(Questie.DEBUG_INFO, "[QuestieLearner] Purged", purgedNpcs, "invalid NPCs from learned data")
end
-- Versioned cleanup: strip spawns from NPCs learned via quest giver/finisher
-- fallback (player position stored as spawn by LearnNPC). Real kill/object
-- evidence must never be removed here, even when it only has one location.
local fallbackSpawnCleanupVersion = 2
if (learned._cleanedFallbackSpawnsVersion or 0) < fallbackSpawnCleanupVersion then
learned._cleanedFallbackSpawns = true
learned._cleanedFallbackSpawnsVersion = fallbackSpawnCleanupVersion
local stripped = 0
for npcId, data in pairs(learned.npcs) do
if data.spawnSource == "fallback" and data[7] and CountUniqueSpawnPositions(data[7]) <= 1 then
data[7] = nil
stripped = stripped + 1
end
end
if stripped > 0 then
Questie:Debug(Questie.DEBUG_INFO, "[QuestieLearner] Stripped fallback spawns from", stripped, "NPCs")
end
end
-- Keep fallback questgiver/turn-in spawn evidence intact so learner-only
-- mode can still render ? / ! quest icons without depending on static DB
-- coordinates. The explicit/learned spawn paths are already isolated by
-- spawnSource and the real kill/object evidence now carries its own tag.
-- Purge Object entries that duplicate NPC entries (mobs learned as both NPC and Object).
-- NPC data is richer (has names, quest IDs), so keep the NPC version and remove the Object.
@@ -3459,7 +3451,7 @@ function QuestieLearner:OnLootOpened()
local itemName, _, _, itemLevel, requiredLevel, _, _, _, _, _, _, itemClassId, itemSubClassId = GetItemInfo(link)
if itemName then
self:LearnItem(itemId, itemName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
if itemClassId == 12 and npcId then self:LearnItemDrop(itemId, npcId) end
if 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 })
@@ -3556,7 +3548,7 @@ function QuestieLearner:OnGetItemInfoReceived(itemId)
local itemName, _, _, itemLevel, requiredLevel, _, _, _, _, _, _, itemClassId, itemSubClassId = GetItemInfo(entry.link)
if itemName then
self:LearnItem(itemId, itemName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
if itemClassId == 12 and entry.npcId then self:LearnItemDrop(itemId, entry.npcId) end
if entry.npcId then self:LearnItemDrop(itemId, entry.npcId) end
else
table.insert(remaining, entry) -- still not cached, keep
end
+5 -17
View File
@@ -265,7 +265,7 @@ describe("QuestieLearner GUID and loot learning", function()
assert.equals("Creature", unitType)
end)
it("learns pending loot items once item info arrives even when class metadata is not quest-item shaped", function()
it("learns loot item drop sources even when class metadata is not quest-item shaped", function()
local learnedItemId = nil
local dropItemId = nil
local dropNpcId = nil
@@ -278,24 +278,12 @@ describe("QuestieLearner GUID and loot learning", function()
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()
QuestieLearner:LearnItem(20470, "Quest Token", 1, 1, 1, 0)
QuestieLearner:LearnItemDrop(20470, 15297)
assert.equals(20470, learnedItemId)
assert.is_nil(dropItemId)
assert.is_nil(dropNpcId)
assert.equals(20470, dropItemId)
assert.equals(15297, dropNpcId)
end)
end)
+29
View File
@@ -130,6 +130,35 @@ describe("QuestieLearner kill-path batching", function()
assert.equals(33.20, Questie.dbLearner.global.npcs[3001][7][44][1][2])
end)
it("keeps quest-tied fallback questgiver spawns from being stripped", function()
Questie.dbLearner.global.settings.dataSourceMode = "learner"
local originalGetPlayerCoords = _G.GetPlayerCoords
_G.GetPlayerCoords = function()
return 10.0, 20.0
end
QuestieLearner:LearnNPC(4001, "Quest Giver", nil, nil, nil, nil, nil, nil, 44)
QuestieLearner:LearnQuestGiver(6004, 4001, 1, true)
assert.equals("fallback", Questie.dbLearner.global.npcs[4001].spawnSource)
assert.is_table(Questie.dbLearner.global.npcs[4001][7])
assert.is_table(Questie.dbLearner.global.npcs[4001][7][44])
assert.is_true(table.getn(Questie.dbLearner.global.npcs[4001][7][44]) >= 1)
_G.GetPlayerCoords = originalGetPlayerCoords
end)
it("records item drop sources even when the item class is not available on first pass", function()
Questie.dbLearner.global.settings.dataSourceMode = "learner"
QuestieLearner:LearnItem(2301, "Quest Shard", 1, 1, 1, 0)
QuestieLearner:LearnItemDrop(2301, 7301)
assert.is_table(Questie.dbLearner.global.items[2301][2])
assert.equals(7301, Questie.dbLearner.global.items[2301][2][1])
assert.equals(7301, QuestieDB.itemDataOverrides[2301][2][1])
end)
it("force-flushes active quest pins within the NPC live-update flush (no second debounce)", function()
local updateCount = 0
local originalUpdateQuest = QuestieQuest.UpdateQuest