fix: gate object learning to quest-related data

This commit is contained in:
Xurkon
2026-06-06 10:35:46 -05:00
parent ccdfa525cf
commit 4e95418594
2 changed files with 143 additions and 24 deletions
+101 -21
View File
@@ -2057,6 +2057,13 @@ function QuestieLearner:LearnQuestObjectiveObject(questId, objectId, objText, ob
table.insert(existing[10][2], { objectId, objText or "" })
end
local recent = _Learner.recentObjects and _Learner.recentObjects[objectId]
if recent then
self:LearnObject(objectId, recent.name or objText, recent.x, recent.y, recent.zoneId, true)
else
self:LearnObject(objectId, objText, nil, nil, GetZoneId(), true)
end
if objectiveIndex then
existing.objIndex = existing.objIndex or {}
local entry = existing.objIndex[objectiveIndex]
@@ -2123,14 +2130,16 @@ local function IsQuestRelevantItem(itemId, itemClass)
end
if qData[10] and qData[10][3] then
for _, entry in ipairs(qData[10][3]) do
if entry[1] == itemId then
local entryId = type(entry) == "table" and entry[1] or entry
if entryId == itemId then
return true
end
end
end
if qData[2] and qData[2][3] then
for _, entry in ipairs(qData[2][3]) do
if entry[1] == itemId then
local entryId = type(entry) == "table" and entry[1] or entry
if entryId == itemId then
return true
end
end
@@ -2174,6 +2183,51 @@ local function HasQuestReferences(itemId)
return false
end
local function HasQuestObjectReferences(objectId)
local learned = Questie and Questie.dbLearner and Questie.dbLearner.global
if not learned then
return false
end
local objectData = learned.objects and learned.objects[objectId]
if objectData and objectData.questRelevant then
return true
end
if not learned.quests then
return false
end
for _, qData in pairs(learned.quests) do
if qData[2] and qData[2][2] then
for _, entry in ipairs(qData[2][2]) do
local entryId = type(entry) == "table" and entry[1] or entry
if entryId == objectId then
return true
end
end
end
if qData[3] and qData[3][2] then
for _, entry in ipairs(qData[3][2]) do
local entryId = type(entry) == "table" and entry[1] or entry
if entryId == objectId then
return true
end
end
end
if qData[10] and qData[10][2] then
for _, entry in ipairs(qData[10][2]) do
local entryId = type(entry) == "table" and entry[1] or entry
if entryId == objectId then
return true
end
end
end
end
return false
end
function QuestieLearner:LearnItem(itemId, name, itemLevel, requiredLevel, itemClass, itemSubClass)
if not self:IsEnabled() then return end
if not Questie.dbLearner.global.settings.learnItems then return end
@@ -2268,14 +2322,21 @@ end
-- Object learning
------------------------------------------------------------------------
function QuestieLearner:LearnObject(objectId, name)
if not self:IsEnabled() then return end
if not Questie.dbLearner.global.settings.learnObjects then return end
function QuestieLearner:LearnObject(objectId, name, spawnX, spawnY, spawnZoneId, questRelevant)
if not self:IsEnabled() then return false end
if not Questie.dbLearner.global.settings.learnObjects then return false end
objectId = tonumber(objectId)
if not objectId or objectId <= 0 then return end
if not objectId or objectId <= 0 then return false end
local zoneId = GetZoneId()
local x, y = GetPlayerCoords()
if not (questRelevant or HasQuestObjectReferences(objectId)) then
return false
end
local zoneId = NormalizeSpawnZoneKey(spawnZoneId or GetZoneId())
local x, y = spawnX, spawnY
if not x or not y then
x, y = GetPlayerCoords()
end
local existing = Questie.dbLearner.global.objects[objectId]
local isNew = existing == nil
@@ -2284,6 +2345,8 @@ function QuestieLearner:LearnObject(objectId, name)
Questie.dbLearner.global.objects[objectId] = existing
end
existing.questRelevant = true
if name and not existing[1] then existing[1] = name end
if zoneId and zoneId > 0 and not existing[5] then existing[5] = zoneId end
@@ -2326,6 +2389,7 @@ function QuestieLearner:LearnObject(objectId, name)
CrossLinkAfterObject(objectId)
end
_Learner:BroadcastIfCommsAvailable("OBJECT", objectId, existing)
return true
end
------------------------------------------------------------------------
@@ -2818,12 +2882,12 @@ function QuestieLearner:InjectLearnedData()
objectIdsToFix[objectId] = oid
end
self:Sanitize(data)
if not QuestieDB.objectDataOverrides[oid or objectId] then
if HasQuestObjectReferences(oid or objectId) and not QuestieDB.objectDataOverrides[oid or objectId] then
QuestieDB.objectDataOverrides[oid or objectId] = data
objectCount = objectCount + 1
else
local existing = QuestieDB.objectDataOverrides[oid or objectId]
if data[4] and not IsAscensionProtected("OBJECT", oid or objectId, 4) then
if existing and data[4] and not IsAscensionProtected("OBJECT", oid or objectId, 4) then
existing[4] = existing[4] or {}
for zoneId, coords in pairs(data[4]) do
existing[4][zoneId] = existing[4][zoneId] or {}
@@ -2833,6 +2897,7 @@ function QuestieLearner:InjectLearnedData()
end
end
-- Adopt other fields
if existing then
for k, v in pairs(data) do
if k ~= "mc" and k ~= 4 and existing[k] == nil and not IsAscensionProtected("OBJECT", oid or objectId, k) then
existing[k] = v
@@ -2840,6 +2905,7 @@ function QuestieLearner:InjectLearnedData()
end
end
end
end
for old, new in pairs(objectIdsToFix) do
learned.objects[new] = learned.objects[old]
learned.objects[old] = nil
@@ -3201,7 +3267,7 @@ function QuestieLearner:OnQuestDetail()
local entityName = UnitName("npc")
if unitType == "GameObject" then
self:LearnQuestGiver(questId, entityId, 2, true)
self:LearnObject(entityId, entityName)
self:LearnObject(entityId, entityName, nil, nil, zoneId, true)
elseif unitType == "Creature" or unitType == "Vehicle" then
self:LearnQuestGiver(questId, entityId, 1, true)
local npcFlags = UnitNPCFlags and UnitNPCFlags("npc") or 2
@@ -3234,7 +3300,7 @@ function QuestieLearner:OnQuestComplete()
local entityName = UnitName("npc")
if unitType == "GameObject" then
self:LearnQuestGiver(questId, entityId, 2, false)
self:LearnObject(entityId, entityName)
self:LearnObject(entityId, entityName, nil, nil, zoneId, true)
elseif unitType == "Creature" or unitType == "Vehicle" then
self:LearnQuestGiver(questId, entityId, 1, false)
local npcFlags = UnitNPCFlags and UnitNPCFlags("npc") or 2
@@ -3463,7 +3529,7 @@ function QuestieLearner:OnQuestAccepted(firstArg, secondArg)
if giverEntity then
if giverEntity.unitType == "GameObject" then
self:LearnQuestGiver(questId, giverEntity.id, 2, true)
self:LearnObject(giverEntity.id, giverEntity.name)
self:LearnObject(giverEntity.id, giverEntity.name, nil, nil, GetZoneId(), true)
elseif giverEntity.unitType == "Creature" or giverEntity.unitType == "Vehicle" then
self:LearnQuestGiver(questId, giverEntity.id, 1, true)
local npcFlags = (npcGuid and UnitNPCFlags and UnitNPCFlags("npc")) or 1
@@ -3488,7 +3554,7 @@ function QuestieLearner:OnQuestTurnedIn(questId, xpReward, moneyReward)
local entityName = UnitName("npc")
if unitType == "GameObject" then
self:LearnQuestGiver(questId, entityId, 2, false)
self:LearnObject(entityId, entityName)
self:LearnObject(entityId, entityName, nil, nil, GetZoneId(), true)
elseif unitType == "Creature" or unitType == "Vehicle" then
self:LearnQuestGiver(questId, entityId, 1, false)
local npcFlags = UnitNPCFlags and UnitNPCFlags("npc") or 2
@@ -3507,12 +3573,10 @@ function QuestieLearner:OnLootOpened()
local targetGuid = UnitGUID("target")
local targetId, targetType = nil, nil
local npcId = nil
local objectId = nil
if targetGuid then
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"))
end
if targetType == "Creature" or targetType == "Vehicle" then
npcId = targetId
else
@@ -3524,6 +3588,7 @@ function QuestieLearner:OnLootOpened()
for i = 1, numItems do
local _, lootName, _, _, lootQuality = GetLootSlotInfo(i)
if lootName then
local objectId = nil
if GetLootSourceInfo then
local sources = { GetLootSourceInfo(i) }
local sourceCount = table.getn(sources)
@@ -3550,7 +3615,7 @@ function QuestieLearner:OnLootOpened()
if (sourceType == "Creature" or sourceType == "Vehicle") and sourceId and sourceId > 0 then
npcId = sourceId
elseif sourceType == "GameObject" and sourceId and sourceId > 0 then
self:LearnObject(sourceId, nil)
objectId = sourceId
end
end
end
@@ -3563,9 +3628,12 @@ function QuestieLearner:OnLootOpened()
if itemName then
local learnedItem = self:LearnItem(itemId, itemName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
if learnedItem and npcId then self:LearnItemDrop(itemId, npcId) end
if learnedItem and objectId then
self:LearnObject(objectId, nil, nil, nil, GetZoneId(), true)
end
else
-- GetItemInfo returned nil; queue for retry (class check happens on retry)
table.insert(_Learner.pendingItemLinks, { link = link, itemId = itemId, npcId = npcId })
table.insert(_Learner.pendingItemLinks, { link = link, itemId = itemId, npcId = npcId, objectId = objectId })
end
end
end
@@ -3580,6 +3648,8 @@ function QuestieLearner:OnGameObjectUsed(objectId)
if not objectId or objectId <= 0 then return end
local objectName = ResolveObjectName(objectId)
local x, y = GetPlayerCoords()
local zoneId = GetZoneId()
Questie:Debug(
Questie.DEBUG_DEVELOP,
"[QuestieLearner:GameObjectUsedTrace]",
@@ -3589,12 +3659,16 @@ function QuestieLearner:OnGameObjectUsed(objectId)
tostring(objectName)
)
TraceLearnerEntity("gameobject_used", nil, "GameObject", objectId, objectName)
self:LearnObject(objectId, objectName)
if HasQuestObjectReferences(objectId) then
self:LearnObject(objectId, objectName, x, y, zoneId, true)
end
_Learner.recentObjects[objectId] = {
objectId = objectId,
name = objectName,
ts = time(),
zoneId = GetZoneId(),
zoneId = zoneId,
x = x,
y = y,
}
end
@@ -3660,6 +3734,9 @@ function QuestieLearner:OnGetItemInfoReceived(itemId)
if itemName then
local learnedItem = self:LearnItem(itemId, itemName, itemLevel, requiredLevel, itemClassId, itemSubClassId)
if learnedItem and entry.npcId then self:LearnItemDrop(itemId, entry.npcId) end
if learnedItem and entry.objectId then
self:LearnObject(entry.objectId, nil, nil, nil, GetZoneId(), true)
end
else
table.insert(remaining, entry) -- still not cached, keep
end
@@ -4633,6 +4710,9 @@ function QuestieLearner:_ApplyIncomingNetworkMerge(typ, id, d, op)
store = Questie.dbLearner.global.items
elseif typ == "OBJECT" then
if not Questie.dbLearner.global.settings.learnObjects then return false end
if not ((type(d) == "table" and d.questRelevant) or HasQuestObjectReferences(id)) then
return false
end
store = Questie.dbLearner.global.objects
else
return false
+39
View File
@@ -165,6 +165,37 @@ describe("QuestieLearner kill-path batching", function()
assert.equals(7301, Questie.dbLearner.global.items[2302][2][1])
end)
it("ignores non-quest objects until they are explicitly quest-related", function()
Questie.dbLearner.global.settings.dataSourceMode = "learner"
local learned = QuestieLearner:LearnObject(3301, "Random Object", 11.1, 22.2, 44)
assert.is_false(learned)
assert.is_nil(Questie.dbLearner.global.objects[3301])
end)
it("stores quest-related object coordinates when promoted", function()
Questie.dbLearner.global.settings.dataSourceMode = "learner"
Questie.dbLearner.global.quests[6005] = {
[10] = {
[2] = {
{ 3302, "Quest Object" },
},
},
}
local learned = QuestieLearner:LearnObject(3302, "Quest Object", 11.1, 22.2, 44, true)
assert.is_true(learned)
assert.is_table(Questie.dbLearner.global.objects[3302])
assert.is_true(Questie.dbLearner.global.objects[3302].questRelevant)
assert.is_table(Questie.dbLearner.global.objects[3302][4])
assert.is_table(Questie.dbLearner.global.objects[3302][4][44])
assert.equals(1, table.getn(Questie.dbLearner.global.objects[3302][4][44]))
assert.equals(11.1, Questie.dbLearner.global.objects[3302][4][44][1][1])
assert.equals(22.2, Questie.dbLearner.global.objects[3302][4][44][1][2])
end)
it("rejects non-quest item network merges without quest references", function()
Questie.dbLearner.global.settings.dataSourceMode = "learner"
local changed = QuestieLearner:_ApplyIncomingNetworkMerge("ITEM", 2401, { [1] = "Arcane Sliver" }, "NEW")
@@ -173,6 +204,14 @@ describe("QuestieLearner kill-path batching", function()
assert.is_nil(Questie.dbLearner.global.items[2401])
end)
it("rejects non-quest object network merges without quest references", function()
Questie.dbLearner.global.settings.dataSourceMode = "learner"
local changed = QuestieLearner:_ApplyIncomingNetworkMerge("OBJECT", 3401, { [1] = "Random Object", [4] = { [44] = { { 11.1, 22.2 } } } }, "NEW")
assert.is_false(changed)
assert.is_nil(Questie.dbLearner.global.objects[3401])
end)
it("clears cached quest data when learner adds questgiver links", function()
Questie.dbLearner.global.settings.dataSourceMode = "learner"
QuestieDB.private.questCache = {