docs: add v1.2.9 changelog — cross-link engine, zone fix, untrack fix

This commit is contained in:
Xurkon
2026-03-17 17:38:43 -05:00
parent 35f2d49b6c
commit 7f16d31294
3 changed files with 70 additions and 0 deletions
+33
View File
@@ -1,5 +1,38 @@
# Changelog
## v1.2.9 — QuestieLearner Cross-Link Engine + Tracker Zone Fix + Untrack Fix
### QuestieLearner.lua — Universal Cross-Link Engine
- **[Cross-link engine — full rewrite]** Replaced the narrow NPC↔Quest cross-link stubs with a comprehensive bidirectional relationship engine covering all four entity types. Three shared primitives underpin the whole system: `_AddToArray(tbl, key, value, ovrTable, ovrId)` adds a value to an array field and mirrors it to the live `*DataOverrides` table in the same call; `_AddToNestedArray` does the same for two-level nested arrays (used for quest starters/finishers sub-slots); `_AddToQuestObjective` inserts `{entityId, text}` pairs into `quest[10][slot]` (the objectives array) with the same dual-write pattern. Every write is idempotent (no-op if value already present).
- **[CrossLinkAfterNPC]** Called whenever a new NPC ID is first committed to `learnedData.npcs`. Iterates all learned quests: (a) if quest`[2][1]` contains this npcId → adds questId to `npc[10]` (questStarts); (b) if quest`[3][1]` contains this npcId → adds questId to `npc[11]` (questEnds); (c) walks quest`[10][3]` (item objectives) — for each item whose `item[2]` (npcDrops) already references this NPC, the NPC is injected into `quest[10][1]` as a creature objective so it can receive map pins and tooltip text.
- **[CrossLinkAfterQuest]** Called when a new quest is first committed to `learnedData.quests`. Links all referenced entities: starter NPCs (quest`[2][1]`) → each known NPC's `npc[10]`; starter objects (quest`[2][2]`) → each known object's `obj[2]`; finisher NPCs (quest`[3][1]`) → `npc[11]`; finisher objects (quest`[3][2]`) → `obj[3]`; source item (quest`[11]`) → `item[5]` (startQuest key per itemDB schema); item drop chain (quest`[10][3]` items whose `item[2]` lists known NPCs) → those NPCs are injected into `quest[10][1]` as creature objectives.
- **[CrossLinkAfterObject]** Called when a new object is first committed to `learnedData.objects`. Scans all learned quests: if quest`[2][2]` references this objectId → adds questId to `obj[2]` (questStarts); if quest`[3][2]` references it → adds questId to `obj[3]` (questEnds).
- **[CrossLinkAfterItem]** Called when a new item is first committed to `learnedData.items` AND whenever a new drop-NPC relationship is added via `LearnItemDrop`. Two passes: (1) scan all learned quests for `quest[11] == itemId` → set `item[5] = questId` (item starts this quest); (2) scan all learned quests for `quest[10][3]` entries matching itemId — for each such quest, every NPC in `item[2]` (drop sources) is injected into `quest[10][1]` as a creature objective. This means: learn a quest that needs Wolf Fur → later kill a wolf that drops it → the wolf NPC immediately becomes a tracked creature objective for that quest.
- **[CrossLinkAfterQuestGiver]** Now handles all three entity type slots (1=NPC, 2=Object, 3=Item) instead of only NPCs. For typeSlot=1: stitches `npc[10/11]``quest[2/3][1]` bidirectionally. For typeSlot=2: stitches `obj[2/3]``quest[2/3][2]`. For typeSlot=3: adds itemId to `quest[2][3]` (item starters slot). All writes go to both `learnedData` (SavedVariables) and live `*DataOverrides` tables simultaneously.
- **[LearnItemDrop hook]** Now calls `CrossLinkAfterItem(itemId)` every time a new NPC is added to an item's drop list, not just on initial item creation. This propagates the drop→quest objective chain retroactively.
- **[LearnQuest hook]** Calls `CrossLinkAfterQuest(questId)` on first creation of a quest record.
- **[LearnObject hook]** Calls `CrossLinkAfterObject(objectId)` on first creation of an object record.
- **[LearnItem hook]** Calls `CrossLinkAfterItem(itemId)` on first creation of an item record.
- **[Item schema correction]** Fixed incorrect use of key `[9]` (itemLevel) for quest-source storage. Now correctly uses `[5]` (`startQuest`) per `QuestieDB.itemKeys` schema.
- **[Mouseover — all NPCs now learned]** Removed the `UnitReaction < 4` early-return guard that blocked hostile/neutral NPC learning via mouseover. All NPCs are now learned on hover. The existing `QUESTGIVER` NPC-flag check remains as the primary filter for quest-relevant NPCs during mouseover. Hostile quest objective NPCs are now captured on any interaction (mouseover, targeting, or kill) and are correctly cross-linked to relevant quests.
### TrackerUtils.lua — Zone Resolution Rewrite
- **[GetQuestLogZoneName — canonical 3.3.5 zone lookup]** Added new local function `GetQuestLogZoneName(questId)`. Implements the canonical 3.3.5a zone resolution method: iterates the quest log from index 1 to `GetNumQuestLogEntries()` to locate the quest's log index, then walks backwards from that index checking `isHeader` until the nearest zone header title is found. This is the only fully reliable zone name source in 3.3.5a — the client places zone header entries (where `isHeader=true`) immediately above the quests belonging to that zone in the quest log flat list. `GetRealZoneText()` and `GetCurrentMapAreaID()` are unreliable because they depend on the currently viewed map, not the quest's actual zone.
- **[BuildFallbackQuest — zone via log walk]** Replaced the previous `GetRealZoneText()` call with the quest log header walk. The existing loop that iterates the quest log to find the quest entry now continues backwards from that index to find the header title. `GetAreaIdByZoneName()` is still called to convert the string to an area ID for `zoneOrSort`, but the string itself is always the ground truth used for display.
- **[_isLogFallback patch block — zone fix]** The block that patches QuestLogCache fallback objects (those with `_isLogFallback=true` but no `IsComplete` method) now calls `GetQuestLogZoneName(capturedId)` immediately after patching `Objectives`/`SpecialObjectives`/`ExtraObjectives`. If a zone name is returned, both `quest.zoneName` and `quest.zoneOrSort` are set on the object. Previously this block left `zoneOrSort=0` and `zoneName=nil`, causing every fallback quest to group under "Unknown Zone".
- **[Fallback cache invalidation]** `_fallbackQuests[qid]` is now evicted and rebuilt if the cached entry has no `zoneName`. This handles the case where `BuildFallbackQuest` was called during addon init (before the quest log was fully populated) and cached a zoneless entry.
- **[GetAreaIdByZoneName preserved]** Still available for converting zone header strings to numeric area IDs for `zoneOrSort`. The area ID is used by the proximity sort and zone grouping logic; the string is used for display labels.
### QuestieTracker.lua — Untrack Fix
- **[UntrackQuestId — logic was inverted]** `UntrackQuestId` was setting `AutoUntrackedQuests[questId] = nil` when untracking. The tracker filter reads `not Questie.db.char.AutoUntrackedQuests[questId]``nil` evaluates as "show this quest", so clearing the entry on untrack immediately re-showed the quest on the next update tick. Fix: when `autoTrackQuests = true`, untrack now sets `AutoUntrackedQuests[questId] = true` (hide); when `autoTrackQuests = false`, it clears `TrackedQuests[questId]` (manual mode). The two modes are now cleanly separated.
- **[AQW_Insert — removed QuestLogFrame gate]** The `IsShiftKeyDown() and QuestLogFrame:IsShown()` guard that was blocking re-tracking quests from outside the quest log frame has been removed. Re-tracking (clearing `AutoUntrackedQuests[questId]`) now works from any context. The shift-click-to-untrack path was already handled by `RemoveQuestWatch``UntrackQuestId`; this gate was only blocking the re-track flow.
- **[Quest completion cleanup]** `_RemoveQuestIdFromCharTables` already clears `AutoUntrackedQuests` when a quest leaves the log (completed or abandoned), preventing stale hidden-quest entries in SavedVariables.
---
## v1.2.7 — WotLKDB Plugin Stats Fix + Kill Tracking Refinement
### QuestieInit.lua — LoadBaseDB Count Tracking
+4
View File
@@ -770,6 +770,7 @@ function TrackerUtils:GetSortedQuestIds()
if type(quest) == "table" and quest._isLogFallback then
-- Patch IsComplete method onto the existing QuestLogCache object so
-- QuestieMap and other modules that read currentQuestlog also get it.
-- Also ensure fields QuestieMap iterates exist to avoid pairs(nil) crashes.
local capturedId = qid
quest.IsComplete = function(self)
for i = 1, GetNumQuestLogEntries() do
@@ -780,6 +781,9 @@ function TrackerUtils:GetSortedQuestIds()
end
return 0
end
if not quest.Objectives then quest.Objectives = {} end
if not quest.SpecialObjectives then quest.SpecialObjectives = {} end
if not quest.ExtraObjectives then quest.ExtraObjectives = {} end
QuestiePlayer.currentQuestlog[qid] = quest
else
-- No object at all — build one from the log
+33
View File
@@ -175,6 +175,39 @@
</div>
</div>
<div class="container">
<h2 id="v129">v1.2.9 — QuestieLearner Cross-Link Engine + Tracker Zone Fix + Untrack Fix</h2>
<p><em>Introduces a universal bidirectional cross-link engine in QuestieLearner that automatically stitches relationships between all four entity types (NPCs, Quests, Objects, Items) as data is learned — no manual wiring needed. Fixes the tracker's persistent "Unknown Zone" header for custom/unknown quests by replacing unreliable map API calls with the canonical 3.3.5a quest log header walk. Fixes a logic inversion in UntrackQuestId that prevented shift-click untacking from working.</em></p>
<h3>QuestieLearner.lua — Universal Cross-Link Engine</h3>
<ul>
<li><strong>[Cross-link engine — full rewrite]</strong> Replaced the narrow NPC↔Quest cross-link stubs with a comprehensive bidirectional relationship engine covering all four entity types. Three shared primitives underpin the whole system: <code>_AddToArray(tbl, key, value, ovrTable, ovrId)</code> adds a value to an array field and mirrors it to the live <code>*DataOverrides</code> table in the same call; <code>_AddToNestedArray</code> does the same for two-level nested arrays (used for quest starters/finishers sub-slots <code>quest[2][1]</code>, <code>quest[2][2]</code>, etc.); <code>_AddToQuestObjective</code> inserts <code>{entityId, text}</code> pairs into <code>quest[10][slot]</code> with the same dual-write pattern. Every write is idempotent — a no-op if the value is already present. All three primitives write to both <code>learnedData</code> (SavedVariables, persistent) and the live <code>*DataOverrides</code> tables (in-memory, immediate effect on map pins and tooltips).</li>
<li><strong>[CrossLinkAfterNPC]</strong> Called whenever a new NPC ID is first committed to <code>learnedData.npcs</code>. Performs three scans across all learned quests: (a) if <code>quest[2][1]</code> contains this npcId → adds questId to <code>npc[10]</code> (questStarts); (b) if <code>quest[3][1]</code> contains this npcId → adds questId to <code>npc[11]</code> (questEnds); (c) walks <code>quest[10][3]</code> (item objectives) — for each item whose <code>item[2]</code> (npcDrops) already references this NPC, the NPC is injected into <code>quest[10][1]</code> as a creature objective so it receives map pins and tooltip kill-count text.</li>
<li><strong>[CrossLinkAfterQuest]</strong> Called when a new quest is first committed to <code>learnedData.quests</code>. Links all referenced entities in one pass: starter NPCs (<code>quest[2][1]</code>) → each known NPC's <code>npc[10]</code>; starter objects (<code>quest[2][2]</code>) → each known object's <code>obj[2]</code>; finisher NPCs (<code>quest[3][1]</code>) → <code>npc[11]</code>; finisher objects (<code>quest[3][2]</code>) → <code>obj[3]</code>; source item (<code>quest[11]</code>) → <code>item[5]</code> (startQuest key per itemDB schema); item drop chain: for each item in <code>quest[10][3]</code> whose <code>item[2]</code> lists known NPCs, those NPCs are injected into <code>quest[10][1]</code> as creature objectives.</li>
<li><strong>[CrossLinkAfterObject]</strong> Called when a new object is first committed to <code>learnedData.objects</code>. Scans all learned quests: if <code>quest[2][2]</code> references this objectId → adds questId to <code>obj[2]</code> (questStarts); if <code>quest[3][2]</code> references it → adds questId to <code>obj[3]</code> (questEnds).</li>
<li><strong>[CrossLinkAfterItem]</strong> Called when a new item is first committed to <code>learnedData.items</code> AND whenever a new drop-NPC relationship is added via <code>LearnItemDrop</code>. Two passes: (1) scan all learned quests for <code>quest[11] == itemId</code> → set <code>item[5] = questId</code> (this item starts that quest); (2) scan all learned quests for <code>quest[10][3]</code> entries matching itemId — for each such quest, every NPC in <code>item[2]</code> (drop sources) is injected into <code>quest[10][1]</code> as a creature objective. Practical example: accept a quest needing "Wolf Pelt" → wolf is killed and the pelt drops → <code>LearnItemDrop</code> fires → <code>CrossLinkAfterItem</code> runs → the wolf NPC is immediately added as a creature objective for the pelt quest with active map pins, no reload.</li>
<li><strong>[CrossLinkAfterQuestGiver]</strong> Now handles all three entity type slots (1=NPC, 2=Object, 3=Item) instead of only NPCs. For typeSlot=1: stitches <code>npc[10/11]</code><code>quest[2/3][1]</code> bidirectionally. For typeSlot=2: stitches <code>obj[2/3]</code><code>quest[2/3][2]</code>. For typeSlot=3: adds itemId to <code>quest[2][3]</code> (item starters sub-slot). Called by <code>LearnQuestGiver</code> for all typeSlots, replacing the previous NPC-only check.</li>
<li><strong>[LearnItemDrop hook]</strong> Now calls <code>CrossLinkAfterItem(itemId)</code> every time a new NPC is added to an item's drop list, not only on initial item creation. This retroactively propagates the drop→quest objective chain for previously learned items.</li>
<li><strong>[LearnQuest / LearnObject / LearnItem hooks]</strong> Each now calls its respective <code>CrossLinkAfterX</code> function on first creation of the entity record. Subsequent updates to an existing record skip the cross-link scan (already linked on creation).</li>
<li><strong>[Item schema correction]</strong> Fixed an incorrect use of key <code>[9]</code> (which maps to <code>itemLevel</code> in <code>QuestieDB.itemKeys</code>) for quest-source storage. The correct key is <code>[5]</code> (<code>startQuest</code>). Affects <code>CrossLinkAfterQuest</code> and <code>CrossLinkAfterItem</code>.</li>
<li><strong>[Mouseover — all NPCs now learned]</strong> Removed the <code>UnitReaction &lt; 4</code> early-return guard that blocked learning hostile or neutral NPCs via mouseover. The existing <code>QUESTGIVER</code> NPC-flag bitmask check (<code>0x02</code>) remains as the primary filter, ensuring only NPCs with a quest-giver relationship are stored. Hostile quest objective NPCs are now captured on any hover, target, or kill and are correctly cross-linked to their quests.</li>
</ul>
<h3>TrackerUtils.lua — Zone Resolution Rewrite</h3>
<ul>
<li><strong>[GetQuestLogZoneName — canonical 3.3.5a zone lookup]</strong> Added new local function <code>GetQuestLogZoneName(questId)</code>. Implements the only fully reliable zone resolution method in 3.3.5a: iterates the quest log from index 1 to <code>GetNumQuestLogEntries()</code> to locate the target quest's log index, then walks backwards from that index until an entry where <code>isHeader=true</code> is found. That header's <code>title</code> string is the zone name (e.g. <code>"Shadowmoon Valley"</code>). <code>GetRealZoneText()</code> and <code>GetCurrentMapAreaID()</code> were previously used but are unreliable because they reflect the currently viewed map, not the quest's zone. The 3.3.5a client places zone header entries directly above their child quests in the flat quest log list — this is guaranteed by the client and is the same method Blizzard's own UI uses to render the zone groups.</li>
<li><strong>[BuildFallbackQuest — zone via log walk]</strong> Replaced the <code>GetRealZoneText()</code> call with the inline header walk. The loop that already iterates the quest log to find the fallback quest's entry now continues backwards from that same index to find the header. <code>GetAreaIdByZoneName()</code> converts the string to an area ID for <code>zoneOrSort</code>; the raw string is stored as <code>quest.zoneName</code> for display. If no header is found (e.g. "Special" category with no zone header), <code>zoneName</code> remains nil and the tracker falls back to the <code>_GetZoneName(zoneOrSort)</code> path.</li>
<li><strong>[_isLogFallback patch block — zone fix]</strong> The block that patches <code>QuestLogCache</code> fallback objects (identified by <code>_isLogFallback=true</code> and absence of an <code>IsComplete</code> method) now calls <code>GetQuestLogZoneName(capturedId)</code> immediately after patching <code>Objectives</code>, <code>SpecialObjectives</code>, and <code>ExtraObjectives</code>. If a zone name is returned, both <code>quest.zoneName</code> and <code>quest.zoneOrSort</code> are set on the object in-place. Previously this block left both values at their <code>QuestLogCache</code> defaults (<code>zoneOrSort=0</code>, <code>zoneName=nil</code>), which caused every fallback quest to group under "Unknown Zone" in the tracker regardless of where the quest was accepted.</li>
<li><strong>[Fallback cache invalidation]</strong> <code>_fallbackQuests[qid]</code> is now evicted and unconditionally rebuilt if the cached entry has no <code>zoneName</code>. This handles the race condition where <code>BuildFallbackQuest</code> was called during addon initialization before the quest log entries were populated, caching a zoneless entry that would persist for the entire session.</li>
</ul>
<h3>QuestieTracker.lua — Untrack Fix</h3>
<ul>
<li><strong>[UntrackQuestId — inverted logic fixed]</strong> <code>UntrackQuestId</code> previously set <code>AutoUntrackedQuests[questId] = nil</code> when the player untracks a quest. The tracker filter expression is <code>not Questie.db.char.AutoUntrackedQuests[questId]</code> — when the value is <code>nil</code>, <code>not nil</code> is <code>true</code>, meaning "show this quest". Clearing the entry on untrack therefore immediately re-showed the quest on the very next tracker update tick, making untacking appear completely broken. Fix: in <code>autoTrackQuests</code> mode, <code>UntrackQuestId</code> now sets <code>AutoUntrackedQuests[questId] = true</code> (evaluated as "hide"). In manual-track mode, it clears <code>TrackedQuests[questId]</code> as before, and <code>AutoUntrackedQuests</code> is cleared instead of being incorrectly toggled. The two tracking modes are now cleanly separated within the function.</li>
<li><strong>[AQW_Insert — QuestLogFrame gate removed]</strong> The condition <code>IsShiftKeyDown() and QuestLogFrame:IsShown()</code> inside <code>AQW_Insert</code> was blocking the re-tracking path (clearing <code>AutoUntrackedQuests[questId]</code>) whenever the quest log frame was not open. Since untacking fires <code>RemoveQuestWatch</code><code>UntrackQuestId</code> directly, this <code>AQW_Insert</code> branch was only ever reached for re-tracking — yet the gate required the quest log to be open. The gate has been removed; re-tracking now works from any UI context.</li>
</ul>
</div>
<div class="container">
<h2 id="v126">v1.2.6 — QuestieLearner Comprehensive Overhaul</h2>
<p><em>Rewrote QuestieLearner from scratch to fix all known data-capture deficiencies. Adds full quest field coverage, grid-based coordinate clustering, quest-giver-only mouseover filtering, async item-info retry, object loot detection, and live inject-on-import so imported data takes effect without a reload.</em></p>