From f9b92178d591ca80776bd9d60f5444de0461fcc5 Mon Sep 17 00:00:00 2001 From: "Nathanial.C.Jones" Date: Thu, 3 Jan 2013 02:50:16 +0000 Subject: [PATCH] Beginnings of conversion from old, modified Astrolabe-0.4 to new, improved Astrolabe-1.0. WARNING: THESE MODIFICATIONS ARE NOT TESTED AND ARE LIKELY TO CAUSE CATASTROPHIC ISSUES AT THIS TIME. ANY ATTEMPT TO USE THIS REVISION IS NOT RECOMMENDED. --- QuestHelper/QuestHelper.toc | 2 +- QuestHelper/arrow.lua | 44 +- QuestHelper/core.lua | 2 +- QuestHelper/flightpath.lua | 138 +- QuestHelper/libs/Astrolabe/Astrolabe.lua | 1461 +++++++++++++++++ QuestHelper/libs/Astrolabe/Astrolabe.toc | 6 + .../libs/Astrolabe/AstrolabeMapMonitor.lua | 235 +++ QuestHelper/libs/Astrolabe/DongleStub.lua | 126 ++ QuestHelper/libs/Astrolabe/Load.xml | 6 + QuestHelper/libs/Astrolabe/lgpl.txt | 504 ++++++ QuestHelper/main.lua | 85 +- 11 files changed, 2460 insertions(+), 149 deletions(-) create mode 100644 QuestHelper/libs/Astrolabe/Astrolabe.lua create mode 100644 QuestHelper/libs/Astrolabe/Astrolabe.toc create mode 100644 QuestHelper/libs/Astrolabe/AstrolabeMapMonitor.lua create mode 100644 QuestHelper/libs/Astrolabe/DongleStub.lua create mode 100644 QuestHelper/libs/Astrolabe/Load.xml create mode 100644 QuestHelper/libs/Astrolabe/lgpl.txt diff --git a/QuestHelper/QuestHelper.toc b/QuestHelper/QuestHelper.toc index 0bd6c51..7580f83 100644 --- a/QuestHelper/QuestHelper.toc +++ b/QuestHelper/QuestHelper.toc @@ -61,7 +61,7 @@ libs\AceGUI-3.0\AceGUI-3.0.xml libs\AceConfig-3.0\AceConfig-3.0.xml bst_astrolabe.lua -libs\AstrolabeQH\Load.xml +libs\Astrolabe\Load.xml #MapMonitor.lua #AstroLabeToLibMapDataConversionStub.lua diff --git a/QuestHelper/arrow.lua b/QuestHelper/arrow.lua index f142380..586c833 100644 --- a/QuestHelper/arrow.lua +++ b/QuestHelper/arrow.lua @@ -19,7 +19,7 @@ QuestHelper_Loadtime["arrow.lua"] = GetTime() copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * The name or alias of the copyright holder may not be used to endorse + * The name or alias of the copyright holder may not be used to endorse or promote products derived from this software without specific prior written permission. @@ -137,7 +137,7 @@ function QH_Arrow_SetScale(scale) else scale = QuestHelper_Pref.arrow_arrowsize end - + if not showDownArrow then wayframe:SetHeight(42 * scale) wayframe:SetWidth(56 * scale) @@ -154,7 +154,7 @@ function QH_Arrow_SetTextScale(scale) else scale = QuestHelper_Pref.arrow_textsize end - + wayframe.title:SetFont(default_font_name, default_font_size * scale, default_font_flags) wayframe.status:SetFont(default_font_name, default_font_size * scale, default_font_flags) wayframe.tta:SetFont(default_font_name, default_font_size * scale, default_font_flags) @@ -189,19 +189,19 @@ local speed_count = 0 OnUpdate = function() local self = wayframe QuestHelper: Assert(self) - - if not active_point.c or QuestHelper.collect_rc ~= active_point.c or QuestHelper.collect_delayed or QuestHelper.InBrokenInstance or not QuestHelper_Pref.arrow then + + if not QuestHelper_Pref.arrow then self:Hide() return end - + self:Show() - local dist, dx, dy = QuestHelper.Astrolabe:ComputeDistance(QuestHelper.collect_rc, QuestHelper.collect_rz, QuestHelper.collect_rx, QuestHelper.collect_ry, active_point.c, active_point.z, active_point.x, active_point.y) + local dist, dx, dy = QuestHelper.Astrolabe:ComputeDistance(QuestHelper.collect_rm, QuestHelper.collect_rf, QuestHelper.collect_rx, QuestHelper.collect_ry, active_point.m, active_point.f, active_point.x, active_point.y) --print(QuestHelper.collect_rc, QuestHelper.collect_rz, QuestHelper.collect_rx, QuestHelper.collect_ry, active_point.c, active_point.z, active_point.x, active_point.y) - + local text = "" - + if dist then -- I had support for miles and kilometers, but decided that distances were rarely large -- enough to warrent it. @@ -211,14 +211,14 @@ OnUpdate = function() text = QHFormat("DISTANCE_YARDS", math.floor(0.5 + dist)) end end - + status:SetText(text) -- Showing the arrival arrow? if dist and dist <= 10 then if not showDownArrow then showDownArrow = true - QH_Arrow_SetScale() + QH_Arrow_SetScale() arrow:SetTexture("Interface\\AddOns\\QuestHelper\\arrow_image_down") arrow:SetVertexColor(0, 1, 0) end @@ -236,7 +236,7 @@ OnUpdate = function() else if showDownArrow then showDownArrow = false - QH_Arrow_SetScale() + QH_Arrow_SetScale() arrow:SetTexture("Interface\\AddOns\\QuestHelper\\arrow_image") showDownArrow = false end @@ -252,7 +252,7 @@ OnUpdate = function() local gr,gg,gb = 0, 1, 0 local mr,mg,mb = 1, 1, 0 local br,bg,bb = 1, 0, 0 - local r,g,b = ColorGradient(perc, br, bg, bb, mr, mg, mb, gr, gg, gb) + local r,g,b = ColorGradient(perc, br, bg, bb, mr, mg, mb, gr, gg, gb) arrow:SetVertexColor(r,g,b) @@ -291,11 +291,11 @@ OnUpdate = function() if speed > 0 then local eta = math.abs(dist / speed) - tta:SetFormattedText("%01d:%02d", eta / 60, eta % 60) + tta:SetFormattedText("%01d:%02d", eta / 60, eta % 60) else tta:SetText("") end - + last_distance = dist tta_throttle = 0 end @@ -317,14 +317,14 @@ function QH_Arrow_PopulateMenu(menu) hide:SetFunction(function () QuestHelper:ToggleArrow() end) --hide:AddTexture(spacer(), true) --hide:AddTexture(spacer(), false) - + local lock = QuestHelper:CreateMenuItem(menu, "Lock") local ltex = QuestHelper:CreateIconTexture(item, 10) lock:SetFunction(function () QuestHelper_Pref.arrow_locked = not QuestHelper_Pref.arrow_locked end) lock:AddTexture(ltex, true) lock:AddTexture(spacer(), false) ltex:SetVertexColor(1, 1, 1, QuestHelper_Pref.arrow_locked and 1 or 0) - + local scale = QuestHelper:CreateMenuItem(menu, "Arrow Scale") local scale_menu = QuestHelper:CreateMenu() scale:SetSubmenu(scale_menu) @@ -341,7 +341,7 @@ function QH_Arrow_PopulateMenu(menu) end it:AddTexture(icon) end - + local tscale = QuestHelper:CreateMenuItem(menu, "Text Scale") local tscale_menu = QuestHelper:CreateMenu() tscale:SetSubmenu(tscale_menu) @@ -358,7 +358,7 @@ function QH_Arrow_PopulateMenu(menu) end it:AddTexture(icon) end - + local reset = QuestHelper:CreateMenuItem(menu, QHText("SETTINGS_MENU_ARROW_RESET")) reset:SetFunction(function () QH_Arrow_Reset() end) end @@ -366,14 +366,14 @@ end local function WayFrame_OnClick(self, button) local menu = QuestHelper:CreateMenu() QuestHelper:CreateMenuTitle(menu, QHText("SETTINGS_ARROWLINK_ARROW")) - + QH_Arrow_PopulateMenu(menu) - + local submenu = QuestHelper:CreateMenu() item = QuestHelper:CreateMenuItem(menu, "Objective") item:SetSubmenu(submenu) QH_Append_NextObjective(submenu) - + menu:ShowAtCursor() end diff --git a/QuestHelper/core.lua b/QuestHelper/core.lua index 1c785b7..2bbc4a6 100644 --- a/QuestHelper/core.lua +++ b/QuestHelper/core.lua @@ -5,7 +5,7 @@ QuestHelper_File["core.lua"] = "4.0.1.$svnversion$" QuestHelper_Loadtime["core.lua"] = GetTime() -QuestHelper.Astrolabe = QH_Astrolabe_Ready and DongleStub("Astrolabe-0.4-QuestHelper") +QuestHelper.Astrolabe = QH_Astrolabe_Ready and DongleStub("Astrolabe-1.0") local walker = QuestHelper:CreateWorldMapWalker() QuestHelper.minimap_marker = QuestHelper:CreateMipmapDodad() diff --git a/QuestHelper/flightpath.lua b/QuestHelper/flightpath.lua index 1255d74..cd6cc22 100644 --- a/QuestHelper/flightpath.lua +++ b/QuestHelper/flightpath.lua @@ -21,7 +21,7 @@ local function LookupName(x, y) best, d2 = TaxiNodeName(i), u end end - + return best end @@ -32,17 +32,17 @@ local function getRoute(id) if routes and routes > 0 and routes < 100 then local origin, dest = TaxiNodeName(i), TaxiNodeName(id) local path_hash = 0 - + if routes > 1 then local path_str = "" - + for j = 1,routes-1 do path_str = string.format("%s/%s", path_str, LookupName(TaxiGetDestX(id, j), TaxiGetDestY(id, j))) end - + path_hash = QuestHelper:HashString(path_str) end - + return origin, dest, path_hash end end @@ -72,15 +72,15 @@ end TaxiNodeOnButtonEnter = function(btn, ...) QuestHelper: Assert(btn) local rv = real_TaxiNodeOnButtonEnter(btn, ...) - + if QuestHelper_Pref.flight_time then local index = btn:GetID() if TaxiNodeGetType(index) == "REACHABLE" then - + local snode, dnode = getSrcDest(index) - + local eta, estimate = getEtaEstimate(snode, dnode) - + if eta then -- Going to replace the tooltip. GameTooltip:SetOwner(btn, "ANCHOR_RIGHT") GameTooltip:ClearLines() @@ -94,27 +94,27 @@ TaxiNodeOnButtonEnter = function(btn, ...) end end end - + return rv end TakeTaxiNode = function(id) local src, dest = getSrcDest(id) - + if src then local flight_data = QuestHelper.flight_data if not flight_data then flight_data = QuestHelper:CreateTable() QuestHelper.flight_data = flight_data end - + flight_data.src = src flight_data.dest = dest flight_data.start_time = nil flight_data.end_time = nil flight_data.end_time_estimate = nil end - + real_TakeTaxiNode(id) end @@ -126,9 +126,9 @@ function QuestHelper:getFlightInstructor(area) return npc end end - + local static = QuestHelper_StaticData[QuestHelper_Locale] - + if static then fi_table = static.flight_instructors and static.flight_instructors[self.faction] if fi_table then @@ -148,7 +148,7 @@ end -- If so, find the flightpath distance and the "walking" distance. Add up walking and flightpath separately, and return the sums. local function getWalkToFlight(tbl, fi1, fi2) local f, w = 0, 0 - + if tbl then for origin, list in pairs(tbl) do for dest, hashlist in pairs(list) do @@ -158,15 +158,15 @@ local function getWalkToFlight(tbl, fi1, fi2) local obj1, obj2 = QuestHelper:GetObjective("monster", npc1), QuestHelper:GetObjective("monster", npc2) obj1:PrepareRouting(true, {failable = true}) obj2:PrepareRouting(true, {failable = true}) - + local pos1, pos2 = obj1:Position(), obj2:Position() - + if pos1 and pos2 then local x, y = pos1[3]-pos2[3], pos1[4]-pos2[4] w = w + math.sqrt(x*x+y*y) f = f + hashlist[0] end - + obj2:DoneRouting() obj1:DoneRouting() end @@ -174,7 +174,7 @@ local function getWalkToFlight(tbl, fi1, fi2) end end end - + return f, w end @@ -184,12 +184,12 @@ function QuestHelper:computeWalkToFlightMult() local s = QuestHelper_StaticData[self.locale] s = s and s.flight_routes s = s and s[self.faction] - + local fi1 = QuestHelper_FlightInstructors_Local[self.faction] local fi2 = QuestHelper_StaticData[self.locale] fi2 = fi2 and fi2.flight_instructors fi2 = fi2 and fi2[self.faction] - + local f1, w1 = getWalkToFlight(l, fi1, fi2) local f2, w2 = getWalkToFlight(s, fi1, fi2) return (f1+f2+0.032876)/(w1+w2+0.1) @@ -200,45 +200,45 @@ function QuestHelper:computeLinkTime(origin, dest, hash, fallback) if origin == dest then return 0 end - + local l = QuestHelper_FlightRoutes_Local[self.faction] local s = QuestHelper_StaticData[self.locale] s = s and s.flight_routes s = s and s[self.faction] - + hash = hash or 0 - + -- Will try to lookup flight time there, failing that, will use the time from there to here. local t = getTime(l, origin, dest, hash) or getTime(s, origin, dest, hash) or getTime(l, dest, origin, hash) or getTime(s, dest, origin, hash) or fallback - + if t == nil then -- Don't have any recored information on this flight time, will estimate based on distances. l = QuestHelper_FlightInstructors_Local[self.faction] s = QuestHelper_StaticData[self.locale] s = s and s.flight_instructors s = s and s[self.faction] - + local npc1, npc2 = (l and l[origin]) or (s and s[origin]), (l and l[dest]) or (s and s[dest]) - + if npc1 and npc2 then local obj1, obj2 = self:GetObjective("monster", npc1), self:GetObjective("monster", npc2) obj1:PrepareRouting(true) obj2:PrepareRouting(true) - + local pos1, pos2 = obj1:Position(), obj2:Position() - + if pos1 and pos2 then local x, y = pos1[3]-pos2[3], pos1[4]-pos2[4] - + t = math.sqrt(x*x+y*y)*self.flight_scalar end - + obj2:DoneRouting() obj1:DoneRouting() end end - + if t and type(t) ~= "number" then QuestHelper:AppendNotificationError("2008-10-11 computelinktime is not a number", string.format("%s %s", type(t), fallback and type(fallback) or "(nil)")) return nil @@ -253,11 +253,11 @@ function QuestHelper:addLinkInfo(data, flight_times) if select(2, UnitClass("player")) ~= "DRUID" then -- As only druids can use the flight point in moonglade, we need to figure out -- where it is so we can ignore it. - + if not moonglade_fp then - + local fi_table = QuestHelper_FlightInstructors_Local[self.faction] - + if fi_table then for area, npc in pairs(fi_table) do local npc_obj = self:GetObjective("monster", npc) npc_obj:PrepareRouting(true, {failable = true}) @@ -269,11 +269,11 @@ function QuestHelper:addLinkInfo(data, flight_times) end npc_obj:DoneRouting() end end - + if not moonglade_fp then fi_table = QuestHelper_StaticData[QuestHelper_Locale] fi_table = fi_table and fi_table.flight_instructors and fi_table.flight_instructors[self.faction] - + if fi_table then for area, npc in pairs(fi_table) do local npc_obj = self:GetObjective("monster", npc) npc_obj:PrepareRouting(true, {failable = true}) @@ -286,7 +286,7 @@ function QuestHelper:addLinkInfo(data, flight_times) npc_obj:DoneRouting() end end end - + if not moonglade_fp then -- This will always be unknown for the session, even if you call buildFlightTimes again -- but if it's unknown then you won't be able to @@ -296,14 +296,14 @@ function QuestHelper:addLinkInfo(data, flight_times) end end end - + for origin, list in pairs(data) do local tbl = flight_times[origin] if not tbl then tbl = self:CreateTable("Flightpath AddLinkInfo origin table") flight_times[origin] = tbl end - + for dest, hashs in pairs(list) do if origin ~= moonglade_fp and QuestHelper_KnownFlightRoutes[dest] and hashs[0] then local tbl2 = tbl[dest] @@ -328,29 +328,29 @@ local function getDataTime(ft, origin, dest) local str = nil local data = ft[origin][dest] local t = data[1] - + for key in pairs(visited) do visited[key] = nil end - + while true do local n = data[2] - + -- We might be asked about a route that visits the same point multiple times, and -- since this is effectively a linked list, we need to check for this to avoid -- infinite loops. if visited[n] then return end visited[n] = true - + local temp = QuestHelper:computeLinkTime(origin, n, str and QuestHelper:HashString(str) or 0, false) - + if temp then t = temp + (n == dest and 0 or ft[n][dest][1]) end - + if n == dest then break end str = string.format("%s/%s", str or "", n) data = ft[n][dest] end - + return t end @@ -360,28 +360,28 @@ QuestHelper_Flight_Updates_Current = 0 function QuestHelper:buildFlightTimes() self.flight_scalar = self:computeWalkToFlightMult() - + local flight_times = self.flight_times if not flight_times then flight_times = self:CreateTable() self.flight_times = flight_times end - + for key, list in pairs(flight_times) do self:ReleaseTable(list) flight_times[key] = nil end - + local l = QuestHelper_FlightRoutes_Local[self.faction] local s = QuestHelper_StaticData[self.locale] s = s and s.flight_routes s = s and s[self.faction] - + self:addLinkInfo(l, flight_times) self:addLinkInfo(s, flight_times) - + QuestHelper_Flight_Updates_Current = 0 - + -- This appears to set up flight_times so it gives directions from any node to any other node. I'm not sure what the getDataTime() call is all about, and I'm also not sure what dat[2] is for. In any case, I don't see anything immediately suspicious about this, just dubious. local cont = true while cont do @@ -391,19 +391,19 @@ function QuestHelper:buildFlightTimes() origin = next(flight_times, origin) if not origin then break end local list = flight_times[origin] - + for dest, data in pairs(list) do QuestHelper_Flight_Updates_Current = QuestHelper_Flight_Updates_Current + 1 if flight_times[dest] then for dest2, data2 in pairs(flight_times[dest]) do if dest2 ~= origin then local dat = list[dest2] - + if not dat then dat = self:CreateTable() dat[1], dat[2] = data[1]+data2[1], dest list[dest2] = dat dat[1] = getDataTime(flight_times, origin, dest2) - + if not dat[1] then self:ReleaseTable(dat) list[dest2] = nil @@ -415,7 +415,7 @@ function QuestHelper:buildFlightTimes() if o2 ~= dest then dat[1], dat[2] = data[1]+data2[1], dest local t2 = getDataTime(flight_times, origin, dest2) - + if t2 and t2 < o1 then dat[1] = t2 cont = true @@ -430,9 +430,9 @@ function QuestHelper:buildFlightTimes() end end end - + QuestHelper_Flight_Updates = QuestHelper_Flight_Updates_Current - + -- Replace the tables with simple times. for orig, list in pairs(flight_times) do for dest, data in pairs(list) do @@ -475,11 +475,11 @@ function QuestHelper:flightBegan() if self.flight_data and not self.flight_data.start_time then self.flight_data.start_time = GetTime() local src, dest = self.flight_data.src, self.flight_data.dest - - + + local eta, estimate = getEtaEstimate(src, dest) - - --[[ + + --[[ NOTE: MUST CONVERT TO M(ap), F(loor) coordinate system before enabling again. local npc = self:getFlightInstructor(self.flight_data.dest) -- Will inform QuestHelper that we're going to be at this NPC in whenever. if npc then local npc_obj = self:GetObjective("monster", npc) @@ -490,20 +490,20 @@ function QuestHelper:flightBegan() local x, y = self.Astrolabe:TranslateWorldMapPosition(c, 0, pos[3]/self.continent_scales_x[c], pos[4]/self.continent_scales_y[c], c, z) - + self:SetTargetLocation(QuestHelper_IndexLookup[c][z], x, y, eta) - + end npc_obj:DoneRouting() end]] - + do local loc = QH_Flight_Destinations[dest] if loc then -- sometimes we just don't have a loc, I think due to flightpath recalculations going on right then QuestHelper.routing_ac, QuestHelper.routing_ax, QuestHelper.routing_ay, QuestHelper.routing_c, QuestHelper.routing_z = QuestHelper_ParentLookup[loc.p], loc.x, loc.y, QuestHelper_ZoneLookup[loc.p][1], QuestHelper_ZoneLookup[loc.p][2] end end - + if eta and QuestHelper_Pref.flight_time then self.flight_data.end_time_estimate = time() + eta self:PerformCustomSearch(flight_updater) -- Reusing the search status indicator to display ETA for flight. @@ -515,7 +515,7 @@ function QuestHelper:flightEnded(interrupted) local flight_data = self.flight_data if flight_data and not flight_data.end_time then flight_data.end_time = GetTime() - + self:UnsetTargetLocation() self:StopCustomSearch() end diff --git a/QuestHelper/libs/Astrolabe/Astrolabe.lua b/QuestHelper/libs/Astrolabe/Astrolabe.lua new file mode 100644 index 0000000..492892f --- /dev/null +++ b/QuestHelper/libs/Astrolabe/Astrolabe.lua @@ -0,0 +1,1461 @@ +--[[ +Name: Astrolabe +Revision: $Rev: 147 $ +$Date: 2012-08-28 09:45:40 -0700 (Tue, 28 Aug 2012) $ +Author(s): Esamynn (esamynn at wowinterface.com) +Inspired By: Gatherer by Norganna + MapLibrary by Kristofer Karlsson (krka at kth.se) +Documentation: http://wiki.esamynn.org/Astrolabe +SVN: http://svn.esamynn.org/astrolabe/ +Description: + This is a library for the World of Warcraft UI system to place + icons accurately on both the Minimap and on Worldmaps. + This library also manages and updates the position of Minimap icons + automatically. + +Copyright (C) 2006-2012 James Carrothers + +License: + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Note: + This library's source code is specifically designed to work with + World of Warcraft's interpreted AddOn system. You have an implicit + licence to use this library with these facilities since that is its + designated purpose as per: + http://www.fsf.org/licensing/licenses/gpl-faq.html#InterpreterIncompat +]] + +-- WARNING!!! +-- DO NOT MAKE CHANGES TO THIS LIBRARY WITHOUT FIRST CHANGING THE LIBRARY_VERSION_MAJOR +-- STRING (to something unique) OR ELSE YOU MAY BREAK OTHER ADDONS THAT USE THIS LIBRARY!!! +local LIBRARY_VERSION_MAJOR = "Astrolabe-1.0" +local LIBRARY_VERSION_MINOR = tonumber(string.match("$Revision: 147 $", "(%d+)") or 1) + +if not DongleStub then error(LIBRARY_VERSION_MAJOR .. " requires DongleStub.") end +if not DongleStub:IsNewerVersion(LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR) then return end + +local Astrolabe = {}; + +-- define local variables for Data Tables (defined at the end of this file) +local WorldMapSize, MicroDungeonSize, MinimapSize, ValidMinimapShapes, zeroData; + +function Astrolabe:GetVersion() + return LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR; +end + + +-------------------------------------------------------------------------------------------------------------- +-- Config Constants +-------------------------------------------------------------------------------------------------------------- + +local configConstants = { + MinimapUpdateMultiplier = true, +} + +-- this constant is multiplied by the current framerate to determine +-- how many icons are updated each frame +Astrolabe.MinimapUpdateMultiplier = 1; + + +-------------------------------------------------------------------------------------------------------------- +-- Working Tables +-------------------------------------------------------------------------------------------------------------- + +Astrolabe.LastPlayerPosition = { 0, 0, 0, 0 }; +Astrolabe.MinimapIcons = {}; +Astrolabe.IconsOnEdge = {}; +Astrolabe.IconsOnEdge_GroupChangeCallbacks = {}; + +Astrolabe.MinimapIconCount = 0 +Astrolabe.ForceNextUpdate = false; +Astrolabe.IconsOnEdgeChanged = false; + +-- This variable indicates whether we know of a visible World Map or not. +-- The state of this variable is controlled by the AstrolabeMapMonitor library. +Astrolabe.WorldMapVisible = false; + +local AddedOrUpdatedIcons = {} +local MinimapIconsMetatable = { __index = AddedOrUpdatedIcons } + + +-------------------------------------------------------------------------------------------------------------- +-- Local Pointers for often used API functions +-------------------------------------------------------------------------------------------------------------- + +local twoPi = math.pi * 2; +local atan2 = math.atan2; +local sin = math.sin; +local cos = math.cos; +local abs = math.abs; +local sqrt = math.sqrt; +local min = math.min +local max = math.max +local yield = coroutine.yield +local next = next +local GetFramerate = GetFramerate + +local real_GetCurrentMapAreaID = GetCurrentMapAreaID +local function GetCurrentMapAreaID() + local id = real_GetCurrentMapAreaID(); + if ( id < 0 and GetCurrentMapContinent() == WORLDMAP_WORLD_ID ) then + return 0; + end + return id; +end + + +-------------------------------------------------------------------------------------------------------------- +-- Internal Utility Functions +-------------------------------------------------------------------------------------------------------------- + +local function assert(level,condition,message) + if not condition then + error(message,level) + end +end + +local function argcheck(value, num, ...) + assert(1, type(num) == "number", "Bad argument #2 to 'argcheck' (number expected, got " .. type(level) .. ")") + + for i=1,select("#", ...) do + if type(value) == select(i, ...) then return end + end + + local types = strjoin(", ", ...) + local name = string.match(debugstack(2,2,0), ": in function [`<](.-)['>]") + error(string.format("Bad argument #%d to 'Astrolabe.%s' (%s expected, got %s)", num, name, types, type(value)), 3) +end + +local function getSystemPosition( mapData, f, x, y ) + if ( f ~= 0 ) then + mapData = rawget(mapData, f) or MicroDungeonSize[mapData.system][f]; + end + x = x * mapData.width + mapData.xOffset; + y = y * mapData.height + mapData.yOffset; + return x, y; +end + +local function printError( ... ) + if ( ASTROLABE_VERBOSE) then + print(...) + end +end + + +-------------------------------------------------------------------------------------------------------------- +-- General Utility Functions +-------------------------------------------------------------------------------------------------------------- + +function Astrolabe:ComputeDistance( m1, f1, x1, y1, m2, f2, x2, y2 ) + --[[ + argcheck(m1, 2, "number"); + assert(3, m1 >= 0, "ComputeDistance: Illegal map id to m1: "..m1); + argcheck(f1, 3, "number", "nil"); + argcheck(x1, 4, "number"); + argcheck(y1, 5, "number"); + argcheck(m2, 6, "number"); + assert(3, m2 >= 0, "ComputeDistance: Illegal map id to m2: "..m2); + argcheck(f2, 7, "number", "nil"); + argcheck(x2, 8, "number"); + argcheck(y2, 9, "number"); + --]] + + if not ( m1 and m2 ) then return end; + f1 = f1 or min(#WorldMapSize[m1], 1); + f2 = f2 or min(#WorldMapSize[m2], 1); + + local dist, xDelta, yDelta; + if ( m1 == m2 and f1 == f2 ) then + -- points in the same zone on the same floor + local mapData = WorldMapSize[m1]; + if ( f1 ~= 0 ) then + mapData = rawget(mapData, f1) or MicroDungeonSize[mapData.system][f1]; + end + xDelta = (x2 - x1) * mapData.width; + yDelta = (y2 - y1) * mapData.height; + + else + local map1 = WorldMapSize[m1]; + local map2 = WorldMapSize[m2]; + if ( map1.system == map2.system ) then + -- points within the same system (continent) + x1, y1 = getSystemPosition(map1, f1, x1, y1); + x2, y2 = getSystemPosition(map2, f2, x2, y2); + xDelta = (x2 - x1); + yDelta = (y2 - y1); + + else + local s1 = map1.system; + local s2 = map2.system; + if ( (m1==0 or WorldMapSize[0][s1]) and (m2==0 or WorldMapSize[0][s2]) ) then + x1, y1 = getSystemPosition(map1, f1, x1, y1); + x2, y2 = getSystemPosition(map2, f2, x2, y2); + if ( m1 ~= 0 ) then + -- translate up from system 1 + local cont1 = WorldMapSize[0][s1]; + x1 = (x1 - cont1.xOffset) * cont1.scale; + y1 = (y1 - cont1.yOffset) * cont1.scale; + end + if ( m2 ~= 0 ) then + -- translate up from system 2 + local cont2 = WorldMapSize[0][s2]; + x2 = (x2 - cont2.xOffset) * cont2.scale; + y2 = (y2 - cont2.yOffset) * cont2.scale; + end + + xDelta = x2 - x1; + yDelta = y2 - y1; + end + + end + + end + if ( xDelta and yDelta ) then + dist = sqrt(xDelta*xDelta + yDelta*yDelta); + end + return dist, xDelta, yDelta; +end + +function Astrolabe:TranslateWorldMapPosition( M, F, xPos, yPos, nM, nF ) + --[[ + argcheck(M, 2, "number"); + argcheck(F, 3, "number", "nil"); + argcheck(xPos, 4, "number"); + argcheck(yPos, 5, "number"); + argcheck(nM, 6, "number"); + argcheck(nF, 7, "number", "nil"); + --]] + + if not ( M and nM ) then return end; + F = F or min(#WorldMapSize[M], 1); + nF = nF or min(#WorldMapSize[nM], 1); + if ( nM < 0 ) then + return; + end + + local mapData; + if ( M == nM and F == nF ) then + return xPos, yPos; + + else + local map = WorldMapSize[M]; + local nMap = WorldMapSize[nM]; + if ( map.system == nMap.system ) then + -- points within the same system (continent) + xPos, yPos = getSystemPosition(map, F, xPos, yPos); + mapData = WorldMapSize[nM]; + if ( nF ~= 0 ) then + mapData = rawget(mapData, nF) or MicroDungeonSize[mapData.system][nF]; + end + + else + -- different continents, same world + local S = map.system; + local nS = nMap.system; + if ( (M==0 or WorldMapSize[0][S]) and (nM==0 or WorldMapSize[0][nS]) ) then + mapData = WorldMapSize[M]; + xPos, yPos = getSystemPosition(mapData, F, xPos, yPos); + if ( M ~= 0 ) then + -- translate up to world map if we aren't there already + local cont = WorldMapSize[0][S]; + xPos = (xPos - cont.xOffset) * cont.scale; + yPos = (yPos - cont.yOffset) * cont.scale; + mapData = WorldMapSize[0]; + end + if ( nM ~= 0 ) then + -- translate down to the new continent + local nCont = WorldMapSize[0][nS]; + xPos = (xPos / nCont.scale) + nCont.xOffset; + yPos = (yPos / nCont.scale) + nCont.yOffset; + mapData = WorldMapSize[nM]; + if ( nF ~= 0 ) then + mapData = rawget(mapData, nF) or MicroDungeonSize[mapData.system][nF]; + end + end + + else + return; + end + + end + -- need to account for the offset in the new system so we can + -- correctly translate into 0-1 style coordinates + xPos = xPos - mapData.xOffset; + yPos = yPos - mapData.yOffset; + + end + + return (xPos / mapData.width), (yPos / mapData.height); +end + +--***************************************************************************** +-- This function will do its utmost to retrieve some sort of valid position +-- for the specified unit, including changing the current map zoom (if needed). +-- Map Zoom is returned to its previous setting before this function returns. +--***************************************************************************** +function Astrolabe:GetUnitPosition( unit, noMapChange ) + local x, y = GetPlayerMapPosition(unit); + if ( x <= 0 and y <= 0 ) then + if ( noMapChange ) then + -- no valid position on the current map, and we aren't allowed + -- to change map zoom, so return + return; + end + local lastMapID, lastFloor = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel(); + SetMapToCurrentZone(); + x, y = GetPlayerMapPosition(unit); + if ( x <= 0 and y <= 0 ) then + WorldMapZoomOutButton_OnClick(); + x, y = GetPlayerMapPosition(unit); + if ( x <= 0 and y <= 0 ) then + -- we are in an instance without a map or otherwise off map + return; + end + end + local M, F = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel(); + if ( M ~= lastMapID or F ~= lastFloor ) then + -- set map zoom back to what it was before + SetMapByID(lastMapID); + SetDungeonMapLevel(lastFloor); + end + return M, F, x, y; + end + return GetCurrentMapAreaID(), GetCurrentMapDungeonLevel(), x, y; +end + +--***************************************************************************** +-- This function will do its utmost to retrieve some sort of valid position +-- for the specified unit, including changing the current map zoom (if needed). +-- However, if a monitored WorldMapFrame (See AstrolabeMapMonitor.lua) is +-- visible, then will simply return nil if the current zoom does not provide +-- a valid position for the player unit. Map Zoom is NOT returned to its previous +-- setting before this function returns, in order to provide better performance. +--***************************************************************************** +function Astrolabe:GetCurrentPlayerPosition() + local x, y = GetPlayerMapPosition("player"); + if ( x <= 0 and y <= 0 ) then + if ( self.WorldMapVisible ) then + -- we know there is a visible world map, so don't cause + -- WORLD_MAP_UPDATE events by changing map zoom + return; + end + SetMapToCurrentZone(); + x, y = GetPlayerMapPosition("player"); + if ( x <= 0 and y <= 0 ) then + WorldMapZoomOutButton_OnClick(); + x, y = GetPlayerMapPosition("player"); + if ( x <= 0 and y <= 0 ) then + -- we are in an instance without a map or otherwise off map + return; + end + end + end + return GetCurrentMapAreaID(), GetCurrentMapDungeonLevel(), x, y; +end + +function Astrolabe:GetMapID(continent, zone) + zone = zone or 0; + local ret = self.ContinentList[continent]; + if ( ret ) then + return ret[zone]; + end + if ( continent == 0 and zone == 0 ) then + return 0; + end +end + +function Astrolabe:GetNumFloors( mapID ) + if ( type(mapID) == "number" ) then + local mapData = WorldMapSize[mapID] + return #mapData + end +end + +function Astrolabe:GetMapInfo( mapID, mapFloor ) + argcheck(mapID, 2, "number"); + assert(3, mapID >= 0, "GetMapInfo: Illegal map id to mapID: "..mapID); + argcheck(mapFloor, 3, "number", "nil"); + + mapFloor = mapFloor or min(#WorldMapSize[mapID], 1); + local mapData = WorldMapSize[mapID]; + local system, systemParent = mapData.system, WorldMapSize[0][mapData.system] and true or false + if ( mapFloor ~= 0 ) then + mapData = rawget(mapData, mapFloor) or MicroDungeonSize[mapData.system][mapFloor]; + end + if ( mapData ~= zeroData ) then + return system, systemParent, mapData.width, mapData.height, mapData.xOffset, mapData.yOffset; + end +end + + +-------------------------------------------------------------------------------------------------------------- +-- Working Table Cache System +-------------------------------------------------------------------------------------------------------------- + +local tableCache = {}; +tableCache["__mode"] = "v"; +setmetatable(tableCache, tableCache); + +local function GetWorkingTable( icon ) + if ( tableCache[icon] ) then + return tableCache[icon]; + else + local T = {}; + tableCache[icon] = T; + return T; + end +end + + +-------------------------------------------------------------------------------------------------------------- +-- Minimap Icon Placement +-------------------------------------------------------------------------------------------------------------- + +--***************************************************************************** +-- local variables specifically for use in this section +--***************************************************************************** +local minimapRotationEnabled = false; +local minimapShape = false; + +local minimapRotationOffset = GetPlayerFacing(); + + +local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, icon, dist, xDist, yDist ) + local mapDiameter; + if ( Astrolabe.minimapOutside ) then + mapDiameter = MinimapSize.outdoor[minimapZoom]; + else + mapDiameter = MinimapSize.indoor[minimapZoom]; + end + local mapRadius = mapDiameter / 2; + local xScale = mapDiameter / mapWidth; + local yScale = mapDiameter / mapHeight; + local iconDiameter = ((icon:GetWidth() / 2) + 3) * xScale; + local iconOnEdge = nil; + local isRound = true; + + if ( minimapRotationEnabled ) then + local sinTheta = sin(minimapRotationOffset) + local cosTheta = cos(minimapRotationOffset) + --[[ + Math Note + The math that is acutally going on in the next 3 lines is: + local dx, dy = xDist, -yDist + xDist = (dx * cosTheta) + (dy * sinTheta) + yDist = -((-dx * sinTheta) + (dy * cosTheta)) + + This is because the origin for map coordinates is the top left corner + of the map, not the bottom left, and so we have to reverse the vertical + distance when doing the our rotation, and then reverse the result vertical + distance because this rotation formula gives us a result with the origin based + in the bottom left corner (of the (+, +) quadrant). + The actual code is a simplification of the above. + ]] + local dx, dy = xDist, yDist + xDist = (dx * cosTheta) - (dy * sinTheta) + yDist = (dx * sinTheta) + (dy * cosTheta) + end + + 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 + + -- for non-circular portions of the Minimap edge + if not ( isRound ) then + dist = max(abs(xDist), abs(yDist)) + end + + if ( (dist + iconDiameter) > mapRadius ) then + -- position along the outside of the Minimap + iconOnEdge = true; + local factor = (mapRadius - iconDiameter) / dist; + xDist = xDist * factor; + yDist = yDist * factor; + end + + if ( Astrolabe.IconsOnEdge[icon] ~= iconOnEdge ) then + Astrolabe.IconsOnEdge[icon] = iconOnEdge; + Astrolabe.IconsOnEdgeChanged = true; + end + + icon:ClearAllPoints(); + icon:SetPoint("CENTER", minimap, "CENTER", xDist/xScale, -yDist/yScale); +end + +function Astrolabe:PlaceIconOnMinimap( icon, mapID, mapFloor, xPos, yPos ) + -- check argument types + argcheck(icon, 2, "table"); + assert(3, icon.SetPoint and icon.ClearAllPoints, "Usage Message"); + argcheck(mapID, 3, "number"); + argcheck(mapFloor, 4, "number", "nil"); + argcheck(xPos, 5, "number"); + argcheck(yPos, 6, "number"); + + -- if the positining system is currently active, just use the player position used by the last incremental (or full) update + -- otherwise, make sure we base our calculations off of the most recent player position (if one is available) + local lM, lF, lx, ly; + if ( self.processingFrame:IsShown() ) then + lM, lF, lx, ly = unpack(self.LastPlayerPosition); + else + lM, lF, lx, ly = self:GetCurrentPlayerPosition(); + if ( lM and lM >= 0 ) then + local lastPosition = self.LastPlayerPosition; + lastPosition[1] = lM; + lastPosition[2] = lF; + lastPosition[3] = lx; + lastPosition[4] = ly; + else + lM, lF, lx, ly = unpack(self.LastPlayerPosition); + end + end + + local dist, xDist, yDist = self:ComputeDistance(lM, lF, lx, ly, mapID, mapFloor, xPos, yPos); + if not ( dist ) then + --icon's position has no meaningful position relative to the player's current location + return -1; + end + + local iconData = GetWorkingTable(icon); + if ( self.MinimapIcons[icon] ) then + self.MinimapIcons[icon] = nil; + else + self.MinimapIconCount = self.MinimapIconCount + 1 + end + + AddedOrUpdatedIcons[icon] = iconData + iconData.mapID = mapID; + iconData.mapFloor = mapFloor; + iconData.xPos = xPos; + iconData.yPos = yPos; + iconData.dist = dist; + iconData.xDist = xDist; + iconData.yDist = yDist; + + minimapRotationEnabled = GetCVar("rotateMinimap") ~= "0" + if ( minimapRotationEnabled ) then + minimapRotationOffset = GetPlayerFacing(); + end + + -- check Minimap Shape + minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()]; + + -- place the icon on the Minimap and :Show() it + local map = Minimap + placeIconOnMinimap(map, map:GetZoom(), map:GetWidth(), map:GetHeight(), icon, dist, xDist, yDist); + icon:Show() + + -- We know this icon's position is valid, so we need to make sure the icon placement system is active. + self.processingFrame:Show() + + return 0; +end + +function Astrolabe:RemoveIconFromMinimap( icon ) + if not ( self.MinimapIcons[icon] ) then + return 1; + end + AddedOrUpdatedIcons[icon] = nil + self.MinimapIcons[icon] = nil; + self.IconsOnEdge[icon] = nil; + icon:Hide(); + + local MinimapIconCount = self.MinimapIconCount - 1 + if ( MinimapIconCount <= 0 ) then + -- no icons left to manage + self.processingFrame:Hide() + MinimapIconCount = 0 -- because I'm paranoid + end + self.MinimapIconCount = MinimapIconCount + + return 0; +end + +function Astrolabe:RemoveAllMinimapIcons() + self:DumpNewIconsCache() + local MinimapIcons = self.MinimapIcons; + local IconsOnEdge = self.IconsOnEdge; + for k, v in pairs(MinimapIcons) do + MinimapIcons[k] = nil; + IconsOnEdge[k] = nil; + k:Hide(); + end + self.MinimapIconCount = 0 + self.processingFrame:Hide() +end + +local lastZoom; -- to remember the last seen Minimap zoom level + +-- local variables to track the status of the two update coroutines +local fullUpdateInProgress = true +local resetIncrementalUpdate = false +local resetFullUpdate = false + +-- Incremental Update Code +do + -- local variables to track the incremental update coroutine + local incrementalUpdateCrashed = true + local incrementalUpdateThread + + local function UpdateMinimapIconPositions( self ) + yield() + + while ( true ) do + self:DumpNewIconsCache() -- put new/updated icons into the main datacache + + resetIncrementalUpdate = false -- by definition, the incremental update is reset if it is here + + local M, F, x, y = self:GetCurrentPlayerPosition(); + if ( M and M >= 0 ) then + local Minimap = Minimap; + local lastPosition = self.LastPlayerPosition; + local lM, lF, lx, ly = unpack(lastPosition); + + minimapRotationEnabled = GetCVar("rotateMinimap") ~= "0" + if ( minimapRotationEnabled ) then + minimapRotationOffset = GetPlayerFacing(); + end + + -- check current frame rate + local numPerCycle = min(50, GetFramerate() * (self.MinimapUpdateMultiplier or 1)) + + -- check Minimap Shape + minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()]; + + if ( lM == M and lF == F and lx == x and ly == y ) then + -- player has not moved since the last update + if ( lastZoom ~= Minimap:GetZoom() or self.ForceNextUpdate or minimapRotationEnabled ) then + local currentZoom = Minimap:GetZoom(); + lastZoom = currentZoom; + local mapWidth = Minimap:GetWidth(); + local mapHeight = Minimap:GetHeight(); + numPerCycle = numPerCycle * 2 + local count = 0 + for icon, data in pairs(self.MinimapIcons) do + placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, data.dist, data.xDist, data.yDist); + + count = count + 1 + if ( count > numPerCycle ) then + count = 0 + yield() + -- check if the incremental update cycle needs to be reset + -- because a full update has been run + if ( resetIncrementalUpdate ) then + break; + end + end + end + self.ForceNextUpdate = false; + end + else + local dist, xDelta, yDelta = self:ComputeDistance(lM, lF, lx, ly, M, F, x, y); + if ( dist ) then + local currentZoom = Minimap:GetZoom(); + lastZoom = currentZoom; + local mapWidth = Minimap:GetWidth(); + local mapHeight = Minimap:GetHeight(); + local count = 0 + for icon, data in pairs(self.MinimapIcons) do + local xDist = data.xDist - xDelta; + local yDist = data.yDist - yDelta; + local dist = sqrt(xDist*xDist + yDist*yDist); + placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, dist, xDist, yDist); + + data.dist = dist; + data.xDist = xDist; + data.yDist = yDist; + + count = count + 1 + if ( count >= numPerCycle ) then + count = 0 + yield() + -- check if the incremental update cycle needs to be reset + -- because a full update has been run + if ( resetIncrementalUpdate ) then + break; + end + end + end + if not ( resetIncrementalUpdate ) then + lastPosition[1] = M; + lastPosition[2] = F; + lastPosition[3] = x; + lastPosition[4] = y; + end + else + self:RemoveAllMinimapIcons() + lastPosition[1] = M; + lastPosition[2] = F; + lastPosition[3] = x; + lastPosition[4] = y; + end + end + else + if not ( self.WorldMapVisible ) then + self.processingFrame:Hide(); + end + end + + -- if we've been reset, then we want to start the new cycle immediately + if not ( resetIncrementalUpdate ) then + yield() + end + end + end + + function Astrolabe:UpdateMinimapIconPositions() + if ( fullUpdateInProgress ) then + -- if we're in the middle a a full update, we want to finish that first + self:CalculateMinimapIconPositions() + else + if ( incrementalUpdateCrashed ) then + incrementalUpdateThread = coroutine.wrap(UpdateMinimapIconPositions) + incrementalUpdateThread(self) --initialize the thread + end + incrementalUpdateCrashed = true + incrementalUpdateThread() + incrementalUpdateCrashed = false + end + end +end + +-- Full Update Code +do + -- local variables to track the full update coroutine + local fullUpdateCrashed = true + local fullUpdateThread + + local function CalculateMinimapIconPositions( self ) + yield() + + while ( true ) do + self:DumpNewIconsCache() -- put new/updated icons into the main datacache + + resetFullUpdate = false -- by definition, the full update is reset if it is here + fullUpdateInProgress = true -- set the flag the says a full update is in progress + + local M, F, x, y = self:GetCurrentPlayerPosition(); + if ( M and M >= 0 ) then + minimapRotationEnabled = GetCVar("rotateMinimap") ~= "0" + if ( minimapRotationEnabled ) then + minimapRotationOffset = GetPlayerFacing(); + end + + -- check current frame rate + local numPerCycle = GetFramerate() * (self.MinimapUpdateMultiplier or 1) * 2 + + -- check Minimap Shape + minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()]; + + local currentZoom = Minimap:GetZoom(); + lastZoom = currentZoom; + local Minimap = Minimap; + local mapWidth = Minimap:GetWidth(); + local mapHeight = Minimap:GetHeight(); + local count = 0 + for icon, data in pairs(self.MinimapIcons) do + local dist, xDist, yDist = self:ComputeDistance(M, F, x, y, data.mapID, data.mapFloor, data.xPos, data.yPos); + if ( dist ) then + placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, dist, xDist, yDist); + + data.dist = dist; + data.xDist = xDist; + data.yDist = yDist; + else + self:RemoveIconFromMinimap(icon) + end + + count = count + 1 + if ( count >= numPerCycle ) then + count = 0 + yield() + -- check if we need to restart due to the full update being reset + if ( resetFullUpdate ) then + break; + end + end + end + + if not ( resetFullUpdate ) then + local lastPosition = self.LastPlayerPosition; + lastPosition[1] = M; + lastPosition[2] = F; + lastPosition[3] = x; + lastPosition[4] = y; + + resetIncrementalUpdate = true + end + else + if not ( self.WorldMapVisible ) then + self.processingFrame:Hide(); + end + end + + -- if we've been reset, then we want to start the new cycle immediately + if not ( resetFullUpdate ) then + fullUpdateInProgress = false + yield() + end + end + end + + function Astrolabe:CalculateMinimapIconPositions( reset ) + if ( fullUpdateCrashed ) then + fullUpdateThread = coroutine.wrap(CalculateMinimapIconPositions) + fullUpdateThread(self) --initialize the thread + elseif ( reset ) then + resetFullUpdate = true + end + fullUpdateCrashed = true + fullUpdateThread() + fullUpdateCrashed = false + + -- return result flag + if ( fullUpdateInProgress ) then + return 1 -- full update started, but did not complete on this cycle + + else + if ( resetIncrementalUpdate ) then + return 0 -- update completed + else + return -1 -- full update did no occur for some reason + end + + end + end +end + +function Astrolabe:GetDistanceToIcon( icon ) + local data = self.MinimapIcons[icon]; + if ( data ) then + return data.dist, data.xDist, data.yDist; + end +end + +function Astrolabe:IsIconOnEdge( icon ) + return self.IconsOnEdge[icon]; +end + +function Astrolabe:GetDirectionToIcon( icon ) + local data = self.MinimapIcons[icon]; + if ( data ) then + local dir = atan2(data.xDist, -(data.yDist)) + if ( dir > 0 ) then + return twoPi - dir; + else + return -dir; + end + end +end + +function Astrolabe:Register_OnEdgeChanged_Callback( func, ident ) + -- check argument types + argcheck(func, 2, "function"); + + self.IconsOnEdge_GroupChangeCallbacks[func] = ident; +end + +--***************************************************************************** +-- INTERNAL USE ONLY PLEASE!!! +-- Calling this function at the wrong time can cause errors +--***************************************************************************** +function Astrolabe:DumpNewIconsCache() + local MinimapIcons = self.MinimapIcons + for icon, data in pairs(AddedOrUpdatedIcons) do + MinimapIcons[icon] = data + AddedOrUpdatedIcons[icon] = nil + end + -- we now need to restart any updates that were in progress + resetIncrementalUpdate = true + resetFullUpdate = true +end + + +-------------------------------------------------------------------------------------------------------------- +-- World Map Icon Placement +-------------------------------------------------------------------------------------------------------------- + +function Astrolabe:PlaceIconOnWorldMap( worldMapFrame, icon, mapID, mapFloor, xPos, yPos ) + -- check argument types + argcheck(worldMapFrame, 2, "table"); + assert(3, worldMapFrame.GetWidth and worldMapFrame.GetHeight, "Usage Message"); + argcheck(icon, 3, "table"); + assert(3, icon.SetPoint and icon.ClearAllPoints, "Usage Message"); + argcheck(mapID, 4, "number"); + argcheck(mapFloor, 5, "number", "nil"); + argcheck(xPos, 6, "number"); + argcheck(yPos, 7, "number"); + + local M, F = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel(); + local nX, nY = self:TranslateWorldMapPosition(mapID, mapFloor, xPos, yPos, M, F); + + -- anchor and :Show() the icon if it is within the boundry of the current map, :Hide() it otherwise + if ( nX and nY and (0 < nX and nX <= 1) and (0 < nY and nY <= 1) ) then + icon:ClearAllPoints(); + icon:SetPoint("CENTER", worldMapFrame, "TOPLEFT", nX * worldMapFrame:GetWidth(), -nY * worldMapFrame:GetHeight()); + icon:Show(); + else + icon:Hide(); + end + return nX, nY; +end + + +-------------------------------------------------------------------------------------------------------------- +-- Handler Scripts +-------------------------------------------------------------------------------------------------------------- + +function Astrolabe:OnEvent( frame, event ) + if ( event == "MINIMAP_UPDATE_ZOOM" ) then + -- update minimap zoom scale + local Minimap = Minimap; + local curZoom = Minimap:GetZoom(); + if ( GetCVar("minimapZoom") == GetCVar("minimapInsideZoom") ) then + if ( curZoom < 2 ) then + Minimap:SetZoom(curZoom + 1); + else + Minimap:SetZoom(curZoom - 1); + end + end + if ( GetCVar("minimapZoom")+0 == Minimap:GetZoom() ) then + self.minimapOutside = true; + else + self.minimapOutside = false; + end + Minimap:SetZoom(curZoom); + + -- re-calculate all Minimap Icon positions + if ( frame:IsVisible() ) then + self:CalculateMinimapIconPositions(true); + end + + elseif ( event == "PLAYER_LEAVING_WORLD" ) then + frame:Hide(); -- yes, I know this is redunant + self:RemoveAllMinimapIcons(); --dump all minimap icons + -- TODO: when I uncouple the point buffer from Minimap drawing, + -- I should consider updating LastPlayerPosition here + + elseif ( event == "PLAYER_ENTERING_WORLD" ) then + frame:Show(); + if not ( frame:IsVisible() ) then + -- do the minimap recalculation anyways if the OnShow script didn't execute + -- this is done to ensure the accuracy of information about icons that were + -- inserted while the Player was in the process of zoning + self:CalculateMinimapIconPositions(true); + end + + elseif ( event == "ZONE_CHANGED_NEW_AREA" ) then + frame:Hide(); + frame:Show(); + + end +end + +function Astrolabe:OnUpdate( frame, elapsed ) + -- on-edge group changed call-backs + if ( self.IconsOnEdgeChanged ) then + self.IconsOnEdgeChanged = false; + for func in pairs(self.IconsOnEdge_GroupChangeCallbacks) do + pcall(func); + end + end + + self:UpdateMinimapIconPositions(); +end + +function Astrolabe:OnShow( frame ) + -- set the world map to a zoom with a valid player position + if not ( self.WorldMapVisible ) then + SetMapToCurrentZone(); + end + local M, F = Astrolabe:GetCurrentPlayerPosition(); + if ( M and M >= 0 ) then + SetMapByID(M); + SetDungeonMapLevel(F); + else + frame:Hide(); + return + end + + -- re-calculate minimap icon positions (if needed) + if ( next(self.MinimapIcons) ) then + self:CalculateMinimapIconPositions(true); + else + -- needed so that the cycle doesn't overwrite an updated LastPlayerPosition + resetIncrementalUpdate = true; + end + + if ( self.MinimapIconCount <= 0 ) then + -- no icons left to manage + frame:Hide(); + end +end + +function Astrolabe:OnHide( frame ) + -- dump the new icons cache here + -- a full update will performed the next time processing is re-actived + self:DumpNewIconsCache() +end + +-- called by AstrolabMapMonitor when all world maps are hidden +function Astrolabe:AllWorldMapsHidden() + if ( IsLoggedIn() ) then + self.processingFrame:Hide(); + self.processingFrame:Show(); + end +end + + +-------------------------------------------------------------------------------------------------------------- +-- Library Registration +-------------------------------------------------------------------------------------------------------------- + +local HARVESTED_DATA_VERSION = 3; -- increment this when the format of the harvested data has to change +local function harvestMapData( HarvestedMapData ) + local mapData = {} + local mapName = GetMapInfo(); + local mapID = GetCurrentMapAreaID(); + local numFloors = GetNumDungeonMapLevels(); + mapData.mapName = mapName; + mapData.cont = (GetCurrentMapContinent()) or -100; + mapData.zone = (GetCurrentMapZone()) or -100; + mapData.numFloors = numFloors; + local _, TLx, TLy, BRx, BRy = GetCurrentMapZone(); + if ( TLx and TLy and BRx and BRy and (TLx~=0 or TLy~=0 or BRx~=0 or BRy~=0) ) then + mapData[0] = {}; + mapData[0].TLx = TLx; + mapData[0].TLy = TLy; + mapData[0].BRx = BRx; + mapData[0].BRy = BRy; + end + if ( not mapData[0] and numFloors == 0 and (GetCurrentMapDungeonLevel()) == 1 ) then + numFloors = 1; + mapData.hiddenFloor = true; + end + if ( numFloors > 0 ) then + for f = 1, numFloors do + SetDungeonMapLevel(f); + local _, TLx, TLy, BRx, BRy = GetCurrentMapDungeonLevel(); + if ( TLx and TLy and BRx and BRy ) then + mapData[f] = {}; + mapData[f].TLx = TLx; + mapData[f].TLy = TLy; + mapData[f].BRx = BRx; + mapData[f].BRy = BRy; + end + end + end + + HarvestedMapData[mapID] = mapData; +end + +local function activate( newInstance, oldInstance ) + if ( oldInstance ) then -- this is an upgrade activate + -- print upgrade debug info + local _, oldVersion = oldInstance:GetVersion(); + printError("Upgrading "..LIBRARY_VERSION_MAJOR.." from version "..oldVersion.." to version "..LIBRARY_VERSION_MINOR); + + if ( oldInstance.DumpNewIconsCache ) then + oldInstance:DumpNewIconsCache() + end + for k, v in pairs(oldInstance) do + if ( type(v) ~= "function" and (not configConstants[k]) ) then + newInstance[k] = v; + end + end + -- sync up the current MinimapIconCount value + local iconCount = 0 + for _ in pairs(newInstance.MinimapIcons) do + iconCount = iconCount + 1 + end + newInstance.MinimapIconCount = iconCount + + Astrolabe = oldInstance; + else + local frame = CreateFrame("Frame"); + newInstance.processingFrame = frame; + end + configConstants = nil -- we don't need this anymore + + if not ( oldInstance and oldInstance.HarvestedMapData.VERSION == HARVESTED_DATA_VERSION ) then + newInstance.HarvestedMapData = { VERSION = HARVESTED_DATA_VERSION }; + local HarvestedMapData = newInstance.HarvestedMapData; + + newInstance.ContinentList = { GetMapContinents() }; + for C in pairs(newInstance.ContinentList) do + local zones = { GetMapZones(C) }; + newInstance.ContinentList[C] = zones; + SetMapZoom(C, 0); + zones[0] = GetCurrentMapAreaID(); + harvestMapData(HarvestedMapData); + for Z in ipairs(zones) do + SetMapZoom(C, Z); + zones[Z] = GetCurrentMapAreaID(); + harvestMapData(HarvestedMapData); + end + end + + for _, id in ipairs(GetAreaMaps()) do + if not ( HarvestedMapData[id] ) then + if ( SetMapByID(id) ) then + harvestMapData(HarvestedMapData); + end + end + end + end + + local frame = newInstance.processingFrame; + frame:Hide(); + frame:SetParent("Minimap"); + frame:UnregisterAllEvents(); + frame:RegisterEvent("MINIMAP_UPDATE_ZOOM"); + frame:RegisterEvent("PLAYER_LEAVING_WORLD"); + frame:RegisterEvent("PLAYER_ENTERING_WORLD"); + frame:RegisterEvent("ZONE_CHANGED_NEW_AREA"); + frame:SetScript("OnEvent", + function( frame, event, ... ) + Astrolabe:OnEvent(frame, event, ...); + end + ); + frame:SetScript("OnUpdate", + function( frame, elapsed ) + Astrolabe:OnUpdate(frame, elapsed); + end + ); + frame:SetScript("OnShow", + function( frame ) + Astrolabe:OnShow(frame); + end + ); + frame:SetScript("OnHide", + function( frame ) + Astrolabe:OnHide(frame); + end + ); + + setmetatable(Astrolabe.MinimapIcons, MinimapIconsMetatable) +end + +DongleStub:Register(Astrolabe, activate) + + +-------------------------------------------------------------------------------------------------------------- +-- Data +-------------------------------------------------------------------------------------------------------------- + +-- diameter of the Minimap in game yards at +-- the various possible zoom levels +MinimapSize = { + 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 + }, +} + +ValidMinimapShapes = { + -- { 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 }, +} + +-- distances across and offsets of the world maps +-- in game yards +WorldMapSize = { + [0] = { + height = 22266.74312, + system = -1, + width = 33400.121, + xOffset = 0, + yOffset = 0, + [1] = { + xOffset = -10311.71318, + yOffset = -19819.33898, + scale = 0.56089997291565, + }, + [0] = { + xOffset = -48226.86993, + yOffset = -16433.90283, + scale = 0.56300002336502, + }, + [571] = { + xOffset = -29750.89905, + yOffset = -11454.50802, + scale = 0.5949000120163, + }, + [870] = { + xOffset = -27693.71178, + yOffset = -29720.0585, + scale = 0.65140002965927, + }, + }, +} + +MicroDungeonSize = {} + +local function zeroDataFunc(tbl, key) + if ( type(key) == "number" ) then + return zeroData; + else + return rawget(zeroData, key); + end +end + +zeroData = { xOffset = 0, height = 1, yOffset = 0, width = 1, __index = zeroDataFunc }; +setmetatable(zeroData, zeroData); + +-- get data on useful transforms +local TRANSFORMS = {} +for _, ID in ipairs(GetWorldMapTransforms()) do + local terrainMapID, newTerrainMapID, _, _, transformMinY, transformMaxY, transformMinX, transformMaxX, offsetY, offsetX = GetWorldMapTransformInfo(ID) + if ( offsetX ~= 0 or offsetY ~= 0 ) then + TRANSFORMS[ID] = { + terrainMapID = terrainMapID, + newTerrainMapID = newTerrainMapID, + BRy = -transformMinY, + TLy = -transformMaxY, + BRx = -transformMinX, + TLx = -transformMaxX, + offsetY = offsetY, + offsetX = offsetX, + } + end +end + +--remove this temporarily +local harvestedDataVersion = Astrolabe.HarvestedMapData.VERSION +Astrolabe.HarvestedMapData.VERSION = nil + +for mapID, harvestedData in pairs(Astrolabe.HarvestedMapData) do + local terrainMapID = GetAreaMapInfo(mapID) + local mapData = WorldMapSize[mapID]; + if not ( mapData ) then mapData = {}; end + if ( harvestedData.numFloors > 0 or harvestedData.hiddenFloor ) then + for f, harvData in pairs(harvestedData) do + if ( type(f) == "number" and f > 0 ) then + if not ( mapData[f] ) then + mapData[f] = {}; + end + local floorData = mapData[f] + local TLx, TLy, BRx, BRy = -harvData.BRx, -harvData.BRy, -harvData.TLx, -harvData.TLy + if not ( TLx < BRx ) then + printError("Bad x-axis Orientation (Floor): ", mapID, f, TLx, BRx); + end + if not ( TLy < BRy) then + printError("Bad y-axis Orientation (Floor): ", mapID, f, TLy, BRy); + end + if not ( floorData.width ) then + floorData.width = BRx - TLx + end + if not ( floorData.height ) then + floorData.height = BRy - TLy + end + if not ( floorData.xOffset ) then + floorData.xOffset = TLx + end + if not ( floorData.yOffset ) then + floorData.yOffset = TLy + end + end + end + for f = 1, harvestedData.numFloors do + if not ( mapData[f] ) then + if ( f == 1 and harvestedData[0] and harvestedData[0].TLx and harvestedData[0].TLy and harvestedData[0].BRx and harvestedData[0].BRy ) then + -- handle dungeon maps which use zone level data for the first floor + mapData[f] = {}; + local floorData = mapData[f] + local harvData = harvestedData[0] + local TLx, TLy, BRx, BRy = -harvData.TLx, -harvData.TLy, -harvData.BRx, -harvData.BRy + if not ( TLx < BRx ) then + printError("Bad x-axis Orientation (Floor from Zone): ", mapID, f, TLx, BRx); + end + if not ( TLy < BRy) then + printError("Bad y-axis Orientation (Floor from Zone): ", mapID, f, TLy, BRy); + end + floorData.width = BRx - TLx + floorData.height = BRy - TLy + floorData.xOffset = TLx + floorData.yOffset = TLy + else + printError(("Astrolabe is missing data for %s [%d], floor %d."):format(harvestedData.mapName, mapID, f)); + end + end + end + if ( harvestedData.hiddenFloor ) then + mapData.width = mapData[1].width + mapData.height = mapData[1].height + mapData.xOffset = mapData[1].xOffset + mapData.yOffset = mapData[1].yOffset + end + + else + local harvData = harvestedData[0] + if ( harvData ~= nil ) then + local TLx, TLy, BRx, BRy = -harvData.TLx, -harvData.TLy, -harvData.BRx, -harvData.BRy + -- apply any necessary transforms + for transformID, transformData in pairs(TRANSFORMS) do + if ( transformData.terrainMapID == terrainMapID ) then + if ( (transformData.TLx < TLx and BRx < transformData.BRx) and (transformData.TLy < TLy and BRy < transformData.BRy) ) then + TLx = TLx - transformData.offsetX; + BRx = BRx - transformData.offsetX; + BRy = BRy - transformData.offsetY; + TLy = TLy - transformData.offsetY; + terrainMapID = transformData.newTerrainMapID; + break; + end + end + end + if not ( TLx==0 and TLy==0 and BRx==0 and BRy==0 ) then + if not ( TLx < BRx ) then + printError("Bad x-axis Orientation (Zone): ", mapID, TLx, BRx); + end + if not ( TLy < BRy) then + printError("Bad y-axis Orientation (Zone): ", mapID, TLy, BRy); + end + end + if not ( mapData.width ) then + mapData.width = BRx - TLx + end + if not ( mapData.height ) then + mapData.height = BRy - TLy + end + if not ( mapData.xOffset ) then + mapData.xOffset = TLx + end + if not ( mapData.yOffset ) then + mapData.yOffset = TLy + end + else + if ( mapID == 751 ) then -- okay, this is Maelstrom continent + else + printError("Astrolabe harvested a map with no data at all: ", mapID) + end + end + + end + + -- if we don't have any data, we're gonna use zeroData, but we also need to + -- setup the system and systemParent IDs so things don't get confused + if not ( next(mapData, nil) ) then + mapData = { xOffset = 0, height = 1, yOffset = 0, width = 1 }; + -- if this is an outside continent level or world map then throw up an extra warning + if ( harvestedData.cont > 0 and harvestedData.zone == 0 ) then + printError(("Astrolabe is missing data for world map %s [%d] (%d, %d)."):format(harvestedData.mapName, mapID, harvestedData.cont, harvestedData.zone)); + end + end + + -- store the data in the WorldMapSize DB + WorldMapSize[mapID] = mapData; + + + if ( mapData and mapData ~= zeroData ) then + -- setup system IDs + if not ( mapData.system ) then + mapData.system = terrainMapID; + end + + -- determine terrainMapID for micro-dungeons + if ( harvestedData.cont > 0 and harvestedData.zone > 0 ) then + MicroDungeonSize[terrainMapID] = {} + end + + setmetatable(mapData, zeroData); + end +end + +-- put the version back +Astrolabe.HarvestedMapData.VERSION = harvestedDataVersion + +-- micro dungeons +for _, ID in ipairs(GetDungeonMaps()) do + local floorIndex, minX, maxX, minY, maxY, terrainMapID, parentWorldMapID = GetDungeonMapInfo(ID); + local TLx, TLy, BRx, BRy = -maxX, -maxY, -minX, -minY + -- apply any necessary transforms + local transformApplied = false + for transformID, transformData in pairs(TRANSFORMS) do + if ( transformData.terrainMapID == terrainMapID ) then + if ( (transformData.TLx < TLx and BRx < transformData.BRx) and (transformData.TLy < TLy and BRy < transformData.BRy) ) then + TLx = TLx - transformData.offsetX; + BRx = BRx - transformData.offsetX; + BRy = BRy - transformData.offsetY; + TLy = TLy - transformData.offsetY; + terrainMapID = transformData.newTerrainMapID; + transformApplied = true; + break; + end + end + end + if ( MicroDungeonSize[terrainMapID] ) then + -- only consider systems that can have micro dungeons + if ( MicroDungeonSize[terrainMapID][floorIndex] and not transformApplied ) then + printError("Astrolabe detected a duplicate microdungeon floor!", terrainMapID, ID); + end + MicroDungeonSize[terrainMapID][floorIndex] = { + width = BRx - TLx, + height = BRy - TLy, + xOffset = TLx, + yOffset = TLy, + }; + end +end + +-- done with Transforms data +TRANSFORMS = nil + +for _, data in pairs(MicroDungeonSize) do + setmetatable(data, zeroData); +end +setmetatable(MicroDungeonSize, zeroData); + +-- make sure we don't have any EXTRA data hanging around +for mapID, mapData in pairs(WorldMapSize) do + if ( mapID ~= 0 and getmetatable(mapData) ~= zeroData ) then + printError("Astrolabe has hard coded data for an invalid map ID", mapID); + end +end + +setmetatable(WorldMapSize, zeroData); -- setup the metatable so that invalid map IDs don't cause Lua errors + +-- register this library with AstrolabeMapMonitor, this will cause a full update if PLAYER_LOGIN has already fired +local AstrolabeMapMonitor = DongleStub("AstrolabeMapMonitor"); +AstrolabeMapMonitor:RegisterAstrolabeLibrary(Astrolabe, LIBRARY_VERSION_MAJOR); + diff --git a/QuestHelper/libs/Astrolabe/Astrolabe.toc b/QuestHelper/libs/Astrolabe/Astrolabe.toc new file mode 100644 index 0000000..41d7335 --- /dev/null +++ b/QuestHelper/libs/Astrolabe/Astrolabe.toc @@ -0,0 +1,6 @@ +## Interface: 50001 +## Title: Astrolabe |cff224477(lib) +## Notes: A library for drawing icons on the Minimap and World Maps. +## Version: 1.0 +## Author: Esamynn (James Carrothers) +Load.xml \ No newline at end of file diff --git a/QuestHelper/libs/Astrolabe/AstrolabeMapMonitor.lua b/QuestHelper/libs/Astrolabe/AstrolabeMapMonitor.lua new file mode 100644 index 0000000..81fa915 --- /dev/null +++ b/QuestHelper/libs/Astrolabe/AstrolabeMapMonitor.lua @@ -0,0 +1,235 @@ +--[[ +Name: AstrolabeMapMonitor +Revision: $Rev: 44 $ +$Date: 2007-03-30 11:56:21 -0700 (Fri, 30 Mar 2007) $ +Author(s): Esamynn (esamynn@wowinterface.com) +Inspired By: Gatherer by Norganna + MapLibrary by Kristofer Karlsson (krka@kth.se) +Website: http://esamynn.wowinterface.com/ +Documentation: http://www.esamynn.org/wiki/Astrolabe/World_Map_Monitor +SVN: http://esamynn.org/svn/astrolabe/ +Description: + This is a small stub library to support the main Astrolabe + library. It's purpose is to monitor the visibility of + various World Map frames, so that Astrolabe can modify its + behaviour accordingly. + +Copyright (C) 2007 James Carrothers + +License: + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Note: + This library's source code is specifically designed to work with + World of Warcraft's interpreted AddOn system. You have an implicit + licence to use this library with these facilities since that is its + designated purpose as per: + http://www.fsf.org/licensing/licenses/gpl-faq.html#InterpreterIncompat +]] + +-- WARNING!!! +-- DO NOT MAKE CHANGES TO THIS LIBRARY WITHOUT FIRST CHANGING THE LIBRARY_VERSION_MAJOR +-- STRING (to something unique) OR ELSE YOU MAY BREAK OTHER ADDONS THAT USE THIS LIBRARY!!! +local LIBRARY_VERSION_MAJOR = "AstrolabeMapMonitor" +local LIBRARY_VERSION_MINOR = tonumber(string.match("$Revision: 44 $", "(%d+)") or 1) + +if not DongleStub then error(LIBRARY_VERSION_MAJOR .. " requires DongleStub.") end +if not DongleStub:IsNewerVersion(LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR) then return end + +local AstrolabeMapMonitor = {}; + +function AstrolabeMapMonitor:GetVersion() + return LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR; +end + + +-------------------------------------------------------------------------------------------------------------- +-- Global World Map Frame Registration Table +-------------------------------------------------------------------------------------------------------------- + +if ( type(WorldMapDisplayFrames) ~= "table" ) then + WorldMapDisplayFrames = { WorldMapFrame }; +else + local worldMapFound = false; + for k, v in pairs(WorldMapDisplayFrames) do + if ( v == WorldMapFrame ) then + worldMapFound = true; + break; + end + end + if not ( worldMapFound ) then + table.insert(WorldMapDisplayFrames, WorldMapFrame); + end +end + + +-------------------------------------------------------------------------------------------------------------- +-- Working Tables and Config Constants +-------------------------------------------------------------------------------------------------------------- + +AstrolabeMapMonitor.TrackedWorldMaps = {}; +AstrolabeMapMonitor.AstrolabeLibrarys = {}; + +AstrolabeMapMonitor.NumVisibleWorldMaps = 0; + + +-------------------------------------------------------------------------------------------------------------- +-- Monitor Frame Script Handlers +-------------------------------------------------------------------------------------------------------------- + +local function onShow( frame ) + AstrolabeMapMonitor.NumVisibleWorldMaps = AstrolabeMapMonitor.NumVisibleWorldMaps + 1; + AstrolabeMapMonitor:Update() +end + +local function onHide( frame ) + AstrolabeMapMonitor.NumVisibleWorldMaps = AstrolabeMapMonitor.NumVisibleWorldMaps - 1; + AstrolabeMapMonitor:Update() +end + +local function setScripts( monitorFrame ) + monitorFrame:SetScript("OnShow", onShow); + monitorFrame:SetScript("OnHide", onHide); +end + + +-------------------------------------------------------------------------------------------------------------- +-- Internal Utility Functions +-------------------------------------------------------------------------------------------------------------- + +local function assert(level,condition,message) + if not condition then + error(message,level) + end +end + +local function argcheck(value, num, ...) + assert(1, type(num) == "number", + "Bad argument #2 to 'argcheck' (number expected, got " .. type(level) .. ")") + + for i=1,select("#", ...) do + if type(value) == select(i, ...) then return end + end + + local types = strjoin(", ", ...) + local name = string.match(debugstack(2,2,0), ": in function [`<](.-)['>]") + error(string.format("Bad argument #%d to 'AstrolabeMapMonitor.%s' (%s expected, got %s)", num, name, types, type(value)), 3) +end + + +-------------------------------------------------------------------------------------------------------------- +-- Public API +-------------------------------------------------------------------------------------------------------------- + +function AstrolabeMapMonitor:MonitorWorldMap( worldMapFrame ) + -- check argument types + argcheck(worldMapFrame, 2, "table"); + assert((worldMapFrame.SetParent), "Usage Message"); + + local TrackedWorldMaps = self.TrackedWorldMaps; + if ( TrackedWorldMaps[worldMapFrame] ) then + return 1; + end + local monitorFrame = CreateFrame("Frame", nil, worldMapFrame); + TrackedWorldMaps[worldMapFrame] = monitorFrame; + setScripts(monitorFrame); + self:ForceUpdate(); + return 0; +end + +function AstrolabeMapMonitor:LookForMapsToRegister() + for k, frame in pairs(WorldMapDisplayFrames) do + if ( type(frame) == "table" and frame.SetParent ) then + self:MonitorWorldMap(frame); + end + end +end + +function AstrolabeMapMonitor:Update() + local visibleMap = false; + if ( (self.NumVisibleWorldMaps) > 0 ) then + visibleMap = true; + end + for lib, versionString in pairs(self.AstrolabeLibrarys) do + lib.WorldMapVisible = visibleMap; + if ( (not visibleMap) and lib.AllWorldMapsHidden ) then + lib:AllWorldMapsHidden(); + end + end + return visibleMap; +end + +function AstrolabeMapMonitor:ForceUpdate() + self.NumVisibleWorldMaps = 0; + for worldMap, monitorFrame in pairs(self.TrackedWorldMaps) do + if ( worldMap:IsVisible() ) then + self.NumVisibleWorldMaps = self.NumVisibleWorldMaps + 1; + end + end + return self:Update(); +end + +function AstrolabeMapMonitor:RegisterAstrolabeLibrary( lib, majorVersionString ) + -- check argument types + argcheck(lib, 2, "table"); + argcheck(majorVersionString, 3, "string"); + + self.AstrolabeLibrarys[lib] = majorVersionString; + self:Update(); +end + + +-------------------------------------------------------------------------------------------------------------- +-- Handler Scripts +-------------------------------------------------------------------------------------------------------------- + +function AstrolabeMapMonitor:OnEvent( frame, event ) + if ( event == "ADDON_LOADED" ) then + self:LookForMapsToRegister(); + self:ForceUpdate(); + end +end + + +-------------------------------------------------------------------------------------------------------------- +-- Library Registration +-------------------------------------------------------------------------------------------------------------- + +local function activate( newInstance, oldInstance ) + if ( oldInstance ) then -- this is an upgrade activate + for k, v in pairs(oldInstance) do + if ( type(v) ~= "function" ) then + newInstance[k] = v; + end + end + AstrolabeMapMonitor = oldInstance; + else + AstrolabeMapMonitor.eventFrame = CreateFrame("Frame"); + end + for worldMap, monitorFrame in pairs(AstrolabeMapMonitor.TrackedWorldMaps) do + setScripts(monitorFrame); + end + local frame = AstrolabeMapMonitor.eventFrame; + frame:Hide(); + frame:UnregisterAllEvents(); + frame:RegisterEvent("ADDON_LOADED"); + frame:SetScript("OnEvent", + function( frame, event, ... ) + AstrolabeMapMonitor:OnEvent(frame, event, ...); + end + ); +end + +DongleStub:Register(AstrolabeMapMonitor, activate) diff --git a/QuestHelper/libs/Astrolabe/DongleStub.lua b/QuestHelper/libs/Astrolabe/DongleStub.lua new file mode 100644 index 0000000..afdbc5e --- /dev/null +++ b/QuestHelper/libs/Astrolabe/DongleStub.lua @@ -0,0 +1,126 @@ +--[[------------------------------------------------------------------------- + James Whitehead II grants anyone the right to use this work for any purpose, + without any conditions, unless such conditions are required by law. +---------------------------------------------------------------------------]] + +local major = "DongleStub" +local minor = tonumber(string.match("$Revision: 313 $", "(%d+)") or 1) + +local g = getfenv(0) + +if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then + local lib = setmetatable({}, { + __call = function(t,k) + if type(t.versions) == "table" and t.versions[k] then + return t.versions[k].instance + else + error("Cannot find a library with name '"..tostring(k).."'", 2) + end + end + }) + + function lib:IsNewerVersion(major, minor) + local versionData = self.versions and self.versions[major] + + -- If DongleStub versions have differing major version names + -- such as DongleStub-Beta0 and DongleStub-1.0-RC2 then a second + -- instance will be loaded, with older logic. This code attempts + -- to compensate for that by matching the major version against + -- "^DongleStub", and handling the version check correctly. + + if major:match("^DongleStub") then + local oldmajor,oldminor = self:GetVersion() + if self.versions and self.versions[oldmajor] then + return minor > oldminor + else + return true + end + end + + if not versionData then return true end + local oldmajor,oldminor = versionData.instance:GetVersion() + return minor > oldminor + end + + local function NilCopyTable(src, dest) + for k,v in pairs(dest) do dest[k] = nil end + for k,v in pairs(src) do dest[k] = v end + end + + function lib:Register(newInstance, activate, deactivate) + assert(type(newInstance.GetVersion) == "function", + "Attempt to register a library with DongleStub that does not have a 'GetVersion' method.") + + local major,minor = newInstance:GetVersion() + assert(type(major) == "string", + "Attempt to register a library with DongleStub that does not have a proper major version.") + assert(type(minor) == "number", + "Attempt to register a library with DongleStub that does not have a proper minor version.") + + -- Generate a log of all library registrations + if not self.log then self.log = {} end + table.insert(self.log, string.format("Register: %s, %s", major, minor)) + + if not self:IsNewerVersion(major, minor) then return false end + if not self.versions then self.versions = {} end + + local versionData = self.versions[major] + if not versionData then + -- New major version + versionData = { + ["instance"] = newInstance, + ["deactivate"] = deactivate, + } + + self.versions[major] = versionData + if type(activate) == "function" then + table.insert(self.log, string.format("Activate: %s, %s", major, minor)) + activate(newInstance) + end + return newInstance + end + + local oldDeactivate = versionData.deactivate + local oldInstance = versionData.instance + + versionData.deactivate = deactivate + + local skipCopy + if type(activate) == "function" then + table.insert(self.log, string.format("Activate: %s, %s", major, minor)) + skipCopy = activate(newInstance, oldInstance) + end + + -- Deactivate the old libary if necessary + if type(oldDeactivate) == "function" then + local major, minor = oldInstance:GetVersion() + table.insert(self.log, string.format("Deactivate: %s, %s", major, minor)) + oldDeactivate(oldInstance, newInstance) + end + + -- Re-use the old table, and discard the new one + if not skipCopy then + NilCopyTable(newInstance, oldInstance) + end + return oldInstance + end + + function lib:GetVersion() return major,minor end + + local function Activate(new, old) + -- This code ensures that we'll move the versions table even + -- if the major version names are different, in the case of + -- DongleStub + if not old then old = g.DongleStub end + + if old then + new.versions = old.versions + new.log = old.log + end + g.DongleStub = new + end + + -- Actually trigger libary activation here + local stub = g.DongleStub or lib + lib = stub:Register(lib, Activate) +end diff --git a/QuestHelper/libs/Astrolabe/Load.xml b/QuestHelper/libs/Astrolabe/Load.xml new file mode 100644 index 0000000..17b3708 --- /dev/null +++ b/QuestHelper/libs/Astrolabe/Load.xml @@ -0,0 +1,6 @@ + +