WoWInterface SVN SimpleCoords

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /
    from Rev 35 to Rev 36
    Reverse comparison

Rev 35 → Rev 36

trunk/libs/HereBeDragons/HereBeDragons-Pins-2.0.lua New file
0,0 → 1,752
-- HereBeDragons-Pins is a library to show pins/icons on the world map and minimap
 
-- HereBeDragons-Pins-2.0 is not supported on WoW 7.x
if select(4, GetBuildInfo()) < 80000 then
return
end
 
local MAJOR, MINOR = "HereBeDragons-Pins-2.0", 5
assert(LibStub, MAJOR .. " requires LibStub")
 
local pins, oldversion = LibStub:NewLibrary(MAJOR, MINOR)
if not pins then return end
 
local HBD = LibStub("HereBeDragons-2.0")
 
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
},
}
 
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
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
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
 
if dist <= 1 or data.floatOnEdge 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, data 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"]
mapRadius = minimap_size[indoors][zoom] / 2
minimapWidth = pins.Minimap:GetWidth() / 2
minimapHeight = pins.Minimap:GetHeight() / 2
 
-- 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 data.instanceID == instanceID and (not data.uiMapID or data.uiMapID == mapID or (data.showInParentZone and IsParentMap(data.uiMapID, mapID))) then
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
mapRadius = minimap_size[indoors][zoom] / 2
-- 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()
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
 
-------------------------------------------------------------------------------------------
-- 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["HereBeDragonsPinsTemplate"] = worldmapPinsPool
 
-- provider base API
function worldmapProvider:RemoveAllData()
self:GetMap():RemoveAllPinsByTemplate("HereBeDragonsPinsTemplate")
end
 
function worldmapProvider:RemovePinByIcon(icon)
for pin in self:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplate") do
if pin.icon == icon then
self:GetMap():RemovePin(pin)
end
end
end
 
function worldmapProvider:RemovePinsByRef(ref)
for pin in self:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplate") do
if pin.icon and worldmapPinRegistry[ref][pin.icon] then
self:GetMap():RemovePin(pin)
end
end
end
 
function worldmapProvider:RefreshAllData(fromOnShow)
self:RemoveAllData()
 
for icon, data in pairs(worldmapPins) do
self:HandlePin(icon, data)
end
end
 
function worldmapProvider:HandlePin(icon, data)
local uiMapID = self:GetMap():GetMapID()
 
-- check for a valid map
if not uiMapID 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 = false
local parentMapID = HBD.mapData[data.uiMapID].parent
while parentMapID and HBD.mapData[parentMapID] do
if parentMapID == uiMapID then
local mapType = HBD.mapData[parentMapID].mapType
-- show on any parent zones if they are normal zones
if data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_PARENT and
(mapType == Enum.UIMapType.Zone or mapType == Enum.UIMapType.Dungeon or mapType == Enum.UIMapType.Micro) then
show = true
-- show on the continent
elseif data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_CONTINENT and
mapType == Enum.UIMapType.Continent then
show = true
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
self:GetMap():AcquirePin("HereBeDragonsPinsTemplate", icon, x, y)
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)
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")
 
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")
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.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")
end
if type(icon) ~= "table" or not icon.SetPoint then
error(MAJOR..": AddMinimapIconMap: 'icon' must be a frame")
end
if type(uiMapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
error(MAJOR..": AddMinimapIconMap: 'uiMapID', 'x' and 'y' must be numbers")
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 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
function pins:AddWorldMapIconWorld(ref, icon, instanceID, x, y, showFlag)
if not ref then
error(MAJOR..": AddWorldMapIconWorld: 'ref' must not be nil")
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 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
 
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
function pins:AddWorldMapIconMap(ref, icon, uiMapID, x, y, showFlag)
if not ref then
error(MAJOR..": AddWorldMapIconMap: 'ref' must not be nil")
end
if type(icon) ~= "table" or not icon.SetPoint then
error(MAJOR..": AddWorldMapIconMap: 'icon' must be a frame")
end
if type(uiMapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
error(MAJOR..": AddWorldMapIconMap: 'uiMapID', 'x' and 'y' must be numbers")
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
 
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)
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])
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
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/HereBeDragons.toc New file
0,0 → 1,23
## Interface: 80000
## 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.00-release
 
LibStub\LibStub.lua
CallbackHandler-1.0\CallbackHandler-1.0.lua
 
# WoW 7.x version
HereBeDragons-1.0.lua
HereBeDragons-Pins-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
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/HereBeDragons-Migrate.lua New file
0,0 → 1,529
-- HereBeDragons-Migrate is not supported on WoW 7.x or earlier
if select(4, GetBuildInfo()) < 80000 then
return
end
 
local MAJOR, MINOR = "HereBeDragons-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
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/HereBeDragons-1.0.lua New file
0,0 → 1,776
-- HereBeDragons is a data API for the World of Warcraft mapping system
 
-- HereBeDragons-1.0 is not supported on WoW 8.0
if select(4, GetBuildInfo()) >= 80000 then
return
end
 
