From 6738027e4d2d0b29aa34f3fb70ccdbd6b18c1628 Mon Sep 17 00:00:00 2001 From: Xurkon Date: Sat, 9 May 2026 06:48:59 -0500 Subject: [PATCH] Fix Sunstrider Isle arrow, Tooltip type guard, mapId 946 override - zoneDB.lua: Add [946] = 3430 to UiMapIdOverrides so GetCurrentZoneId() returns 3430 (Sunstrider Isle areaId) even when the game returns uiMapId 946 (ghost/loading map). Previously 946 had no override, causing zone lookups to fall through and return 946 instead of the real zone, breaking arrow distance calculation and target filtering. - QuestieArrow.lua: UpdateNearestTargets uses QuestiePlayer:GetCurrentUiMapId() (backed by C_Map.GetBestMapForUnit) for player position. When that returns an invalid/ghost map (946/947/0), fall back to ZoneDB lookup via the actual zoneId. This ensures the arrow gets real world coordinates regardless of whether the world map is open or closed. Also includes per-frame debug output when debugArrow profile is enabled. - Tooltip.lua: Add type guard 'if type(objList) ~= table then break end' before iterating learnedNpc[10] and learnedObj[10] in both m_/NPC and o_/object paths. Prevents 'attempt to index field questData (a string value)' error when the questData field is unexpectedly a string instead of a table. The original loop used 'for questId, objList in next, learnedNpc[10]' which iterates key-value pairs in insertion order. The _AddToArray helper stores values as sequential array elements (tbl[key]=value via table.insert), but the iteration was treating it as a questId->objList map. Fixed to use ipairs-style iteration with a type check for robustness. --- Database/Zones/zoneDB.lua | 2 + Modules/Arrow/QuestieArrow.lua | 171 ++++++++++++++++++++++++++--- Modules/Tooltips/Tooltip.lua | 194 ++++++++++++++++++--------------- 3 files changed, 262 insertions(+), 105 deletions(-) diff --git a/Database/Zones/zoneDB.lua b/Database/Zones/zoneDB.lua index 5700c2b..529efa5 100644 --- a/Database/Zones/zoneDB.lua +++ b/Database/Zones/zoneDB.lua @@ -49,6 +49,8 @@ local UiMapIdOverrides = { [246] = 3713, [1415] = 668, -- Eastern Kingdoms (matches Undercity on Ascension) [947] = 668, -- Azeroth (matches Undercity on Ascension) + [1241] = 3430, -- Sunstrider Isle (uiMapId 1241 → areaId 3430) + [946] = 3430, -- Sunstrider Isle ghost/loading map → areaId 3430 } local parentZoneToSubZone = {} -- Generated local zoneMap = {} -- Generated diff --git a/Modules/Arrow/QuestieArrow.lua b/Modules/Arrow/QuestieArrow.lua index 92917bc..7bbabae 100644 --- a/Modules/Arrow/QuestieArrow.lua +++ b/Modules/Arrow/QuestieArrow.lua @@ -257,7 +257,7 @@ local function EnsureArrowFrame() end end) - arrowFrame:SetScript("OnUpdate", function(self) +arrowFrame:SetScript("OnUpdate", function(self) local now = GetTime() local target = sortedTargets[1] @@ -273,30 +273,96 @@ local function EnsureArrowFrame() if (self._lastUpdate or 0) + UPDATE_THROTTLE_SECONDS > now then return end - self._lastUpdate = now +self._lastUpdate = now - local playerX, playerY, playerInstance = HBD:GetPlayerWorldPosition() - if not playerX or not playerY or not playerInstance then - self.distance:SetText("Distance: --") - return + -- Persistent debug: print every frame so we can see what OnUpdate sees + local debugArrow = Questie and Questie.db and Questie.db.profile and Questie.db.profile.debugArrow + if debugArrow then + print(string.format("QuestieArrow OnUpdate: frameShown=%s target=%s pX=%s pY=%s pInst=%s _playerUiMapId=%s targetUiMapId=%s", + tostring(self:IsShown()), tostring(target and target.title), + tostring(_arrow_playerX), tostring(_arrow_playerY), tostring(_arrow_playerInstance), + tostring(_arrow_playerUiMapId), tostring(target and target.uiMapId))) end - local targetX, targetY, targetInstance = HBD:GetWorldCoordinatesFromZone(target.x / 100.0, target.y / 100.0, - target.uiMapId) - if not targetX or not targetY or not targetInstance then - self.distance:SetText("Distance: --") - return +local pX, pY, pInst = _arrow_playerX, _arrow_playerY, _arrow_playerInstance + if not pX or not pY or not pInst then + -- Fallback to HBD if upvalues aren't set yet + pX, pY, pInst = HBD:GetPlayerWorldPosition() end - - if targetInstance ~= playerInstance then + if not pX or not pY or not pInst then + local debugArrow = Questie and Questie.db and Questie.db.profile and Questie.db.profile.debugArrow + if debugArrow then + print(string.format("QuestieArrow OnUpdate: player position nil (x=%s y=%s inst=%s)", tostring(pX), tostring(pY), tostring(pInst))) + end + self.distance:SetText("Distance: --") self:Hide() return end + -- Use the same uiMapId that _CollectObjective used for spawns to ensure consistent + -- instance ID. If the player is on the same map as the target (same uiMapId), their + -- instances must match. We get this from _arrow_playerUiMapId as a fallback. + local playerUiMapId = _arrow_playerUiMapId or 0 + local targetUiMapId = target.uiMapId or 0 + + -- Declare world coords outside the branches so they're in scope for direction calc + local playerWorldX, playerWorldY + +-- If target and player are on the same uiMapId, they're in the same instance. + -- If they're on different maps, we need to compare instances via HBD. + if playerUiMapId ~= targetUiMapId then + -- Different maps: just skip instance check since UnitPosition gives us + -- the real instance — if player and target instances differ, HBD will return + -- nil for distance anyway. Fall through to direction calc with player coords. + end + -- Calculate arrow direction using pfQuest's method, but in world coordinates -- (map coordinates break when the target is in a different zone) - local xDelta = (playerX - targetX) * 1.5 - local yDelta = (playerY - targetY) + local targetX, targetY, targetInstance + if playerUiMapId == targetUiMapId and playerUiMapId ~= 0 then + -- Same uiMapId: compute target world coords using HBD + targetX, targetY, targetInstance = HBD:GetWorldCoordinatesFromZone(target.x / 100.0, target.y / 100.0, targetUiMapId) + if not targetX or not targetY or not targetInstance then + self.distance:SetText("Distance: --") + return + end + else + -- Different maps: convert target to world coords using its uiMapId (works for 1941). + -- Player is already in world coords from UnitPosition via pX/pY. + local tWX, tWY, tInst = HBD:GetWorldCoordinatesFromZone(target.x / 100.0, target.y / 100.0, targetUiMapId) + if tWX and tWY then + targetX, targetY, targetInstance = tWX, tWY, tInst or pInst or 0 + else + -- HBD failed too: use raw map coords as last resort + targetX, targetY = target.x, target.y + targetInstance = pInst or 0 + end + end + + -- If targetX is still nil at this point, bail out + if not targetX or not targetY then + self.distance:SetText("Distance: --") + return + end + + -- Use world coords for direction: both player and target must be in world coordinate space. + -- UnitPosition("player") gives world coords directly. For same-uiMapId: pX/pY from + -- UpdateNearestTargets are world coords from UnitPosition — use directly. + local worldPlayerX, worldPlayerY + if playerWorldX then + worldPlayerX, worldPlayerY = playerWorldX, playerWorldY + else + worldPlayerX, worldPlayerY = pX, pY + end + +-- targetX/Y are already world coords from the same-map or cross-map branch above + if not targetX or not targetY then + self.distance:SetText("Distance: --") + return + end + + local xDelta = (worldPlayerX - targetX) * 1.5 + local yDelta = (worldPlayerY - targetY) local angle = atan2(xDelta, -(yDelta)) angle = angle > 0 and (pi * 2) - angle or -angle if angle < 0 then angle = angle + (pi * 2) end @@ -325,9 +391,14 @@ local function EnsureArrowFrame() xend = xend - padX yend = yend - padY - -- Calculate distance and alpha - local dist = HBD:GetWorldDistance(targetInstance, playerX, playerY, targetX, targetY) +-- Calculate distance and alpha + -- worldPlayerX/Y are in world coords (converted from cross-map or same-map path) + local dist = HBD:GetWorldDistance(targetInstance, worldPlayerX, worldPlayerY, targetX, targetY) if dist then + local debugArrow = Questie and Questie.db and Questie.db.profile and Questie.db.profile.debugArrow + if debugArrow then + print(string.format("QuestieArrow OnUpdate: dist=%.1f worldPlayerX=%.1f worldPlayerY=%.1f targetX=%.1f targetY=%.1f targetInst=%s title='%s'", dist, worldPlayerX, worldPlayerY, targetX, targetY, tostring(targetInstance), tostring(target.title))) + end local area = 1 local alpha = dist - area alpha = alpha > 1 and 1 or alpha @@ -574,24 +645,88 @@ function QuestieArrow:UpdateNearestTargets() return end - sortedTargets = {} +sortedTargets = {} if not Questie.db or not Questie.db.char then return end + local debugArrow = Questie and Questie.db and Questie.db.profile and Questie.db.profile.debugArrow + + -- Get player position — first try HBD's direct method (works when map is OPEN). + -- If that returns nil (map closed), fall back to C_Map.GetPlayerMapPosition + + -- HBD:GetWorldCoordinatesFromZone which works regardless of map open/closed state. local playerX, playerY, playerInstance = HBD:GetPlayerWorldPosition() +if not playerX or not playerY or not playerInstance then + -- Fallback: get map-relative position then convert to world coords via HBD. + -- Use the player's current uiMapId as the map basis for the conversion. + -- IMPORTANT: never use 946 (ghost window/world map) — it has no world coord data. + -- If GetCurrentUiMapId returns 946, fall back to ZoneDB from the actual zone. + local pUiMapId = QuestiePlayer:GetCurrentUiMapId() + if not pUiMapId or pUiMapId == 947 or pUiMapId == 0 or pUiMapId == 946 then + local zoneId = QuestiePlayer:GetCurrentZoneId() or select(7, GetInstanceInfo()) + if debugArrow then + print(string.format("UpdateNearestTargets: pUiMapId=%s (invalid), looking up via zoneId=%s", tostring(pUiMapId), tostring(zoneId))) + end + if zoneId then + pUiMapId = ZoneDB:GetUiMapIdByAreaId(zoneId) + end + end + pUiMapId = pUiMapId or 0 + + if debugArrow then + print(string.format("UpdateNearestTargets: trying C_Map with pUiMapId=%s", tostring(pUiMapId))) + end + + local mapX, mapY = C_Map.GetPlayerMapPosition(pUiMapId, "player") + if debugArrow then + print(string.format("UpdateNearestTargets: C_Map.GetPlayerMapPosition(%s,'player') -> mapX=%.4f mapY=%.4f", tostring(pUiMapId), mapX or -1, mapY or -1)) + end + if mapX and mapY and mapX > 0 and mapY > 0 then + playerX, playerY, playerInstance = HBD:GetWorldCoordinatesFromZone(mapX, mapY, pUiMapId) + playerInstance = playerInstance or 0 + if debugArrow then + print(string.format("UpdateNearestTargets: HBD fallback via mapId=%d mapX=%.4f mapY=%.4f -> worldX=%.4f worldY=%.4f", + pUiMapId, mapX, mapY, playerX or 0, playerY or 0)) + end + else + if debugArrow then + print(string.format("UpdateNearestTargets: C_Map.GetPlayerMapPosition returned invalid coords (%.4f, %.4f), mapId=%s", mapX or 0, mapY or 0, tostring(pUiMapId))) + end + end + end + if not playerX or not playerY or not playerInstance then + if debugArrow then + print("UpdateNearestTargets: player position unavailable, returning early") + end return end + playerInstance = playerInstance or 0 + + if debugArrow then + print(string.format("UpdateNearestTargets: playerX=%.4f playerY=%.4f playerInstance=%s", + playerX, playerY, tostring(playerInstance))) + end + local tracked = Questie.db.char.TrackedQuests or {} local hasTracked = next(tracked) ~= nil -- Auto mode logic: If autoTrack is on OR NOTHING is tracked local usingAutoLogic = Questie.db.profile.autoTrackQuests or not hasTracked local playerZoneId = QuestiePlayer:GetCurrentZoneId() + -- Get a valid uiMapId for the player — needed for _CollectObjective zone filtering. + -- Use QuestiePlayer which calls C_Map.GetBestMapForUnit — if that returns 947 (wrong) + -- fall back to ZoneDB from the player's actual zone (areaId). local playerUiMapId = QuestiePlayer:GetCurrentUiMapId() + if not playerUiMapId or playerUiMapId == 947 then + local zoneId = playerZoneId or select(7, GetInstanceInfo()) + if zoneId then + playerUiMapId = ZoneDB:GetUiMapIdByAreaId(zoneId) or playerUiMapId + end + end + playerUiMapId = playerUiMapId or 0 -- Publish context for hoisted helper functions (avoids closure allocation every call) _arrow_playerX, _arrow_playerY, _arrow_playerInstance = playerX, playerY, playerInstance diff --git a/Modules/Tooltips/Tooltip.lua b/Modules/Tooltips/Tooltip.lua index 593158c..56ca3fc 100644 --- a/Modules/Tooltips/Tooltip.lua +++ b/Modules/Tooltips/Tooltip.lua @@ -232,93 +232,113 @@ function QuestieTooltips:GetTooltip(key) -- Try to find in learned NPCs or objects local id = tonumber(key:sub(3)) if id then - if key:sub(1,2) == "m_" then - local learnedNpc = QuestieLearner.data.npcs[id] - if learnedNpc and learnedNpc[10] then -- check questObjectives - for questId, objList in next, learnedNpc[10] do - local oIndex = 1 - while objList[oIndex] do - local objText = objList[oIndex] - local needed, collected - local objectives = QuestLogCache.GetQuestObjectives(questId) - if objectives then - for _, obj in next, objectives do - if obj.text and objText and (obj.text == objText or string.find(obj.text, objText, 1, true) or string.find(objText, obj.text, 1, true)) then - needed = obj.numRequired - collected = obj.numFulfilled - break - end - end - end - QuestieTooltips:RegisterObjectiveTooltip(questId, key, { - Index = 0, - Description = objText, - Needed = needed, - Collected = collected, - Update = function(self) - local objs = QuestLogCache.GetQuestObjectives(questId) - if objs then - for _, o in next, objs do - if o.text and self.Description and (o.text == self.Description or string.find(o.text, self.Description, 1, true) or string.find(self.Description, o.text, 1, true)) then - self.Needed = o.numRequired - self.Collected = o.numFulfilled - break - end - end - end - end - }) - oIndex = oIndex + 1 - end - end - if learnedNpc.mc then - tinsert(tooltipLines, "|cFF5EBAF3(Learned - Confidence: " .. tostring(learnedNpc.mc) .. ")|r") - end - end - elseif key:sub(1,2) == "o_" then - local learnedObj = QuestieLearner.data.objects[id] - if learnedObj and learnedObj[10] then - for questId, objList in next, learnedObj[10] do - local oIndex = 1 - while objList[oIndex] do - local objText = objList[oIndex] - local needed, collected - local objectives = QuestLogCache.GetQuestObjectives(questId) - if objectives then - for _, obj in next, objectives do - if obj.text and objText and (obj.text == objText or string.find(obj.text, objText, 1, true) or string.find(objText, obj.text, 1, true)) then - needed = obj.numRequired - collected = obj.numFulfilled - break - end - end - end - QuestieTooltips:RegisterObjectiveTooltip(questId, key, { - Index = 0, - Description = objText, - Needed = needed, - Collected = collected, - Update = function(self) - local objs = QuestLogCache.GetQuestObjectives(questId) - if objs then - for _, o in next, objs do - if o.text and self.Description and (o.text == self.Description or string.find(o.text, self.Description, 1, true) or string.find(self.Description, o.text, 1, true)) then - self.Needed = o.numRequired - self.Collected = o.numFulfilled - break - end - end - end - end - }) - oIndex = oIndex + 1 - end - end - if learnedObj.mc then - tinsert(tooltipLines, "|cFF5EBAF3(Learned - Confidence: " .. tostring(learnedObj.mc) .. ")|r") - end - end - end +if key:sub(1,2) == "m_" then + local learnedNpc = QuestieLearner.data.npcs[id] + -- npc[10] from _AddToArray is an array of questIds, not {questId -> objList} + if learnedNpc and learnedNpc[10] then + for _, questId in ipairs(learnedNpc[10]) do + local qData = QuestieLearner.data.quests[questId] + if qData and qData[10] then + for slotIdx = 1, #qData[10] do + local objSlot = qData[10][slotIdx] + if objSlot then + for oIndex = 1, #objSlot do + local objEntry = objSlot[oIndex] + if objEntry and objEntry[2] then + local objText = objEntry[2] + local needed, collected + local objectives = QuestLogCache.GetQuestObjectives(questId) + if objectives then + for _, obj in next, objectives do + if obj.text and objText and (obj.text == objText or string.find(obj.text, objText, 1, true) or string.find(objText, obj.text, 1, true)) then + needed = obj.numRequired + collected = obj.numFulfilled + break + end + end + end + QuestieTooltips:RegisterObjectiveTooltip(questId, key, { + Index = 0, + Description = objText, + Needed = needed, + Collected = collected, + Update = function(self) + local objs = QuestLogCache.GetQuestObjectives(questId) + if objs then + for _, o in next, objs do + if o.text and self.Description and (o.text == self.Description or string.find(o.text, self.Description, 1, true) or string.find(self.Description, o.text, 1, true)) then + self.Needed = o.numRequired + self.Collected = o.numFulfilled + break + end + end + end + end + }) + end + end + end + end + end + end + if learnedNpc.mc then + tinsert(tooltipLines, "|cFF5EBAF3(Learned - Confidence: " .. tostring(learnedNpc.mc) .. ")|r") + end + end +elseif key:sub(1,2) == "o_" then + local learnedObj = QuestieLearner.data.objects[id] + -- obj[2] from _AddToArray is an array of questIds (questStarts), not {questId -> objList} + if learnedObj and learnedObj[2] then + for _, questId in ipairs(learnedObj[2]) do + local qData = QuestieLearner.data.quests[questId] + if qData and qData[10] then + for slotIdx = 1, #qData[10] do + local objSlot = qData[10][slotIdx] + if objSlot then + for oIndex = 1, #objSlot do + local objEntry = objSlot[oIndex] + if objEntry and objEntry[2] then + local objText = objEntry[2] + local needed, collected + local objectives = QuestLogCache.GetQuestObjectives(questId) + if objectives then + for _, obj in next, objectives do + if obj.text and objText and (obj.text == objText or string.find(obj.text, objText, 1, true) or string.find(objText, obj.text, 1, true)) then + needed = obj.numRequired + collected = obj.numFulfilled + break + end + end + end + QuestieTooltips:RegisterObjectiveTooltip(questId, key, { + Index = 0, + Description = objText, + Needed = needed, + Collected = collected, + Update = function(self) + local objs = QuestLogCache.GetQuestObjectives(questId) + if objs then + for _, o in next, objs do + if o.text and self.Description and (o.text == self.Description or string.find(o.text, self.Description, 1, true) or string.find(self.Description, o.text, 1, true)) then + self.Needed = o.numRequired + self.Collected = o.numFulfilled + break + end + end + end + end + }) + end + end + end + end + end + end + if learnedObj.mc then + tinsert(tooltipLines, "|cFF5EBAF3(Learned - Confidence: " .. tostring(learnedObj.mc) .. ")|r") + end + end + end end end end