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.
This commit is contained in:
@@ -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
|
||||
|
||||
+153
-18
@@ -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
|
||||
|
||||
+107
-87
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user