local MAJOR, MINOR = "HereBeDragons-1.0", 33
assert(LibStub, MAJOR .. " requires LibStub")
 
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.continentZoneMap = HereBeDragons.continentZoneMap or { [-1] = { [0] = WORLDMAP_COSMIC_ID }, [0] = { [0] = WORLDMAP_AZEROTH_ID }}
HereBeDragons.mapToID = HereBeDragons.mapToID or { Cosmic = WORLDMAP_COSMIC_ID, World = WORLDMAP_AZEROTH_ID }
HereBeDragons.microDungeons = HereBeDragons.microDungeons or {}
HereBeDragons.transforms = HereBeDragons.transforms or {}
 
HereBeDragons.callbacks = HereBeDragons.callbacks or CBH:New(HereBeDragons, nil, nil, false)
 
-- constants
local TERRAIN_MATCH = "_terrain%d+$"
 
-- Lua upvalues
local PI2 = math.pi * 2
local atan2 = math.atan2
local pairs, ipairs = pairs, ipairs
local type = type
local band = bit.band
 
-- WoW API upvalues
local UnitPosition = UnitPosition
 
-- data table upvalues
local mapData = HereBeDragons.mapData -- table { width, height, left, top }
local continentZoneMap = HereBeDragons.continentZoneMap
local mapToID = HereBeDragons.mapToID
local microDungeons = HereBeDragons.microDungeons
local transforms = HereBeDragons.transforms
 
local currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon
 
-- 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
[1612] = 1220, -- Feral Druid Artifact Scenario (Suramar)
[1626] = 1220, -- Suramar Withered Scenario
[1662] = 1220, -- Suramar Invasion Scenario
}
 
-- unregister and store all WORLD_MAP_UPDATE registrants, to avoid excess processing when
-- retrieving info from stateful map APIs
local wmuRegistry
local function UnregisterWMU()
wmuRegistry = {GetFramesRegisteredForEvent("WORLD_MAP_UPDATE")}
for _, frame in ipairs(wmuRegistry) do
frame:UnregisterEvent("WORLD_MAP_UPDATE")
end
end
 
-- restore WORLD_MAP_UPDATE to all frames in the registry
local function RestoreWMU()
assert(wmuRegistry)
for _, frame in ipairs(wmuRegistry) do
frame:RegisterEvent("WORLD_MAP_UPDATE")
end
wmuRegistry = nil
end
 
-- gather map info, but only if this isn't an upgrade (or the upgrade version forces a re-map)
if not oldversion or oldversion < 33 then
-- wipe old data, if required, otherwise the upgrade path isn't triggered
if oldversion then
wipe(mapData)
wipe(microDungeons)
end
 
local MAPS_TO_REMAP = {
-- alliance garrison
[973] = 971,
[974] = 971,
[975] = 971,
[991] = 971,
-- horde garrison
[980] = 976,
[981] = 976,
[982] = 976,
[990] = 976,
}
 
-- some zones will remap initially, but have a fixup later
local REMAP_FIXUP_EXEMPT = {
-- main draenor garrison maps
[971] = true,
[976] = true,
 
-- legion class halls
[1072] = { Z = 10, mapFile = "TrueshotLodge" }, -- true shot lodge
[1077] = { Z = 7, mapFile = "TheDreamgrove" }, -- dreamgrove
}
 
local function processTransforms()
wipe(transforms)
for _, tID in ipairs(GetWorldMapTransforms()) do
local terrainMapID, newTerrainMapID, _, _, transformMinY, transformMaxY, transformMinX, transformMaxX, offsetY, offsetX, flags = GetWorldMapTransformInfo(tID)
-- flag 4 indicates the transform is only for the flight map
if band(flags, 4) ~= 4 and (offsetY ~= 0 or offsetX ~= 0) then
local transform = {
instanceID = terrainMapID,
newInstanceID = newTerrainMapID,
minY = transformMinY,
maxY = transformMaxY,
minX = transformMinX,
maxX = transformMaxX,
offsetY = offsetY,
offsetX = offsetX
}
table.insert(transforms, transform)
end
end
end
 
local function applyMapTransforms(instanceID, left, right, top, bottom)
for _, transformData in ipairs(transforms) do
if transformData.instanceID == instanceID then
if left < transformData.maxX and right > transformData.minX and top < transformData.maxY and bottom > transformData.minY then
instanceID = transformData.newInstanceID
left = left + transformData.offsetX
right = right + transformData.offsetX
top = top + transformData.offsetY
bottom = bottom + transformData.offsetY
break
end
end
end
return instanceID, left, right, top, bottom
end
 
-- gather the data of one zone (by mapID)
local function processZone(id)
if not id or mapData[id] then return end
 
-- set the map and verify it could be set
local success = SetMapByID(id)
if not success then
return
elseif id ~= GetCurrentMapAreaID() and not REMAP_FIXUP_EXEMPT[id] then
-- this is an alias zone (phasing terrain changes), just skip it and remap it later
if not MAPS_TO_REMAP[id] then
MAPS_TO_REMAP[id] = GetCurrentMapAreaID()
end
return
end
 
