feat: release v1.3.9 - fix BackdropTemplate crash and quest re-tracking reliability

This commit is contained in:
Xurkon
2026-03-19 15:14:05 -05:00
parent c5c3897421
commit 5cb64a4c30
17 changed files with 119 additions and 44 deletions
+21 -2
View File
@@ -1,5 +1,18 @@
# Changelog
## v1.3.9 — BackdropTemplate & Tracking Reliability
- **[Quest Tracking]** Significantly improved the reliability of shift-clicking to track or untrack quests. Added more robust user action detection to identify clicks directly in the tracker or the Quest Log.
- **[Quest Re-tracking]** Resolved an issue where hidden quests would refuse to re-track after being manually untracked. Toggling quests back on is now consistently recognized as a manual user action across all WoW versions.
- **[AceGUI Fix]** Fixed a critical crash: `Couldn't find inherited node "BackdropTemplate"`. Implemented a cross-version safe method for frame inheritance in AceGUI-3.0, ensuring stability on WotLK and Classic clients.
- **[Verification]** Performed a comprehensive syntax audit using `luaparse` for all modified core files and AceGUI widgets.
## v1.3.8 — UseAction Taint Fixes
- **[Taint Resolution]** Successfully resolved the `ADDON_ACTION_BLOCKED: UseAction()` error by updating internal libraries and eliminating global namespace pollution.
- **[Library Update]** Updated `Compat/embeds.xml` to use modern, taint-free versions of core libraries.
- **[Addon Stability]** Audited `QuestieInit.lua` and `QuestieLoader.lua` for safe global population.
## v1.3.7 — Quest Tracking & Robustness
- **[Quest Tracking]** Resolved inconsistent quest tracking/untracking by making the tracking state idempotent. This eliminates "doing nothing" results while toggling quests in the Quest Log and prevents tracking loops caused by Blizzard's auto-track feature.
@@ -66,6 +79,12 @@
---
## v1.3.8
- **[QuestieTracker]** Resolved persistent "stuck" quest tracking/untracking by ensuring `AQW_Insert` and `RemoveQuestWatch` hooks respect manual untracking state.
- **[QuestieTracker]** Refined watch hooks to distinguish between manual user actions (e.g. shift-clicking in Quest Log) and internal Blizzard/server objective updates.
- **[QuestieTracker]** Added robustness for Quest IDs passed directly to Blizzard watch APIs (common on custom WoW private servers).
- **[Global Safety]** Added lint suppressions for external frames (`VoiceOverFrame`, etc.) in `QuestieTracker.lua`.
## v1.3.1
- Refined data-sharing mechanism to use exclusively hidden global channels, removing guild-channel broadcasts to minimize chat traffic.
@@ -101,7 +120,7 @@
- **[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.
- **[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 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.
- **[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.
@@ -381,7 +400,7 @@
### TOC / Addon Identity
- **[TOC]** Core addon `.toc` files updated to `Questie-X` title and `v1.1.4` version.
- **[TOC]** Core addon `.toc` files updated to `Questie-X` title and `v1.3.8`
- **[Libs]** Added `LibDeflate`, `XXH_Lua_Lib`, `LibDBIcon-1.0`, and `LibDataBroker-1.1` to the Libs directory.
### Bug Fixes
@@ -125,7 +125,7 @@ local function Constructor()
dropdown.frame:Show()
dropdown:SetLabel("")
local border = CreateFrame("Frame", nil, frame, "BackdropTemplate")
local border = CreateFrame("Frame", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
border:SetPoint("TOPLEFT", 0, -26)
border:SetPoint("BOTTOMRIGHT", 0, 3)
border:SetBackdrop(PaneBackdrop)
@@ -176,7 +176,7 @@ local PaneBackdrop = {
}
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
local frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate" or nil)
frame:Hide()
frame:EnableMouse(true)
@@ -203,7 +203,7 @@ local function Constructor()
closebutton:SetWidth(100)
closebutton:SetText(CLOSE)
local statusbg = CreateFrame("Button", nil, frame, "BackdropTemplate")
local statusbg = CreateFrame("Button", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
statusbg:SetPoint("BOTTOMLEFT", 15, 15)
statusbg:SetPoint("BOTTOMRIGHT", -132, 15)
statusbg:SetHeight(24)
@@ -75,7 +75,7 @@ local function Constructor()
titletext:SetJustifyH("LEFT")
titletext:SetHeight(18)
local border = CreateFrame("Frame", nil, frame, "BackdropTemplate")
local border = CreateFrame("Frame", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
border:SetPoint("TOPLEFT", 0, -17)
border:SetPoint("BOTTOMRIGHT", -1, 3)
border:SetBackdrop(PaneBackdrop)
@@ -502,7 +502,7 @@ local function Constructor()
titletext:SetHeight(18)
titletext:SetText("")
local border = CreateFrame("Frame", nil, frame, "BackdropTemplate")
local border = CreateFrame("Frame", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
border:SetPoint("TOPLEFT", 1, -27)
border:SetPoint("BOTTOMRIGHT", -1, 3)
border:SetBackdrop(PaneBackdrop)
@@ -637,7 +637,7 @@ local function Constructor()
local num = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", nil, UIParent)
local treeframe = CreateFrame("Frame", nil, frame, "BackdropTemplate")
local treeframe = CreateFrame("Frame", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
treeframe:SetPoint("TOPLEFT")
treeframe:SetPoint("BOTTOMLEFT")
treeframe:SetWidth(DEFAULT_TREE_WIDTH)
@@ -656,7 +656,7 @@ local function Constructor()
treeframe:SetScript("OnSizeChanged", Tree_OnSizeChanged)
treeframe:SetScript("OnMouseWheel", Tree_OnMouseWheel)
local dragger = CreateFrame("Frame", nil, treeframe, "BackdropTemplate")
local dragger = CreateFrame("Frame", nil, treeframe, BackdropTemplateMixin and "BackdropTemplate" or nil)
dragger:SetWidth(8)
dragger:SetPoint("TOP", treeframe, "TOPRIGHT")
dragger:SetPoint("BOTTOM", treeframe, "BOTTOMRIGHT")
@@ -681,7 +681,7 @@ local function Constructor()
scrollbg:SetAllPoints(scrollbar)
scrollbg:SetColorTexture(0,0,0,0.4)
local border = CreateFrame("Frame", nil, frame, "BackdropTemplate")
local border = CreateFrame("Frame", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
border:SetPoint("TOPLEFT", treeframe, "TOPRIGHT")
border:SetPoint("BOTTOMRIGHT")
border:SetBackdrop(PaneBackdrop)
@@ -249,7 +249,7 @@ do
local function Constructor()
local count = AceGUI:GetNextWidgetNum(widgetType)
local frame = CreateFrame("Frame", "AceGUI30Pullout"..count, UIParent, "BackdropTemplate")
local frame = CreateFrame("Frame", "AceGUI30Pullout"..count, UIParent, BackdropTemplateMixin and "BackdropTemplate" or nil)
local self = {}
self.count = count
self.type = widgetType
@@ -300,7 +300,7 @@ do
scrollFrame.obj = self
itemFrame.obj = self
local slider = CreateFrame("Slider", "AceGUI30PulloutScrollbar"..count, scrollFrame, "BackdropTemplate")
local slider = CreateFrame("Slider", "AceGUI30PulloutScrollbar"..count, scrollFrame, BackdropTemplateMixin and "BackdropTemplate" or nil)
slider:SetOrientation("VERTICAL")
slider:SetHitRectInsets(0, 0, -10, 0)
slider:SetBackdrop(sliderBackdrop)
@@ -210,7 +210,7 @@ local function Constructor()
label:SetJustifyH("CENTER")
label:SetHeight(18)
local msgframe = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
local msgframe = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate" or nil)
msgframe:SetHeight(30)
msgframe:SetBackdrop(ControlBackdrop)
msgframe:SetBackdropColor(0,0,0)
@@ -299,7 +299,7 @@ local function Constructor()
text:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -5, 1)
text:SetJustifyV("MIDDLE")
local scrollBG = CreateFrame("Frame", nil, frame, "BackdropTemplate")
local scrollBG = CreateFrame("Frame", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
scrollBG:SetBackdrop(backdrop)
scrollBG:SetBackdropColor(0, 0, 0)
scrollBG:SetBackdropBorderColor(0.4, 0.4, 0.4)
@@ -221,7 +221,7 @@ local function Constructor()
label:SetJustifyH("CENTER")
label:SetHeight(15)
local slider = CreateFrame("Slider", nil, frame, "BackdropTemplate")
local slider = CreateFrame("Slider", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
slider:SetOrientation("HORIZONTAL")
slider:SetHeight(15)
slider:SetHitRectInsets(0, 0, -10, 0)
@@ -243,7 +243,7 @@ local function Constructor()
local hightext = slider:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
hightext:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", -2, 3)
local editbox = CreateFrame("EditBox", nil, frame, "BackdropTemplate")
local editbox = CreateFrame("EditBox", nil, frame, BackdropTemplateMixin and "BackdropTemplate" or nil)
editbox:SetAutoFocus(false)
editbox:SetFontObject(GameFontHighlightSmall)
editbox:SetPoint("TOP", slider, "BOTTOM")
+11 -2
View File
@@ -42,8 +42,17 @@ function Hooks:HookQuestLogTitle()
-- For all other clicks (including tracking/untracking), use the original function
-- only call Questie's tracker if we actually want to fix this quest (normal quests already call AQW_insert)
if Questie.db.profile.trackerEnabled and GetNumQuestLeaderBoards(questLogLineIndex) == 0 and (not IsQuestWatched(questLogLineIndex)) then
QuestieTracker:AQW_Insert(questLogLineIndex, QUEST_WATCH_NO_EXPIRE)
if Questie.db.profile.trackerEnabled and GetNumQuestLeaderBoards(questLogLineIndex) == 0 then
local _, _, _, _, _, _, _, questId = GetQuestLogTitle(questLogLineIndex)
if questId and questId > 0 then
if Questie.db.char.TrackedQuests[questId] or (Questie.db.profile.autoTrackQuests and (not Questie.db.char.AutoUntrackedQuests[questId])) then
-- Quest is currently tracked — hidden it
QuestieTracker:UntrackQuestId(questId)
else
-- Quest is currently hidden — show it
QuestieTracker:AQW_Insert(questLogLineIndex, QUEST_WATCH_NO_EXPIRE)
end
end
if WatchFrame_Update then
WatchFrame_Update()
end
+62 -19
View File
@@ -2287,19 +2287,36 @@ function QuestieTracker.RemoveQuestWatch(index, isQuestie)
if not isQuestie then
if index then
local questId = select(8, GetQuestLogTitle(index))
if (not questId) or questId == 0 then
-- When an objective progresses in TBC "index" is the questId, but when a quest is manually removed from
-- the quest watch (e.g. shift clicking it in the quest log) "index" is the questLogIndex.
-- We should NOT untrack when Blizzard calls this with a questId internally during objective updates.
if questId == 0 then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker.RemoveQuestWatch] - Internal Blizzard update, skipping untrack for ID:", index)
end
local _, _, _, isHeader, _, _, _, questId = GetQuestLogTitle(index)
if isHeader then
return
end
if questId then
QuestieTracker:UntrackQuestId(questId)
if (not questId) or questId == 0 then
-- Maybe index was already a questId (Ascension/Ebonhold extension)
if index > 0 and (GetQuestLogIndexByID and GetQuestLogIndexByID(index) > 0) then
questId = index
else
return
end
end
if questId and questId > 0 then
local stack = debugstack(2, 5, 0)
local isUserAction = stack:find("QuestLog") or stack:find("OnClick") or stack:find("QuestWatch") or stack:find("TrackerLinePool") or stack:find("TrackerMenu") or IsShiftKeyDown()
if not isUserAction then
-- Internal update: Only untrack if we are NOT in auto-track mode
if not Questie.db.profile.autoTrackQuests then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker.RemoveQuestWatch] - Internal update (untrack):", questId)
QuestieTracker:UntrackQuestId(questId)
end
else
-- User triggered untrack: Explicitly follow it.
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker.RemoveQuestWatch] - User action (untrack):", questId)
QuestieTracker:UntrackQuestId(questId)
end
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker.RemoveQuestWatch] - by Blizzard", questId)
end
end
@@ -2341,7 +2358,7 @@ function QuestieTracker:AQW_Insert(index, expire)
-- This prevents double calling this function
local now = GetTime()
if index and index == QuestieTracker.last_aqw and (now - lastAQW) < 0.1 then
if index and index == QuestieTracker.last_aqw and (now - lastAQW) < 0.05 then
return
end
@@ -2352,24 +2369,50 @@ function QuestieTracker:AQW_Insert(index, expire)
-- that is all the player will see. This also prevents hitting the Blizzard Quest Watch Limit.
RemoveQuestWatch(index, true)
local questId = select(8, GetQuestLogTitle(index))
local _, _, _, isHeader, _, _, _, questId = GetQuestLogTitle(index)
if isHeader then return end
if (not questId) or questId == 0 then
-- When an objective progresses in TBC "index" is the questId, but when a quest is manually added to the quest watch
-- (e.g. shift clicking it in the quest log) "index" is the questLogIndex.
questId = index
if index and index > 50 and GetQuestLogIndexByID and GetQuestLogIndexByID(index) > 0 then
questId = index
else
return
end
end
if questId and questId > 0 then
-- Check if this was a manual user action (Shift-Click in Quest Log or similar)
local stack = debugstack(2, 8, 0)
local isUserAction = stack:find("QuestLog") or stack:find("OnClick") or stack:find("QuestWatch") or stack:find("Toggle") or stack:find("TrackerLinePool") or stack:find("TrackerMenu") or IsShiftKeyDown()
-- These checks makes sure the only way to track a quest is through the Blizzard Quest Log
-- or another Addon hooked into the Blizzard Quest Log that replaces the default Quest Log.
if not Questie.db.profile.autoTrackQuests then
-- Manual track mode: force track when AddQuestWatch is called
Questie.db.char.TrackedQuests[questId] = true
Questie.db.char.AutoUntrackedQuests[questId] = nil
else
if Questie.db.char.AutoUntrackedQuests[questId] then
-- Quest was manually hidden — shift-click re-tracks it
if isUserAction then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker:AQW_Insert] - Manual mode, user re-track:", questId)
Questie.db.char.TrackedQuests[questId] = true
Questie.db.char.AutoUntrackedQuests[questId] = nil
else
-- In manual mode, we normally only track on user action, but if Blizzard calls it,
-- it might be a re-sync of something the user HAD tracked.
if Questie.db.char.TrackedQuests[questId] then
-- Keep it tracked
else
-- Blizzard auto-tracked something we didn't ask for? Ignore it in Manual mode.
return
end
end
else
if isUserAction then
-- Auto track mode: User manually tracked it (un-hiding)
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker:AQW_Insert] - Auto mode, user un-hide:", questId)
Questie.db.char.AutoUntrackedQuests[questId] = nil
elseif Questie.db.char.AutoUntrackedQuests[questId] then
-- Auto track mode: Internal update for a HIDDEN quest — respect the hidden state!
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker:AQW_Insert] - Auto mode, skipping hidden quest:", questId)
return
end
end
+4
View File
@@ -1017,6 +1017,10 @@ TrackerLinePool.OnClickQuest = function(self, button)
end
end
else
local questLogIndex = GetQuestLogIndexByID(self.Quest.Id)
if questLogIndex and questLogIndex > 0 then
RemoveQuestWatch(questLogIndex)
end
QuestieTracker:UntrackQuestId(self.Quest.Id)
local questLogFrame = QuestLogExFrame or ClassicQuestLog or QuestLogFrame
if questLogFrame:IsShown() then
+2 -2
View File
@@ -1,11 +1,11 @@
## Interface: 30300
## Title: |cFF5EBAF3Questie|r|cFFDAFAFD-X|r-Classic|cFF00FF00 v1.3.7|r
## Title: |cFF5EBAF3Questie|r|cFFDAFAFD-X|r-Classic|cFF00FF00 v1.3.9|r
## Notes: A standalone Classic QuestHelper
## Notes-esMX: Ayundante de misión
## Notes-esES: Ayundante de misión
## Notes-ptBR: Ajudante de missão
## Notes-frFR: Assistant de quête
## Version: 1.3.7
## Version: 1.3.9
## RequiredDeps:
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
## SavedVariables: QuestieConfig
+2 -2
View File
@@ -1,11 +1,11 @@
## Interface: 30300
## Title: |cFF5EBAF3Questie|r|cFFDAFAFD-X|r-TBC|cFF00FF00 v1.3.7|r
## Title: |cFF5EBAF3Questie|r|cFFDAFAFD-X|r-TBC|cFF00FF00 v1.3.9|r
## Notes: A standalone Classic QuestHelper
## Notes-esMX: Ayundante de misión
## Notes-esES: Ayundante de misión
## Notes-ptBR: Ajudante de missão
## Notes-frFR: Assistant de quête
## Version: 1.3.7
## Version: 1.3.9
## RequiredDeps:
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
## SavedVariables: QuestieConfig
+2 -2
View File
@@ -1,11 +1,11 @@
## Interface: 11200
## Title: |cFF5EBAF3Questie|r|cFFDAFAFD-X|r-Turtle|cFF00FF00 v1.3.7|r
## Title: |cFF5EBAF3Questie|r|cFFDAFAFD-X|r-Turtle|cFF00FF00 v1.3.9|r
## Notes: A standalone Classic QuestHelper
## Notes-esMX: Ayundante de misiones
## Notes-esES: Ayundante de misiones
## Notes-ptBR: Ajudante de misiones
## Notes-frFR: Assistant de quêtes
## Version: 1.3.7
## Version: 1.3.9
## RequiredDeps:
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu, Questie-X-TurtleDB
## SavedVariables: QuestieConfig
+1 -1
View File
@@ -11,7 +11,7 @@
## Notes-esES: Ayundante de misión
## Notes-ptBR: Ajudante de missão
## Notes-frFR: Assistant de quête
## Version: 1.3.7
## Version: 1.3.9
## RequiredDeps:
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu, Questie-X-WotLKDB, Questie-X-ClassicDB, Questie-X-TBCDB, Questie-X-TurtleDB, Questie-X-AscensionDB, Questie-X-EbonholdDB
## SavedVariables: QuestieConfig, QuestieLearnerDB