diff --git a/TomTom.lua b/TomTom.lua index d57bfeb..7f35915 100755 --- a/TomTom.lua +++ b/TomTom.lua @@ -7,7 +7,7 @@ -- Simple localization table for messages local L = TomTomLocals local ldb = LibStub("LibDataBroker-1.1") -local hbd = LibStub("HereBeDragons-1.0") +local hbd = LibStub("HereBeDragons-2.0") local addonName, addon = ... local TomTom = addon @@ -125,7 +125,7 @@ function TomTom:Initialize(event, addon) } self.db = LibStub("AceDB-3.0"):New("TomTomDB", self.defaults, "Default") - self.waydb = LibStub("AceDB-3.0"):New("TomTomWaypointsMF", self.waydefaults) + self.waydb = LibStub("AceDB-3.0"):New("TomTomWaypointsM", self.waydefaults) self.db.RegisterCallback(self, "OnProfileChanged", "ReloadOptions") self.db.RegisterCallback(self, "OnProfileCopied", "ReloadOptions") @@ -147,7 +147,8 @@ function TomTom:Initialize(event, addon) self:RegisterEvent("PLAYER_LEAVING_WORLD") self:RegisterEvent("CHAT_MSG_ADDON") - RegisterAddonMessagePrefix("TOMTOM3") + -- Since we are now using just (map, x, y), register a new protocol number + C_ChatInfo.RegisterAddonMessagePrefix("TOMTOM4") -- Watch for pet battle start/end so we can hide/show the arrow self:RegisterEvent("PET_BATTLE_OPENING_START", "ShowHideCrazyArrow") @@ -177,7 +178,7 @@ function TomTom:Initialize(event, addon) end counter = 0 - local m, f, x, y = TomTom:GetCurrentPlayerPosition() + local m, x, y = TomTom:GetCurrentPlayerPosition() if x and y then local opt = TomTom.db.profile.feeds @@ -190,24 +191,19 @@ end -- Some utility functions that can pack/unpack data from a waypoint -- Returns a hashable 'key' for a given waypoint consisting of the --- map, floor, x, y and the waypoints title. This isn't truly +-- map, x, y and the waypoints title. This isn't truly -- unique, but should be close enough to determine duplicates, etc. function TomTom:GetKey(waypoint) - local m,f,x,y = unpack(waypoint) - return self:GetKeyArgs(m, f, x, y, waypoint.title) + local m,x,y = unpack(waypoint) + return self:GetKeyArgs(m, x, y, waypoint.title) end -function TomTom:GetKeyArgs(m, f, x, y, title) - if not f then - local floors = hbd:GetNumFloors(m) - f = floors == 0 and 0 or 1 - end - +function TomTom:GetKeyArgs(m, x, y, title) -- Fudge the x/y values so they avoid precision/printf issues local x = x * 10000 local y = y * 10000 - local key = string.format("%d:%d:%s:%s:%s", m, f, x*10e4, y*10e4, tostring(title)) + local key = string.format("%d:%s:%s:%s", m, x*10e4, y*10e4, tostring(title)) return key end @@ -216,15 +212,15 @@ end -- weird if you zoom the map out to your parent, but there is no way to -- recover this without changing/setting the map zoom. Deal with it =) function TomTom:GetCurrentCoords() - local x, y = GetPlayerMapPosition("player"); + local x, y = hbd:GetPlayerZonePosition() if x and y and x > 0 and y > 0 then return x, y end end function TomTom:GetCurrentPlayerPosition() - local x, y, mapID, mapFloor = hbd:GetPlayerZonePosition() - return mapID, mapFloor, x, y + local x, y, mapID = hbd:GetPlayerZonePosition() + return mapID, x, y end function TomTom:ReloadOptions() @@ -234,7 +230,7 @@ function TomTom:ReloadOptions() self:ShowHideWorldCoords() self:ShowHideCoordBlock() self:ShowHideCrazyArrow() - self:EnableDisablePOIIntegration() + --LFO: self:EnableDisablePOIIntegration() end function TomTom:ClearAllWaypoints() @@ -255,7 +251,7 @@ function TomTom:ResetWaypointOptions() for map, data in pairs(self.waypointprofile) do for key, waypoint in pairs(data) do waypoint.minimap = minimap - waypoint.world = sorld + waypoint.world = world waypoint.cleardistance = cleardistance waypoint.arrivaldistance = arrivaldistance end @@ -269,14 +265,14 @@ function TomTom:ReloadWaypoints() self.waypoints = waypoints self.waypointprofile = self.waydb.profile - local cm, cf, cx, cy = TomTom:GetCurrentPlayerPosition() + local cm, cx, cy = TomTom:GetCurrentPlayerPosition() for mapId,data in pairs(self.waypointprofile) do local same = mapId == cm local minimap = self.profile.minimap.enable and (self.profile.minimap.otherzone or same) local world = self.profile.worldmap.enable and (self.profile.worldmap.otherzone or same) for key,waypoint in pairs(data) do - local m,f,x,y = unpack(waypoint) + local m,x,y = unpack(waypoint) local title = waypoint.title -- Set up default options @@ -300,7 +296,7 @@ function TomTom:ReloadWaypoints() end end - self:AddMFWaypoint(m, f, x, y, options) + self:AddWaypoint(m, x, y, options) end end end @@ -309,23 +305,13 @@ function TomTom:UpdateCoordFeedThrottle() self:_privateupdatecoordthrottle(self.db.profile.feeds.coords_throttle) end --- Hook some global functions so we know when the world map size changes -local mapSizedUp = not (WORLDMAP_SETTINGS.size == WORLDMAP_WINDOWED_SIZE); -hooksecurefunc("WorldMap_ToggleSizeUp", function() - mapSizedUp = true - TomTom:ShowHideWorldCoords() -end) -hooksecurefunc("WorldMap_ToggleSizeDown", function() - mapSizedUp = false - TomTom:ShowHideWorldCoords() -end) function TomTom:ShowHideWorldCoords() -- Bail out if we're not supposed to be showing this frame if self.profile.mapcoords.playerenable or self.db.profile.mapcoords.cursorenable then -- Create the frame if it doesn't exist if not TomTomWorldFrame then - TomTomWorldFrame = CreateFrame("Frame", nil, WorldMapFrame) + TomTomWorldFrame = CreateFrame("Frame", "TomTomWorldFrame", WorldMapFrame.BorderFrame) TomTomWorldFrame.Player = TomTomWorldFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") TomTomWorldFrame.Cursor = TomTomWorldFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") TomTomWorldFrame:SetScript("OnUpdate", WorldMap_OnUpdate) @@ -334,7 +320,7 @@ function TomTom:ShowHideWorldCoords() TomTomWorldFrame.Player:ClearAllPoints() TomTomWorldFrame.Cursor:ClearAllPoints() - if mapSizedUp then + if WorldMapMixin.isMaximized then TomTomWorldFrame.Player:SetPoint("TOPLEFT", WorldMapFrame.BorderFrame, "TOPLEFT", 30, -6) TomTomWorldFrame.Cursor:SetPoint("TOPLEFT", WorldMapFrame.BorderFrame, "TOPRIGHT", -170, -6) else @@ -428,40 +414,35 @@ local world_click_verify = { ["S"] = function() return IsShiftKeyDown() end, } -local origScript = WorldMapButton_OnClick -WorldMapButton_OnClick = function(self, ...) - if WorldMapButton.ignoreClick then - WorldMapButton.ignoreClick = false; - return; - end - +-- This is now a registered click handler. +-- If we return false, it gets passed on to the next handler. +-- We need to return true when we handle the click. +local function WorldMap_OnClick (self, ...) local mouseButton, button = ... if mouseButton == "RightButton" then -- Check for all the modifiers that are currently set for mod in TomTom.db.profile.worldmap.create_modifier:gmatch("[ACS]") do if not world_click_verify[mod] or not world_click_verify[mod]() then - return origScript and origScript(self, ...) or true + return false end end - local m,f = GetCurrentMapAreaID() - local x,y = GetCurrentCursorPosition() + local m = WorldMapFrame.mapID + local x,y = WorldMapFrame:GetNormalizedCursorPosition() - if not m or m == WORLDMAP_COSMIC_ID then - return origScript and origScript(self, ...) or true + if not m or m == 0 then + return false end - local uid = TomTom:AddMFWaypoint(m, f, x, y, { - title = L["TomTom waypoint"], - }) + local uid = TomTom:AddWaypoint(m, x, y, { title = L["TomTom waypoint"],}) + return true else - return origScript and origScript(self, ...) or true + return false end end -if WorldMapButton:GetScript("OnClick") == origScript then - WorldMapButton:SetScript("OnClick", WorldMapButton_OnClick) -end +-- Add WorldMap_OnClick as a Click Handler on the WorldMapFrame Canvas +WorldMapFrame:AddCanvasClickHandler(WorldMap_OnClick,10) local function WaypointCallback(event, arg1, arg2, arg3) if event == "OnDistanceArrive" then @@ -663,26 +644,25 @@ end function TomTom:SendWaypoint(uid, channel) local data = uid local m, f, x, y = unpack(data) - local msg = string.format("%d:%d:%f:%f:%s", m, f, x, y, data.title or "") - SendAddonMessage("TOMTOM3", msg, channel) + local msg = string.format("%d:%f:%f:%s", m, x, y, data.title or "") + C_ChatInfo.SendAddonMessage("TOMTOM4", msg, channel) end function TomTom:CHAT_MSG_ADDON(event, prefix, data, channel, sender) - if prefix ~= "TOMTOM3" then return end + if prefix ~= "TOMTOM4" then return end if sender == UnitName("player") then return end - local m,f,x,y,title = string.split(":", data) + local m,x,y,title = string.split(":", data) if not title:match("%S") then title = string.format(L["Waypoint from %s"], sender) end m = tonumber(m) - f = tonumber(f) x = tonumber(x) y = tonumber(y) local zoneName = hbd:GetLocalizedMap(m) - self:AddMFWaypoint(m, f, x, y, {title = title}) + self:AddWaypoint(m, x, y, {title = title}) local msg = string.format(L["|cffffff78TomTom|r: Added '%s' (sent from %s) to zone %s"], title, sender, zoneName) ChatFrame1:AddMessage(msg) end @@ -713,7 +693,7 @@ local function _both_tooltip_show(event, tooltip, uid, dist) else tooltip:AddLine(L["Unknown distance"]) end - local m,f,x,y = unpack(data) + local m,x,y = unpack(data) local zoneName = hbd:GetLocalizedMap(m) tooltip:AddLine(string.format(L["%s (%.2f, %.2f)"], zoneName, x*100, y*100), 0.7, 0.7, 0.7) @@ -793,45 +773,14 @@ function TomTom:RemoveWaypoint(uid) end end --- TODO: Make this not suck -function TomTom:AddWaypoint(x, y, desc, persistent, minimap, world, silent) - local c,z = GetCurrentMapContinent(), GetCurrentMapZone() - - if not c or not z or c < 1 then - --self:Print("Cannot find a valid zone to place the coordinates") - return - end - - return self:AddZWaypoint(c, z, x, y, desc, persistent, minimap, world, nil, silent) -end - -function TomTom:AddZWaypoint(c, z, x, y, desc, persistent, minimap, world, callbacks, silent, crazy) - -- Convert the c,z,x,y tuple to m,f,x,y and pass the work off to AddMFWaypoint() - local mapId, floor = hbd:GetMapIDFromCZ(c, z) - if not mapId then - return - end - - return self:AddMFWaypoint(mapId, floor, x/100, y/100, { - title = desc, - persistent = persistent, - minimap = minimap, - world = world, - callbacks = callbacks, - silent = silent, - crazy = crazy, - }) -end function TomTom:AddWaypointToCurrentZone(x, y, desc) - local m, f = TomTom:GetCurrentPlayerPosition() + local m = TomTom:GetCurrentPlayerPosition() if not m then return end - return self:AddMFWaypoint(m, f, x/100, y/100, { - title = desc, - }) + return self:AddMFWaypoint(m, f, x/100, y/100, {title = desc}) end -- Return a set of default callbacks that can be used by addons to provide @@ -891,7 +840,7 @@ function TomTom:DefaultCallbacks(opts) return callbacks end -function TomTom:AddMFWaypoint(m, f, x, y, opts) +function TomTom:AddWaypoint(m, x, y, opts) opts = opts or {} -- Default values @@ -908,22 +857,16 @@ function TomTom:AddMFWaypoint(m, f, x, y, opts) local zoneName = hbd:GetLocalizedMap(m) - -- Get the default map floor, if necessary - if not f then - local floors = hbd:GetNumFloors(m) - f = floors == 0 and 0 or 1 - end - -- Ensure there isn't already a waypoint at this location - local key = self:GetKey({m, f, x, y, title = opts.title}) + local key = self:GetKey({m, x, y, title = opts.title}) if waypoints[m] and waypoints[m][key] then return waypoints[m][key] end -- uid is the 'new waypoint' called this for historical reasons - local uid = {m, f, x, y, title = opts.title} + local uid = {m, x, y, title = opts.title} - -- Copy over any options, so we have em + -- Copy over any options, so we have them for k,v in pairs(opts) do if not uid[k] then uid[k] = v @@ -966,8 +909,8 @@ function TomTom:IsValidWaypoint(waypoint) end end -function TomTom:WaypointMFExists(m, f, x, y, desc) - local key = self:GetKeyArgs(m, f, x, y, desc) +function TomTom:WaypointExists(m, x, y, desc) + local key = self:GetKeyArgs(m, x, y, desc) if waypoints[m] and waypoints[m][key] then return true else @@ -975,31 +918,15 @@ function TomTom:WaypointMFExists(m, f, x, y, desc) end end -function TomTom:WaypointExists(c, z, x, y, desc) - local m, f = hbd:GetMapIDFromCZ(c, z) - return self:WaypointMFExists(m, f, x, y, desc) -end - -function TomTom:SetCustomWaypoint(c,z,x,y,callback,minimap,world,silent) - return self:AddZWaypoint(c, z, x, y, nil, false, minimap, world, callback, silent) -end - -function TomTom:SetCustomMFWaypoint(m, f, x, y, opts) +function TomTom:SetCustomWaypoint(m, x, y, opts) opts.persistent = false - - return self:AddMFWaypoint(m, f, x, y, opts) + return self:AddWaypoint(m, x, y, opts) end do - -- Code courtesy ckknight + -- Original Code courtesy ckknight, modified for BFA by Ludovicus function GetCurrentCursorPosition() - local x, y = GetCursorPosition() - local left, top = WorldMapDetailFrame:GetLeft(), WorldMapDetailFrame:GetTop() - local width = WorldMapDetailFrame:GetWidth() - local height = WorldMapDetailFrame:GetHeight() - local scale = WorldMapDetailFrame:GetEffectiveScale() - local cx = (x/scale - left) / width - local cy = (top - y/scale) / height + local cx, cy = WorldMapFrame:GetNormalizedCursorPosition() if cx < 0 or cx > 1 or cy < 0 or cy > 1 then return nil, nil @@ -1046,6 +973,7 @@ do local bcounter = 0 function Block_OnUpdate(self, elapsed) bcounter = bcounter + elapsed + if (not TomTom) or not (TomTom.profile) then return; end if bcounter > TomTom.profile.block.throttle then bcounter = bcounter - TomTom.profile.block.throttle @@ -1072,26 +1000,26 @@ do end function Block_OnClick(self, button, down) - local m,f,x,y = TomTom:GetCurrentPlayerPosition() + local m,x,y = TomTom:GetCurrentPlayerPosition() local zoneName = hbd:GetLocalizedMap(m) local desc = string.format("%s: %.2f, %.2f", zoneName, x*100, y*100) - TomTom:AddMFWaypoint(m, f, x, y, { + TomTom:AddWaypoint(m, x, y, { title = desc, }) end end -function TomTom:DebugListWaypoints() - local m,f,x,y = self:GetCurrentPlayerPosition() +function TomTom:DebugListLocalWaypoints() + local m,x,y = self:GetCurrentPlayerPosition() local ctxt = RoundCoords(x, y, 2) local czone = hbd:GetLocalizedMap(m) - self:Printf(L["You are at (%s) in '%s' (map: %d, floor: %d)"], ctxt, czone or "UNKNOWN", m, f) + self:Printf(L["You are at (%s) in '%s' (map: %d)"], ctxt, czone or "UNKNOWN", m) if waypoints[m] then for key, wp in pairs(waypoints[m]) do - local ctxt = RoundCoords(wp[3], wp[4], 2) + local ctxt = RoundCoords(wp[2], wp[3], 2) local desc = wp.title and wp.title or L["Unknown waypoint"] local indent = " " - self:Printf(L["%s%s - %s (map: %d, floor: %d)"], indent, desc, ctxt, wp[1], wp[2]) + self:Printf(L["%s%s - %s (map: %d)"], indent, desc, ctxt, wp[1]) end else local indent = " " @@ -1099,18 +1027,56 @@ function TomTom:DebugListWaypoints() end end +function TomTom:DebugListAllWaypoints() + local m,x,y = self:GetCurrentPlayerPosition() + local ctxt = RoundCoords(x, y, 2) + local czone = hbd:GetLocalizedMap(m) + self:Printf(L["You are at (%s) in '%s' (map: %d)"], ctxt, czone or "UNKNOWN", m) + for m in pairs(waypoints) do + local zoneName = hbd:GetLocalizedMap(m) + self:Printf("%s:", zoneName) + for key, wp in pairs(waypoints[m]) do + local ctxt = RoundCoords(wp[2], wp[3], 2) + local desc = wp.title and wp.title or L["Unknown waypoint"] + local indent = " " + self:Printf(L["%s%s - %s (map: %d)"], indent, desc, ctxt, wp[1]) + end + end +end + local function usage() ChatFrame1:AddMessage(L["|cffffff78TomTom |r/way |cffffff78Usage:|r"]) ChatFrame1:AddMessage(L["|cffffff78/way <x> <y> [desc]|r - Adds a waypoint at x,y with descrtiption desc"]) ChatFrame1:AddMessage(L["|cffffff78/way <zone> <x> <y> [desc]|r - Adds a waypoint at x,y in zone with description desc"]) ChatFrame1:AddMessage(L["|cffffff78/way reset all|r - Resets all waypoints"]) ChatFrame1:AddMessage(L["|cffffff78/way reset <zone>|r - Resets all waypoints in zone"]) - ChatFrame1:AddMessage(L["|cffffff78/way list|r - Lists active waypoints in current zone"]) + ChatFrame1:AddMessage(L["|cffffff78/way local|r - Lists active waypoints in current zone"]) + ChatFrame1:AddMessage(L["|cffffff78/way list|r - Lists all active waypoints"]) +end + + +function TomTom:GetCZWFromMapID(m) + local zone, continent, world + + local mapInfo = C_Map.GetMapInfo(m) + repeat + mapInfo = C_Map.GetMapInfo(m) + if mapInfo.mapInfo == 3 then + -- Its a zone map + zone = m + elseif mapInfo.mapInfo == 2 then + continent = m + elseif mapInfo.mapInfo == 1 then + world = m + end + m = mapInfo.parentMapID + until (m == 946) + return continent, world, zone end function TomTom:GetClosestWaypoint() - local m,f,x,y = self:GetCurrentPlayerPosition() - local c = hbd:GetCZFromMapID(m) + local m,x,y = self:GetCurrentPlayerPosition() + local c = TomTom:GetCZWFromMapID(m) local closest_waypoint = nil local closest_dist = nil @@ -1129,7 +1095,7 @@ function TomTom:GetClosestWaypoint() else -- Search all waypoints on this continent for map, waypoints in pairs(waypoints) do - if c == hbd:GetCZFromMapID(m) then + if c == TomTom:GetCZWFromMapID(m) then for key, waypoint in pairs(waypoints) do local dist, x, y = TomTom:GetDistanceToWaypoint(waypoint) if (dist and closest_dist == nil) or (dist and dist < closest_dist) then @@ -1168,8 +1134,8 @@ SlashCmdList["TOMTOM_WAYBACK"] = function(msg) title = msg end - local backm,backf,backx,backy = TomTom:GetCurrentPlayerPosition() - TomTom:AddMFWaypoint(backm, backf, backx, backy, { + local backm,backx,backy = TomTom:GetCurrentPlayerPosition() + TomTom:AddWaypoint(backm,backx, backy, { title = title, }) end @@ -1178,57 +1144,43 @@ SLASH_TOMTOM_WAY1 = "/way" SLASH_TOMTOM_WAY2 = "/tway" SLASH_TOMTOM_WAY3 = "/tomtomway" -local nameToMapId = {} +TomTom.NameToMapId = {} +local NameToMapId = TomTom.NameToMapId do - -- Fetch the names of the continents - local continentNames = {} - local continentData = {GetMapContinents()} - - for c = 1, (#continentData / 2) do - local index = (c*2) - 1 - local areaId, name = continentData[index], continentData[index+1] - local instanceId = GetAreaMapInfo(areaId) - continentNames[instanceId] = name - end - - for idx, areaMapId in pairs(GetAreaMaps()) do - local name = GetMapNameByID(areaMapId) - local a,b,c = GetAreaMapInfo(areaMapId) - local parent = (c == -1 and a or c) - local parentName = continentNames[parent] or GetMapNameByID(parent) - - if name and nameToMapId[name] then - if type(nameToMapId[name]) ~= "table" then - -- convert to a table - nameToMapId[name] = {nameToMapId[name]} + -- Fetch the names of the zones + for id in pairs(hbd.mapData) do +-- if (hbd.mapData[id].mapType == Enum.UIMapType.Zone) or (hbd.mapData[id].mapType == Enum.UIMapType.Micro) then + if hbd.mapData[id][1] > 0 then + -- Record only Zone or Micro maps + local name = hbd.mapData[id].name + if name and NameToMapId[name] then + if type(NameToMapId[name]) ~= "table" then + -- convert to table + NameToMapId[name] = {NameToMapId[name]} + end + table.insert(NameToMapId[name], id) + else + NameToMapId[name] = id end - - table.insert(nameToMapId[name], areaMapId) - else - nameToMapId[name] = areaMapId end end - -- Handle any duplicates local newEntries = {} - for name, areaId in pairs(nameToMapId) do - if type(areaId) == "table" then - nameToMapId[name] = nil - for idx, areaId in pairs(areaId) do - local a,b,c = GetAreaMapInfo(areaId) - local parent = (c == -1 and a or c) - local parentName = continentNames[parent] or GetMapNameByID(parent) + for name, mapID in pairs(NameToMapId) do + if type(mapID) == "table" then + NameToMapId[name] = nil + for idx, mapId in pairs(mapID) do + local parent = hbd.mapData[mapId].parent + local parentName = hbd.mapData[parent].name if parentName then - local newName = name .. ':' .. parentName - newEntries[newName] = areaId + newEntries[name .. ":" .. parentName] = mapId end end end end - -- Add the de-duplicated entries - for name, areaId in pairs(newEntries) do - nameToMapId[name] = areaId + for name, mapID in pairs(newEntries) do + NameToMapId[name] = mapID end end @@ -1246,8 +1198,11 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) -- Lower the first token local ltoken = tokens[1] and tokens[1]:lower() - if ltoken == "list" then - TomTom:DebugListWaypoints() + if ltoken == "local" then + TomTom:DebugListLocalWaypoints() + return + elseif ltoken == "list" then + TomTom:DebugListAllWaypoints() return elseif ltoken == "reset" then local ltoken2 = tokens[2] and tokens[2]:lower() @@ -1267,7 +1222,7 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) local matches = {} local lzone = lowergsub(zone) - for name, mapId in pairs(nameToMapId) do + for name, mapId in pairs(NameToMapId) do local lname = lowergsub(name) if lname == lzone then -- We have an exact match @@ -1278,7 +1233,7 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) end end - if #matches > 5 then + if #matches > 7 then local msg = string.format(L["Found %d possible matches for zone %s. Please be more specific"], #matches, zone) ChatFrame1:AddMessage(msg) return @@ -1294,7 +1249,7 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) end local zoneName = matches[1] - local mapId = nameToMapId[zoneName] + local mapId = NameToMapId[zoneName] local numRemoved = 0 if waypoints[mapId] then @@ -1343,7 +1298,7 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) local matches = {} local lzone = lowergsub(zone) - for name,mapId in pairs(nameToMapId) do + for name,mapId in pairs(NameToMapId) do local lname = lowergsub(name) if lname == lzone then -- We have an exact match @@ -1354,7 +1309,7 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) end end - if #matches > 5 then + if #matches > 7 then local msg = string.format(L["Found %d possible matches for zone %s. Please be more specific"], #matches, zone) ChatFrame1:AddMessage(msg) return @@ -1370,7 +1325,7 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) -- There was only one match, so proceed local zoneName = matches[1] - local mapId = nameToMapId[zoneName] + local mapId = NameToMapId[zoneName] x = x and tonumber(x) y = y and tonumber(y) @@ -1381,7 +1336,7 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) x = tonumber(x) y = tonumber(y) - TomTom:AddMFWaypoint(mapId, nil, x/100, y/100, { + TomTom:AddWaypoint(mapId, x/100, y/100, { title = desc or L["TomTom waypoint"], }) elseif tonumber(tokens[1]) then @@ -1398,9 +1353,9 @@ SlashCmdList["TOMTOM_WAY"] = function(msg) x = tonumber(x) y = tonumber(y) - local m, f = TomTom:GetCurrentPlayerPosition() + local m = TomTom:GetCurrentPlayerPosition() if m and x and y then - TomTom:AddMFWaypoint(m, f, x/100, y/100, { + TomTom:AddWaypoint(m, x/100, y/100, { title = desc or L["TomTom waypoint"], }) end diff --git a/TomTom.toc b/TomTom.toc index 99ab918..19476e5 100755 --- a/TomTom.toc +++ b/TomTom.toc @@ -1,4 +1,4 @@ -## Interface: 70300 +## Interface: 80000 ## Title: TomTom ## Author: jnwhiteh ## Version: @project-version@ @@ -8,8 +8,8 @@ ## Notes-zhCN:一个简单的导航助手。 ## Notes-ruRU: Удобный портативный навигатор по игровой карте ## Author: Cladhaire -## SavedVariables: TomTomDB, TomTomWaypoints, TomTomWaypointsMF -## OptionalDeps: HereBeDragons-1.0 +## SavedVariables: TomTomDB, TomTomWaypoints, TomTomWaypointsM +## OptionalDeps: HereBeDragons-2.0 ## X-Localizations: enUS, enGB, deDE, frFR, zhCN, ruRU, zhTW ## X-CompatibleLocales: enUS, enGB, deDE, frFR, esES, esMX, zhCN, zhTW, koKR, ruRU @@ -22,8 +22,8 @@ libs\AceEvent-3.0\AceEvent-3.0.xml libs\AceDB-3.0\AceDB-3.0.xml libs\AceDBOptions-3.0\AceDBOptions-3.0.xml libs\LibDataBroker-1.1\LibDataBroker-1.1.lua -libs\HereBeDragons-1.0\HereBeDragons-1.0.lua -libs\HereBeDragons-1.0\HereBeDragons-Pins-1.0.lua +libs\HereBeDragons\HereBeDragons-2.0.lua +libs\HereBeDragons\HereBeDragons-Pins-2.0.lua AddonCore.lua diff --git a/TomTom_Corpse.lua b/TomTom_Corpse.lua index b811acf..1f33b91 100755 --- a/TomTom_Corpse.lua +++ b/TomTom_Corpse.lua @@ -12,8 +12,9 @@ eventFrame:RegisterEvent("PLAYER_DEAD") eventFrame:RegisterEvent("PLAYER_UNGHOST") eventFrame:Hide() --- Local variables to store map, floor, x, y and uid or corpse waypoint -local m,f,x,y,uid + +-- Local variables to store map, x, y and uid or corpse waypoint +local m,x,y,uid local function StartCorpseSearch() if not IsInInstance() then @@ -24,39 +25,40 @@ end local function ClearCorpseArrow() if uid then TomTom:RemoveWaypoint(uid) - m,f,x,y,uid = nil, nil, nil, nil, nil + m,x,y,uid = nil, nil, nil, nil, nil end end local function GetCorpseLocation() -- If the player isn't dead or a ghost, drop out if not UnitIsDeadOrGhost("player") then - if m or f or x or y then + if m or x or y then ClearCorpseArrow() end end -- Cache the result so we don't scan the maps multiple times - if m and f and x and y then - return m, f, x, y + if m and x and y then + return m, x, y end -- See if the player corpse is on the current map - local om = GetCurrentMapAreaID() - local of = GetCurrentMapDungeonLevel() - - local cx, cy = GetCorpseMapPosition() + local om = C_Map.GetBestMapForUnit("player") + if not om then return nil; end + local corpse = C_DeathInfo.GetCorpseMapPosition(om) + if not corpse then return nil; end + local cx, cy = corpse:GetXY() if cx ~= 0 and cy ~= 0 then m = om - f = of x = cx y = cy end + return m, x, y end local function SetCorpseArrow() - if m and f and x and y then - uid = TomTom:AddMFWaypoint(m, f, x, y, { + if m and x and y then + uid = TomTom:AddWaypoint(m, x, y, { title = L["My Corpse"], persistent = false, corpse = true, @@ -99,12 +101,9 @@ eventFrame:SetScript("OnEvent", function(self, event, arg1, ...) ClearCorpseArrow() end elseif event == "PLAYER_DEAD" then - -- Cheat a bit and avoid the map flipping - SetMapToCurrentZone() - m = GetCurrentMapAreaID() - f = GetCurrentMapDungeonLevel() + m = C_Map.GetBestMapForUnit("player") if not IsInInstance() then - x,y = GetPlayerMapPosition("player") + x,y = C_Map.GetPlayerMapPosition(m, "player"):GetXY() end StartCorpseSearch() elseif event == "PLAYER_UNGHOST" then diff --git a/TomTom_POIIntegration.lua b/TomTom_POIIntegration.lua index a8f5bb5..9de9dbb 100755 --- a/TomTom_POIIntegration.lua +++ b/TomTom_POIIntegration.lua @@ -1,5 +1,5 @@ local addonName, addon = ... -local hbd = addon.hbd +local hbd = LibStub("HereBeDragons-2.0") local enableClicks = true -- True if waypoint-clicking is enabled to set points local enableClosest = true -- True if 'Automatic' quest waypoints are enabled @@ -36,11 +36,19 @@ local function ObjectivesChanged() scanning = true end - local map, floor = GetCurrentMapAreaID() - local floors = hbd:GetNumFloors(map) - floor = (floors == 0 and 0 or 1) + local map = C_Map.GetBestMapForUnit("player") + if not map then + scanning = false + return + end + + local player = C_Map.GetPlayerMapPosition(map, "player") + if not player then + scanning = false + return + end - local px, py = GetPlayerMapPosition("player") + local px, py = player:GetXY() -- Bail out if we can't get the player's position if not px or not py or px <= 0 or py <= 0 then @@ -72,7 +80,7 @@ local function ObjectivesChanged() local completed, x, y, objective = QuestPOIGetIconInfo(qid) if x and y then - local dist = hbd:GetZoneDistance(map, floor, px, py, map, floor, x, y) + local dist = hbd:GetZoneDistance(map, px, py, map, x, y) if dist < closestdist then closest = watchIndex closestdist = dist @@ -95,8 +103,8 @@ local function ObjectivesChanged() if lastWaypoint then -- This is a hack that relies on the UID format, do not use this -- in your addons, please. - local pm, pf, px, py = unpack(lastWaypoint) - if map == pm and floor == pf and x == px and y == py and lastWaypoint.title == title then + local pm, px, py = unpack(lastWaypoint) + if map == pm and x == px and y == py and lastWaypoint.title == title then -- This is the same waypoint, do nothing setWaypoint = false else @@ -107,7 +115,7 @@ local function ObjectivesChanged() if setWaypoint then -- Set the new waypoint - lastWaypoint = TomTom:AddMFWaypoint(map, floor, x, y, { + lastWaypoint = TomTom:AddWaypoint(map, x, y, { title = title, persistent = false, arrivaldistance = TomTom.profile.poi.arrival, @@ -164,8 +172,7 @@ local function poi_OnClick(self, button) SetCVar("questPOI", 1) -- Run our logic, and set a waypoint for this button - local m = GetCurrentMapAreaID() - local f = GetCurrentMapDungeonLevel() + local m = C_Map.GetBestMapForUnit("player") QuestPOIUpdateIcons() @@ -181,12 +188,6 @@ local function poi_OnClick(self, button) completed = false x, y = C_TaskQuest.GetQuestLocation(self.questID) m = select(2, C_TaskQuest.GetQuestZoneID(self.questID)) or m - for i, q in pairs(C_TaskQuest.GetQuestsForPlayerByMapID(m)) do - -- Attemp to find the floor for the world quest - if q.questID == questID then - f = q.floor - end - end end if completed then @@ -200,7 +201,7 @@ local function poi_OnClick(self, button) return end - local key = TomTom:GetKeyArgs(m, f, x, y, title) + local key = TomTom:GetKeyArgs(m, x, y, title) local alreadySet = false if poiclickwaypoints[key] then @@ -212,7 +213,7 @@ local function poi_OnClick(self, button) end if not alreadySet then - local uid = TomTom:AddMFWaypoint(m, f, x, y, { + local uid = TomTom:AddWaypoint(m, x, y, { title = title, arrivaldistance = TomTom.profile.poi.arrival, }) @@ -225,13 +226,14 @@ local function poi_OnClick(self, button) SetCVar("questPOI", cvar and 1 or 0) end -hooksecurefunc("TaskPOI_OnClick", function(self, button) - poi_OnClick(self, button) -end) - -hooksecurefunc("QuestPOIButton_OnClick", function(self, button) - poi_OnClick(self, button) -end) +---LFO: Something needs to replace this! +---hooksecurefunc("TaskPOI_OnClick", function(self, button) +--- poi_OnClick(self, button) +---end) +--- +---hooksecurefunc("QuestPOIButton_OnClick", function(self, button) +--- poi_OnClick(self, button) +---end) function TomTom:EnableDisablePOIIntegration() enableClicks= TomTom.profile.poi.enable diff --git a/TomTom_Waypoints.lua b/TomTom_Waypoints.lua index ea95ce8..062e6b8 100755 --- a/TomTom_Waypoints.lua +++ b/TomTom_Waypoints.lua @@ -10,7 +10,7 @@ local addon_name, addon = ... local hbd = addon.hbd -local hbdp = LibStub("HereBeDragons-Pins-1.0") +local hbdp = LibStub("HereBeDragons-Pins-2.0") -- Create a tooltip to be used when mousing over waypoints local tooltip = CreateFrame("GameTooltip", "TomTomTooltip", UIParent, "GameTooltipTemplate") @@ -69,7 +69,7 @@ end local waypointMap = {} function TomTom:SetWaypoint(waypoint, callbacks, show_minimap, show_world) - local m, f, x, y = unpack(waypoint) + local m, x, y = unpack(waypoint) -- Try to acquire a waypoint from the frame pool local point = table.remove(pool) @@ -86,10 +86,14 @@ function TomTom:SetWaypoint(waypoint, callbacks, show_minimap, show_world) table.insert(all_points, minimap) minimap.icon = minimap:CreateTexture("BACKGROUND") - minimap.icon:SetTexture("Interface\\AddOns\\TomTom\\Images\\GoldGreenDot") + if waypoint.minimap_icon then + minimap.icon:SetTexture(waypoint.minimap_icon) + else + minimap.icon:SetTexture("Interface\\AddOns\\TomTom\\Images\\GoldGreenDot") + end minimap.icon:SetPoint("CENTER", 0, 0) - minimap.icon:SetHeight(12) - minimap.icon:SetWidth(12) + minimap.icon:SetHeight(16) + minimap.icon:SetWidth(16) minimap.arrow = minimap:CreateTexture("BACKGROUND") minimap.arrow:SetTexture("Interface\\AddOns\\TomTom\\Images\\MinimapArrow-Green") @@ -107,19 +111,24 @@ function TomTom:SetWaypoint(waypoint, callbacks, show_minimap, show_world) minimap:SetScript("OnEvent", Minimap_OnEvent) if not TomTomMapOverlay then - local overlay = CreateFrame("Frame", "TomTomMapOverlay", WorldMapButton) + local overlay = CreateFrame("Frame", "TomTomMapOverlay", WorldMapFrame) + overlay:SetFrameStrata("HIGH") + overlay:SetFrameLevel(10) overlay:SetAllPoints(true) end local worldmap = CreateFrame("Button", nil, TomTomMapOverlay) - worldmap:SetHeight(12) - worldmap:SetWidth(12) + worldmap:SetHeight(16) + worldmap:SetWidth(16) worldmap:RegisterForClicks("RightButtonUp") worldmap.icon = worldmap:CreateTexture("ARTWORK") worldmap.icon:SetAllPoints() - worldmap.icon:SetTexture("Interface\\AddOns\\TomTom\\Images\\GoldGreenDot") - - worldmap:RegisterEvent("WORLD_MAP_UPDATE") + if waypoint.worldmap_icon then + worldmap.icon:SetTexture(waypoint.worldmap_icon) + else + worldmap.icon:SetTexture("Interface\\AddOns\\TomTom\\Images\\GoldGreenDot") + end + worldmap:RegisterEvent("NEW_WMO_CHUNK") worldmap:SetScript("OnEnter", World_OnEnter) worldmap:SetScript("OnLeave", World_OnLeave) worldmap:SetScript("OnClick", World_OnClick) @@ -131,7 +140,6 @@ function TomTom:SetWaypoint(waypoint, callbacks, show_minimap, show_world) waypointMap[waypoint] = point point.m = m - point.f = f point.x = x point.y = y point.show_world = show_world @@ -161,10 +169,10 @@ function TomTom:SetWaypoint(waypoint, callbacks, show_minimap, show_world) point.uid = waypoint -- Place the waypoint - hbdp:AddMinimapIconMF(self, point.minimap, m, f, x, y, true) + hbdp:AddMinimapIconMap(self, point.minimap, m, x, y, true) if show_world then - hbdp:AddWorldMapIconMF(self, point.worldmap, m, f, x, y) + hbdp:AddWorldMapIconMap(self, point.worldmap, m, x, y) else point.worldmap.disabled = true end @@ -407,7 +415,7 @@ do if event == "PLAYER_ENTERING_WORLD" then local data = self.point if data and data.uid and waypointMap[data.uid] then - hbdp:AddMinimapIconMF(TomTom, self, data.m, data.f, data.x, data.y, true) + hbdp:AddMinimapIconMap(TomTom, self, data.m, data.x, data.y, true) end end end diff --git a/libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua b/libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua index f2d5266..66416e8 100755 --- a/libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua +++ b/libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua @@ -1,13 +1,13 @@ --- AceConfigDialog-3.0 generates AceGUI-3.0 based windows based on option tables. -- @class file -- @name AceConfigDialog-3.0 --- @release $Id: AceConfigDialog-3.0.lua 1163 2017-08-14 14:04:39Z nevcairiel $ +-- @release $Id: AceConfigDialog-3.0.lua 1169 2018-02-27 16:18:28Z nevcairiel $ local LibStub = LibStub local gui = LibStub("AceGUI-3.0") local reg = LibStub("AceConfigRegistry-3.0") -local MAJOR, MINOR = "AceConfigDialog-3.0", 64 +local MAJOR, MINOR = "AceConfigDialog-3.0", 66 local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceConfigDialog then return end @@ -740,7 +740,7 @@ local function ActivateControl(widget, event, ...) else validationErrorPopup(validated) end - PlaySound(PlaySoundKitID and "igPlayerInviteDecline" or 882) -- SOUNDKIT.IG_PLAYER_INVITE_DECLINE || XXX _DECLINE is actually missing from the table + PlaySound(882) -- SOUNDKIT.IG_PLAYER_INVITE_DECLINE || _DECLINE is actually missing from the table del(info) return true else @@ -1034,6 +1034,7 @@ local function BuildGroups(group, options, path, appName, recurse) entry.value = k entry.text = GetOptionsMemberValue("name", v, options, path, appName) entry.icon = GetOptionsMemberValue("icon", v, options, path, appName) + entry.iconCoords = GetOptionsMemberValue("iconCoords", v, options, path, appName) entry.disabled = CheckOptionDisabled(v, options, path, appName) tinsert(tree,entry) if recurse and (v.childGroups or "tree") == "tree" then @@ -1226,6 +1227,8 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin radio:SetWidth(width_multiplier * 2) elseif width == "half" then radio:SetWidth(width_multiplier / 2) + elseif (type(width) == "number") then + radio:SetWidth(width_multiplier * width) elseif width == "full" then radio.width = "fill" else @@ -1288,6 +1291,8 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin control:SetWidth(width_multiplier * 2) elseif width == "half" then control:SetWidth(width_multiplier / 2) + elseif (type(width) == "number") then + control:SetWidth(width_multiplier * width) elseif width == "full" then control.width = "fill" else @@ -1324,6 +1329,8 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin check:SetWidth(width_multiplier * 2) elseif width == "half" then check:SetWidth(width_multiplier / 2) + elseif (type(width) == "number") then + control:SetWidth(width_multiplier * width) elseif width == "full" then check.width = "fill" else @@ -1405,6 +1412,8 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin control:SetWidth(width_multiplier * 2) elseif width == "half" then control:SetWidth(width_multiplier / 2) + elseif (type(width) == "number") then + control:SetWidth(width_multiplier * width) elseif width == "full" then control.width = "fill" else diff --git a/libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua b/libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua index b84e770..f8ac3f9 100755 --- a/libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua +++ b/libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua @@ -8,10 +8,10 @@ -- :IterateOptionsTables() (and :GetOptionsTable() if only given one argument) return a function reference that the requesting config handling addon must call with valid "uiType", "uiName". -- @class file -- @name AceConfigRegistry-3.0 --- @release $Id: AceConfigRegistry-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $ +-- @release $Id: AceConfigRegistry-3.0.lua 1169 2018-02-27 16:18:28Z nevcairiel $ local CallbackHandler = LibStub("CallbackHandler-1.0") -local MAJOR, MINOR = "AceConfigRegistry-3.0", 17 +local MAJOR, MINOR = "AceConfigRegistry-3.0", 18 local AceConfigRegistry = LibStub:NewLibrary(MAJOR, MINOR) if not AceConfigRegistry then return end @@ -67,6 +67,7 @@ local optmethodbool={["nil"]=true,["string"]=true,["function"]=true,["boolean"]= local opttable={["nil"]=true,["table"]=true, _="table"} local optbool={["nil"]=true,["boolean"]=true, _="boolean"} local optboolnumber={["nil"]=true,["boolean"]=true,["number"]=true, _="boolean or number"} +local optstringnumber={["nil"]=true,["string"]=true,["number"]=true, _="string or number"} local basekeys={ type=isstring, @@ -90,7 +91,7 @@ local basekeys={ set=optmethodfalse, func=optmethodfalse, arg={["*"]=true}, - width=optstring, + width=optstringnumber, } local typedkeys={ diff --git a/libs/AceGUI-3.0/AceGUI-3.0.lua b/libs/AceGUI-3.0/AceGUI-3.0.lua index 9853644..08904e4 100755 --- a/libs/AceGUI-3.0/AceGUI-3.0.lua +++ b/libs/AceGUI-3.0/AceGUI-3.0.lua @@ -24,8 +24,8 @@ -- f:AddChild(btn) -- @class file -- @name AceGUI-3.0 --- @release $Id: AceGUI-3.0.lua 1102 2013-10-25 14:15:23Z nevcairiel $ -local ACEGUI_MAJOR, ACEGUI_MINOR = "AceGUI-3.0", 34 +-- @release $Id: AceGUI-3.0.lua 1177 2018-06-25 12:12:48Z nevcairiel $ +local ACEGUI_MAJOR, ACEGUI_MINOR = "AceGUI-3.0", 36 local AceGUI, oldminor = LibStub:NewLibrary(ACEGUI_MAJOR, ACEGUI_MINOR) if not AceGUI then return end -- No upgrade needed @@ -811,3 +811,221 @@ AceGUI:RegisterLayout("Flow", height = height + rowheight + 3 safecall(content.obj.LayoutFinished, content.obj, nil, height) end) + +-- Get alignment method and value. Possible alignment methods are a callback, a number, "start", "middle", "end", "fill" or "TOPLEFT", "BOTTOMRIGHT" etc. +local GetCellAlign = function (dir, tableObj, colObj, cellObj, cell, child) + local fn = cellObj and (cellObj["align" .. dir] or cellObj.align) + or colObj and (colObj["align" .. dir] or colObj.align) + or tableObj["align" .. dir] or tableObj.align + or "CENTERLEFT" + local child, cell, val = child or 0, cell or 0, nil + + if type(fn) == "string" then + fn = fn:lower() + fn = dir == "V" and (fn:sub(1, 3) == "top" and "start" or fn:sub(1, 6) == "bottom" and "end" or fn:sub(1, 6) == "center" and "middle") + or dir == "H" and (fn:sub(-4) == "left" and "start" or fn:sub(-5) == "right" and "end" or fn:sub(-6) == "center" and "middle") + or fn + val = (fn == "start" or fn == "fill") and 0 or fn == "end" and cell - child or (cell - child) / 2 + elseif type(fn) == "function" then + val = fn(child or 0, cell, dir) + else + val = fn + end + + return fn, max(0, min(val, cell)) +end + +-- Get width or height for multiple cells combined +local GetCellDimension = function (dir, laneDim, from, to, space) + local dim = 0 + for cell=from,to do + dim = dim + (laneDim[cell] or 0) + end + return dim + max(0, to - from) * (space or 0) +end + +--[[ Options +============ +Container: + - columns ({col, col, ...}): Column settings. "col" can be a number (<= 0: content width, <1: rel. width, <10: weight, >=10: abs. width) or a table with column setting. + - space, spaceH, spaceV: Overall, horizontal and vertical spacing between cells. + - align, alignH, alignV: Overall, horizontal and vertical cell alignment. See GetCellAlign() for possible values. +Columns: + - width: Fixed column width (nil or <=0: content width, <1: rel. width, >=1: abs. width). + - min or 1: Min width for content based width + - max or 2: Max width for content based width + - weight: Flexible column width. The leftover width after accounting for fixed-width columns is distributed to weighted columns according to their weights. + - align, alignH, alignV: Overwrites the container setting for alignment. +Cell: + - colspan: Makes a cell span multiple columns. + - rowspan: Makes a cell span multiple rows. + - align, alignH, alignV: Overwrites the container and column setting for alignment. +]] +AceGUI:RegisterLayout("Table", + function (content, children) + local obj = content.obj + obj:PauseLayout() + + local tableObj = obj:GetUserData("table") + local cols = tableObj.columns + local spaceH = tableObj.spaceH or tableObj.space or 0 + local spaceV = tableObj.spaceV or tableObj.space or 0 + local totalH = (content:GetWidth() or content.width or 0) - spaceH * (#cols - 1) + + -- We need to reuse these because layout events can come in very frequently + local layoutCache = obj:GetUserData("layoutCache") + if not layoutCache then + layoutCache = {{}, {}, {}, {}, {}, {}} + obj:SetUserData("layoutCache", layoutCache) + end + local t, laneH, laneV, rowspans, rowStart, colStart = unpack(layoutCache) + + -- Create the grid + local n, slotFound = 0 + for i,child in ipairs(children) do + if child:IsShown() then + repeat + n = n + 1 + local col = (n - 1) % #cols + 1 + local row = ceil(n / #cols) + local rowspan = rowspans[col] + local cell = rowspan and rowspan.child or child + local cellObj = cell:GetUserData("cell") + slotFound = not rowspan + + -- Rowspan + if not rowspan and cellObj and cellObj.rowspan then + rowspan = {child = child, from = row, to = row + cellObj.rowspan - 1} + rowspans[col] = rowspan + end + if rowspan and i == #children then + rowspan.to = row + end + + -- Colspan + local colspan = max(0, min((cellObj and cellObj.colspan or 1) - 1, #cols - col)) + n = n + colspan + + -- Place the cell + if not rowspan or rowspan.to == row then + t[n] = cell + rowStart[cell] = rowspan and rowspan.from or row + colStart[cell] = col + + if rowspan then + rowspans[col] = nil + end + end + until slotFound + end + end + + local rows = ceil(n / #cols) + + -- Determine fixed size cols and collect weights + local extantH, totalWeight = totalH, 0 + for col,colObj in ipairs(cols) do + laneH[col] = 0 + + if type(colObj) == "number" then + colObj = {[colObj >= 1 and colObj < 10 and "weight" or "width"] = colObj} + cols[col] = colObj + end + + if colObj.weight then + -- Weight + totalWeight = totalWeight + (colObj.weight or 1) + else + if not colObj.width or colObj.width <= 0 then + -- Content width + for row=1,rows do + local child = t[(row - 1) * #cols + col] + if child then + local f = child.frame + f:ClearAllPoints() + local childH = f:GetWidth() or 0 + + laneH[col] = max(laneH[col], childH - GetCellDimension("H", laneH, colStart[child], col - 1, spaceH)) + end + end + + laneH[col] = max(colObj.min or colObj[1] or 0, min(laneH[col], colObj.max or colObj[2] or laneH[col])) + else + -- Rel./Abs. width + laneH[col] = colObj.width < 1 and colObj.width * totalH or colObj.width + end + extantH = max(0, extantH - laneH[col]) + end + end + + -- Determine sizes based on weight + local scale = totalWeight > 0 and extantH / totalWeight or 0 + for col,colObj in pairs(cols) do + if colObj.weight then + laneH[col] = scale * colObj.weight + end + end + + -- Arrange children + for row=1,rows do + local rowV = 0 + + -- Horizontal placement and sizing + for col=1,#cols do + local child = t[(row - 1) * #cols + col] + if child then + local colObj = cols[colStart[child]] + local cellObj = child:GetUserData("cell") + local offsetH = GetCellDimension("H", laneH, 1, colStart[child] - 1, spaceH) + (colStart[child] == 1 and 0 or spaceH) + local cellH = GetCellDimension("H", laneH, colStart[child], col, spaceH) + + local f = child.frame + f:ClearAllPoints() + local childH = f:GetWidth() or 0 + + local alignFn, align = GetCellAlign("H", tableObj, colObj, cellObj, cellH, childH) + f:SetPoint("LEFT", content, offsetH + align, 0) + if child:IsFullWidth() or alignFn == "fill" or childH > cellH then + f:SetPoint("RIGHT", content, "LEFT", offsetH + align + cellH, 0) + end + + if child.DoLayout then + child:DoLayout() + end + + rowV = max(rowV, (f:GetHeight() or 0) - GetCellDimension("V", laneV, rowStart[child], row - 1, spaceV)) + end + end + + laneV[row] = rowV + + -- Vertical placement and sizing + for col=1,#cols do + local child = t[(row - 1) * #cols + col] + if child then + local colObj = cols[colStart[child]] + local cellObj = child:GetUserData("cell") + local offsetV = GetCellDimension("V", laneV, 1, rowStart[child] - 1, spaceV) + (rowStart[child] == 1 and 0 or spaceV) + local cellV = GetCellDimension("V", laneV, rowStart[child], row, spaceV) + + local f = child.frame + local childV = f:GetHeight() or 0 + + local alignFn, align = GetCellAlign("V", tableObj, colObj, cellObj, cellV, childV) + if child:IsFullHeight() or alignFn == "fill" then + f:SetHeight(cellV) + end + f:SetPoint("TOP", content, 0, -(offsetV + align)) + end + end + end + + -- Calculate total height + local totalV = GetCellDimension("V", laneV, 1, #laneV, spaceV) + + -- Cleanup + for _,v in pairs(layoutCache) do wipe(v) end + + safecall(obj.LayoutFinished, obj, nil, totalV) + obj:ResumeLayout() + end) diff --git a/libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua b/libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua index 020eaf6..80fd582 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua @@ -21,7 +21,7 @@ local CreateFrame, UIParent = CreateFrame, UIParent Scripts -------------------------------------------------------------------------------]] local function Button_OnClick(frame) - PlaySound(PlaySoundKitID and "gsTitleOptionExit" or 799) -- SOUNDKIT.GS_TITLE_OPTION_EXIT + PlaySound(799) -- SOUNDKIT.GS_TITLE_OPTION_EXIT frame.obj:Hide() end diff --git a/libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua b/libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua index d00470e..95544c5 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua @@ -63,7 +63,7 @@ Scripts -------------------------------------------------------------------------------]] local function Tab_OnClick(frame) if not (frame.selected or frame.disabled) then - PlaySound(PlaySoundKitID and "igCharacterInfoTab" or 841) -- SOUNDKIT.IG_CHARACTER_INFO_TAB + PlaySound(841) -- SOUNDKIT.IG_CHARACTER_INFO_TAB frame.obj:SelectTab(frame.value) end end diff --git a/libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua b/libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua index 617d5dc..236f633 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua @@ -2,10 +2,12 @@ TreeGroup Container Container that uses a tree control to switch between groups. -------------------------------------------------------------------------------]] -local Type, Version = "TreeGroup", 40 +local Type, Version = "TreeGroup", 41 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end +local WoW80 = select(4, GetBuildInfo()) >= 80000 + -- Lua APIs local next, pairs, ipairs, assert, type = next, pairs, ipairs, assert, type local math_min, math_max, floor = math.min, math.max, floor @@ -162,7 +164,7 @@ end local function FirstFrameUpdate(frame) local self = frame.obj frame:SetScript("OnUpdate", nil) - self:RefreshTree() + self:RefreshTree(nil, true) end local function BuildUniqueValue(...) @@ -300,6 +302,8 @@ local methods = { ["OnRelease"] = function(self) self.status = nil + self.tree = nil + self.frame:SetScript("OnUpdate", nil) for k, v in pairs(self.localstatus) do if k == "groups" then for k2 in pairs(v) do @@ -388,8 +392,8 @@ local methods = { end end, - ["RefreshTree"] = function(self,scrollToSelection) - local buttons = self.buttons + ["RefreshTree"] = function(self,scrollToSelection,fromOnUpdate) + local buttons = self.buttons local lines = self.lines for i, v in ipairs(buttons) do @@ -420,6 +424,12 @@ local methods = { local maxlines = (floor(((self.treeframe:GetHeight()or 0) - 20 ) / 18)) if maxlines <= 0 then return end + -- workaround for lag spikes on WoW 8.0 + if WoW80 and self.frame:GetParent() == UIParent and not fromOnUpdate then + self.frame:SetScript("OnUpdate", FirstFrameUpdate) + return + end + local first, last scrollToSelection = status.scrollToSelection diff --git a/libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua b/libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua index 6c36aca..6825420 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua @@ -32,7 +32,7 @@ do end local function closeOnClick(this) - PlaySound(PlaySoundKitID and "gsTitleOptionExit" or 799) -- SOUNDKIT.GS_TITLE_OPTION_EXIT + PlaySound(799) -- SOUNDKIT.GS_TITLE_OPTION_EXIT this.obj:Hide() end diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua index 55b7bc8..0a23be4 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua @@ -18,7 +18,7 @@ Scripts -------------------------------------------------------------------------------]] local function Button_OnClick(frame, ...) AceGUI:ClearFocus() - PlaySound(PlaySoundKitID and "igMainMenuOption" or 852) -- SOUNDKIT.IG_MAINMENU_OPTION + PlaySound(852) -- SOUNDKIT.IG_MAINMENU_OPTION frame.obj:Fire("OnClick", ...) end diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua index b8426e3..b96ac59 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua @@ -60,9 +60,9 @@ local function CheckBox_OnMouseUp(frame) self:ToggleChecked() if self.checked then - PlaySound(PlaySoundKitID and "igMainMenuOptionCheckBoxOn" or 856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON else -- for both nil and false (tristate) - PlaySound(PlaySoundKitID and "igMainMenuOptionCheckBoxOff" or 857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF end self:Fire("OnValueChanged", self.checked) diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua index cb9c14c..5748e4f 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua @@ -1,4 +1,4 @@ ---[[ $Id: AceGUIWidget-DropDown-Items.lua 1161 2017-08-12 14:30:16Z funkydude $ ]]-- +--[[ $Id: AceGUIWidget-DropDown-Items.lua 1167 2017-08-29 22:08:48Z funkydude $ ]]-- local AceGUI = LibStub("AceGUI-3.0") @@ -343,9 +343,9 @@ do if self.disabled then return end self.value = not self.value if self.value then - PlaySound(PlaySoundKitID and "igMainMenuOptionCheckBoxOn" or 856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON else - PlaySound(PlaySoundKitID and "igMainMenuOptionCheckBoxOff" or 857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF end UpdateToggle(self) self:Fire("OnValueChanged", self.value) diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua index 37a365b..cf0b0aa 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua @@ -1,4 +1,4 @@ ---[[ $Id: AceGUIWidget-DropDown.lua 1161 2017-08-12 14:30:16Z funkydude $ ]]-- +--[[ $Id: AceGUIWidget-DropDown.lua 1167 2017-08-29 22:08:48Z funkydude $ ]]-- local AceGUI = LibStub("AceGUI-3.0") -- Lua APIs @@ -381,7 +381,7 @@ do local function Dropdown_TogglePullout(this) local self = this.obj - PlaySound(PlaySoundKitID and "igMainMenuOptionCheckBoxOn" or 856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON if self.open then self.open = nil self.pullout:Close() diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua index 6594684..29f7e00 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua @@ -1,7 +1,7 @@ --[[----------------------------------------------------------------------------- EditBox Widget -------------------------------------------------------------------------------]] -local Type, Version = "EditBox", 27 +local Type, Version = "EditBox", 28 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end @@ -73,7 +73,7 @@ local function EditBox_OnEnterPressed(frame) local value = frame:GetText() local cancel = self:Fire("OnEnterPressed", value) if not cancel then - PlaySound(PlaySoundKitID and "igMainMenuOptionCheckBoxOn" or 856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON HideButton(self) end end @@ -81,23 +81,21 @@ end local function EditBox_OnReceiveDrag(frame) local self = frame.obj local type, id, info = GetCursorInfo() + local name if type == "item" then - self:SetText(info) - self:Fire("OnEnterPressed", info) - ClearCursor() + name = info elseif type == "spell" then - local name = GetSpellInfo(id, info) - self:SetText(name) - self:Fire("OnEnterPressed", name) - ClearCursor() + name = GetSpellInfo(id, info) elseif type == "macro" then - local name = GetMacroInfo(id) + name = GetMacroInfo(id) + end + if name then self:SetText(name) self:Fire("OnEnterPressed", name) ClearCursor() + HideButton(self) + AceGUI:ClearFocus() end - HideButton(self) - AceGUI:ClearFocus() end local function EditBox_OnTextChanged(frame) diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua index 5946d8d..20d0887 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua @@ -108,7 +108,7 @@ local function EditBox_OnEnterPressed(frame) end if value then - PlaySound(PlaySoundKitID and "igMainMenuOptionCheckBoxOn" or 856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON self.slider:SetValue(value) self:Fire("OnMouseUp", value) end diff --git a/libs/HereBeDragons-1.0/HereBeDragons-1.0.lua b/libs/HereBeDragons-1.0/HereBeDragons-1.0.lua deleted file mode 100755 index 6db3fca..0000000 --- a/libs/HereBeDragons-1.0/HereBeDragons-1.0.lua +++ /dev/null @@ -1,771 +0,0 @@ --- HereBeDragons is a data API for the World of Warcraft mapping system - -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 = 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[0] == 0 or data[1] == 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[0] == 0 or data[1] == 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 diff --git a/libs/HereBeDragons-1.0/HereBeDragons-Pins-1.0.lua b/libs/HereBeDragons-1.0/HereBeDragons-Pins-1.0.lua deleted file mode 100755 index 58b706c..0000000 --- a/libs/HereBeDragons-1.0/HereBeDragons-Pins-1.0.lua +++ /dev/null @@ -1,645 +0,0 @@ --- HereBeDragons-Pins is a library to show pins/icons on the world map and minimap - -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 diff --git a/libs/HereBeDragons/CHANGES.txt b/libs/HereBeDragons/CHANGES.txt new file mode 100755 index 0000000..99c6647 --- /dev/null +++ b/libs/HereBeDragons/CHANGES.txt @@ -0,0 +1,58 @@ +tag cd12ccfd13832690f1422c5795a3921c452298a7 1.92-beta +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Sat Jun 23 20:03:11 2018 +0200 + +Tag as 1.92-beta + +commit f1b0f720ccce59c8139ae5e0cbfac21100c24874 +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Sat Jun 23 18:53:33 2018 +0200 + + Fix minimap drawing zone limitation + +commit d087939c3b4734ac18a010420b12a4a3810ab5a2 +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Sat Jun 23 18:53:18 2018 +0200 + + Use HBD parent map data for pin drawing + +commit 70c7ea002a3d702dd482958d11b7660f6ad36b7c +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Sat Jun 23 18:52:40 2018 +0200 + + Store the maps parent in the data table + +commit 3ecf06773358df59c80f66ae91de16da88b4264e +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Sat Jun 23 18:27:36 2018 +0200 + + Fix show flag handling in world map drawing for instance-based coordinates + +commit ec1e0d064b7a02ac97980fc5b7fc10adda044390 +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Wed Jun 20 20:19:26 2018 +0200 + + Set a scaling limit for the pins so they show in a consistent size on all maps + + Fixes WoWAce Issue #12 + +commit e8e542646986796906d14abe6a8cba716e2f58c6 +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Wed Jun 20 20:19:01 2018 +0200 + + Properly reset existing icons when re-using pins + + Fixes GitHub Issue #4 + +commit 61383763a1ce7b87379d50b534150693e2d17ab7 +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Wed Jun 13 19:50:42 2018 +0200 + + Fix drawing pins on the world map + +commit af699f6637a2d07ba3000273534116aefaffc679 +Author: Hendrik Leppkes <h.leppkes@gmail.com> +Date: Sat May 26 19:15:44 2018 +0200 + + Optimize transform storage for faster lookups + diff --git a/libs/HereBeDragons/HereBeDragons-1.0.lua b/libs/HereBeDragons/HereBeDragons-1.0.lua new file mode 100755 index 0000000..78faf34 --- /dev/null +++ b/libs/HereBeDragons/HereBeDragons-1.0.lua @@ -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 diff --git a/libs/HereBeDragons/HereBeDragons-2.0.lua b/libs/HereBeDragons/HereBeDragons-2.0.lua new file mode 100755 index 0000000..4231a8a --- /dev/null +++ b/libs/HereBeDragons/HereBeDragons-2.0.lua @@ -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", 5 +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, 1, -6933.33, 533.33, -16000, -8000, 10133.3, 17600 }, + { 530, 0, 4800, 16000, -10133.3, -2666.67, -2400, 2400 }, + { 732, 0, -20000, 20000, -20000, 20000, -1600, 2800 }, + { 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 diff --git a/libs/HereBeDragons/HereBeDragons-Migrate.lua b/libs/HereBeDragons/HereBeDragons-Migrate.lua new file mode 100755 index 0000000..b6efc1b --- /dev/null +++ b/libs/HereBeDragons/HereBeDragons-Migrate.lua @@ -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 diff --git a/libs/HereBeDragons/HereBeDragons-Pins-1.0.lua b/libs/HereBeDragons/HereBeDragons-Pins-1.0.lua new file mode 100755 index 0000000..d0a79c5 --- /dev/null +++ b/libs/HereBeDragons/HereBeDragons-Pins-1.0.lua @@ -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 diff --git a/libs/HereBeDragons/HereBeDragons-Pins-2.0.lua b/libs/HereBeDragons/HereBeDragons-Pins-2.0.lua new file mode 100755 index 0000000..8b8cf9e --- /dev/null +++ b/libs/HereBeDragons/HereBeDragons-Pins-2.0.lua @@ -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