-- dimensions of the map
local originalInstanceID, _, _, left, right, top, bottom = GetAreaMapInfo(id)
local instanceID = originalInstanceID
if (left and top and right and bottom and (left ~= 0 or top ~= 0 or right ~= 0 or bottom ~= 0)) then
instanceID, left, right, top, bottom = applyMapTransforms(originalInstanceID, left, right, top, bottom)
mapData[id] = { left - right, top - bottom, left, top }
else
mapData[id] = { 0, 0, 0, 0 }
end
 
mapData[id].instance = instanceID
mapData[id].name = GetMapNameByID(id)
 
-- store the original instance id (ie. not remapped for map transforms) for micro dungeons
mapData[id].originalInstance = originalInstanceID
 
local mapFile = type(REMAP_FIXUP_EXEMPT[id]) == "table" and REMAP_FIXUP_EXEMPT[id].mapFile or GetMapInfo()
if mapFile then
-- remove phased terrain from the map names
mapFile = mapFile:gsub(TERRAIN_MATCH, "")
 
if not mapToID[mapFile] then mapToID[mapFile] = id end
mapData[id].mapFile = mapFile
end
 
local C, Z = GetCurrentMapContinent(), GetCurrentMapZone()
 
-- maps that remap generally have wrong C/Z info, so allow the fixup table to override it
if type(REMAP_FIXUP_EXEMPT[id]) == "table" then
C = REMAP_FIXUP_EXEMPT[id].C or C
Z = REMAP_FIXUP_EXEMPT[id].Z or Z
end
 
mapData[id].C = C or -100
mapData[id].Z = Z or -100
 
if mapData[id].C > 0 and mapData[id].Z >= 0 then
-- store C/Z lookup table
if not continentZoneMap[C] then
continentZoneMap[C] = {}
end
if not continentZoneMap[C][Z] then
continentZoneMap[C][Z] = id
end
end
 
-- retrieve floors
local floors = { GetNumDungeonMapLevels() }
 
-- offset floors for terrain map
if DungeonUsesTerrainMap() then
for i = 1, #floors do
floors[i] = floors[i] + 1
end
end
 
-- check for fake floors
if #floors == 0 and GetCurrentMapDungeonLevel() > 0 then
floors[1] = GetCurrentMapDungeonLevel()
mapData[id].fakefloor = GetCurrentMapDungeonLevel()
end
 
mapData[id].floors = {}
mapData[id].numFloors = #floors
for i = 1, mapData[id].numFloors do
local f = floors[i]
SetDungeonMapLevel(f)
local _, right, bottom, left, top = GetCurrentMapDungeonLevel()
if left and top and right and bottom then
instanceID, left, right, top, bottom = applyMapTransforms(originalInstanceID, left, right, top, bottom)
mapData[id].floors[f] = { left - right, top - bottom, left, top }
mapData[id].floors[f].instance = mapData[id].instance
elseif f == 1 and DungeonUsesTerrainMap() then
mapData[id].floors[f] = { mapData[id][1], mapData[id][2], mapData[id][3], mapData[id][4] }
mapData[id].floors[f].instance = mapData[id].instance
end
end
 
-- setup microdungeon storage if the its a zone map or has no floors of its own
if (mapData[id].C > 0 and mapData[id].Z > 0) or mapData[id].numFloors == 0 then
if not microDungeons[originalInstanceID] then
microDungeons[originalInstanceID] = { global = {} }
end
end
end
 
local function processMicroDungeons()
for _, dID in ipairs(GetDungeonMaps()) do
local floorIndex, minX, maxX, minY, maxY, terrainMapID, parentWorldMapID, flags = GetDungeonMapInfo(dID)
 
-- apply transform
local originalTerrainMapID = terrainMapID
terrainMapID, maxX, minX, maxY, minY = applyMapTransforms(terrainMapID, maxX, minX, maxY, minY)
 
-- check if this zone can have microdungeons
if microDungeons[originalTerrainMapID] then
-- store per-zone info
if not microDungeons[originalTerrainMapID][parentWorldMapID] then
microDungeons[originalTerrainMapID][parentWorldMapID] = {}
end
 
microDungeons[originalTerrainMapID][parentWorldMapID][floorIndex] = { maxX - minX, maxY - minY, maxX, maxY }
microDungeons[originalTerrainMapID][parentWorldMapID][floorIndex].instance = terrainMapID
 
-- store global info, as some microdungeon are associated to the wrong zone when phasing is involved (garrison, and more)
-- but only store the first, since there can be overlap on the same continent otherwise
if not microDungeons[originalTerrainMapID].global[floorIndex] then
microDungeons[originalTerrainMapID].global[floorIndex] = microDungeons[originalTerrainMapID][parentWorldMapID][floorIndex]
end
end
end
end
 
