1c0a891efd
The cutoff hid icons beyond its yard value before checking the minimap's visible radius, so the default 100 hid icons that were clearly on the minimap (view radius is 133-466 yd by zoom). Now it only clips icons outside the visible circle: HBD's pin renderer gates the cutoff on dist > 1, and QuestieMap FadeLogic raises the effective cutoff to at least the current minimap view radius via new HBDPins:GetMinimapRadius().
1199 lines
46 KiB
Lua
1199 lines
46 KiB
Lua
---@type QuestieMap
|
|
local QuestieMap = QuestieLoader:ImportModule("QuestieMap")
|
|
|
|
local mapData = QuestieCompat.UiMapData -- table { width, height, left, top, .instance, .name, .mapType }
|
|
local worldMapData = QuestieCompat.worldMapData -- table { width, height, left, top }
|
|
|
|
-- Keep a reference to the real HBD library (if available) so we can fall back to its map data
|
|
-- for maps not present in Questie's UiMapData (e.g., custom Ascension maps).
|
|
local RealHBD
|
|
|
|
-- Try to load the real HBD library under several known names, and if that fails,
|
|
-- scan the global environment for any table that looks like an HBD library (has .mapData).
|
|
local function LoadRealHBD()
|
|
-- Common library identifiers
|
|
for _, name in ipairs({"HereBeDragonsQuestie-2.0", "HereBeDragons-2.0", "HereBeDragons"}) do
|
|
local ok, lib = pcall(LibStub, name, true)
|
|
if ok and lib and lib.mapData then
|
|
return lib
|
|
end
|
|
end
|
|
-- Fallback: brute-force global scan for a table with a mapData field
|
|
-- Use pcall protection because Ascension's client may have protected globals.
|
|
local ok, _ = pcall(function()
|
|
for _, v in pairs(_G) do
|
|
if type(v) == "table" and v.mapData and type(v.mapData) == "table" then
|
|
RealHBD = v
|
|
error("_found") -- break out of pcall early
|
|
end
|
|
end
|
|
end)
|
|
-- RealHBD was set inside the pcall if found; otherwise stays nil
|
|
return RealHBD
|
|
end
|
|
|
|
-- Eager attempt at load; will also lazy-load on first use.
|
|
-- Wrap in pcall so HBD.lua doesn't fail to load if the global scan hits a protected table.
|
|
pcall(function()
|
|
RealHBD = LoadRealHBD()
|
|
end)
|
|
|
|
local HBD = {mapData = mapData}
|
|
QuestieCompat.HBD = HBD
|
|
|
|
-- Ascension zone remapping: Sunstrider Isle (1241) and its ghost map (946) share
|
|
-- Eversong Woods' (1941) rendered area on Ascension's client. On Ascension,
|
|
-- the Sunstrider Isle map actually uses Eversong's coordinate space — not retail's
|
|
-- separate Sunstrider bounds. This means:
|
|
-- 1. Visibility: zones sharing the same space should show each other's pins
|
|
-- 2. Bounds: mapData[1241] must use Eversong's bounds so coordinate round-trips
|
|
-- produce correct positions on Ascension's Sunstrider map
|
|
-- 3. Data: Questie spawn coords for Sunstrider (uiMapId=1241) are in the
|
|
-- 33-38%/18-25% range, which maps to Sunstrider's location WITHIN Eversong
|
|
--
|
|
-- ZONE_REDIRECT: used by ResolveZone() for visibility logic (isSameZoneSpace).
|
|
-- NOTE: 1241 is NOT redirected to 1941. Map 1241 (Sunstrider) has its own
|
|
-- calibrated bounds that produce a different world coordinate space than
|
|
-- Eversong. Cross-map pin visibility (1241 pin showing on 1941 map, and vice
|
|
-- versa) works because QuestieMap.lua's DrawWorldIcon uses _ResolveMapUiMapId()
|
|
-- to register pins against the resolved parent map, and HBD's mapData[1241]
|
|
-- shares Eversong's bounds on Ascension — so the simplified isSameZoneSpace
|
|
-- check (effectiveUiMapID == effectiveDataUiMapID) passes for both maps.
|
|
-- Do not remove 1241 from the redirect table or split its bounds from 1941
|
|
-- without also re-introducing the sharesSunstriderSpace chain.
|
|
local ZONE_REDIRECT = {
|
|
[946] = 1941, -- Ghost/transition map -> Eversong Woods (for Sunstrider loading)
|
|
}
|
|
|
|
--- Resolve a zone ID through the redirect table.
|
|
--- If the zone has a redirect, return the target zone; otherwise return the zone as-is.
|
|
local function ResolveZone(zone)
|
|
return ZONE_REDIRECT[zone] or zone
|
|
end
|
|
|
|
-- Expose for external use (e.g. QuestieCompat coordinate calibration)
|
|
HBD.ResolveZone = ResolveZone
|
|
|
|
-- Ascension bounds override: On Ascension, Sunstrider Isle (1241) uses Eversong
|
|
-- Woods' coordinate space, not retail's separate Sunstrider bounds. The retail
|
|
-- mapData[1241] bounds (510, 500, -6983.33, 9766.67) are incompatible with
|
|
-- Ascension's world coordinates — UnitPosition returns Eversong-scale values
|
|
-- like (503.4, 267.9) which produce zone coords of (-14.68, 18.99) when converted
|
|
-- through retail Sunstrider bounds. Using Eversong's bounds makes coordinate
|
|
-- round-trips work correctly and positions pins at their actual locations.
|
|
--
|
|
-- Ghost map 946 has all-zeros bounds in retail, which makes it unusable; redirect
|
|
-- to Eversong bounds as well (it shares the same rendered area on Ascension).
|
|
local ASCENSION_ZONE_BOUNDS = {
|
|
[1241] = { 1600.0, 1066.666666666667, -2721.0066, 8433.9360 }, -- Calibrated bounds for Sunstrider Isle
|
|
[946] = { 4925.0, 3283.33333, -1824.6778, 8641.6666 }, -- Ghost map shares Eversong geometry
|
|
}
|
|
|
|
|
|
-- Apply Ascension bounds overrides immediately
|
|
local _boundsApplied = false
|
|
local function ApplyAscensionBounds()
|
|
if _boundsApplied then return end
|
|
_boundsApplied = true
|
|
|
|
if not RealHBD then
|
|
pcall(function() RealHBD = LoadRealHBD() end)
|
|
end
|
|
|
|
for zoneId, bounds in pairs(ASCENSION_ZONE_BOUNDS) do
|
|
local data = mapData[zoneId]
|
|
if data then
|
|
-- [HBD-Ascension] bounds override applied (debug disabled)
|
|
data[1], data[2], data[3], data[4] = bounds[1], bounds[2], bounds[3], bounds[4]
|
|
else
|
|
-- [HBD-Ascension] WARNING: No mapData for zone (debug disabled)
|
|
end
|
|
|
|
-- Override the real HBD map data as well, because HBD-Pins bypasses QuestieCompat
|
|
if RealHBD and RealHBD.mapData and RealHBD.mapData[zoneId] then
|
|
local rData = RealHBD.mapData[zoneId]
|
|
rData[1], rData[2], rData[3], rData[4] = bounds[1], bounds[2], bounds[3], bounds[4]
|
|
end
|
|
end
|
|
end
|
|
ApplyAscensionBounds()
|
|
|
|
-- One-shot debug: print mapData bounds for key zones on PLAYER_LOGIN
|
|
local _boundsDebugPrinted = false
|
|
local function PrintBoundsDebug()
|
|
if _boundsDebugPrinted then return end
|
|
_boundsDebugPrinted = true
|
|
ApplyAscensionBounds()
|
|
for _, id in ipairs({1241, 946, 1941}) do
|
|
local d = mapData[id]
|
|
if d then
|
|
-- [HBD-Bounds] debug disabled
|
|
else
|
|
-- [HBD-Bounds] no mapData (debug disabled)
|
|
end
|
|
end
|
|
end
|
|
local f = CreateFrame("Frame")
|
|
f:RegisterEvent("PLAYER_LOGIN")
|
|
f:SetScript("OnEvent", function(self, event)
|
|
PrintBoundsDebug()
|
|
self:UnregisterEvent(event)
|
|
end)
|
|
|
|
--- Convert local/point coordinates to world coordinates in yards
|
|
--- @param x X position in 0-1 point coordinates
|
|
--- @param y Y position in 0-1 point coordinates
|
|
--- @param zone uiMapID of the zone
|
|
function HBD:GetWorldCoordinatesFromZone(x, y, zone)
|
|
-- Ascension: mapData[946] has been overridden with Eversong bounds.
|
|
-- mapData[1241] has its own calibrated bounds that match the game engine's
|
|
-- Sunstrider coordinate space. Both convert through their own bounds —
|
|
-- no redirect needed here because _ResolveMapUiMapId passes 1241 through.
|
|
local data = mapData[zone]
|
|
if not data or data[1] == 0 or data[2] == 0 then
|
|
-- Attempt to lazy-load the real HBD if we haven't yet
|
|
if not RealHBD then
|
|
pcall(function() RealHBD = LoadRealHBD() end)
|
|
end
|
|
if RealHBD and RealHBD.mapData then
|
|
data = RealHBD.mapData[zone]
|
|
end
|
|
if not data or data[1] == 0 or data[2] == 0 then
|
|
return nil, nil, nil
|
|
end
|
|
end
|
|
if not x or not y then return nil, nil, nil end
|
|
|
|
local width, height, left, top = data[1], data[2], data[3], data[4]
|
|
x, y = left - width * x, top - height * y
|
|
|
|
return x, y, data.instance
|
|
end
|
|
|
|
--- Convert world coordinates to local/point zone coordinates
|
|
--- @param x Global X position
|
|
--- @param y Global Y position
|
|
--- @param zone uiMapID of the zone
|
|
--- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned
|
|
function HBD:GetZoneCoordinatesFromWorld(x, y, zone, allowOutOfBounds)
|
|
-- Ascension: mapData[946] has been overridden with Eversong bounds.
|
|
-- mapData[1241] has its own calibrated bounds matching the engine's space.
|
|
-- No redirect needed — callers pass the correct zone directly.
|
|
local data = mapData[zone]
|
|
if not data or data[1] == 0 or data[2] == 0 then
|
|
if not RealHBD then
|
|
pcall(function() RealHBD = LoadRealHBD() end)
|
|
end
|
|
if RealHBD and RealHBD.mapData then
|
|
data = RealHBD.mapData[zone]
|
|
end
|
|
if not data or data[1] == 0 or data[2] == 0 then
|
|
return nil, nil
|
|
end
|
|
end
|
|
if not x or not y then return nil, nil end
|
|
|
|
local width, height, left, top = data[1], data[2], data[3], data[4]
|
|
x, y = (left - x) / width, (top - y) / height
|
|
|
|
-- verify the coordinates fall into the zone
|
|
if not allowOutOfBounds and (x < 0 or x > 1 or y < 0 or y > 1) then return nil, nil end
|
|
|
|
return x, y
|
|
end
|
|
|
|
--- Convert world coordinates to local/point zone coordinates on the azeroth world map
|
|
-- @param x Global X position
|
|
-- @param y Global Y position
|
|
-- @param instance Instance to translate coordinates from
|
|
-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned
|
|
function HBD:GetAzerothWorldMapCoordinatesFromWorld(x, y, instance, allowOutOfBounds)
|
|
local data = worldMapData[instance]
|
|
if not data or data[1] == 0 or data[2] == 0 then return nil, nil end
|
|
if not x or not y then return nil, nil end
|
|
|
|
local width, height, left, top = data[1], data[2], data[3], data[4]
|
|
x, y = (left - x) / width, (top - y) / height
|
|
|
|
-- verify the coordinates fall into the zone
|
|
if not allowOutOfBounds and (x < 0 or x > 1 or y < 0 or y > 1) then return nil, nil end
|
|
|
|
return x, y
|
|
end
|
|
|
|
--- Return the distance from an origin position to a destination position in the same instance/continent.
|
|
-- @param instanceID instance ID
|
|
-- @param oX origin X
|
|
-- @param oY origin Y
|
|
-- @param dX destination X
|
|
-- @param dY destination Y
|
|
-- @return distance, deltaX, deltaY
|
|
function HBD:GetWorldDistance(instanceID, oX, oY, dX, dY)
|
|
if not oX or not oY or not dX or not dY then return nil, nil, nil end
|
|
local deltaX, deltaY = dX - oX, dY - oY
|
|
return (deltaX * deltaX + deltaY * deltaY)^0.5, deltaX, deltaY
|
|
end
|
|
|
|
--- Return the distance between two points on the same continent
|
|
-- @param oZone origin zone uiMapID
|
|
-- @param oX origin X, in local zone/point coordinates
|
|
-- @param oY origin Y, in local zone/point coordinates
|
|
-- @param dZone destination zone uiMapID
|
|
-- @param dX destination X, in local zone/point coordinates
|
|
-- @param dY destination Y, in local zone/point coordinates
|
|
-- @return distance, deltaX, deltaY in yards
|
|
function HBD:GetZoneDistance(oZone, oX, oY, dZone, dX, dY)
|
|
local oInstance, dInstance
|
|
oX, oY, oInstance = self:GetWorldCoordinatesFromZone(oX, oY, oZone)
|
|
if not oX then return nil, nil, nil end
|
|
|
|
-- translate dX, dY to the origin zone
|
|
dX, dY, dInstance = self:GetWorldCoordinatesFromZone(dX, dY, dZone)
|
|
if not dX then return nil, nil, nil end
|
|
|
|
if oInstance ~= dInstance then return nil, nil, nil end
|
|
|
|
return self:GetWorldDistance(oInstance, oX, oY, dX, dY)
|
|
end
|
|
|
|
-- Position cache: avoid hammering the C API more than 20 times per second.
|
|
-- These are invalidated on zone-change events below.
|
|
local _pos_cache_interval = 0.05
|
|
local _pzp_x, _pzp_y, _pzp_mapID, _pzp_time = nil, nil, nil, 0
|
|
local _pwp_x, _pwp_y, _pwp_inst, _pwp_time = nil, nil, nil, 0
|
|
|
|
--- Get the current world position of the player
|
|
-- The position is transformed to the current continent, if applicable
|
|
-- @return x, y, instanceID
|
|
function HBD:GetPlayerWorldPosition()
|
|
local now = GetTime()
|
|
if now - _pwp_time < _pos_cache_interval then
|
|
return _pwp_x, _pwp_y, _pwp_inst
|
|
end
|
|
|
|
local x, y, uiMapID = HBD:GetPlayerZonePosition()
|
|
if not x or not y then
|
|
_pwp_x, _pwp_y, _pwp_inst = nil, nil, nil
|
|
_pwp_time = now
|
|
return nil, nil, nil
|
|
end
|
|
|
|
local wx, wy, inst = HBD:GetWorldCoordinatesFromZone(x, y, uiMapID)
|
|
_pwp_x, _pwp_y, _pwp_inst = wx, wy, inst
|
|
_pwp_time = now
|
|
if wx and wy then
|
|
return wx, wy, inst
|
|
end
|
|
return nil, nil, nil
|
|
end
|
|
|
|
--- Get the current zone and level of the player
|
|
-- The returned mapFile can represent a micro dungeon, if the player currently is inside one.
|
|
-- @return uiMapID, mapType
|
|
function HBD:GetPlayerZone()
|
|
return QuestieCompat.GetCurrentPlayerPosition()
|
|
end
|
|
|
|
--- Get the current position of the player on a zone level
|
|
-- The returned values are local point coordinates, 0-1. The mapFile can represent a micro dungeon.
|
|
-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned
|
|
-- @return x, y, uiMapID, mapType
|
|
function HBD:GetPlayerZonePosition(allowOutOfBounds)
|
|
local now = GetTime()
|
|
if now - _pzp_time < _pos_cache_interval then
|
|
return _pzp_x, _pzp_y, _pzp_mapID
|
|
end
|
|
|
|
local uiMapID, x, y = QuestieCompat.GetCurrentPlayerPosition()
|
|
_pzp_x, _pzp_y, _pzp_mapID = x, y, uiMapID
|
|
_pzp_time = now
|
|
|
|
if uiMapID and x and y then
|
|
return x, y, uiMapID
|
|
end
|
|
return nil, nil, nil, nil
|
|
end
|
|
|
|
local function _InvalidatePositionCache()
|
|
_pzp_time = 0
|
|
_pwp_time = 0
|
|
end
|
|
|
|
-- Data Constants
|
|
local WORLD_MAP_ID = 947
|
|
|
|
-- upvalue lua api
|
|
local cos, sin, max = math.cos, math.sin, math.max
|
|
local type, pairs = type, pairs
|
|
|
|
-- upvalue wow api
|
|
local GetPlayerFacing = GetPlayerFacing
|
|
|
|
-- storage for minimap pins
|
|
local minimapPins = {}
|
|
local activeMinimapPins = {}
|
|
local minimapPinRegistry = {}
|
|
|
|
-- and worldmap pins
|
|
local worldmapPins = {}
|
|
local worldmapPinRegistry = {}
|
|
|
|
-- 3.3.5a: C_Minimap.GetViewRadius does NOT exist. Use Minimap:GetViewRadius() (native 3.3.5a API).
|
|
-- C_Minimap path is kept as secondary fallback in case some custom client adds it.
|
|
local MinimapRadiusAPI = (C_Minimap and C_Minimap.GetViewRadius) or Minimap.GetViewRadius
|
|
|
|
local pins = {
|
|
Minimap = Minimap,
|
|
updateFrame = CreateFrame("Frame"),
|
|
activeMinimapPins = activeMinimapPins,
|
|
worldmapPins = worldmapPins,
|
|
worldmapProvider = {
|
|
GetMap = function(self) return QuestieCompat.WorldMapFrame end,
|
|
},
|
|
}
|
|
QuestieCompat.HBDPins = pins
|
|
|
|
local minimap_size = {
|
|
indoor = {
|
|
[0] = 300, -- scale
|
|
[1] = 240, -- 1.25
|
|
[2] = 180, -- 5/3
|
|
[3] = 120, -- 2.5
|
|
[4] = 80, -- 3.75
|
|
[5] = 50, -- 6
|
|
},
|
|
outdoor = {
|
|
-- Ascension (Warcraft Reborn) calibrated values.
|
|
-- Zoom 5 base value 250.
|
|
-- Each lower zoom level multiplies by 1.2 (standard WoW zoom step ratio).
|
|
[0] = 622.08, -- zoom5 * 1.2^5
|
|
[1] = 518.40, -- zoom5 * 1.2^4
|
|
[2] = 432.00, -- zoom5 * 1.2^3
|
|
[3] = 360.00, -- zoom5 * 1.2^2
|
|
[4] = 300.00, -- zoom5 * 1.2^1
|
|
[5] = 250.00, -- base value
|
|
},
|
|
}
|
|
|
|
---@class MinimapShapes
|
|
local minimap_shapes = {
|
|
-- { upper-left, lower-left, upper-right, lower-right }
|
|
["SQUARE"] = { false, false, false, false },
|
|
["CORNER-TOPLEFT"] = { true, false, false, false },
|
|
["CORNER-TOPRIGHT"] = { false, false, true, false },
|
|
["CORNER-BOTTOMLEFT"] = { false, true, false, false },
|
|
["CORNER-BOTTOMRIGHT"] = { false, false, false, true },
|
|
["SIDE-LEFT"] = { true, true, false, false },
|
|
["SIDE-RIGHT"] = { false, false, true, true },
|
|
["SIDE-TOP"] = { true, false, true, false },
|
|
["SIDE-BOTTOM"] = { false, true, false, true },
|
|
["TRICORNER-TOPLEFT"] = { true, true, true, false },
|
|
["TRICORNER-TOPRIGHT"] = { true, false, true, true },
|
|
["TRICORNER-BOTTOMLEFT"] = { true, true, false, true },
|
|
["TRICORNER-BOTTOMRIGHT"] = { false, true, true, true },
|
|
}
|
|
|
|
local tableCache = setmetatable({}, {__mode='k'})
|
|
|
|
local function newCachedTable()
|
|
local t = next(tableCache)
|
|
if t then
|
|
tableCache[t] = nil
|
|
else
|
|
t = {}
|
|
end
|
|
return t
|
|
end
|
|
|
|
local function recycle(t)
|
|
tableCache[t] = true
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------
|
|
-- Minimap pin position logic
|
|
|
|
-- minimap rotation
|
|
local rotateMinimap = GetCVar("rotateMinimap") == "1"
|
|
|
|
-- is the minimap indoors or outdoors
|
|
local indoors = GetCVar("minimapZoom")+0 == pins.Minimap:GetZoom() and "outdoor" or "indoor"
|
|
|
|
local minimapPinCount, queueFullUpdate = 0, false
|
|
---@type unknown, MinimapShapes?
|
|
local minimapScale, minimapShape, mapRadius, minimapWidth, minimapHeight, mapSin, mapCos
|
|
local lastZoom, lastFacing, lastXY, lastYY
|
|
|
|
local function drawMinimapPin(pin, data)
|
|
local xDist, yDist = lastXY - data.x, lastYY - data.y
|
|
|
|
-- handle rotation
|
|
if rotateMinimap then
|
|
local dx, dy = xDist, yDist
|
|
xDist = dx*mapCos - dy*mapSin
|
|
yDist = dx*mapSin + dy*mapCos
|
|
end
|
|
|
|
-- adapt delta position to the map radius
|
|
local diffX = xDist / mapRadius
|
|
local diffY = yDist / mapRadius
|
|
|
|
-- different minimap shapes
|
|
---@type boolean|number
|
|
local isRound = true
|
|
if minimapShape and not (xDist == 0 or yDist == 0) then
|
|
isRound = (xDist < 0) and 1 or 3
|
|
if yDist < 0 then
|
|
isRound = minimapShape[isRound]
|
|
else
|
|
isRound = minimapShape[isRound + 1]
|
|
end
|
|
end
|
|
|
|
-- calculate distance from the center of the map
|
|
local dist
|
|
if isRound then
|
|
dist = (diffX*diffX + diffY*diffY) / 0.9^2
|
|
else
|
|
dist = max(diffX*diffX, diffY*diffY) / 0.9^2
|
|
end
|
|
|
|
-- if distance > 1, then adapt node position to slide on the border
|
|
if dist > 1 and data.floatOnEdge then
|
|
dist = dist^0.5
|
|
diffX = diffX/dist
|
|
diffY = diffY/dist
|
|
end
|
|
|
|
-- Questie Modification.
|
|
-- data.floatOnEdge is replaced by (data.floatOnEdge and ((pin.texture and pin.texture.a and pin.texture.a ~= 0) or pin.texture == nil))
|
|
-- icons will now only float on edge if they have an opacity which is not 0 or if no texture exist.
|
|
data.distanceFromMinimapCenter = dist
|
|
-- Minimap range cutoff: only clamp icons that fall OUTSIDE the minimap's visible
|
|
-- radius (dist > 1, i.e. edge-floating icons). Icons that are actually within the
|
|
-- visible minimap circle must always show, otherwise the cutoff hides quest icons
|
|
-- that are clearly on the minimap whenever its view radius (133-466 yd depending on
|
|
-- zoom) exceeds the cutoff yard value — the regression reported in #17. The cutoff
|
|
-- then only limits how far edge-floating icons reach for far-apart objectives.
|
|
local minimapVisibilityCutoff = pin.minimapVisibilityCutoff
|
|
or (Questie and Questie.db and Questie.db.profile and Questie.db.profile.minimapIconRangeCutoff)
|
|
if dist > 1 and minimapVisibilityCutoff and lastXY and lastYY and data.x and data.y then
|
|
local xd, yd = lastXY - data.x, lastYY - data.y
|
|
local distance = (xd * xd + yd * yd)^0.5
|
|
if distance > minimapVisibilityCutoff then
|
|
pin:Hide()
|
|
data.onEdge = nil
|
|
data.keep = nil
|
|
return
|
|
end
|
|
end
|
|
|
|
if dist <= 1 or (data.floatOnEdge and ((pin.texture and pin.texture.a and pin.texture.a ~= 0) or pin.texture == nil)) then
|
|
pin:Show()
|
|
pin:ClearAllPoints()
|
|
pin:SetPoint("CENTER", pins.Minimap, "CENTER", diffX * minimapWidth, -diffY * minimapHeight)
|
|
data.onEdge = (dist > 1)
|
|
else
|
|
pin:Hide()
|
|
data.onEdge = nil
|
|
pin.keep = nil
|
|
end
|
|
end
|
|
|
|
local function _GetEffectiveMinimapPlayerWorldPosition()
|
|
-- For minimap pins we MUST use HBD's native world coordinate space.
|
|
-- Minimap pins store their positions in HBD world coords, so the player
|
|
-- position must also be in HBD world coords. The calibrated pseudo-world
|
|
-- space (used by the arrow) has a different origin/scale on Ascension and
|
|
-- must never be mixed with minimap pin positions.
|
|
return HBD:GetPlayerWorldPosition()
|
|
end
|
|
|
|
local function UpdateMinimapPins(force)
|
|
-- get the current player position
|
|
local x, y, instanceID = _GetEffectiveMinimapPlayerWorldPosition()
|
|
|
|
-- get data from the API for calculations
|
|
local zoom = pins.Minimap:GetZoom()
|
|
local diffZoom = zoom ~= lastZoom
|
|
|
|
-- for rotating minimap support
|
|
local facing
|
|
if rotateMinimap then
|
|
facing = GetPlayerFacing()
|
|
else
|
|
facing = lastFacing
|
|
end
|
|
|
|
-- check for all values to be available (starting with 7.1.0, instances don't report coordinates)
|
|
if not x or not y or (rotateMinimap and not facing) then
|
|
minimapPinCount = 0
|
|
for pin in pairs(activeMinimapPins) do
|
|
pin:Hide()
|
|
activeMinimapPins[pin] = nil
|
|
end
|
|
return
|
|
end
|
|
|
|
local newScale = pins.Minimap:GetScale()
|
|
if minimapScale ~= newScale then
|
|
minimapScale = newScale
|
|
force = true
|
|
end
|
|
|
|
if x ~= lastXY or y ~= lastYY or diffZoom or facing ~= lastFacing or force then
|
|
-- minimap information
|
|
minimapShape = GetMinimapShape and minimap_shapes[GetMinimapShape() or "ROUND"]
|
|
-- Compute mapRadius and width/height from the same minimap_size table each frame.
|
|
-- Both values must stay in sync regardless of UI scale changes.
|
|
-- mapRadius: used for diffX/diffY normalization in drawMinimapPin
|
|
-- minimapWidth/minimapHeight: used for pixel offset in SetPoint
|
|
if MinimapRadiusAPI then
|
|
mapRadius = MinimapRadiusAPI(Minimap)
|
|
else
|
|
local sizeTable = minimap_size[indoors] or minimap_size.outdoor
|
|
local size = sizeTable[zoom]
|
|
or sizeTable[5] or sizeTable[4] or sizeTable[3]
|
|
or sizeTable[2] or sizeTable[1] or sizeTable[0]
|
|
mapRadius = size / 2
|
|
end
|
|
-- minimapWidth = actual half-width of the visible minimap frame in screen pixels.
|
|
-- diffX (yards) / mapRadius (yards in viewport) * minimapWidth (pixels) = correct screen offset.
|
|
-- No additional scaling needed when mapRadius comes from the live API.
|
|
minimapWidth = pins.Minimap:GetWidth() * pins.Minimap:GetScale() / 2
|
|
minimapHeight = pins.Minimap:GetHeight() * pins.Minimap:GetScale() / 2
|
|
|
|
--[[ DEBUG: One-shot per (zoom, indoors, diffZoom) — shows pixel math state
|
|
if not _G.QuestieDebugPinMath then _G.QuestieDebugPinMath = {} end
|
|
local dbgKey = zoom .. ":" .. (indoors or "?") .. ":" .. tostring(diffZoom)
|
|
if not _G.QuestieDebugPinMath[dbgKey] then
|
|
_G.QuestieDebugPinMath[dbgKey] = true
|
|
print("QDMATH:", dbgKey,
|
|
"mw="..math.floor(minimapWidth),
|
|
"mh="..math.floor(minimapHeight),
|
|
"mapR="..math.floor(mapRadius),
|
|
"ratio="..math.floor((minimapWidth/mapRadius)*100)/100)
|
|
end
|
|
--]]
|
|
|
|
local px, py = x, y
|
|
local pInst = instanceID
|
|
--[[ DEBUG: One-shot on first UpdateMinimapPins entry
|
|
if not _G.QuestieDebugPinMath.entering then
|
|
_G.QuestieDebugPinMath.entering = true
|
|
print("UPDATE entering")
|
|
end
|
|
--]]
|
|
lastFacing = facing
|
|
lastXY, lastYY = x, y
|
|
|
|
if rotateMinimap then
|
|
mapSin = sin(facing)
|
|
mapCos = cos(facing)
|
|
end
|
|
|
|
for pin, data in pairs(minimapPins) do
|
|
if instanceID == data.instanceID and math.abs(x-data.x) + math.abs(y-data.y) < 500 then -- questie specific fix
|
|
activeMinimapPins[pin] = data
|
|
data.keep = true
|
|
-- draw the pin (this may reset data.keep if outside of the map)
|
|
drawMinimapPin(pin, data)
|
|
end
|
|
end
|
|
|
|
minimapPinCount = 0
|
|
for pin, data in pairs(activeMinimapPins) do
|
|
if not data.keep then
|
|
pin:Hide()
|
|
activeMinimapPins[pin] = nil
|
|
else
|
|
minimapPinCount = minimapPinCount + 1
|
|
data.keep = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function UpdateMinimapIconPosition()
|
|
-- we have no active minimap pins, just return early
|
|
if minimapPinCount == 0 then return end
|
|
|
|
local x, y = _GetEffectiveMinimapPlayerWorldPosition()
|
|
|
|
--[[ DEBUG: per-frame player world X trace — fires on first 5 calls then on x change
|
|
if not _G.QuestieDebugPinMath.iconCalled then
|
|
_G.QuestieDebugPinMath.iconCalled = 0
|
|
_G.QuestieDebugPinMath.iconCalled_lastXY = -9999
|
|
end
|
|
_G.QuestieDebugPinMath.iconCalled = _G.QuestieDebugPinMath.iconCalled + 1
|
|
local call = _G.QuestieDebugPinMath.iconCalled
|
|
if call <= 5 or x ~= _G.QuestieDebugPinMath.iconCalled_lastXY then
|
|
print("ICON", call, "x=", x and math.floor(x*100)/100, "lastXY=", lastXY and math.floor(lastXY*100)/100, "diffX=", x and (x-lastXY) and math.floor((x-lastXY)*100)/100 or "nil", "zoom=", zoom)
|
|
end
|
|
_G.QuestieDebugPinMath.iconCalled_lastXY = x
|
|
--]]
|
|
|
|
local zoom = pins.Minimap:GetZoom()
|
|
local diffZoom = zoom ~= lastZoom
|
|
-- if the map zoom changed, run a full update sweep
|
|
if diffZoom then
|
|
UpdateMinimapPins()
|
|
return
|
|
end
|
|
|
|
-- for rotating minimap support
|
|
local facing
|
|
if rotateMinimap then
|
|
facing = GetPlayerFacing()
|
|
else
|
|
facing = lastFacing
|
|
end
|
|
|
|
-- check for all values to be available (starting with 7.1.0, instances don't report coordinates)
|
|
if not x or not y or (rotateMinimap and not facing) then
|
|
UpdateMinimapPins()
|
|
return
|
|
end
|
|
|
|
local refresh
|
|
local newScale = pins.Minimap:GetScale()
|
|
if minimapScale ~= newScale then
|
|
minimapScale = newScale
|
|
refresh = true
|
|
end
|
|
|
|
if x ~= lastXY or y ~= lastYY or facing ~= lastFacing or refresh then
|
|
-- Use mapRadius for width/height to stay self-consistent with the minimap_size
|
|
-- lookup table. GetWidth() returns scaled size which diverges from mapRadius
|
|
-- when UI/interface scale changes, causing drift at different zoom levels.
|
|
if MinimapRadiusAPI then
|
|
mapRadius = MinimapRadiusAPI(Minimap)
|
|
else
|
|
local sizeTable = minimap_size[indoors] or minimap_size.outdoor
|
|
local size = sizeTable[zoom]
|
|
or sizeTable[5]
|
|
or sizeTable[4]
|
|
or sizeTable[3]
|
|
or sizeTable[2]
|
|
or sizeTable[1]
|
|
or sizeTable[0]
|
|
|
|
mapRadius = size / 2
|
|
end
|
|
-- minimapWidth = actual half-width of the visible minimap frame in screen pixels.
|
|
minimapWidth = pins.Minimap:GetWidth() * pins.Minimap:GetScale() / 2
|
|
minimapHeight = pins.Minimap:GetHeight() * pins.Minimap:GetScale() / 2
|
|
|
|
--[[ DEBUG: One-shot per (zoom, indoors) per-pin — shows ratio stability across zooms
|
|
if not _G.QuestieDebugPinMath.pinMath then _G.QuestieDebugPinMath.pinMath = {} end
|
|
local pmKey = zoom .. ":" .. (indoors or "?")
|
|
if not _G.QuestieDebugPinMath.pinMath[pmKey] then
|
|
_G.QuestieDebugPinMath.pinMath[pmKey] = true
|
|
-- diffX = xDist / mapRadius, then final = diffX * minimapWidth
|
|
-- If mapRadius changes but minimapWidth doesn't, the multiplier (minimapWidth/mapRadius) changes per zoom
|
|
-- If both change proportionally, multiplier stays near 1.0
|
|
print("QDMATH:", pmKey,
|
|
"mw="..math.floor(minimapWidth),
|
|
"mh="..math.floor(minimapHeight),
|
|
"mapR="..math.floor(mapRadius),
|
|
"ratio="..math.floor((minimapWidth/mapRadius)*100)/100)
|
|
end
|
|
--]]
|
|
|
|
-- Compute pixel offset for this pin
|
|
lastXY, lastYY = x, y
|
|
lastFacing = facing
|
|
|
|
if rotateMinimap then
|
|
mapSin = sin(facing)
|
|
mapCos = cos(facing)
|
|
end
|
|
|
|
-- iterate all nodes and check if they are still in range of our minimap display
|
|
for pin, data in pairs(activeMinimapPins) do
|
|
-- update the position of the node
|
|
drawMinimapPin(pin, data)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function UpdateMinimapZoom()
|
|
if not MinimapRadiusAPI then
|
|
local zoom = pins.Minimap:GetZoom()
|
|
if GetCVar("minimapZoom") == GetCVar("minimapInsideZoom") then
|
|
pins.Minimap:SetZoom(zoom < 2 and zoom + 1 or zoom - 1)
|
|
end
|
|
indoors = GetCVar("minimapZoom")+0 == pins.Minimap:GetZoom() and "outdoor" or "indoor"
|
|
pins.Minimap:SetZoom(zoom)
|
|
end
|
|
end
|
|
|
|
function pins:RefreshMinimap()
|
|
queueFullUpdate = true
|
|
UpdateMinimapPins(true)
|
|
UpdateMinimapIconPosition()
|
|
end
|
|
|
|
-- Returns the minimap's current visible radius in yards (last value computed by
|
|
-- UpdateMinimapPins). Used by Questie's minimap FadeLogic so the range cutoff only
|
|
-- clips icons that are genuinely OUTSIDE the visible minimap, never icons within it.
|
|
function pins:GetMinimapRadius()
|
|
return mapRadius
|
|
end
|
|
|
|
function pins:SetMinimapVisibilityCutoff(cutoff)
|
|
for pin, data in pairs(minimapPins) do
|
|
pin.minimapVisibilityCutoff = cutoff
|
|
data.minimapVisibilityCutoff = cutoff
|
|
end
|
|
|
|
for pin, data in pairs(activeMinimapPins) do
|
|
pin.minimapVisibilityCutoff = cutoff
|
|
data.minimapVisibilityCutoff = cutoff
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------
|
|
-- WorldMap data provider
|
|
|
|
local Enum = {
|
|
-- https://wowpedia.fandom.com/wiki/Enum.UIMapType
|
|
UIMapType = {
|
|
Cosmic = 0,
|
|
World = 1,
|
|
Continent = 2,
|
|
Zone = 3,
|
|
Dungeon = 4,
|
|
Micro = 5,
|
|
Orphan = 6
|
|
}
|
|
}
|
|
|
|
local worldmapWidth, worldmapHeight
|
|
|
|
local function HandleWorldMapPin(icon, data)
|
|
if not WorldMapFrame:IsVisible() then return end
|
|
|
|
local uiMapID = QuestieCompat.GetCurrentUiMapID()
|
|
|
|
-- check for a valid map
|
|
if not uiMapID then return end
|
|
|
|
--Questie Modification
|
|
-- Child-map / zone-redirect exception: if we're viewing a child map (e.g. Sunstrider 1241)
|
|
-- and the pin belongs to a parent map (e.g. Eversong 1941) or a zone that shares the same
|
|
-- coordinate space, SHOW_CURRENT pins should still be visible.
|
|
-- Also handles the reverse: pins tagged 1241/946 should show on 1941, and vice versa,
|
|
-- because these maps share the same rendered area on Ascension.
|
|
local effectiveUiMapID = ResolveZone(uiMapID)
|
|
local effectiveDataUiMapID = ResolveZone(data.uiMapID)
|
|
local isChildMap = false
|
|
local ancestorMapID = HBD.mapData[uiMapID] and HBD.mapData[uiMapID].parentMapID
|
|
while ancestorMapID and HBD.mapData[ancestorMapID] do
|
|
if ancestorMapID == data.uiMapID then
|
|
isChildMap = true
|
|
break
|
|
end
|
|
ancestorMapID = HBD.mapData[ancestorMapID].parentMapID
|
|
end
|
|
-- Zone-redirect equivalence: if the viewed map and pin's map redirect to the same
|
|
-- target, they share the same coordinate space and should show each other's pins.
|
|
local isSameZoneSpace = (effectiveUiMapID == effectiveDataUiMapID)
|
|
|
|
if (Questie.db.profile.hideIconsOnContinents == true) and (HBD.mapData[uiMapID].mapType == Enum.UIMapType.Continent or uiMapID == 947) or (uiMapID ~= data.uiMapID and data.worldMapShowFlag == HBD_PINS_WORLDMAP_SHOW_CURRENT and not isChildMap and not isSameZoneSpace) then
|
|
icon:Hide();
|
|
return;
|
|
elseif(uiMapID == data.uiMapID and data.worldMapShowFlag == HBD_PINS_WORLDMAP_SHOW_CURRENT) or (isChildMap and data.worldMapShowFlag == HBD_PINS_WORLDMAP_SHOW_CURRENT) or (isSameZoneSpace and data.worldMapShowFlag == HBD_PINS_WORLDMAP_SHOW_CURRENT) then
|
|
icon:Show();
|
|
end
|
|
|
|
if icon.type == "line" then return end
|
|
|
|
local x, y
|
|
if uiMapID == WORLD_MAP_ID then
|
|
-- should this pin show on the world map?
|
|
if uiMapID ~= data.uiMapID and data.worldMapShowFlag ~= HBD_PINS_WORLDMAP_SHOW_WORLD then return end
|
|
|
|
-- translate to the world map
|
|
x, y = HBD:GetAzerothWorldMapCoordinatesFromWorld(data.x, data.y, data.instanceID)
|
|
else
|
|
-- check that it matches the instance
|
|
if not HBD.mapData[uiMapID] or HBD.mapData[uiMapID].instance ~= data.instanceID then return end
|
|
|
|
if uiMapID ~= data.uiMapID then
|
|
local mapType = HBD.mapData[uiMapID].mapType
|
|
if not data.uiMapID then
|
|
if mapType == Enum.UIMapType.Continent and data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_CONTINENT then
|
|
--pass
|
|
elseif mapType ~= Enum.UIMapType.Zone and mapType ~= Enum.UIMapType.Dungeon and mapType ~= Enum.UIMapType.Micro then
|
|
-- fail
|
|
return
|
|
end
|
|
else
|
|
local show = true -- Questie fix to show icons in neighbour areas
|
|
local parentMapID = HBD.mapData[data.uiMapID].parentMapID
|
|
while parentMapID and HBD.mapData[parentMapID] do
|
|
if parentMapID == uiMapID then
|
|
local parentMapType = HBD.mapData[parentMapID].mapType
|
|
-- show on any parent zones if they are normal zones
|
|
if data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_PARENT and
|
|
(parentMapType == Enum.UIMapType.Zone or parentMapType == Enum.UIMapType.Dungeon or parentMapType == Enum.UIMapType.Micro) then
|
|
show = true
|
|
-- show on the continent
|
|
elseif data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_CONTINENT and
|
|
parentMapType == Enum.UIMapType.Continent then
|
|
show = true
|
|
elseif data.worldMapShowFlag == HBD_PINS_WORLDMAP_SHOW_CURRENT then
|
|
-- Questie modifications!
|
|
show = false
|
|
end
|
|
break
|
|
-- worldmap is handled above already
|
|
else
|
|
parentMapID = HBD.mapData[parentMapID].parentMapID
|
|
end
|
|
end
|
|
|
|
if not show then return end
|
|
end
|
|
end
|
|
|
|
-- translate coordinates
|
|
-- Ascension: mapData[1241] has calibrated Sunstrider bounds matching
|
|
-- the game engine's coordinate space. Pins on zone 1241 use these
|
|
-- bounds for world coord conversion, so they align with the player.
|
|
x, y = HBD:GetZoneCoordinatesFromWorld(data.x, data.y, uiMapID)
|
|
-- [HBD-Pins] pin position debug disabled
|
|
end
|
|
|
|
if x and y then
|
|
icon:ClearAllPoints()
|
|
icon:SetPoint("CENTER", WorldMapButton, "TOPLEFT", x * worldmapWidth, -y * worldmapHeight)
|
|
icon:Show()
|
|
else
|
|
icon:Hide()
|
|
end
|
|
end
|
|
|
|
-- map event handling
|
|
local function UpdateMinimap()
|
|
UpdateMinimapZoom()
|
|
UpdateMinimapPins()
|
|
end
|
|
|
|
local function UpdateWorldMap()
|
|
if not WorldMapFrame:IsVisible() then return end
|
|
|
|
local scale = WorldMapButton:GetScale()
|
|
worldmapWidth = WorldMapButton:GetWidth()*scale
|
|
worldmapHeight = WorldMapButton:GetHeight()*scale
|
|
|
|
local mapScale = QuestieMap.GetScaleValue()
|
|
|
|
for icon, data in pairs(worldmapPins) do
|
|
icon:Hide()
|
|
--icon:ClearAllPoints()
|
|
|
|
QuestieMap.utils:RescaleIcon(icon, mapScale)
|
|
HandleWorldMapPin(icon, data)
|
|
end
|
|
end
|
|
pins.UpdateWorldMap = UpdateWorldMap
|
|
|
|
local last_update = 0
|
|
local function OnUpdateHandler(frame, elapsed)
|
|
last_update = last_update + elapsed
|
|
if last_update > 1 or queueFullUpdate then
|
|
UpdateMinimapPins(queueFullUpdate)
|
|
last_update = 0
|
|
queueFullUpdate = false
|
|
else
|
|
UpdateMinimapIconPosition()
|
|
end
|
|
end
|
|
pins.updateFrame:SetScript("OnUpdate", OnUpdateHandler)
|
|
|
|
local function OnEventHandler(frame, event, ...)
|
|
if event == "CVAR_UPDATE" then
|
|
local cvar, value = ...
|
|
if cvar == "ROTATE_MINIMAP" then
|
|
rotateMinimap = (value == "1")
|
|
queueFullUpdate = true
|
|
end
|
|
elseif event == "MINIMAP_UPDATE_ZOOM" then
|
|
UpdateMinimap()
|
|
elseif event == "PLAYER_LOGIN" then
|
|
-- recheck cvars after login
|
|
rotateMinimap = GetCVar("rotateMinimap") == "1"
|
|
elseif event == "PLAYER_ENTERING_WORLD" then
|
|
_InvalidatePositionCache()
|
|
UpdateMinimap()
|
|
UpdateWorldMap()
|
|
elseif event == "WORLD_MAP_UPDATE" then
|
|
UpdateWorldMap()
|
|
elseif string.find(event, "ZONE_CHANGED") then
|
|
_InvalidatePositionCache()
|
|
UpdateMinimap()
|
|
UpdateWorldMap()
|
|
end
|
|
end
|
|
|
|
pins.updateFrame:SetScript("OnEvent", OnEventHandler)
|
|
pins.updateFrame:RegisterEvent("CVAR_UPDATE")
|
|
pins.updateFrame:RegisterEvent("MINIMAP_UPDATE_ZOOM")
|
|
pins.updateFrame:RegisterEvent("WORLD_MAP_UPDATE")
|
|
pins.updateFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
|
pins.updateFrame:RegisterEvent("ZONE_CHANGED")
|
|
pins.updateFrame:RegisterEvent("ZONE_CHANGED_INDOORS")
|
|
pins.updateFrame:RegisterEvent("PLAYER_LOGIN")
|
|
pins.updateFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
|
|
-- Showing quest objectives does not trigger WORLD_MAP_UPDATE event
|
|
hooksecurefunc("WorldMapFrame_DisplayQuests", UpdateWorldMap)
|
|
|
|
--- Add a icon to the minimap (x/y world coordinate version)
|
|
-- Note: This API does not let you specify a map to limit the pin to, it'll be shown on all maps these coordinates are valid for.
|
|
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
|
|
-- @param icon Icon Frame
|
|
-- @param instanceID Instance ID of the map to add the icon to
|
|
-- @param x X position in world coordinates
|
|
-- @param y Y position in world coordinates
|
|
-- @param floatOnEdge flag if the icon should float on the edge of the minimap when going out of range, or hide immediately (default false)
|
|
function pins:AddMinimapIconWorld(ref, icon, instanceID, x, y, floatOnEdge)
|
|
if not ref then
|
|
error(MAJOR..": AddMinimapIconWorld: 'ref' must not be nil", 2)
|
|
end
|
|
if type(icon) ~= "table" or not icon.SetPoint then
|
|
error(MAJOR..": AddMinimapIconWorld: 'icon' must be a frame", 2)
|
|
end
|
|
if type(instanceID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
|
|
error(MAJOR..": AddMinimapIconWorld: 'instanceID', 'x' and 'y' must be numbers", 2)
|
|
end
|
|
|
|
if not minimapPinRegistry[ref] then
|
|
minimapPinRegistry[ref] = {}
|
|
end
|
|
|
|
minimapPinRegistry[ref][icon] = true
|
|
|
|
local t = minimapPins[icon] or newCachedTable()
|
|
t.instanceID = instanceID
|
|
t.x = x
|
|
t.y = y
|
|
t.floatOnEdge = floatOnEdge
|
|
t.uiMapID = nil
|
|
t.showInParentZone = nil
|
|
|
|
minimapPins[icon] = t
|
|
queueFullUpdate = true
|
|
|
|
icon:SetParent(pins.MinimapGroup or pins.Minimap)
|
|
end
|
|
|
|
--- Add a icon to the minimap (UiMapID zone coordinate version)
|
|
-- The pin will only be shown on the map specified, or optionally its parent map if specified
|
|
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
|
|
-- @param icon Icon Frame
|
|
-- @param uiMapID uiMapID of the map to place the icon on
|
|
-- @param x X position in local/point coordinates (0-1), relative to the zone
|
|
-- @param y Y position in local/point coordinates (0-1), relative to the zone
|
|
-- @param showInParentZone flag if the icon should be shown in its parent zone - ie. an icon in a microdungeon in the outdoor zone itself (default false)
|
|
-- @param floatOnEdge flag if the icon should float on the edge of the minimap when going out of range, or hide immediately (default false)
|
|
function pins:AddMinimapIconMap(ref, icon, uiMapID, x, y, showInParentZone, floatOnEdge)
|
|
if not ref then
|
|
error(MAJOR..": AddMinimapIconMap: 'ref' must not be nil", 2)
|
|
end
|
|
if type(icon) ~= "table" or not icon.SetPoint then
|
|
error(MAJOR..": AddMinimapIconMap: 'icon' must be a frame", 2)
|
|
end
|
|
if type(uiMapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
|
|
error(MAJOR..": AddMinimapIconMap: 'uiMapID', 'x' and 'y' must be numbers", 2)
|
|
end
|
|
|
|
-- convert to world coordinates and use our known adding function
|
|
local xCoord, yCoord, instanceID = HBD:GetWorldCoordinatesFromZone(x, y, uiMapID)
|
|
if not xCoord then return end
|
|
|
|
self:AddMinimapIconWorld(ref, icon, instanceID, xCoord, yCoord, floatOnEdge)
|
|
|
|
-- store extra information
|
|
minimapPins[icon].uiMapID = uiMapID
|
|
minimapPins[icon].showInParentZone = showInParentZone
|
|
end
|
|
|
|
--- Check if a floating minimap icon is on the edge of the map
|
|
-- @param icon the minimap icon
|
|
function pins:IsMinimapIconOnEdge(icon)
|
|
if not icon then return false end
|
|
local data = minimapPins[icon]
|
|
if not data then return nil end
|
|
|
|
return data.onEdge
|
|
end
|
|
|
|
--- Remove a minimap icon
|
|
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
|
|
-- @param icon Icon Frame
|
|
function pins:RemoveMinimapIcon(ref, icon)
|
|
if not ref or not icon or not minimapPinRegistry[ref] then return end
|
|
minimapPinRegistry[ref][icon] = nil
|
|
if minimapPins[icon] then
|
|
recycle(minimapPins[icon])
|
|
minimapPins[icon] = nil
|
|
activeMinimapPins[icon] = nil
|
|
end
|
|
icon:Hide()
|
|
end
|
|
|
|
--- Remove all minimap icons belonging to your addon (as tracked by "ref")
|
|
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
|
|
function pins:RemoveAllMinimapIcons(ref)
|
|
if not ref or not minimapPinRegistry[ref] then return end
|
|
for icon in pairs(minimapPinRegistry[ref]) do
|
|
recycle(minimapPins[icon])
|
|
minimapPins[icon] = nil
|
|
activeMinimapPins[icon] = nil
|
|
icon:Hide()
|
|
end
|
|
wipe(minimapPinRegistry[ref])
|
|
end
|
|
|
|
--- Set the minimap object to position the pins on. Needs to support the usual functions a Minimap-type object exposes.
|
|
-- @param minimapObject The new minimap object, or nil to restore the default
|
|
function pins:SetMinimapObject(minimapObject)
|
|
pins.Minimap = minimapObject or Minimap
|
|
for pin in pairs(minimapPins) do
|
|
pin:SetParent(pins.Minimap)
|
|
end
|
|
UpdateMinimapPins(true)
|
|
end
|
|
|
|
-- world map constants
|
|
-- show worldmap pin only on zone map (Questie modification)
|
|
HBD_PINS_WORLDMAP_SHOW_CURRENT = -1
|
|
-- show worldmap pin on its parent zone map (if any)
|
|
HBD_PINS_WORLDMAP_SHOW_PARENT = 1
|
|
-- show worldmap pin on the continent map
|
|
HBD_PINS_WORLDMAP_SHOW_CONTINENT = 2
|
|
-- show worldmap pin on the continent and world map
|
|
HBD_PINS_WORLDMAP_SHOW_WORLD = 3
|
|
|
|
--- Add a icon to the world map (x/y world coordinate version)
|
|
-- Note: This API does not let you specify a map to limit the pin to, it'll be shown on all maps these coordinates are valid for.
|
|
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
|
|
-- @param icon Icon Frame
|
|
-- @param instanceID Instance ID of the map to add the icon to
|
|
-- @param x X position in world coordinates
|
|
-- @param y Y position in world coordinates
|
|
-- @param showFlag Flag to control on which maps this pin will be shown
|
|
-- @param frameLevel Optional Frame Level type registered with the WorldMapFrame, defaults to PIN_FRAME_LEVEL_AREA_POI
|
|
function pins:AddWorldMapIconWorld(ref, icon, instanceID, x, y, showFlag, frameLevel)
|
|
if not ref then
|
|
error(MAJOR..": AddWorldMapIconWorld: 'ref' must not be nil", 2)
|
|
end
|
|
if type(icon) ~= "table" or not icon.SetPoint then
|
|
error(MAJOR..": AddWorldMapIconWorld: 'icon' must be a frame", 2)
|
|
end
|
|
if type(instanceID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
|
|
error(MAJOR..": AddWorldMapIconWorld: 'instanceID', 'x' and 'y' must be numbers", 2)
|
|
end
|
|
if showFlag ~= nil and type(showFlag) ~= "number" then
|
|
error(MAJOR..": AddWorldMapIconWorld: 'showFlag' must be a number (or nil)", 2)
|
|
end
|
|
|
|
if not worldmapPinRegistry[ref] then
|
|
worldmapPinRegistry[ref] = {}
|
|
end
|
|
|
|
worldmapPinRegistry[ref][icon] = true
|
|
|
|
local t = worldmapPins[icon] or newCachedTable()
|
|
t.instanceID = instanceID
|
|
t.x = x
|
|
t.y = y
|
|
t.uiMapID = nil
|
|
t.worldMapShowFlag = showFlag or 0
|
|
t.frameLevelType = frameLevel
|
|
|
|
worldmapPins[icon] = t
|
|
|
|
icon.icon = icon --LOL!
|
|
icon:SetParent(WorldMapButton)
|
|
HandleWorldMapPin(icon, t)
|
|
end
|
|
|
|
--- Add a icon to the world map (uiMapID zone coordinate version)
|
|
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
|
|
-- @param icon Icon Frame
|
|
-- @param uiMapID uiMapID of the map to place the icon on
|
|
-- @param x X position in local/point coordinates (0-1), relative to the zone
|
|
-- @param y Y position in local/point coordinates (0-1), relative to the zone
|
|
-- @param showFlag Flag to control on which maps this pin will be shown
|
|
-- @param frameLevel Optional Frame Level type registered with the WorldMapFrame, defaults to PIN_FRAME_LEVEL_AREA_POI
|
|
function pins:AddWorldMapIconMap(ref, icon, uiMapID, x, y, showFlag, frameLevel)
|
|
if not ref then
|
|
error(MAJOR..": AddWorldMapIconMap: 'ref' must not be nil", 2)
|
|
end
|
|
if type(icon) ~= "table" or not icon.SetPoint then
|
|
error(MAJOR..": AddWorldMapIconMap: 'icon' must be a frame", 2)
|
|
end
|
|
if type(uiMapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
|
|
error(MAJOR..": AddWorldMapIconMap: 'uiMapID', 'x' and 'y' must be numbers", 2)
|
|
end
|
|
if showFlag ~= nil and type(showFlag) ~= "number" then
|
|
error(MAJOR..": AddWorldMapIconMap: 'showFlag' must be a number (or nil)", 2)
|
|
end
|
|
|
|
-- convert to world coordinates
|
|
local xCoord, yCoord, instanceID = HBD:GetWorldCoordinatesFromZone(x, y, uiMapID)
|
|
if not xCoord then return end
|
|
|
|
if not worldmapPinRegistry[ref] then
|
|
worldmapPinRegistry[ref] = {}
|
|
end
|
|
|
|
worldmapPinRegistry[ref][icon] = true
|
|
|
|
local t = worldmapPins[icon] or newCachedTable()
|
|
t.instanceID = instanceID
|
|
t.x = xCoord
|
|
t.y = yCoord
|
|
t.uiMapID = uiMapID
|
|
t.worldMapShowFlag = showFlag or 0
|
|
t.frameLevelType = frameLevel
|
|
|
|
worldmapPins[icon] = t
|
|
|
|
icon.icon = icon --LOL!
|
|
icon:SetParent(WorldMapDetailFrame or WorldMapButton or WorldMapFrame)
|
|
HandleWorldMapPin(icon, t)
|
|
end
|
|
|
|
--- Remove a worldmap icon
|
|
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
|
|
-- @param icon Icon Frame
|
|
function pins:RemoveWorldMapIcon(ref, icon)
|
|
if not ref or not icon or not worldmapPinRegistry[ref] then return end
|
|
worldmapPinRegistry[ref][icon] = nil
|
|
if worldmapPins[icon] then
|
|
recycle(worldmapPins[icon])
|
|
worldmapPins[icon] = nil
|
|
end
|
|
icon:Hide()
|
|
icon:ClearAllPoints()
|
|
icon:SetParent(UiParent)
|
|
end
|
|
|
|
--- Remove all worldmap icons belonging to your addon (as tracked by "ref")
|
|
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
|
|
function pins:RemoveAllWorldMapIcons(ref)
|
|
if not ref or not worldmapPinRegistry[ref] then return end
|
|
for icon in pairs(worldmapPinRegistry[ref]) do
|
|
recycle(worldmapPins[icon])
|
|
worldmapPins[icon] = nil
|
|
icon:Hide()
|
|
icon:ClearAllPoints()
|
|
icon:SetParent(UiParent)
|
|
end
|
|
wipe(worldmapPinRegistry[ref])
|
|
end
|