v9.7.2: Ebonhold Database integration and core logic refinements

This commit is contained in:
Xurkon
2026-02-14 21:13:44 -06:00
parent 0a2cc15641
commit 6ef85d4e2d
573 changed files with 1751730 additions and 2 deletions
+15
View File
@@ -0,0 +1,15 @@
# Lib: HereBeDragons
## [2.09-release](https://github.com/Nevcairiel/HereBeDragons/tree/2.09-release) (2022-08-29)
[Full Changelog](https://github.com/Nevcairiel/HereBeDragons/compare/2.08-release...2.09-release) [Previous Releases](https://github.com/Nevcairiel/HereBeDragons/releases)
- Unify WoW version checks
- Update TOC
- Move DK starting area override into transforms
The instance ID overrides were meant for dynamic phasing, not
permanently instanced zones, which the transform was designed for
instead, even if the map coordinates are not transformed.
This should make the behavior more consistent for users.
- Don't use expansion level checks, they may not be present in all clients
- Fix handling of pins from phased sub maps
- Add Wrath Classic support
@@ -0,0 +1,217 @@
--[[ $Id: CallbackHandler-1.0.lua 22 2018-07-21 14:17:22Z nevcairiel $ ]]
local MAJOR, MINOR = "CallbackHandler-1.0", 7
---@class CallbackHandler-1.0
---@diagnostic disable-next-line: assign-type-mismatch
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
if not CallbackHandler then return end -- No upgrade needed
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
-- Lua APIs
local tconcat = table.concat
local assert, error, loadstring = assert, error, loadstring
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
local next, select, pairs, type, tostring = next, select, pairs, type, tostring
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: geterrorhandler
local xpcall = xpcall
local function errorhandler(err)
return geterrorhandler()(err)
end
local function Dispatch(handlers, ...)
local index, method = next(handlers)
if not method then return end
repeat
xpcall(method, errorhandler, ...)
index, method = next(handlers, index)
until not method
end
--------------------------------------------------------------------------
-- CallbackHandler:New
--
-- target - target object to embed public APIs in
-- RegisterName - name of the callback registration API, default "RegisterCallback"
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName)
RegisterName = RegisterName or "RegisterCallback"
UnregisterName = UnregisterName or "UnregisterCallback"
if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
UnregisterAllName = "UnregisterAllCallbacks"
end
-- we declare all objects and exported APIs inside this closure to quickly gain access
-- to e.g. function names, the "target" parameter, etc
-- Create the registry object
local events = setmetatable({}, meta)
---@class CallbackHandlerRegistry
---@field OnUsed function?
---@field OnUnused function?
local registry = { recurse=0, events=events }
-- registry:Fire() - fires the given event/message into the registry
function registry:Fire(eventname, ...)
if not rawget(events, eventname) or not next(events[eventname]) then return end
local oldrecurse = registry.recurse
registry.recurse = oldrecurse + 1
Dispatch(events[eventname], eventname, ...)
registry.recurse = oldrecurse
if registry.insertQueue and oldrecurse==0 then
-- Something in one of our callbacks wanted to register more callbacks; they got queued
for eventname,callbacks in pairs(registry.insertQueue) do
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
for self,func in pairs(callbacks) do
events[eventname][self] = func
-- fire OnUsed callback?
if first and registry.OnUsed then
registry.OnUsed(registry, target, eventname)
first = false
end
end
end
registry.insertQueue = nil
end
end
-- Registration of a callback, handles:
-- self["method"], leads to self["method"](self, ...)
-- self with function ref, leads to functionref(...)
-- "addonId" (instead of self) with function ref, leads to functionref(...)
-- all with an optional arg, which, if present, gets passed as first argument (after self if present)
target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
if type(eventname) ~= "string" then
error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
end
method = method or eventname
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
if type(method) ~= "string" and type(method) ~= "function" then
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
end
local regfunc
if type(method) == "string" then
-- self["method"] calling style
if type(self) ~= "table" then
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
elseif self==target then
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
elseif type(self[method]) ~= "function" then
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
end
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
local arg=select(1,...)
regfunc = function(...) self[method](self,arg,...) end
else
regfunc = function(...) self[method](self,...) end
end
else
-- function ref with self=object or self="addonId" or self=thread
if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
end
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
local arg=select(1,...)
regfunc = function(...) method(arg,...) end
else
regfunc = method
end
end
if events[eventname][self] or registry.recurse<1 then
-- if registry.recurse<1 then
-- we're overwriting an existing entry, or not currently recursing. just set it.
events[eventname][self] = regfunc
-- fire OnUsed callback?
if registry.OnUsed and first then
registry.OnUsed(registry, target, eventname)
end
else
-- we're currently processing a callback in this registry, so delay the registration of this new entry!
-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
registry.insertQueue = registry.insertQueue or setmetatable({},meta)
registry.insertQueue[eventname][self] = regfunc
end
end
-- Unregister a callback
target[UnregisterName] = function(self, eventname)
if not self or self==target then
error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
end
if type(eventname) ~= "string" then
error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
end
if rawget(events, eventname) and events[eventname][self] then
events[eventname][self] = nil
-- Fire OnUnused callback?
if registry.OnUnused and not next(events[eventname]) then
registry.OnUnused(registry, target, eventname)
end
end
if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
registry.insertQueue[eventname][self] = nil
end
end
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds
if UnregisterAllName then
target[UnregisterAllName] = function(...)
if select("#",...)<1 then
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
end
if select("#",...)==1 and ...==target then
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
end
for i=1,select("#",...) do
local self = select(i,...)
if registry.insertQueue then
for eventname, callbacks in pairs(registry.insertQueue) do
if callbacks[self] then
callbacks[self] = nil
end
end
end
for eventname, callbacks in pairs(events) do
if callbacks[self] then
callbacks[self] = nil
-- Fire OnUnused callback?
if registry.OnUnused and not next(callbacks) then
registry.OnUnused(registry, target, eventname)
end
end
end
end
end
end
return registry
end
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
-- try to upgrade old implicit embeds since the system is selfcontained and
-- relies on closures to work.
@@ -0,0 +1,4 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Script file="CallbackHandler-1.0.lua"/>
</Ui>
+586
View File
@@ -0,0 +1,586 @@
---@diagnostic disable: assign-type-mismatch
-- HereBeDragons is a data API for the World of Warcraft mapping system
local MAJOR, MINOR = "HereBeDragonsQuestie-2.0", 20
assert(LibStub, MAJOR .. " requires LibStub")
---@class HereBeDragonsQuestie-2.0
local HereBeDragons, oldversion = LibStub:NewLibrary(MAJOR, MINOR)
if not HereBeDragons then return end
local CBH = LibStub("CallbackHandler-1.0")
HereBeDragons.eventFrame = HereBeDragons.eventFrame or CreateFrame("Frame")
HereBeDragons.mapData = HereBeDragons.mapData or {}
HereBeDragons.worldMapData = HereBeDragons.worldMapData or {}
HereBeDragons.transforms = HereBeDragons.transforms or {}
HereBeDragons.callbacks = HereBeDragons.callbacks or CBH:New(HereBeDragons, nil, nil, false)
local WOW_INTERFACE_VER = select(4, GetBuildInfo())
local WoWClassic = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC)
local WoWBC = (WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE) and WOW_INTERFACE_VER >= 20500 and WOW_INTERFACE_VER < 30000
local WoWWrath = (WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE) and WOW_INTERFACE_VER >= 30400 and WOW_INTERFACE_VER < 40000
-- Data Constants
local COSMIC_MAP_ID = 946
local WORLD_MAP_ID = 947
-- Lua upvalues
local PI2 = math.pi * 2
local atan2 = math.atan2
local pairs, ipairs = pairs, ipairs
-- WoW API upvalues
local UnitPosition = UnitPosition
local C_Map = C_Map
-- data table upvalues
local mapData = HereBeDragons.mapData -- table { width, height, left, top, .instance, .name, .mapType }
local worldMapData = HereBeDragons.worldMapData -- table { width, height, left, top }
local transforms = HereBeDragons.transforms
local currentPlayerUIMapID, currentPlayerUIMapType
-- Override instance ids for phased content
local instanceIDOverrides = {
-- Draenor
[1152] = 1116, -- Horde Garrison 1
[1330] = 1116, -- Horde Garrison 2
[1153] = 1116, -- Horde Garrison 3
[1154] = 1116, -- Horde Garrison 4 (unused)
[1158] = 1116, -- Alliance Garrison 1
[1331] = 1116, -- Alliance Garrison 2
[1159] = 1116, -- Alliance Garrison 3
[1160] = 1116, -- Alliance Garrison 4 (unused)
[1191] = 1116, -- Ashran PvP Zone
[1203] = 1116, -- Frostfire Finale Scenario
[1207] = 1116, -- Talador Finale Scenario
[1277] = 1116, -- Defense of Karabor Scenario (SMV)
[1402] = 1116, -- Gorgrond Finale Scenario
[1464] = 1116, -- Tanaan
[1465] = 1116, -- Tanaan
-- Legion
[1478] = 1220, -- Temple of Elune Scenario (Val'Sharah)
[1495] = 1220, -- Protection Paladin Artifact Scenario (Stormheim)
[1498] = 1220, -- Havoc Demon Hunter Artifact Scenario (Suramar)
[1502] = 1220, -- Dalaran Underbelly
[1533] = 0, -- Karazhan Artifact Scenario
[1539] = 0, -- Arm Warrior Artifact Tirisfal Scenario
[1612] = 1220, -- Feral Druid Artifact Scenario (Suramar)
[1626] = 1220, -- Suramar Withered Scenario
[1662] = 1220, -- Suramar Invasion Scenario
-- BfA
[2213] = 0, -- Horrific Vision of Stormwind
[2241] = 1, -- Uldum N'zoth assault
[2274] = 1, -- Uldum N'zoth Minor Vision
[2275] = 870, -- Vale of Eternal Blossoms N'zoth Minor Vision
}
local dynamicInstanceIDOverrides = {}
instanceIDOverrides = setmetatable(instanceIDOverrides, { __index = dynamicInstanceIDOverrides })
local function overrideInstance(instance) return instanceIDOverrides[instance] or instance end
-- debug only
HereBeDragons.___DIIDO = dynamicInstanceIDOverrides
-- gather map info, but only if this isn't an upgrade (or the upgrade version forces a re-map)
if not oldversion or oldversion < 17 then
-- wipe old data, if required, otherwise the upgrade path isn't triggered
if oldversion then
wipe(mapData)
wipe(worldMapData)
wipe(transforms)
end
-- map transform data extracted from UIMapAssignment.db2 (see HereBeDragons-Scripts on GitHub)
-- format: instanceID, newInstanceID, minY, maxY, minX, maxX, offsetY, offsetX
local transformData
if WoWClassic then
transformData = {}
elseif WoWBC then
transformData = {
{ 530, 0, 4800, 16000, -10133.3, -2666.67, -2400, 2662.8 },
{ 530, 1, -6933.33, 533.33, -16000, -8000, 10339.7, 17600 },
}
elseif WoWWrath then
transformData = {
{ 530, 0, 4800, 16000, -10133.3, -2666.67, -2400, 2662.8 },
{ 530, 1, -6933.33, 533.33, -16000, -8000, 10339.7, 17600 },
{ 609, 0, -10000, 10000, -10000, 10000, 0, 0 },
}
else
transformData = {
{ 530, 1, -6933.33, 533.33, -16000, -8000, 9916, 17600 },
{ 530, 0, 4800, 16000, -10133.3, -2666.67, -2400, 2400 },
{ 732, 0, -3200, 533.3, -533.3, 2666.7, -611.8, 3904.3 },
{ 1064, 870, 5391, 8148, 3518, 7655, -2134.2, -2286.6 },
{ 1208, 1116, -2666, -2133, -2133, -1600, 10210.7, 2411.4 },
{ 1460, 1220, -1066.7, 2133.3, 0, 3200, -2333.9, 966.7 },
{ 1599, 1, 4800, 5866.7, -4266.7, -3200, -490.6, -0.4 },
{ 1609, 571, 6400, 8533.3, -1600, 533.3, 512.8, 545.3 },
}
end
local function processTransforms()
for _, transform in pairs(transformData) do
local instanceID, newInstanceID, minY, maxY, minX, maxX, offsetY, offsetX = unpack(transform)
if not transforms[instanceID] then
transforms[instanceID] = {}
end
table.insert(transforms[instanceID], { newInstanceID = newInstanceID, minY = minY, maxY = maxY, minX = minX, maxX = maxX, offsetY = offsetY, offsetX = offsetX })
end
end
local function applyMapTransforms(instanceID, left, right, top, bottom)
if transforms[instanceID] then
for _, data in ipairs(transforms[instanceID]) do
if left <= data.maxX and right >= data.minX and top <= data.maxY and bottom >= data.minY then
instanceID = data.newInstanceID
left = left + data.offsetX
right = right + data.offsetX
top = top + data.offsetY
bottom = bottom + data.offsetY
break
end
end
end
return instanceID, left, right, top, bottom
end
local vector00, vector05 = CreateVector2D(0, 0), CreateVector2D(0.5, 0.5)
-- gather the data of one map (by uiMapID)
local function processMap(id, data, parent)
if not id or not data or mapData[id] then return end
if data.parentMapID and data.parentMapID ~= 0 then
parent = data.parentMapID
elseif not parent then
parent = 0
end
-- get two positions from the map, we use 0/0 and 0.5/0.5 to avoid issues on some maps where 1/1 is translated inaccurately
local instance, topLeft = C_Map.GetWorldPosFromMapPos(id, vector00)
local _, bottomRight = C_Map.GetWorldPosFromMapPos(id, vector05)
if topLeft and bottomRight then
local top, left = topLeft:GetXY()
local bottom, right = bottomRight:GetXY()
bottom = top + (bottom - top) * 2
right = left + (right - left) * 2
instance, left, right, top, bottom = applyMapTransforms(instance, left, right, top, bottom)
mapData[id] = {left - right, top - bottom, left, top, instance = instance, name = data.name, mapType = data.mapType, parent = parent }
else
mapData[id] = {0, 0, 0, 0, instance = instance or -1, name = data.name, mapType = data.mapType, parent = parent }
end
end
local function processMapChildrenRecursive(parent)
local children = C_Map.GetMapChildrenInfo(parent)
if children and #children > 0 then
for i = 1, #children do
local id = children[i].mapID
if id and not mapData[id] then
processMap(id, children[i], parent)
processMapChildrenRecursive(id)
-- process sibling maps (in the same group)
-- in some cases these are not discovered by GetMapChildrenInfo above
local groupID = C_Map.GetMapGroupID(id)
if groupID then
local groupMembers = C_Map.GetMapGroupMembersInfo(groupID)
if groupMembers then
for k = 1, #groupMembers do
local memberId = groupMembers[k].mapID
if memberId and not mapData[memberId] then
processMap(memberId, C_Map.GetMapInfo(memberId), parent)
processMapChildrenRecursive(memberId)
end
end
end
end
end
end
end
end
local function fixupZones()
local cosmic = C_Map.GetMapInfo(COSMIC_MAP_ID)
if cosmic then
mapData[COSMIC_MAP_ID] = {0, 0, 0, 0}
mapData[COSMIC_MAP_ID].instance = -1
mapData[COSMIC_MAP_ID].name = cosmic.name
mapData[COSMIC_MAP_ID].mapType = cosmic.mapType
end
-- data for the azeroth world map
if WoWClassic then
worldMapData[0] = { 44688.53, 29795.11, 32601.04, 9894.93 }
worldMapData[1] = { 44878.66, 29916.10, 8723.96, 14824.53 }
elseif WoWBC then
worldMapData[0] = { 44688.53, 29791.24, 32681.47, 11479.44 }
worldMapData[1] = { 44878.66, 29916.10, 8723.96, 14824.53 }
elseif WoWWrath then
worldMapData[0] = { 48033.24, 32020.8, 36867.97, 14848.84 }
worldMapData[1] = { 47908.72, 31935.28, 8552.61, 18467.83 }
worldMapData[571] = { 47662.7, 31772.19, 25198.53, 11072.07 }
else
worldMapData[0] = { 76153.14, 50748.62, 65008.24, 23827.51 }
worldMapData[1] = { 77803.77, 51854.98, 13157.6, 28030.61 }
worldMapData[571] = { 71773.64, 50054.05, 36205.94, 12366.81 }
worldMapData[870] = { 67710.54, 45118.08, 33565.89, 38020.67 }
worldMapData[1220] = { 82758.64, 55151.28, 52943.46, 24484.72 }
worldMapData[1642] = { 77933.3, 51988.91, 44262.36, 32835.1 }
worldMapData[1643] = { 76060.47, 50696.96, 55384.8, 25774.35 }
end
end
local function gatherMapData()
processTransforms()
-- find all maps in well known structures
if WoWClassic then
processMap(WORLD_MAP_ID)
processMapChildrenRecursive(WORLD_MAP_ID)
else
processMapChildrenRecursive(COSMIC_MAP_ID)
end
fixupZones()
-- try to fill in holes in the map list
for i = 1, 2500 do
if not mapData[i] then
local mapInfo = C_Map.GetMapInfo(i)
if mapInfo and mapInfo.name then
processMap(i, mapInfo, nil)
end
end
end
end
gatherMapData()
end
-- Transform a set of coordinates based on the defined map transformations
local function applyCoordinateTransforms(x, y, instanceID)
if transforms[instanceID] then
for _, transformData in ipairs(transforms[instanceID]) do
if transformData.minX <= x and transformData.maxX >= x and transformData.minY <= y and transformData.maxY >= y then
instanceID = transformData.newInstanceID
x = x + transformData.offsetX
y = y + transformData.offsetY
break
end
end
end
return x, y, overrideInstance(instanceID)
end
local StartUpdateTimer
local function UpdateCurrentPosition(instanceCheck)
-- retrieve current zone
local uiMapID = C_Map.GetBestMapForUnit("player")
-- try to override the instance if possible
if instanceCheck then
local _x, _y, instance = HereBeDragons:GetPlayerWorldPosition()
if instance and instance ~= -1 and mapData[uiMapID] and mapData[uiMapID].instance ~= instance and uiMapID ~= -1 and not instanceIDOverrides[instance] and not instanceIDOverrides[mapData[uiMapID].instance] then
dynamicInstanceIDOverrides[instance] = mapData[uiMapID].instance
end
end
if uiMapID ~= currentPlayerUIMapID then
-- update location upvalues
currentPlayerUIMapID, currentPlayerUIMapType = uiMapID, mapData[uiMapID] and mapData[uiMapID].mapType or 0
-- signal callback
HereBeDragons.callbacks:Fire("PlayerZoneChanged", currentPlayerUIMapID, currentPlayerUIMapType)
end
-- start a timer to update in micro dungeons since multi-level micro dungeons do not reliably fire events
if currentPlayerUIMapType == Enum.UIMapType.Micro then
StartUpdateTimer()
end
end
-- upgradeable timer callback, don't want to keep calling the old function if the library is upgraded
HereBeDragons.UpdateCurrentPosition = UpdateCurrentPosition
local function UpdateTimerCallback()
-- signal that the timer ran
HereBeDragons.updateTimerActive = nil
-- run update now
HereBeDragons.UpdateCurrentPosition()
end
function StartUpdateTimer()
if not HereBeDragons.updateTimerActive then
-- prevent running multiple timers
HereBeDragons.updateTimerActive = true
-- and queue an update
C_Timer.After(1, UpdateTimerCallback)
end
end
local function OnEvent(frame, event, ...)
UpdateCurrentPosition(true)
end
HereBeDragons.eventFrame:SetScript("OnEvent", OnEvent)
HereBeDragons.eventFrame:UnregisterAllEvents()
HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED")
HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED_INDOORS")
HereBeDragons.eventFrame:RegisterEvent("NEW_WMO_CHUNK")
HereBeDragons.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
--- Return the localized zone name for a given uiMapID
-- @param uiMapID uiMapID of the zone
function HereBeDragons:GetLocalizedMap(uiMapID)
return mapData[uiMapID] and mapData[uiMapID].name or nil
end
--- Get the size of the zone
-- @param uiMapID uiMapID of the zone
-- @return width, height of the zone, in yards
function HereBeDragons:GetZoneSize(uiMapID)
local data = mapData[uiMapID]
if not data then return 0, 0 end
return data[1], data[2]
end
--- Get a list of all map IDs
-- @return array-style table with all known/valid map IDs
function HereBeDragons:GetAllMapIDs()
local t = {}
for id in pairs(mapData) do
table.insert(t, id)
end
return t
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 HereBeDragons:GetWorldCoordinatesFromZone(x, y, zone)
local data = mapData[zone]
if not data or data[1] == 0 or data[2] == 0 then return nil, nil, nil 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, overrideInstance(data.instance)
end
--- Convert local/point coordinates to world coordinates in yards. The coordinates have to come from the Azeroth World Map
-- @param x X position in 0-1 point coordinates
-- @param y Y position in 0-1 point coordinates
-- @param instance Instance to use for the world coordinates
function HereBeDragons:GetWorldCoordinatesFromAzerothWorldMap(x, y, instance)
local data = worldMapData[instance]
if not data or data[1] == 0 or data[2] == 0 then return nil, nil, nil 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, 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 HereBeDragons:GetZoneCoordinatesFromWorld(x, y, zone, allowOutOfBounds)
local data = mapData[zone]
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
--- 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 HereBeDragons: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
-- Helper function to handle world map coordinate translation
local function TranslateAzerothWorldMapCoordinates(self, x, y, oZone, dZone, allowOutOfBounds)
if (oZone ~= WORLD_MAP_ID and not mapData[oZone]) or (dZone ~= WORLD_MAP_ID and not mapData[dZone]) then return nil, nil end
-- determine the instance we're working with
local instance = overrideInstance((oZone == WORLD_MAP_ID) and mapData[dZone].instance or mapData[oZone].instance)
if not worldMapData[instance] then return nil, nil end
if oZone == WORLD_MAP_ID then
x, y = self:GetWorldCoordinatesFromAzerothWorldMap(x, y, instance)
return self:GetZoneCoordinatesFromWorld(x, y, dZone, allowOutOfBounds)
else
x, y = self:GetWorldCoordinatesFromZone(x, y, oZone)
return self:GetAzerothWorldMapCoordinatesFromWorld(x, y, instance, allowOutOfBounds)
end
end
--- Translate zone coordinates from one zone to another
-- @param x X position in 0-1 point coordinates, relative to the origin zone
-- @param y Y position in 0-1 point coordinates, relative to the origin zone
-- @param oZone Origin Zone, uiMapID
-- @param dZone Destination Zone, uiMapID
-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned
function HereBeDragons:TranslateZoneCoordinates(x, y, oZone, dZone, allowOutOfBounds)
if oZone == dZone then return x, y end
if oZone == WORLD_MAP_ID or dZone == WORLD_MAP_ID then
return TranslateAzerothWorldMapCoordinates(self, x, y, oZone, dZone, allowOutOfBounds)
end
local xCoord, yCoord, instance = self:GetWorldCoordinatesFromZone(x, y, oZone)
if not xCoord then return nil, nil end
local data = mapData[dZone]
if not data or overrideInstance(data.instance) ~= instance then return nil, nil end
return self:GetZoneCoordinatesFromWorld(xCoord, yCoord, dZone, allowOutOfBounds)
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 HereBeDragons: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 HereBeDragons: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
--- Return the angle and 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 angle, distance where angle is in radians and distance in yards
function HereBeDragons:GetWorldVector(instanceID, oX, oY, dX, dY)
local distance, deltaX, deltaY = self:GetWorldDistance(instanceID, oX, oY, dX, dY)
if not distance or not deltaX or not deltaY then return nil, nil end
-- calculate the angle from deltaY and deltaX
local angle = atan2(-deltaX, deltaY)
-- normalize the angle
if angle > 0 then
angle = PI2 - angle
else
angle = -angle
end
return angle, distance
end
--- Get the current world position of the specified unit
-- The position is transformed to the current continent, if applicable
-- NOTE: The same restrictions as for the UnitPosition() API apply,
-- which means a very limited set of unit ids will actually work.
-- @param unitId Unit Id
-- @return x, y, instanceID
function HereBeDragons:GetUnitWorldPosition(unitId)
-- get the current position
local y, x, _z, instanceID = UnitPosition(unitId)
if not x or not y then return nil, nil, overrideInstance(instanceID) end
-- return transformed coordinates
return applyCoordinateTransforms(x, y, instanceID)
end
--- Get the current world position of the player
-- The position is transformed to the current continent, if applicable
-- @return x, y, instanceID
function HereBeDragons:GetPlayerWorldPosition()
-- get the current position
local y, x, _z, instanceID = UnitPosition("player")
if not x or not y then return nil, nil, overrideInstance(instanceID) end
-- return transformed coordinates
return applyCoordinateTransforms(x, y, instanceID)
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 HereBeDragons:GetPlayerZone()
return currentPlayerUIMapID, currentPlayerUIMapType
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 HereBeDragons:GetPlayerZonePosition(allowOutOfBounds)
if not currentPlayerUIMapID then return nil, nil, nil, nil end
local x, y, _instanceID = self:GetPlayerWorldPosition()
if not x or not y then return nil, nil, nil, nil end
x, y = self:GetZoneCoordinatesFromWorld(x, y, currentPlayerUIMapID, allowOutOfBounds)
if x and y then
return x, y, currentPlayerUIMapID, currentPlayerUIMapType
end
return nil, nil, nil, nil
end
-- if we're loading after entering the world (ie. on demand), update position now
-- This needs to remain at the bottom of the library to ensure all functions are loaded before they are needed
if IsLoggedIn() then
UpdateCurrentPosition(true)
end
@@ -0,0 +1,524 @@
local MAJOR, MINOR = "HereBeDragonsQuestie-Migrate", 2
assert(LibStub, MAJOR .. " requires LibStub")
local HBDMigrate, _oldversion = LibStub:NewLibrary(MAJOR, MINOR)
if not HBDMigrate then return end
local SetupMigrationData
local MapMigrationData, mapFileToIdMap, uiMapIdToIdMap
--- Return the uiMapId from the specified mapAreaId/floor combination
-- @param mapId mapAreaId to lookup
-- @param floor floor to lookup (if nil, the first floor will be used)
-- @return The uiMapId corresponding to this map, if any
function HBDMigrate:GetUIMapIDFromMapAreaId(mapId, floor)
if not mapId then return nil end
local data = MapMigrationData[mapId]
if not data then return nil end
if not floor then
if data[0] then
floor = 0
elseif data.defaultFloor then
floor = data.defaultFloor
else
for i = 1, 50 do
if data[i] then
floor = i
break
end
end
data.defaultFloor = floor
end
end
return data[floor]
end
--- Return the uiMapId from the specified mapFile/floor combination
-- @param mapFile mapFile to lookup
-- @param floor floor to lookup (if nil, the first floor will be used)
-- @return The uiMapId corresponding to this map, if any
function HBDMigrate:GetUIMapIDFromMapFile(mapFile, floor)
if not mapFile then return nil end
if not mapFileToIdMap then SetupMigrationData() end
return self:GetUIMapIDFromMapAreaId(mapFileToIdMap[mapFile], floor)
end
--- Return the legacy map information for the specified uiMapId
-- @param uiMapId uiMapId to lookup
-- @return mapAreaId, floor, mapFile
function HBDMigrate:GetLegacyMapInfo(uiMapId)
if not uiMapId then return nil end
if not uiMapIdToIdMap then SetupMigrationData() end
local c = uiMapIdToIdMap[uiMapId]
if not c then return end
local m, f = floor(c / 10000), (c % 10000)
return m, f, MapMigrationData[m].mapFile
end
MapMigrationData = {
[4] = { mapFile = "Durotar", [0] = 1, [8] = 2, [12] = 5, [19] = 6, [11] = 4, [10] = 3},
[9] = { mapFile = "Mulgore", [0] = 7, [6] = 8, [7] = 9},
[11] = { mapFile = "Barrens", [0] = 10, [20] = 11},
[13] = { mapFile = "Kalimdor", [0] = 12},
[14] = { mapFile = "Azeroth", [0] = 13},
[16] = { mapFile = "Arathi", [0] = 14},
[17] = { mapFile = "Badlands", [0] = 15, [18] = 16},
[19] = { mapFile = "BlastedLands", [0] = 17},
[20] = { mapFile = "Tirisfal", [0] = 18, [13] = 19, [25] = 20},
[21] = { mapFile = "Silverpine", [0] = 21},
[22] = { mapFile = "WesternPlaguelands", [0] = 22},
[23] = { mapFile = "EasternPlaguelands", [0] = 23, [20] = 24},
[24] = { mapFile = "HillsbradFoothills", [0] = 25},
[26] = { mapFile = "Hinterlands", [0] = 26},
[27] = { mapFile = "DunMorogh", [6] = 28, [7] = 29, [11] = 31, [10] = 30, [0] = 27},
[28] = { mapFile = "SearingGorge", [0] = 32, [15] = 34, [14] = 33, [16] = 35},
[29] = { mapFile = "BurningSteppes", [0] = 36},
[30] = { mapFile = "Elwynn", [1] = 38, [2] = 39, [0] = 37, [19] = 40, [21] = 41},
[32] = { mapFile = "DeadwindPass", [0] = 42, [24] = 45, [22] = 43, [23] = 44, [27] = 46},
[758] = { mapFile = "TheBastionofTwilight", [1] = 294, [2] = 295, [3] = 296},
[886] = { mapFile = "TerraceOfEndlessSpring", [0] = 456},
[1014] = { mapFile = "Dalaran70", [0] = 625, [12] = 629, [4] = 626, [11] = 628, [10] = 627},
[759] = { mapFile = "HallsofOrigination", [1] = 297, [2] = 298, [3] = 299},
[887] = { mapFile = "SiegeofNiuzaoTemple", [1] = 458, [2] = 459, [0] = 457},
[1015] = { mapFile = "Azsuna", [0] = 630, [17] = 631, [19] = 633, [18] = 632},
[760] = { mapFile = "RazorfenDowns", [1] = 300},
[888] = { mapFile = "ShadowglenStart", [0] = 460},
[761] = { mapFile = "RazorfenKraul", [1] = 301},
[889] = { mapFile = "ValleyofTrialsStart", [0] = 461},
[1017] = { mapFile = "Stormheim", [1] = 635, [0] = 634, [28] = 640, [27] = 639, [26] = 638, [9] = 636, [25] = 637},
[762] = { mapFile = "ScarletMonastery", [1] = 302, [2] = 303, [3] = 304, [4] = 305},
[890] = { mapFile = "CampNaracheStart", [0] = 462},
[1018] = { mapFile = "Valsharah", [0] = 641, [13] = 642, [15] = 644, [14] = 643},
[763] = { mapFile = "Scholomance", [1] = 306, [2] = 307, [3] = 308, [4] = 309},
[891] = { mapFile = "EchoIslesStart", [0] = 463, [9] = 464},
[510] = { mapFile = "CrystalsongForest", [0] = 127},
[40] = { mapFile = "Wetlands", [0] = 56},
[764] = { mapFile = "ShadowfangKeep", [1] = 310, [2] = 311, [3] = 312, [4] = 313, [5] = 314, [6] = 315, [7] = 316},
[892] = { mapFile = "DeathknellStart", [0] = 465, [12] = 466},
[1020] = { mapFile = "TwistingNether70", [0] = 645},
[765] = { mapFile = "Stratholme", [1] = 317, [2] = 318},
[893] = { mapFile = "SunstriderIsleStart", [0] = 467},
[1021] = { mapFile = "BrokenShore", [1] = 647, [2] = 648, [0] = 646},
[766] = { mapFile = "AhnQiraj", [1] = 319, [2] = 320, [3] = 321},
[894] = { mapFile = "AmmenValeStart", [0] = 468},
[1022] = { mapFile = "Helheim", [0] = 649},
[767] = { mapFile = "ThroneofTides", [1] = 322, [2] = 323},
[895] = { mapFile = "NewTinkertownStart", [0] = 469, [8] = 470},
[512] = { mapFile = "StrandoftheAncients", [0] = 128},
[640] = { mapFile = "Deepholm", [1] = 208, [2] = 209, [0] = 207},
[768] = { mapFile = "TheStonecore", [1] = 324},
[896] = { mapFile = "MogushanVaults", [1] = 471, [2] = 472, [3] = 473},
[1024] = { mapFile = "Highmountain", [0] = 650, [29] = 657, [8] = 653, [16] = 654, [5] = 651, [40] = 660, [20] = 655, [21] = 656, [6] = 652, [31] = 659, [30] = 658},
[321] = { mapFile = "Orgrimmar", [1] = 86, [0] = 85},
[769] = { mapFile = "Skywall", [1] = 325},
[897] = { mapFile = "HeartofFear", [1] = 474, [2] = 475},
[1026] = { mapFile = "HellfireRaid", [1] = 662, [2] = 663, [3] = 664, [4] = 665, [5] = 666, [6] = 667, [7] = 668, [8] = 669, [9] = 670, [0] = 661},
[161] = { mapFile = "Tanaris", [0] = 71, [17] = 74, [15] = 72, [16] = 73, [18] = 75},
[1027] = { mapFile = "AraukNashalIntroScenario", [0] = 671},
[898] = { mapFile = "Scholomance", [1] = 476, [2] = 477, [3] = 478, [4] = 479},
[1028] = { mapFile = "MardumtheShatteredAbyss", [1] = 673, [2] = 674, [3] = 675, [0] = 672},
[899] = { mapFile = "ProvingGrounds", [1] = 480},
[772] = { mapFile = "AhnQirajTheFallenKingdom", [0] = 327},
[900] = { mapFile = "AncientMoguCrypt", [1] = 481, [2] = 482},
[1032] = { mapFile = "VaultOfTheWardensDH", [1] = 677, [2] = 678, [3] = 679},
[81] = { mapFile = "StonetalonMountains", [0] = 65},
[773] = { mapFile = "ThroneoftheFourWinds", [1] = 328},
[1034] = { mapFile = "HelmouthShallows", [0] = 694},
[1035] = { mapFile = "ValhallasWarriorOrderHome", [1] = 695},
[775] = { mapFile = "CoTMountHyjal", [0] = 329},
[520] = { mapFile = "TheNexus", [1] = 129},
[776] = { mapFile = "GruulsLair", [1] = 330},
[521] = { mapFile = "CoTStratholme", [1] = 131, [0] = 130},
[1041] = { mapFile = "HallsofValor", [1] = 704, [2] = 705, [0] = 703},
[522] = { mapFile = "Ahnkahet", [1] = 132},
[906] = { mapFile = "DustwallowMarshScenarioAlliance", [0] = 483},
[523] = { mapFile = "UtgardeKeep", [1] = 133, [2] = 134, [3] = 135},
[779] = { mapFile = "MagtheridonsLair", [1] = 331},
[524] = { mapFile = "UtgardePinnacle", [1] = 136, [2] = 137},
[41] = { mapFile = "Teldrassil", [2] = 58, [3] = 59, [4] = 60, [0] = 57, [5] = 61},
[780] = { mapFile = "CoilfangReservoir", [1] = 332},
[525] = { mapFile = "HallsofLightning", [1] = 138, [2] = 139},
[781] = { mapFile = "ZulAman", [0] = 333},
[526] = { mapFile = "Ulduar77", [1] = 140},
[782] = { mapFile = "TempestKeep", [1] = 334},
[527] = { mapFile = "TheEyeofEternity", [1] = 141},
[911] = { mapFile = "KrasarangAlliance", [0] = 486},
[528] = { mapFile = "Nexus80", [1] = 143, [2] = 144, [3] = 145, [4] = 146, [0] = 142},
[912] = { mapFile = "KrasarangPatience", [0] = 487},
[529] = { mapFile = "Ulduar", [1] = 148, [2] = 149, [3] = 150, [4] = 151, [5] = 152, [0] = 147},
[1057] = { mapFile = "MaelstromShaman", [0] = 726},
[530] = { mapFile = "Gundrak", [1] = 154, [0] = 153},
[1059] = { mapFile = "TerraceofEndlessSpringScenario", [0] = 728},
[914] = { mapFile = "VoljinScenario", [1] = 489, [0] = 488},
[531] = { mapFile = "TheObsidianSanctum", [0] = 155},
[532] = { mapFile = "VaultofArchavon", [1] = 156},
[533] = { mapFile = "AzjolNerub", [1] = 157, [2] = 158, [3] = 159},
[789] = { mapFile = "SunwellPlateau", [1] = 336, [0] = 335},
[534] = { mapFile = "DrakTharonKeep", [1] = 160, [2] = 161},
[1067] = { mapFile = "DarkheartThicket", [0] = 733},
[535] = { mapFile = "Naxxramas", [1] = 162, [2] = 163, [3] = 164, [4] = 165, [5] = 166, [6] = 167},
[1069] = { mapFile = "TheBeyond", [1] = 736},
[919] = { mapFile = "BlackTempleScenario", [1] = 491, [2] = 492, [3] = 493, [4] = 494, [5] = 495, [6] = 496, [7] = 497, [0] = 490},
[536] = { mapFile = "VioletHold", [1] = 168},
[1071] = { mapFile = "FirelandsShaman", [0] = 738},
[920] = { mapFile = "KrasarangHorde", [0] = 498},
[1072] = { mapFile = "TrueshotLodge", [0] = 739},
[793] = { mapFile = "ZulGurub", [0] = 337},
[461] = { mapFile = "ArathiBasin", [0] = 93},
[1075] = { mapFile = "AbyssalMawShamanAcquisition", [1] = 742, [2] = 743},
[922] = { mapFile = "DeeprunTram", [1] = 499, [2] = 500},
[1076] = { mapFile = "UlduarMagni", [1] = 744, [2] = 745, [3] = 746},
[795] = { mapFile = "MoltenFront", [0] = 338},
[462] = { mapFile = "EversongWoods", [0] = 94},
[34] = { mapFile = "Duskwood", [0] = 47},
[42] = { mapFile = "Darkshore", [0] = 62},
[796] = { mapFile = "BlackTemple", [1] = 340, [2] = 341, [3] = 342, [4] = 343, [5] = 344, [6] = 345, [7] = 346, [0] = 339},
[924] = { mapFile = "DalaranCity", [1] = 501, [2] = 502},
[541] = { mapFile = "HrothgarsLanding", [0] = 170},
[797] = { mapFile = "HellfireRamparts", [1] = 347},
[925] = { mapFile = "BrawlgarArena", [1] = 503},
[542] = { mapFile = "TheArgentColiseum", [1] = 171},
[798] = { mapFile = "MagistersTerrace", [1] = 348, [2] = 349},
[543] = { mapFile = "TheArgentColiseum", [1] = 172, [2] = 173},
[799] = { mapFile = "Karazhan", [1] = 350, [2] = 351, [3] = 352, [4] = 353, [5] = 354, [6] = 355, [7] = 356, [8] = 357, [9] = 358, [10] = 359, [11] = 360, [12] = 361, [13] = 362, [14] = 363, [15] = 364, [16] = 365, [17] = 366},
[464] = { mapFile = "AzuremystIsle", [0] = 97, [2] = 98, [3] = 99},
[544] = { mapFile = "TheLostIsles", [1] = 175, [2] = 176, [3] = 177, [4] = 178, [0] = 174},
[800] = { mapFile = "Firelands", [1] = 368, [2] = 369, [0] = 367},
[928] = { mapFile = "IsleoftheThunderKing", [1] = 505, [2] = 506, [0] = 504},
[545] = { mapFile = "Gilneas", [1] = 180, [2] = 181, [3] = 182, [0] = 179},
[673] = { mapFile = "TheCapeOfStranglethorn", [0] = 210},
[401] = { mapFile = "AlteracValley", [0] = 91},
[929] = { mapFile = "IsleOfGiants", [0] = 507},
[1090] = { mapFile = "TolBaradWarlockScenario", [1] = 774, [0] = 773},
[201] = { mapFile = "UngoroCrater", [0] = 78, [14] = 79},
[930] = { mapFile = "ThunderKingRaid", [1] = 508, [2] = 509, [3] = 510, [4] = 511, [5] = 512, [6] = 513, [7] = 514, [8] = 515},
[1092] = { mapFile = "AzuremystIsleScenario", [0] = 776},
[803] = { mapFile = "TheNexusLegendary", [1] = 370},
[466] = { mapFile = "Expansion01", [0] = 101},
[1094] = { mapFile = "NightmareRaid", [1] = 777, [2] = 778, [3] = 779, [4] = 780, [5] = 781, [6] = 782, [7] = 783, [8] = 784, [9] = 785, [10] = 786, [11] = 787, [12] = 788, [13] = 789},
[1096] = { mapFile = "AszunaDungeonExterior", [0] = 790},
[101] = { mapFile = "Desolace", [0] = 66, [22] = 68, [21] = 67},
[933] = { mapFile = "IsleoftheThunderKingScenario", [1] = 517, [0] = 516},
[806] = { mapFile = "TheJadeForest", [6] = 372, [7] = 373, [15] = 374, [16] = 375, [0] = 371},
[934] = { mapFile = "ThunderKingLootRoom", [1] = 518},
[1100] = { mapFile = "KarazhanScenario", [1] = 794, [2] = 795, [3] = 796, [4] = 797},
[807] = { mapFile = "ValleyoftheFourWinds", [0] = 376, [14] = 377},
[935] = { mapFile = "GoldRush", [0] = 519},
[1102] = { mapFile = "ArcwayScenario", [1] = 798},
[680] = { mapFile = "Ragefire", [1] = 213},
[808] = { mapFile = "TheWanderingIsle", [0] = 378},
[1104] = { mapFile = "MageCampaignTheOculus", [1] = 800, [2] = 801, [3] = 802, [4] = 803, [0] = 799},
[341] = { mapFile = "Ironforge", [0] = 87},
[809] = { mapFile = "KunLaiSummit", [0] = 379, [8] = 380, [9] = 381, [10] = 382, [20] = 386, [11] = 383, [21] = 387, [12] = 384, [17] = 385},
[937] = { mapFile = "ValeOfEternalBlossomsScenario", [1] = 521, [0] = 520},
[810] = { mapFile = "TownlongWastes", [0] = 388, [13] = 389},
[938] = { mapFile = "EmberdeepScenario", [1] = 522},
[811] = { mapFile = "ValeofEternalBlossoms", [1] = 391, [2] = 392, [3] = 393, [4] = 394, [0] = 390, [19] = 396, [18] = 395},
[939] = { mapFile = "DunMoroghScenario", [0] = 523},
[35] = { mapFile = "LochModan", [0] = 48},
[43] = { mapFile = "Ashenvale", [0] = 63},
[940] = { mapFile = "tempKrasarangHordeBase", [0] = 524},
[685] = { mapFile = "RuinsofGilneasCity", [0] = 218},
[813] = { mapFile = "NetherstormArena", [0] = 397},
[471] = { mapFile = "TheExodar", [0] = 103},
[1114] = { mapFile = "HelheimRaid", [1] = 807, [2] = 808, [0] = 806},
[686] = { mapFile = "ZulFarrak", [0] = 219},
[1115] = { mapFile = "LegionKarazhanDungeon", [1] = 809, [2] = 810, [3] = 811, [4] = 812, [5] = 813, [6] = 814, [7] = 815, [8] = 816, [9] = 817, [10] = 818, [11] = 819, [12] = 820, [13] = 821, [14] = 822},
[1116] = { mapFile = "PitofSaronDK", [0] = 823},
[687] = { mapFile = "TheTempleOfAtalHakkar", [1] = 220},
[688] = { mapFile = "BlackfathomDeeps", [1] = 221, [2] = 222, [3] = 223},
[816] = { mapFile = "WellofEternity", [0] = 398},
[281] = { mapFile = "Winterspring", [0] = 83},
[689] = { mapFile = "StranglethornVale", [0] = 224},
[473] = { mapFile = "ShadowmoonValley", [0] = 104},
[141] = { mapFile = "Dustwallow", [0] = 70},
[690] = { mapFile = "TheStockade", [1] = 225},
[946] = { mapFile = "Talador", [0] = 535, [13] = 536, [14] = 537, [30] = 538},
[691] = { mapFile = "Gnomeregan", [1] = 226, [2] = 227, [3] = 228, [4] = 229},
[819] = { mapFile = "HourofTwilight", [1] = 400, [0] = 399},
[947] = { mapFile = "ShadowmoonValleyDR", [0] = 539, [22] = 541, [15] = 540},
[1126] = {[0] = 824},
[692] = { mapFile = "Uldaman", [1] = 230, [2] = 231},
[820] = { mapFile = "EndTime", [1] = 402, [2] = 403, [3] = 404, [4] = 405, [5] = 406, [0] = 401},
[948] = { mapFile = "SpiresOfArak", [0] = 542},
[181] = { mapFile = "Aszhara", [0] = 76},
[1220] = {[0] = 981},
[1129] = { mapFile = "CaveoftheBloodtotemScenario", [1] = 826},
[949] = { mapFile = "Gorgrond", [0] = 543, [17] = 545, [21] = 549, [20] = 548, [19] = 547, [16] = 544, [18] = 546},
[1130] = { mapFile = "StratholmePaladinClassMount", [1] = 827},
[1219] = {[1] = 975, [2] = 976, [3] = 977, [4] = 978, [5] = 979, [6] = 980, [0] = 974},
[1131] = { mapFile = "TheEyeofEternityMageClassMount", [1] = 828},
[950] = { mapFile = "NagrandDraenor", [11] = 552, [12] = 553, [0] = 550, [10] = 551},
[1132] = { mapFile = "HallsOfValorWarriorClassMount", [1] = 829},
[1050] = { mapFile = "WarlockClassShrine", [0] = 717},
[823] = { mapFile = "DarkmoonFaireIsland", [1] = 408, [0] = 407},
[476] = { mapFile = "BloodmystIsle", [0] = 106},
[1216] = { mapFile = "VoidElfScenario", [0] = 972},
[696] = { mapFile = "MoltenCore", [1] = 232},
[824] = { mapFile = "DragonSoul", [1] = 410, [2] = 411, [3] = 412, [4] = 413, [5] = 414, [6] = 415, [0] = 409},
[1215] = { mapFile = "VoidElfHub", [0] = 971},
[1136] = { mapFile = "ColdridgeValleyScenario", [0] = 834},
[697] = { mapFile = "ZulGurub", [0] = 233},
[1137] = { mapFile = "TheDeadminesPetBattle", [1] = 835, [2] = 836},
[477] = { mapFile = "Nagrand", [0] = 107},
[1052] = { mapFile = "DemonHunterOrderHallTerrain", [1] = 720, [2] = 721, [0] = 719},
[1054] = { mapFile = "TheVioletHoldAcquisition", [1] = 723},
[1139] = { mapFile = "ArathiBasinWinter", [0] = 837},
[1212] = { mapFile = "LightforgedVindicaar", [1] = 940, [2] = 941},
[1140] = { mapFile = "BattleforBlackrockMountain", [0] = 838},
[699] = { mapFile = "DireMaul", [1] = 235, [2] = 236, [3] = 237, [4] = 238, [5] = 239, [6] = 240, [0] = 234},
[1211] = {[0] = 939},
[478] = { mapFile = "TerokkarForest", [0] = 108},
[36] = { mapFile = "Redridge", [0] = 49},
[700] = { mapFile = "TwilightHighlands", [0] = 241},
[1143] = { mapFile = "GnomereganPetBattle", [1] = 840, [2] = 841, [3] = 842},
[1210] = {[0] = 938},
[1144] = { mapFile = "SmallBattlegroundC", [0] = 843},
[1066] = { mapFile = "LegionVioletHoldDungeon", [1] = 732},
[1145] = {[0] = 844},
[479] = { mapFile = "Netherstorm", [0] = 109},
[1146] = { mapFile = "TombofSargerasDungeon", [1] = 845, [2] = 846, [3] = 847, [4] = 848, [5] = 849},
[1204] = {[1] = 934, [2] = 935},
[1147] = { mapFile = "TombRaid", [1] = 850, [2] = 851, [3] = 852, [4] = 853, [5] = 854, [6] = 855, [7] = 856},
[1202] = { mapFile = "LightforgedDraeneiSwamp", [0] = 933},
[1148] = { mapFile = "ThroneoftheFourWinds", [1] = 857},
[1201] = { mapFile = "InvasionPointVal", [0] = 932},
[1149] = { mapFile = "AssaultonBrokenShoreScenario", [0] = 858},
[480] = { mapFile = "SilvermoonCity", [0] = 110},
[1150] = {[0] = 859},
[704] = { mapFile = "BlackrockDepths", [1] = 242, [2] = 243},
[1151] = { mapFile = "TheRubySanctumDKMountScenario", [0] = 860},
[1200] = { mapFile = "InvasionPointSangua", [0] = 931},
[1152] = { mapFile = "FelwingLedgeMardumArea", [0] = 861},
[1199] = { mapFile = "InvasionPointNaigtal", [0] = 930},
[1153] = {[0] = 862},
[481] = { mapFile = "ShattrathCity", [0] = 111},
[1154] = {[0] = 863},
[1068] = { mapFile = "MageClassShrine", [1] = 734, [2] = 735},
[1155] = {[0] = 864},
[241] = { mapFile = "Moonglade", [0] = 80},
[1156] = { mapFile = "StormheimInvasionScenario", [1] = 865, [2] = 866},
[1070] = { mapFile = "TheVortexPinnacle", [1] = 737},
[1157] = { mapFile = "AzsunaInvasionScenario", [1] = 867},
[482] = { mapFile = "NetherstormArena", [0] = 112},
[1158] = { mapFile = "ValsharahInvasionScenario", [1] = 868},
[708] = { mapFile = "TolBarad", [0] = 244},
[1159] = { mapFile = "HighmountainInvasionScenario", [1] = 869, [2] = 870},
[964] = { mapFile = "OgreMines", [1] = 573},
[1160] = { mapFile = "LostGlacierDKMountScenario", [0] = 871},
[709] = { mapFile = "TolBaradDailyArea", [0] = 245},
[1161] = { mapFile = "StormstoutBreweryScenario", [1] = 873, [2] = 874, [0] = 872},
[121] = { mapFile = "Feralas", [0] = 69},
[1162] = {[0] = 875},
[710] = { mapFile = "TheShatteredHalls", [1] = 246},
[1163] = {[0] = 876},
[1073] = { mapFile = "ArtifactSubtletyRogueAcquisition", [1] = 740, [2] = 741},
[1164] = { mapFile = "HallsofValor", [0] = 877},
[1078] = { mapFile = "Niskara", [0] = 748},
[1165] = { mapFile = "DemonHunterOrderHallTerrain", [1] = 879, [2] = 880, [0] = 878},
[1079] = { mapFile = "SuamarCatacombsDungeon", [1] = 749},
[1166] = { mapFile = "TheEyeofEternityMageClassMount", [1] = 881},
[1080] = { mapFile = "ThunderTotem", [0] = 750},
[1081] = { mapFile = "BlackRookHoldDungeon", [1] = 751, [2] = 752, [3] = 753, [4] = 754, [5] = 755, [6] = 756},
[1082] = { mapFile = "UrsocsLairScenario", [0] = 757},
[1084] = { mapFile = "GloamingReef", [0] = 758},
[1085] = { mapFile = "70BlackTempleLegion", [1] = 759},
[1086] = { mapFile = "MalornesNightmare", [0] = 760},
[485] = { mapFile = "Northrend", [0] = 113},
[1170] = { mapFile = "ArgusMacAree", [0] = 882, [3] = 883, [4] = 884},
[1087] = { mapFile = "SuramarNoblesDistrict", [1] = 762, [2] = 763, [0] = 761},
[1171] = { mapFile = "ArgusCore", [0] = 885, [6] = 887, [5] = 886},
[970] = { mapFile = "TanaanJungleIntro", [1] = 578, [0] = 577},
[1172] = { mapFile = "HallOfCommunion", [1] = 888},
[1091] = { mapFile = "TheExodar", [0] = 775},
[1173] = { mapFile = "TKArcatrazScenario", [1] = 889, [2] = 890},
[486] = { mapFile = "BoreanTundra", [0] = 114},
[37] = { mapFile = "StranglethornJungle", [0] = 50},
[1097] = { mapFile = "ArtifactBrewmasterScenario", [1] = 791, [2] = 792},
[1175] = {[0] = 895},
[61] = { mapFile = "ThousandNeedles", [0] = 64},
[1176] = {[0] = 896},
[717] = { mapFile = "RuinsofAhnQiraj", [0] = 247},
[1177] = { mapFile = "DragonblightChromieScenario", [1] = 898, [2] = 899, [3] = 900, [4] = 901, [5] = 902, [0] = 897},
[973] = { mapFile = "garrisonsmvalliance_tier1", [0] = 582},
[1178] = { mapFile = "ArgusDungeon", [0] = 903},
[718] = { mapFile = "OnyxiasLair", [1] = 248},
[1099] = { mapFile = "BlackRookHoldScenario", [0] = 793},
[1174] = { mapFile = "AzuremystScenario", [1] = 892, [2] = 893, [3] = 894, [0] = 891},
[1142] = { mapFile = "PriestClassMountScenario", [1] = 839},
[1135] = { mapFile = "ArgusSurface", [1] = 831, [2] = 832, [0] = 830, [7] = 833},
[1127] = { mapFile = "WailingCavernsPetBattle", [1] = 825},
[488] = { mapFile = "Dragonblight", [0] = 115},
[1105] = { mapFile = "ScarletMonestaryDK", [1] = 804, [2] = 805},
[720] = { mapFile = "Uldum", [0] = 249},
[1183] = { mapFile = "SilithusBrawl", [0] = 904},
[976] = { mapFile = "garrisonffhorde", [27] = 586, [28] = 587, [26] = 585},
[1184] = { mapFile = "Argus", [0] = 994},
[721] = { mapFile = "BlackrockSpire", [1] = 250, [2] = 251, [3] = 252, [4] = 253, [5] = 254, [6] = 255},
[1185] = {[0] = 906},
[1088] = { mapFile = "SuramarRaid", [1] = 764, [2] = 765, [3] = 766, [4] = 767, [5] = 768, [6] = 769, [7] = 770, [8] = 771, [9] = 772},
[1186] = { mapFile = "AzeriteBG", [0] = 907},
[722] = { mapFile = "AuchenaiCrypts", [1] = 256, [2] = 257},
[1187] = {[0] = 908},
[978] = { mapFile = "Ashran", [0] = 588, [29] = 589},
[1188] = { mapFile = "ArgusRaid", [1] = 910, [2] = 911, [3] = 912, [4] = 913, [5] = 914, [6] = 915, [7] = 916, [8] = 917, [9] = 918, [10] = 919, [11] = 920, [0] = 909},
[723] = { mapFile = "SethekkHalls", [1] = 258, [2] = 259},
[851] = { mapFile = "DustwallowMarshScenario", [0] = 416},
[490] = { mapFile = "GrizzlyHills", [0] = 116},
[1190] = { mapFile = "InvasionPointAurinor", [0] = 921},
[724] = { mapFile = "ShadowLabyrinth", [1] = 260},
[1191] = { mapFile = "InvasionPointBonich", [0] = 922},
[980] = { mapFile = "garrisonffhorde_tier1", [0] = 590},
[1192] = { mapFile = "InvasionPointCengar", [0] = 923},
[725] = { mapFile = "TheBloodFurnace", [1] = 261},
[1193] = { mapFile = "InvasionPointNaigtal", [0] = 924},
[491] = { mapFile = "HowlingFjord", [0] = 117},
[1194] = { mapFile = "InvasionPointSangua", [0] = 925},
[726] = { mapFile = "TheUnderbog", [1] = 262},
[1195] = { mapFile = "InvasionPointVal", [0] = 926},
[1077] = { mapFile = "TheDreamgrove", [0] = 747},
[1196] = { mapFile = "InvasionPointAurinor", [0] = 927},
[727] = { mapFile = "TheSteamvault", [1] = 263, [2] = 264},
[1197] = { mapFile = "InvasionPointBonich", [0] = 928},
[492] = { mapFile = "IcecrownGlacier", [0] = 118},
[1198] = { mapFile = "InvasionPointCengar", [0] = 929},
[728] = { mapFile = "TheSlavePens", [1] = 265},
[856] = { mapFile = "TempleofKotmogu", [0] = 417},
[984] = { mapFile = "DraenorAuchindoun", [1] = 593},
[601] = { mapFile = "TheForgeofSouls", [1] = 183},
[729] = { mapFile = "TheBotanica", [1] = 266},
[857] = { mapFile = "Krasarang", [1] = 419, [2] = 420, [3] = 421, [0] = 418},
[493] = { mapFile = "SholazarBasin", [0] = 119},
[602] = { mapFile = "PitofSaron", [0] = 184},
[730] = { mapFile = "TheMechanar", [1] = 267, [2] = 268},
[858] = { mapFile = "DreadWastes", [0] = 422},
[986] = { mapFile = "TaladorScenario", [0] = 594},
[603] = { mapFile = "HallsofReflection", [1] = 185},
[731] = { mapFile = "TheArcatraz", [1] = 269, [2] = 270, [3] = 271},
[1205] = {[0] = 936},
[987] = { mapFile = "IronDocks", [1] = 595},
[38] = { mapFile = "SwampOfSorrows", [0] = 51},
[732] = { mapFile = "ManaTombs", [1] = 272},
[860] = { mapFile = "STVDiamondMineBG", [1] = 423},
[988] = { mapFile = "FoundryRaid", [1] = 596, [2] = 597, [3] = 598, [4] = 599, [5] = 600},
[605] = { mapFile = "Kezan", [6] = 196, [7] = 197, [5] = 195, [0] = 194},
[733] = { mapFile = "CoTTheBlackMorass", [0] = 273},
[1065] = { mapFile = "NeltharionsLair", [0] = 731},
[495] = { mapFile = "TheStormPeaks", [0] = 120},
[606] = { mapFile = "Hyjal", [0] = 198},
[734] = { mapFile = "CoTHillsbradFoothills", [0] = 274},
[862] = { mapFile = "Pandaria", [0] = 424},
[1060] = { mapFile = "DeepholmShamanAcquisition", [1] = 729},
[607] = { mapFile = "SouthernBarrens", [0] = 199},
[1056] = { mapFile = "MaelstromShamanHubIntro", [0] = 725},
[1213] = {[0] = 942},
[496] = { mapFile = "ZulDrak", [0] = 121},
[1214] = {[0] = 943},
[736] = { mapFile = "GilneasBattleground2", [0] = 275},
[864] = { mapFile = "Northshire", [0] = 425, [3] = 426},
[1051] = { mapFile = "DreadscarRift", [0] = 718},
[609] = { mapFile = "TheRubySanctum", [0] = 200},
[737] = { mapFile = "TheMaelstrom", [0] = 276},
[1217] = { mapFile = "TheSunwellUnlockScenario", [1] = 973},
[993] = { mapFile = "BlackrockTrainDepotDungeon", [1] = 606, [2] = 607, [3] = 608, [4] = 609},
[610] = { mapFile = "VashjirKelpForest", [0] = 201},
[1049] = { mapFile = "ArtifactSkywall", [1] = 716},
[866] = { mapFile = "ColdridgeValley", [0] = 427, [9] = 428},
[994] = { mapFile = "HighmaulRaid", [1] = 611, [2] = 612, [3] = 613, [4] = 614, [5] = 615, [0] = 610},
[611] = { mapFile = "GilneasCity", [0] = 202},
[1048] = { mapFile = "EmeraldDreamway", [0] = 715},
[867] = { mapFile = "EastTemple", [1] = 429, [2] = 430},
[995] = { mapFile = "UpperBlackrockSpire", [1] = 616, [2] = 617, [3] = 618},
[1047] = { mapFile = "Niskara", [0] = 714},
[1046] = { mapFile = "AszunaDungeon", [0] = 713},
[1045] = { mapFile = "VaultOfTheWardens", [1] = 710, [2] = 711, [3] = 712},
[1044] = { mapFile = "MonkOrderHallTheWanderingIsle", [0] = 709},
[613] = { mapFile = "Vashjir", [0] = 203},
[1042] = { mapFile = "HelheimDungeonDock", [1] = 707, [2] = 708, [0] = 706},
[1040] = { mapFile = "NetherlightTemple", [1] = 702},
[499] = { mapFile = "Sunwell", [0] = 122},
[614] = { mapFile = "VashjirDepths", [0] = 204},
[1039] = { mapFile = "IcecrownCitadelDeathKnight", [1] = 698, [2] = 699, [3] = 700, [4] = 701},
[1038] = { mapFile = "HulnFlashback", [0] = 697},
[1037] = { mapFile = "StormheimArtifactProtWarrior", [0] = 696},
[615] = { mapFile = "VashjirRuins", [0] = 205},
[1033] = { mapFile = "Suramar", [24] = 683, [33] = 685, [35] = 687, [39] = 691, [41] = 692, [42] = 693, [32] = 684, [34] = 686, [36] = 688, [38] = 690, [37] = 689, [22] = 681, [23] = 682, [0] = 680},
[871] = { mapFile = "ScarletHalls", [1] = 431, [2] = 432},
[1031] = { mapFile = "BrokenShorePaladin", [0] = 676},
[301] = { mapFile = "StormwindCity", [0] = 84},
[475] = { mapFile = "BladesEdgeMountains", [0] = 105},
[382] = { mapFile = "Undercity", [0] = 998},
[953] = { mapFile = "OrgrimmarRaid", [1] = 557, [2] = 558, [3] = 559, [4] = 560, [5] = 561, [6] = 562, [7] = 563, [8] = 564, [9] = 565, [10] = 566, [11] = 567, [12] = 568, [13] = 569, [14] = 570, [0] = 556},
[1007] = { mapFile = "BrokenIsles", [0] = 619},
[989] = { mapFile = "SpiresofArakDungeon", [1] = 601, [2] = 602},
[873] = { mapFile = "TheHiddenPass", [0] = 433, [5] = 434},
[501] = { mapFile = "LakeWintergrasp", [0] = 123},
[983] = { mapFile = "DefenseofKarabor", [0] = 592},
[971] = { mapFile = "garrisonsmvalliance", [24] = 580, [25] = 581, [23] = 579},
[874] = { mapFile = "ScarletCathedral", [1] = 435, [2] = 436},
[969] = { mapFile = "ShadowmoonDungeon", [1] = 574, [2] = 575, [3] = 576},
[261] = { mapFile = "Silithus", [0] = 81, [13] = 82},
[747] = { mapFile = "LostCityofTolvir", [0] = 277},
[875] = { mapFile = "TheGreatWall", [1] = 437, [2] = 438},
[502] = { mapFile = "ScarletEnclave", [0] = 124},
[39] = { mapFile = "Westfall", [0] = 52, [17] = 55, [4] = 53, [5] = 54},
[962] = { mapFile = "Draenor", [0] = 572},
[876] = { mapFile = "StormstoutBrewery", [1] = 439, [2] = 440, [3] = 441, [4] = 442},
[955] = { mapFile = "CelestialChallenge", [0] = 571},
[951] = { mapFile = "TimelessIsle", [0] = 554, [22] = 555},
[749] = { mapFile = "WailingCaverns", [1] = 279},
[877] = { mapFile = "ShadowpanHideout", [1] = 444, [2] = 445, [3] = 446, [0] = 443},
[945] = { mapFile = "TanaanJungle", [0] = 534},
[941] = { mapFile = "FrostfireRidge", [1] = 526, [2] = 527, [3] = 528, [4] = 529, [6] = 530, [7] = 531, [8] = 532, [0] = 525, [9] = 533},
[750] = { mapFile = "Maraudon", [1] = 280, [2] = 281},
[878] = { mapFile = "BrewmasterScenario01", [0] = 447},
[684] = { mapFile = "RuinsofGilneas", [0] = 217},
[362] = { mapFile = "ThunderBluff", [0] = 88},
[751] = { mapFile = "TheMaelstromContinent", [0] = 948},
[182] = { mapFile = "Felwood", [0] = 77},
[504] = { mapFile = "Dalaran", [1] = 125, [2] = 126},
[465] = { mapFile = "Hellfire", [0] = 100},
[752] = { mapFile = "BaradinHold", [1] = 282},
[880] = { mapFile = "TheJadeForestScenario", [0] = 448},
[1008] = { mapFile = "OvergrownOutpost", [1] = 621, [0] = 620},
[443] = { mapFile = "WarsongGulch", [0] = 92},
[753] = { mapFile = "BlackrockCaverns", [1] = 283, [2] = 284},
[881] = { mapFile = "ValleyOfPowerScenario", [0] = 449},
[1009] = { mapFile = "AshranAllianceFactionHub", [0] = 622},
[626] = { mapFile = "TwinPeaks", [0] = 206},
[754] = { mapFile = "BlackwingDescent", [1] = 285, [2] = 286},
[882] = { mapFile = "BrewmasterScenario03", [0] = 450},
[1010] = { mapFile = "HillsbradFoothillsBG", [0] = 623},
[463] = { mapFile = "Ghostlands", [1] = 96, [0] = 95},
[755] = { mapFile = "BlackwingLair", [1] = 287, [2] = 288, [3] = 289, [4] = 290},
[883] = { mapFile = "Tyrivess", [0] = 451},
[1011] = { mapFile = "AshranHordeFactionHub", [0] = 624},
[381] = { mapFile = "Darnassus", [0] = 89},
[756] = { mapFile = "TheDeadmines", [1] = 291, [2] = 292},
[884] = { mapFile = "KunLaiPassScenario", [0] = 452},
[540] = { mapFile = "IsleofConquest", [0] = 169},
[604] = { mapFile = "IcecrownCitadel", [1] = 186, [2] = 187, [3] = 188, [4] = 189, [5] = 190, [6] = 191, [7] = 192, [8] = 193},
[757] = { mapFile = "GrimBatol", [1] = 293},
[885] = { mapFile = "MogushanPalace", [1] = 453, [2] = 454, [3] = 455},
[467] = { mapFile = "Zangarmarsh", [0] = 102},
}
function SetupMigrationData()
mapFileToIdMap = {}
for id, t in pairs(MapMigrationData) do
if t.mapFile then
mapFileToIdMap[t.mapFile] = id
end
end
uiMapIdToIdMap = {}
for id, t in pairs(MapMigrationData) do
for floor, uiMapId in pairs(t) do
if floor ~= "mapFile" and floor ~= "defaultFloor" then
uiMapIdToIdMap[uiMapId] = id * 10000 + floor
end
end
end
end
@@ -0,0 +1,810 @@
---@diagnostic disable: assign-type-mismatch
-- HereBeDragons-Pins is a library to show pins/icons on the world map and minimap
local MAJOR, MINOR = "HereBeDragonsQuestie-Pins-2.0", 8
assert(LibStub, MAJOR .. " requires LibStub")
---@class HereBeDragonsQuestie-Pins-2.0
---@field MinimapGroup Frame
local pins, _oldversion = LibStub:NewLibrary(MAJOR, MINOR)
if not pins then return end
local HBD = LibStub("HereBeDragonsQuestie-2.0")
local MinimapRadiusAPI = C_Minimap and C_Minimap.GetViewRadius
pins.updateFrame = pins.updateFrame or CreateFrame("Frame")
-- storage for minimap pins
pins.minimapPins = pins.minimapPins or {}
pins.activeMinimapPins = pins.activeMinimapPins or {}
pins.minimapPinRegistry = pins.minimapPinRegistry or {}
-- and worldmap pins
pins.worldmapPins = pins.worldmapPins or {}
pins.worldmapPinRegistry = pins.worldmapPinRegistry or {}
pins.worldmapPinsPool = pins.worldmapPinsPool or CreateFramePool("FRAME")
pins.worldmapProvider = pins.worldmapProvider or CreateFromMixins(MapCanvasDataProviderMixin)
pins.worldmapProviderPin = pins.worldmapProviderPin or CreateFromMixins(MapCanvasPinMixin)
-- store a reference to the active minimap object
pins.Minimap = pins.Minimap or Minimap
-- 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
-- upvalue data tables
local minimapPins = pins.minimapPins
local activeMinimapPins = pins.activeMinimapPins
local minimapPinRegistry = pins.minimapPinRegistry
local worldmapPins = pins.worldmapPins
local worldmapPinRegistry = pins.worldmapPinRegistry
local worldmapPinsPool = pins.worldmapPinsPool
local worldmapProvider = pins.worldmapProvider
local worldmapProviderPin = pins.worldmapProviderPin
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 = {
[0] = 466 + 2/3, -- scale
[1] = 400, -- 7/6
[2] = 333 + 1/3, -- 1.4
[3] = 266 + 2/6, -- 1.75
[4] = 200, -- 7/3
[5] = 133 + 1/3, -- 3.5
},
}
---@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
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 IsParentMap(originMapId, toCheckMapId)
local parentMapID = HBD.mapData[originMapId].parent
while parentMapID and HBD.mapData[parentMapID] do
local mapType = HBD.mapData[parentMapID].mapType
if mapType ~= Enum.UIMapType.Zone and mapType ~= Enum.UIMapType.Dungeon and mapType ~= Enum.UIMapType.Micro then
return false
end
if parentMapID == toCheckMapId then
return true
end
parentMapID = HBD.mapData[parentMapID].parent
end
return false
end
local function UpdateMinimapPins(force)
-- get the current player position
local x, y, instanceID = HBD:GetPlayerWorldPosition()
local mapID = HBD:GetPlayerZone()
-- 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"]
minimapWidth = pins.Minimap:GetWidth() / 2
minimapHeight = pins.Minimap:GetHeight() / 2
if MinimapRadiusAPI then
mapRadius = C_Minimap.GetViewRadius()
else
mapRadius = minimap_size[indoors][zoom] / 2
end
-- update upvalues for icon placement
lastZoom = zoom
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()
-- get the current map zoom
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
-- we have no active minimap pins, just return early
if minimapPinCount == 0 then return end
local x, y = HBD:GetPlayerWorldPosition()
-- 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
-- update radius of the map
if MinimapRadiusAPI then
mapRadius = C_Minimap.GetViewRadius()
else
mapRadius = minimap_size[indoors][zoom] / 2
end
-- update upvalues for icon placement
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
-------------------------------------------------------------------------------------------
-- WorldMap data provider
-- setup pin pool
worldmapPinsPool.parent = WorldMapFrame:GetCanvas()
worldmapPinsPool.creationFunc = function(framePool)
local frame = CreateFrame(framePool.frameType, nil, framePool.parent)
frame:SetSize(1, 1)
return Mixin(frame, worldmapProviderPin)
end
worldmapPinsPool.resetterFunc = function(pinPool, pin)
FramePool_HideAndClearAnchors(pinPool, pin)
pin:OnReleased()
pin.pinTemplate = nil
pin.owningMap = nil
end
-- register pin pool with the world map
WorldMapFrame.pinPools["HereBeDragonsPinsTemplateQuestie"] = worldmapPinsPool
-- provider base API
function worldmapProvider:RemoveAllData()
self:GetMap():RemoveAllPinsByTemplate("HereBeDragonsPinsTemplateQuestie")
end
function worldmapProvider:RemovePinByIcon(icon)
for pin in self:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplateQuestie") do
if pin.icon == icon then
self:GetMap():RemovePin(pin)
end
end
end
function worldmapProvider:RemovePinsByRef(ref)
for pin in self:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplateQuestie") do
if pin.icon and worldmapPinRegistry[ref][pin.icon] then
self:GetMap():RemovePin(pin)
end
end
end
-- questie specific change
local lastUiMapId = -1;
worldmapProvider.forceUpdate = false; --Put into worldmapProvider to allow addons to force update from outside of HBD.
function worldmapProvider:RefreshAllData(fromOnShow)
local mapId = self:GetMap():GetMapID()
if(lastUiMapId ~= mapId or worldmapProvider.forceUpdate) then
self:RemoveAllData()
local cacheMap = self:GetMap()
local uiMapID = cacheMap:GetMapID()
for icon, data in pairs(worldmapPins) do
self:HandlePin(icon, data, uiMapID, cacheMap)
end
--DEFAULT_CHAT_FRAME:AddMessage(mapId .. " - " .. lastUiMapId .. " : " .. tostring(worldmapProvider.forceUpdate));
lastUiMapId = mapId;
worldmapProvider.forceUpdate = false;
end
end
function worldmapProvider:HandlePin(icon, data, uiMapId, cacheMap)
local uiMapID = uiMapId or self:GetMap():GetMapID()
-- check for a valid map
if not uiMapID then return end
--Questie Modification
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) then
icon:Hide();
return;
elseif(uiMapID == data.uiMapID and data.worldMapShowFlag == HBD_PINS_WORLDMAP_SHOW_CURRENT) then
icon:Show();
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].parent
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].parent
end
end
if not show then return end
end
end
-- translate coordinates
x, y = HBD:GetZoneCoordinatesFromWorld(data.x, data.y, uiMapID)
end
if x and y then
--worldmapProvider.forceUpdate = true;
(cacheMap or self:GetMap()):AcquirePin("HereBeDragonsPinsTemplateQuestie", icon, x, y, data.frameLevelType)
end
end
-- map pin base API
function worldmapProviderPin:OnLoad()
self:UseFrameLevelType("PIN_FRAME_LEVEL_AREA_POI")
self:SetScalingLimits(1, 1.0, 1.2)
end
function worldmapProviderPin:OnAcquired(icon, x, y, frameLevelType)
self:UseFrameLevelType(frameLevelType or "PIN_FRAME_LEVEL_AREA_POI")
self:SetPosition(x, y)
self.icon = icon
icon:SetParent(self)
icon:ClearAllPoints()
icon:SetPoint("CENTER", self, "CENTER")
icon:Show()
end
function worldmapProviderPin:OnReleased()
if self.icon then
self.icon:Hide()
self.icon:SetParent(UIParent)
self.icon:ClearAllPoints()
self.icon = nil
end
end
-- register with the world map
WorldMapFrame:AddDataProvider(worldmapProvider)
-- map event handling
local function UpdateMinimap()
UpdateMinimapZoom()
UpdateMinimapPins()
end
local function UpdateWorldMap()
worldmapProvider:RefreshAllData()
end
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
UpdateMinimap()
UpdateWorldMap()
end
end
pins.updateFrame:SetScript("OnEvent", OnEventHandler)
pins.updateFrame:UnregisterAllEvents()
pins.updateFrame:RegisterEvent("CVAR_UPDATE")
pins.updateFrame:RegisterEvent("MINIMAP_UPDATE_ZOOM")
pins.updateFrame:RegisterEvent("PLAYER_LOGIN")
pins.updateFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
--- Fuck adding too much emmy support to libs.
---@diagnostic disable-next-line: undefined-field
HBD.RegisterCallback(pins, "PlayerZoneChanged", UpdateMinimap)
--- 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
worldmapProvider:HandlePin(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
worldmapProvider:HandlePin(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
worldmapProvider:RemovePinByIcon(icon)
worldmapProvider.forceUpdate = true;
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
end
worldmapProvider:RemovePinsByRef(ref)
wipe(worldmapPinRegistry[ref])
worldmapProvider.forceUpdate = true;
end
--- Return the angle and distance from the player to the specified pin
-- @param icon icon object (minimap or worldmap)
-- @return angle, distance where angle is in radians and distance in yards
function pins:GetVectorToIcon(icon)
if not icon then return nil, nil end
local data = minimapPins[icon] or worldmapPins[icon]
if not data then return nil, nil end
local x, y, instance = HBD:GetPlayerWorldPosition()
if not x or not y or instance ~= data.instanceID then return nil end
return HBD:GetWorldVector(instance, x, y, data.x, data.y)
end
+20
View File
@@ -0,0 +1,20 @@
## Interface: 90207
## Title: Lib: HereBeDragons
## Notes: HereBeDragons is a data API for the World of Warcraft mapping system
## Author: Nevcairiel
## X-eMail: h.leppkes@gmail.com
## X-Category: Library
## X-License: BSD
## X-Website: http://www.wowace.com/addons/herebedragons/
## Version: 2.09-release
## X-Curse-Project-ID: 94348
LibStub\LibStub.lua
CallbackHandler-1.0\CallbackHandler-1.0.lua
# WoW 8.0+ version
HereBeDragons-2.0.lua
HereBeDragons-Pins-2.0.lua
# Migration Data for 7.x -> 8.0 migration
HereBeDragons-Migrate.lua
+30
View File
@@ -0,0 +1,30 @@
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
local LibStub = _G[LIBSTUB_MAJOR]
if not LibStub or LibStub.minor < LIBSTUB_MINOR then
LibStub = LibStub or {libs = {}, minors = {} }
_G[LIBSTUB_MAJOR] = LibStub
LibStub.minor = LIBSTUB_MINOR
function LibStub:NewLibrary(major, minor)
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
local oldminor = self.minors[major]
if oldminor and oldminor >= minor then return nil end
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
return self.libs[major], oldminor
end
function LibStub:GetLibrary(major, silent)
if not self.libs[major] and not silent then
error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
end
return self.libs[major], self.minors[major]
end
function LibStub:IterateLibraries() return pairs(self.libs) end
setmetatable(LibStub, { __call = LibStub.GetLibrary })
end
+9
View File
@@ -0,0 +1,9 @@
## Interface: 20400
## Title: Lib: LibStub
## Notes: Universal Library Stub
## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel
## X-Website: http://jira.wowace.com/browse/LS
## X-Category: Library
## X-License: Public Domain
LibStub.lua