local function fixupZones()
-- fake cosmic map
mapData[WORLDMAP_COSMIC_ID] = {0, 0, 0, 0}
mapData[WORLDMAP_COSMIC_ID].instance = -1
mapData[WORLDMAP_COSMIC_ID].mapFile = "Cosmic"
mapData[WORLDMAP_COSMIC_ID].floors = {}
mapData[WORLDMAP_COSMIC_ID].C = -1
mapData[WORLDMAP_COSMIC_ID].Z = 0
mapData[WORLDMAP_COSMIC_ID].name = WORLD_MAP
 
-- fake azeroth world map
-- the world map has one "floor" per continent it contains, which allows
-- using these floors to translate coordinates from and to the world map.
-- note: due to artistic differences in the drawn azeroth maps, the values
-- used for the continents are estimates and not perfectly accurate
mapData[WORLDMAP_AZEROTH_ID] = { 63570, 42382, 53730, 19600 } -- Eastern Kingdoms, or floor 0
mapData[WORLDMAP_AZEROTH_ID].floors = {
-- Kalimdor
[1] = { 65700, 43795, 11900, 23760, instance = 1 },
-- Northrend
[571] = { 65700, 43795, 33440, 11960, instance = 571 },
-- Pandaria
[870] = { 58520, 39015, 29070, 34410, instance = 870 },
-- Broken Isles
[1220] = { 96710, 64476, 63100, 29960, instance = 1220 },
}
mapData[WORLDMAP_AZEROTH_ID].instance = 0
mapData[WORLDMAP_AZEROTH_ID].mapFile = "World"
mapData[WORLDMAP_AZEROTH_ID].C = 0
mapData[WORLDMAP_AZEROTH_ID].Z = 0
mapData[WORLDMAP_AZEROTH_ID].name = WORLD_MAP
 
-- alliance draenor garrison
if mapData[971] then
mapData[971].Z = 5
 
mapToID["garrisonsmvalliance_tier1"] = 971
mapToID["garrisonsmvalliance_tier2"] = 971
mapToID["garrisonsmvalliance_tier3"] = 971
end
 
-- horde draenor garrison
if mapData[976] then
mapData[976].Z = 3
 
mapToID["garrisonffhorde_tier1"] = 976
mapToID["garrisonffhorde_tier2"] = 976
mapToID["garrisonffhorde_tier3"] = 976
end
 
-- remap zones with alias IDs
for remapID, validMapID in pairs(MAPS_TO_REMAP) do
if mapData[validMapID] then
mapData[remapID] = mapData[validMapID]
end
end
end
 
local function gatherMapData()
-- unregister WMU to reduce the processing burden
UnregisterWMU()
 
-- load transforms
processTransforms()
 
-- load the main zones
-- these should be processed first so they take precedence in the mapFile lookup table
local continents = {GetMapContinents()}
for i = 1, #continents, 2 do
processZone(continents[i])
local zones = {GetMapZones((i + 1) / 2)}
for z = 1, #zones, 2 do
processZone(zones[z])
end
end
 
-- process all other zones, this includes dungeons and more
local areas = GetAreaMaps()
for idx, zoneID in pairs(areas) do
processZone(zoneID)
end
 
-- fix a few zones with data lookup problems
fixupZones()
 
-- and finally, the microdungeons
processMicroDungeons()
 
-- restore WMU
RestoreWMU()
end
 
gatherMapData()
end
 
-- Transform a set of coordinates based on the defined map transformations
local function applyCoordinateTransforms(x, y, instanceID)
for _, transformData in ipairs(transforms) do
if transformData.instanceID == instanceID then
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
if instanceIDOverrides[instanceID] then
instanceID = instanceIDOverrides[instanceID]
end
return x, y, instanceID
end
 
-- get the data table for a map and its level (floor)
local function getMapDataTable(mapID, level)
if not mapID then return nil end
if type(mapID) == "string" then
mapID = mapID:gsub(TERRAIN_MATCH, "")
mapID = mapToID[mapID]
end
local data = mapData[mapID]
if not data then return nil end
 
if (type(level) ~= "number" or level == 0) and data.fakefloor then
level = data.fakefloor
end
 
if type(level) == "number" and level > 0 then
if data.floors[level] then
return data.floors[level]
elseif data.originalInstance and microDungeons[data.originalInstance] then
if microDungeons[data.originalInstance][mapID] and microDungeons[data.originalInstance][mapID][level] then
return microDungeons[data.originalInstance][mapID][level]
elseif microDungeons[data.originalInstance].global[level] then
return microDungeons[data.originalInstance].global[level]
end
end
else
return data
end
end
 
local StartUpdateTimer
local function UpdateCurrentPosition()
UnregisterWMU()
 
-- save active map and level
local prevContinent
local prevMapID, prevLevel = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel()
 
-- handle continent maps (751 is the maelstrom continent, which fails with SetMapByID)
if not prevMapID or prevMapID < 0 or prevMapID == 751 then
prevContinent = GetCurrentMapContinent()
end
 
-- set current map
SetMapToCurrentZone()
 
-- retrieve active values
local newMapID, newLevel = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel()
local mapFile, _, _, isMicroDungeon, microFile = GetMapInfo()
 
-- we want to ignore any terrain phasings
if mapFile then
mapFile = mapFile:gsub(TERRAIN_MATCH, "")
end
 
-- hack to update the mapfile for the garrison map (as it changes when the player updates his garrison)
-- its not ideal to only update it when the player is in the garrison, but updates should only really happen then
if (newMapID == 971 or newMapID == 976) and mapData[newMapID] and mapFile ~= mapData[newMapID].mapFile then
mapData[newMapID].mapFile = mapFile
end
 
-- restore previous map
if prevContinent then
SetMapZoom(prevContinent)
else
-- reset map if it changed, or we need to go back to level 0
if prevMapID and (prevMapID ~= newMapID or (prevLevel ~= newLevel and prevLevel == 0)) then
SetMapByID(prevMapID)
end
if prevLevel and prevLevel > 0 then
SetDungeonMapLevel(prevLevel)
end
end
 
RestoreWMU()
 
if newMapID ~= currentPlayerZoneMapID or newLevel ~= currentPlayerLevel then
-- store micro dungeon map lookup, if available
if microFile and not mapToID[microFile] then mapToID[microFile] = newMapID end
 
-- update upvalues and signal callback
currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon = newMapID, newLevel, microFile or mapFile, isMicroDungeon
HereBeDragons.callbacks:Fire("PlayerZoneChanged", currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon)
end
 
-- start a timer to update in micro dungeons since multi-level micro dungeons do not reliably fire events
if isMicroDungeon 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()
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")
 
-- if we're loading after entering the world (ie. on demand), update position now
if IsLoggedIn() then
UpdateCurrentPosition()
end
 
--- Return the localized zone name for a given mapID or mapFile
-- @param mapID numeric mapID or mapFile
function HereBeDragons:GetLocalizedMap(mapID)
if type(mapID) == "string" then
mapID = mapID:gsub(TERRAIN_MATCH, "")
mapID = mapToID[mapID]
end
return mapData[mapID] and mapData[mapID].name or nil
end
 
--- Return the map id to a mapFile
-- @param mapFile Map File
function HereBeDragons:GetMapIDFromFile(mapFile)
if mapFile then
mapFile = mapFile:gsub(TERRAIN_MATCH, "")
return mapToID[mapFile]
end
return nil
end
 
--- Return the mapFile to a map ID
-- @param mapID Map ID
function HereBeDragons:GetMapFileFromID(mapID)
return mapData[mapID] and mapData[mapID].mapFile or nil
end
 
--- Lookup the map ID for a Continent / Zone index combination
-- @param C continent index from GetCurrentMapContinent
-- @param Z zone index from GetCurrentMapZone
function HereBeDragons:GetMapIDFromCZ(C, Z)
if C and continentZoneMap[C] then
return Z and continentZoneMap[C][Z]
end
return nil
end
 
--- Lookup the C/Z values for map
-- @param mapID the MapID
function HereBeDragons:GetCZFromMapID(mapID)
if mapData[mapID] then
return mapData[mapID].C, mapData[mapID].Z
end
return nil, nil
end
 
--- Get the size of the zone
-- @param mapID Map ID or MapFile of the zone
-- @param level Optional map level
-- @return width, height of the zone, in yards
function HereBeDragons:GetZoneSize(mapID, level)
local data = getMapDataTable(mapID, level)
if not data then return 0, 0 end
 
return data[1], data[2]
end
 
--- Get the number of floors for a map
-- @param mapID map ID or mapFile of the zone
function HereBeDragons:GetNumFloors(mapID)
if not mapID then return 0 end
if type(mapID) == "string" then
mapID = mapID:gsub(TERRAIN_MATCH, "")
mapID = mapToID[mapID]
end
 
if not mapData[mapID] or not mapData[mapID].numFloors then return 0 end
 
return mapData[mapID].numFloors
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 MapID or MapFile of the zone
-- @param level Optional level of the zone
function HereBeDragons:GetWorldCoordinatesFromZone(x, y, zone, level)
local data = getMapDataTable(zone, level)
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, data.instance
end
 
--- Convert world coordinates to local/point zone coordinates
-- @param x Global X position
-- @param y Global Y position
-- @param zone MapID or MapFile of the zone
-- @param level Optional level 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, level, allowOutOfBounds)
local data = getMapDataTable(zone, level)
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
 
--- 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, mapID or mapFile
-- @param oLevel Origin Zone Level
-- @param dZone Destination Zone, mapID or mapFile
-- @param dLevel Destination Zone Level
-- @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, oLevel, dZone, dLevel, allowOutOfBounds)
local xCoord, yCoord, instance = self:GetWorldCoordinatesFromZone(x, y, oZone, oLevel)
if not xCoord then return nil, nil end
 
local data = getMapDataTable(dZone, dLevel)
if not data or data.instance ~= instance then return nil, nil end
 
return self:GetZoneCoordinatesFromWorld(xCoord, yCoord, dZone, dLevel, 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 map id or mapfile
-- @param oLevel optional origin zone level (floor)
-- @param oX origin X, in local zone/point coordinates
-- @param oY origin Y, in local zone/point coordinates
-- @param dZone destination zone map id or mapfile
-- @param dLevel optional destination zone level (floor)
-- @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, oLevel, oX, oY, dZone, dLevel, dX, dY)
local oX, oY, oInstance = self:GetWorldCoordinatesFromZone(oX, oY, oZone, oLevel)
if not oX then return nil, nil, nil end
 
-- translate dX, dY to the origin zone
local dX, dY, dInstance = self:GetWorldCoordinatesFromZone(dX, dY, dZone, dLevel)
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 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, instanceIDOverrides[instanceID] or 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, instanceIDOverrides[instanceID] or 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 mapID, level, mapFile, isMicroDungeon
function HereBeDragons:GetPlayerZone()
return currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon
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, mapID, level, mapFile, isMicroDungeon
function HereBeDragons:GetPlayerZonePosition(allowOutOfBounds)
if not currentPlayerZoneMapID 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, currentPlayerZoneMapID, currentPlayerLevel, allowOutOfBounds)
if x and y then
return x, y, currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon
end
return nil, nil, nil, nil
end
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/LibStub/LibStub.toc New 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
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/LibStub/LibStub.lua New 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
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/HereBeDragons-2.0.lua New file
0,0 → 1,496
-- HereBeDragons is a data API for the World of Warcraft mapping system
 
-- HereBeDragons-2.0 is not supported on WoW 7.x or earlier
if select(4, GetBuildInfo()) < 80000 then
return
end
 
local MAJOR, MINOR = "HereBeDragons-2.0", 6
assert(LibStub, MAJOR .. " requires LibStub")
 
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)
 
-- 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
local type = type
local band = bit.band
 
-- 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
[1612] = 1220, -- Feral Druid Artifact Scenario (Suramar)
[1626] = 1220, -- Suramar Withered Scenario
[1662] = 1220, -- Suramar Invasion Scenario
}
 
-- gather map info, but only if this isn't an upgrade (or the upgrade version forces a re-map)
if not oldversion or oldversion < 3 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 = {
{ 530, 0, 4800, 16000, -10133.3, -2666.67, -2400, 2400 },
{ 530, 1, -6933.33, 533.33, -16000, -8000, 10133.3, 17600 },
{ 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, 2410 },
{ 1460, 1220, -1066.7, 2133.3, 0, 3200, -2333.9, 966.7 },
}
 
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 _, transformData in ipairs(transforms[instanceID]) do
if left <= transformData.maxX and right >= transformData.minX and top <= transformData.maxY and bottom >= transformData.minY then
instanceID = transformData.newInstanceID
left = left + transformData.offsetX
right = right + transformData.offsetX
top = top + transformData.offsetY
bottom = bottom + transformData.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)
if not id or mapData[id] then return 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 = data.parentMapID}
else
mapData[id] = {0, 0, 0, 0, instance = instance or -1, name = data.name, mapType = data.mapType, parent = data.parentMapID }
end
end
 
local function processMapChildrenRecursive(id)
local children = C_Map.GetMapChildrenInfo(id)
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])
processMapChildrenRecursive(id)
end
end
end
end
 
local function fixupZones()
local cosmic = C_Map.GetMapInfo(COSMIC_MAP_ID)
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
 
-- data for the azeroth world map
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
 
local function gatherMapData()
processTransforms()
 
processMapChildrenRecursive(COSMIC_MAP_ID)
 
fixupZones()
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
if instanceIDOverrides[instanceID] then
instanceID = instanceIDOverrides[instanceID]
end
return x, y, instanceID
end
 
local StartUpdateTimer
local function UpdateCurrentPosition()
-- retrieve current zone
local uiMapID = C_Map.GetBestMapForUnit("player")
 
if uiMapID ~= currentPlayerUIMapID then
-- update upvalues and signal callback
currentPlayerUIMapID, currentPlayerUIMapType = uiMapID, mapData[uiMapID] and mapData[uiMapID].mapType or 0
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()
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")
 
-- if we're loading after entering the world (ie. on demand), update position now
if IsLoggedIn() then
UpdateCurrentPosition()
end
 
--- 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, 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 = (oZone == WORLD_MAP_ID) and mapData[dZone].instance or mapData[oZone].instance
if not worldMapData[instance] then return nil, nil end
 
local data = worldMapData[instance]
local width, height, left, top = data[1], data[2], data[3], data[4]
 
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 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 oX, oY, oInstance = self:GetWorldCoordinatesFromZone(oX, oY, oZone)
if not oX then return nil, nil, nil end
 
-- translate dX, dY to the origin zone
local 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 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, instanceIDOverrides[instanceID] or 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, instanceIDOverrides[instanceID] or 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
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/CHANGES.txt New file
0,0 → 1,18
tag a0d9d7d23b61dfd50e81bb5d5008ed2a9168a001 2.00-release
Author: Hendrik Leppkes <h.leppkes@gmail.com>
Date: Tue Jul 17 14:46:43 2018 +0200
 
Tag as 2.00-release
 
commit fbe9b033a29ba62bb525d65e9ed1197ea2738041
Author: Hendrik Leppkes <h.leppkes@gmail.com>
Date: Tue Jul 17 14:46:33 2018 +0200
 
Update TOC for 8.0
 
commit d6e04fda96a84f89a43d248c3a6e195b4e414bcf
Author: Hendrik Leppkes <h.leppkes@gmail.com>
Date: Sun Jul 1 12:37:59 2018 +0200
 
Update transform data for the latest beta build
 
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/CallbackHandler-1.0/CallbackHandler-1.0.xml New file
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>
\ No newline at end of file Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/CallbackHandler-1.0/CallbackHandler-1.0.lua New file
0,0 → 1,238
--[[ $Id: CallbackHandler-1.0.lua 18 2014-10-16 02:52:20Z mikk $ ]]
local MAJOR, MINOR = "CallbackHandler-1.0", 6
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 CreateDispatcher(argCount)
local code = [[
local next, xpcall, eh = ...
 
local method, ARGS
local function call() method(ARGS) end
 
local function dispatch(handlers, ...)
local index
index, method = next(handlers)
if not method then return end
local OLD_ARGS = ARGS
ARGS = ...
repeat
xpcall(call, eh)
index, method = next(handlers, index)
until not method
ARGS = OLD_ARGS
end
 
return dispatch
]]
 
local ARGS, OLD_ARGS = {}, {}
for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", "))
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
end
 
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
local dispatcher = CreateDispatcher(argCount)
rawset(self, argCount, dispatcher)
return dispatcher
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)
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
 
Dispatchers[select('#', ...) + 1](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 = nil
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.
 
Property changes : Added: svn:executable + *
trunk/libs/HereBeDragons/HereBeDragons-Pins-1.0.lua New file
0,0 → 1,651
-- HereBeDragons-Pins is a library to show pins/icons on the world map and minimap
 
-- HereBeDragons-Pins-1.0 is not supported on WoW 8.0
if select(4, GetBuildInfo()) >= 80000 then
return
end
 
 
local MAJOR, MINOR = "HereBeDragons-Pins-1.0", 16
assert(LibStub, MAJOR .. " requires LibStub")
 
local pins, oldversion = LibStub:NewLibrary(MAJOR, MINOR)
if not pins then return end
 
local HBD = LibStub("HereBeDragons-1.0")
 
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 {}
 
-- store a reference to the active minimap object
pins.Minimap = pins.Minimap or Minimap
 
-- 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 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
},
}
 
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 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
local minimapScale, minimapShape, mapRadius, minimapWidth, minimapHeight, mapSin, mapCos
local lastZoom, lastFacing, lastXY, lastYY
 
local worldmapWidth, worldmapHeight = WorldMapButton:GetWidth(), WorldMapButton:GetHeight()
 
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
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
 
if dist <= 1 or data.floatOnEdge 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 UpdateMinimapPins(force)
-- get the current player position
local x, y, instanceID = HBD:GetPlayerWorldPosition()
local mapID, mapFloor = 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, data 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"]
mapRadius = minimap_size[indoors][zoom] / 2
minimapWidth = pins.Minimap:GetWidth() / 2
minimapHeight = pins.Minimap:GetHeight() / 2
 
-- 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 data.instanceID == instanceID and (not data.floor or (data.floor == mapFloor and (data.floor == 0 or data.mapID == mapID))) then
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
mapRadius = minimap_size[indoors][zoom] / 2
-- 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()
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
 
local function PositionWorldMapIcon(icon, data, currentMapID, currentMapFloor)
-- special handling for the azeroth world map
-- translating coordinates to the azeroth map requires passing the instance ID
-- of the origin continent, so the appropriate coordinates can be calculated
if currentMapID == WORLDMAP_AZEROTH_ID then
currentMapFloor = data.instanceID
end
 
local x, y = HBD:GetZoneCoordinatesFromWorld(data.x, data.y, currentMapID, currentMapFloor)
if x and y then
icon:ClearAllPoints()
icon:SetPoint("CENTER", WorldMapButton, "TOPLEFT", x * worldmapWidth, -y * worldmapHeight)
icon:Show()
else
icon:Hide()
end
end
 
local function GetWorldMapLocation()
local mapID, mapFloor = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel()
 
-- override the mapID for the azeroth world map
if mapID == -1 and GetCurrentMapContinent() == 0 and GetCurrentMapZone() == 0 then
mapID = WORLDMAP_AZEROTH_ID
mapFloor = 0
end
 
return mapID, mapFloor
end
 
local function UpdateWorldMap()
if not WorldMapButton:IsVisible() then return end
 
local mapID, mapFloor = GetWorldMapLocation()
 
-- not viewing a valid map
if not mapID or mapID == -1 then
for icon in pairs(worldmapPins) do
icon:Hide()
end
return
end
 
local instanceID = HBD.mapData[mapID] and HBD.mapData[mapID].instance or -1
 
worldmapWidth = WorldMapButton:GetWidth()
worldmapHeight = WorldMapButton:GetHeight()
 
for icon, data in pairs(worldmapPins) do
if (instanceID == data.instanceID or mapID == WORLDMAP_AZEROTH_ID) and (not data.floor or (data.floor == mapFloor and (data.floor == 0 or data.mapID == mapID))) then
PositionWorldMapIcon(icon, data, mapID, mapFloor)
else
icon:Hide()
end
end
end
 
local function UpdateMaps()
UpdateMinimapZoom()
UpdateMinimapPins()
UpdateWorldMap()
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
UpdateMinimapZoom()
UpdateMinimapPins()
elseif event == "PLAYER_LOGIN" then
-- recheck cvars after login
rotateMinimap = GetCVar("rotateMinimap") == "1"
elseif event == "PLAYER_ENTERING_WORLD" then
UpdateMaps()
elseif event == "WORLD_MAP_UPDATE" then
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")
pins.updateFrame:RegisterEvent("WORLD_MAP_UPDATE")
 
HBD.RegisterCallback(pins, "PlayerZoneChanged", UpdateMaps)
 
 
--- Add a icon to the minimap (x/y world coordinate version)
-- Note: This API does not let you specify a floor, as floors are map-specific, not instance/world wide. Use the Map/Floor API to specify a floor.
-- @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")
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.mapID = nil
t.floor = nil
 
minimapPins[icon] = t
queueFullUpdate = true
 
icon:SetParent(pins.Minimap)
end
 
--- Add a icon to the minimap (mapid/floor coordinate version)
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
-- @param icon Icon Frame
-- @param mapID Map ID of the map to place the icon on
-- @param mapFloor Floor to place the icon on (or nil for all floors)
-- @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 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:AddMinimapIconMF(ref, icon, mapID, mapFloor, x, y, floatOnEdge)
if not ref then
error(MAJOR..": AddMinimapIconMF: 'ref' must not be nil")
end
if type(icon) ~= "table" or not icon.SetPoint then
error(MAJOR..": AddMinimapIconMF: 'icon' must be a frame")
end
if type(mapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
error(MAJOR..": AddMinimapIconMF: 'mapID', 'x' and 'y' must be numbers")
end
 
-- convert to world coordinates and use our known adding function
local xCoord, yCoord, instanceID = HBD:GetWorldCoordinatesFromZone(x, y, mapID, mapFloor)
if not xCoord then return end
 
self:AddMinimapIconWorld(ref, icon, instanceID, xCoord, yCoord, floatOnEdge)
 
-- store extra information
minimapPins[icon].mapID = mapID
minimapPins[icon].floor = mapFloor
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
 
--- Add a icon to the world map (x/y world coordinate version)
-- Note: This API does not let you specify a floor, as floors are map-specific, not instance/world wide. Use the Map/Floor API to specify a floor.
-- @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
function pins:AddWorldMapIconWorld(ref, icon, instanceID, x, y)
if not ref then
error(MAJOR..": AddWorldMapIconWorld: 'ref' must not be nil")
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 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.mapID = nil
t.floor = nil
 
worldmapPins[icon] = t
 
if WorldMapButton:IsVisible() then
local currentMapID, currentMapFloor = GetWorldMapLocation()
if currentMapID and HBD.mapData[currentMapID] and (HBD.mapData[currentMapID].instance == instanceID or currentMapID == WORLDMAP_AZEROTH_ID) then
PositionWorldMapIcon(icon, t, currentMapID, currentMapFloor)
else
icon:Hide()
end
end
end
 
--- Add a icon to the world map (mapid/floor coordinate version)
-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier)
-- @param icon Icon Frame
-- @param mapID Map ID of the map to place the icon on
-- @param mapFloor Floor to place the icon on (or nil for all floors)
-- @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
function pins:AddWorldMapIconMF(ref, icon, mapID, mapFloor, x, y)
if not ref then
error(MAJOR..": AddWorldMapIconMF: 'ref' must not be nil")
end
if type(icon) ~= "table" or not icon.SetPoint then
error(MAJOR..": AddWorldMapIconMF: 'icon' must be a frame")
end
if type(mapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then
error(MAJOR..": AddWorldMapIconMF: 'mapID', 'x' and 'y' must be numbers")
end
 
-- convert to world coordinates
local xCoord, yCoord, instanceID = HBD:GetWorldCoordinatesFromZone(x, y, mapID, mapFloor)
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.mapID = mapID
t.floor = mapFloor
 
worldmapPins[icon] = t
 
if WorldMapButton:IsVisible() then
local currentMapID, currentMapFloor = GetWorldMapLocation()
if currentMapID and HBD.mapData[currentMapID] and (HBD.mapData[currentMapID].instance == instanceID or currentMapID == WORLDMAP_AZEROTH_ID)
and (not mapFloor or (currentMapFloor == mapFloor and (mapFloor == 0 or currentMapID == mapID))) then
PositionWorldMapIcon(icon, t, currentMapID, currentMapFloor)
else
icon:Hide()
end
end
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()
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()
end
wipe(worldmapPinRegistry[ref])
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
Property changes : Added: svn:executable + *