diff --git a/libs/AceConfig-3.0/AceConfig-3.0.lua b/libs/AceConfig-3.0/AceConfig-3.0.lua index 3bedf8c..a99ddf7 100755 --- a/libs/AceConfig-3.0/AceConfig-3.0.lua +++ b/libs/AceConfig-3.0/AceConfig-3.0.lua @@ -3,7 +3,7 @@ -- as well as associate it with a slash command. -- @class file -- @name AceConfig-3.0 --- @release $Id: AceConfig-3.0.lua 969 2010-10-07 02:11:48Z shefki $ +-- @release $Id: AceConfig-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $ --[[ AceConfig-3.0 @@ -12,13 +12,14 @@ Very light wrapper library that combines all the AceConfig subcomponents into on ]] -local MAJOR, MINOR = "AceConfig-3.0", 2 +local cfgreg = LibStub("AceConfigRegistry-3.0") +local cfgcmd = LibStub("AceConfigCmd-3.0") + +local MAJOR, MINOR = "AceConfig-3.0", 3 local AceConfig = LibStub:NewLibrary(MAJOR, MINOR) if not AceConfig then return end -local cfgreg = LibStub("AceConfigRegistry-3.0") -local cfgcmd = LibStub("AceConfigCmd-3.0") --TODO: local cfgdlg = LibStub("AceConfigDialog-3.0", true) --TODO: local cfgdrp = LibStub("AceConfigDropdown-3.0", true) diff --git a/libs/AceConfig-3.0/AceConfig-3.0.xml b/libs/AceConfig-3.0/AceConfig-3.0.xml index 87972ad..a3569b7 100755 --- a/libs/AceConfig-3.0/AceConfig-3.0.xml +++ b/libs/AceConfig-3.0/AceConfig-3.0.xml @@ -5,4 +5,4 @@ <Include file="AceConfigDialog-3.0\AceConfigDialog-3.0.xml"/> <!--<Include file="AceConfigDropdown-3.0\AceConfigDropdown-3.0.xml"/>--> <Script file="AceConfig-3.0.lua"/> -</Ui> \ No newline at end of file +</Ui> diff --git a/libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua b/libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua index 2023981..33f9fe1 100755 --- a/libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua +++ b/libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua @@ -1,7 +1,7 @@ --- AceConfigCmd-3.0 handles access to an options table through the "command line" interface via the ChatFrames. -- @class file -- @name AceConfigCmd-3.0 --- @release $Id: AceConfigCmd-3.0.lua 1045 2011-12-09 17:58:40Z nevcairiel $ +-- @release $Id: AceConfigCmd-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $ --[[ AceConfigCmd-3.0 @@ -14,8 +14,9 @@ REQUIRES: AceConsole-3.0 for command registration (loaded on demand) -- TODO: plugin args +local cfgreg = LibStub("AceConfigRegistry-3.0") -local MAJOR, MINOR = "AceConfigCmd-3.0", 13 +local MAJOR, MINOR = "AceConfigCmd-3.0", 14 local AceConfigCmd = LibStub:NewLibrary(MAJOR, MINOR) if not AceConfigCmd then return end @@ -23,7 +24,6 @@ if not AceConfigCmd then return end AceConfigCmd.commands = AceConfigCmd.commands or {} local commands = AceConfigCmd.commands -local cfgreg = LibStub("AceConfigRegistry-3.0") local AceConsole -- LoD local AceConsoleName = "AceConsole-3.0" diff --git a/libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml b/libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml index 188d354..9e157b5 100755 --- a/libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml +++ b/libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml @@ -1,4 +1,4 @@ <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <Script file="AceConfigCmd-3.0.lua"/> -</Ui> \ No newline at end of file +</Ui> 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 8dbd134..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,10 +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 1113 2014-09-11 20:18:16Z nevcairiel $ +-- @release $Id: AceConfigDialog-3.0.lua 1169 2018-02-27 16:18:28Z nevcairiel $ local LibStub = LibStub -local MAJOR, MINOR = "AceConfigDialog-3.0", 59 +local gui = LibStub("AceGUI-3.0") +local reg = LibStub("AceConfigRegistry-3.0") + +local MAJOR, MINOR = "AceConfigDialog-3.0", 66 local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceConfigDialog then return end @@ -17,9 +20,6 @@ AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {} AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {} AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {} -local gui = LibStub("AceGUI-3.0") -local reg = LibStub("AceConfigRegistry-3.0") - -- Lua APIs local tconcat, tinsert, tsort, tremove, tsort = table.concat, table.insert, table.sort, table.remove, table.sort local strmatch, format = string.match, string.format @@ -542,16 +542,16 @@ local function OptionOnMouseOver(widget, event) if descStyle and descStyle ~= "tooltip" then return end - GameTooltip:SetText(name, 1, .82, 0, 1) + GameTooltip:SetText(name, 1, .82, 0, true) if opt.type == "multiselect" then - GameTooltip:AddLine(user.text,0.5, 0.5, 0.8, 1) + GameTooltip:AddLine(user.text, 0.5, 0.5, 0.8, true) end if type(desc) == "string" then - GameTooltip:AddLine(desc, 1, 1, 1, 1) + GameTooltip:AddLine(desc, 1, 1, 1, true) end if type(usage) == "string" then - GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) + GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, true) end GameTooltip:Show() @@ -611,6 +611,31 @@ local function confirmPopup(appName, rootframe, basepath, info, message, func, . end end +local function validationErrorPopup(message) + if not StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] then + StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] = {} + end + local t = StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] + t.text = message + t.button1 = OKAY + t.preferredIndex = STATICPOPUP_NUMDIALOGS + local dialog, oldstrata + t.OnAccept = function() + if dialog and oldstrata then + dialog:SetFrameStrata(oldstrata) + end + end + t.timeout = 0 + t.whileDead = 1 + t.hideOnEscape = 1 + + dialog = StaticPopup_Show("ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG") + if dialog then + oldstrata = dialog:GetFrameStrata() + dialog:SetFrameStrata("TOOLTIP") + end +end + local function ActivateControl(widget, event, ...) --This function will call the set / execute handler for the widget --widget:GetUserDataTable() contains the needed info @@ -696,32 +721,26 @@ local function ActivateControl(widget, event, ...) end local rootframe = user.rootframe - if type(validated) == "string" then - --validate function returned a message to display - if rootframe.SetStatusText then - rootframe:SetStatusText(validated) - else - -- TODO: do something else. - end - PlaySound("igPlayerInviteDecline") - del(info) - return true - elseif not validated then - --validate returned false - if rootframe.SetStatusText then + if not validated or type(validated) == "string" then + if not validated then if usage then - rootframe:SetStatusText(name..": "..usage) + validated = name..": "..usage else if pattern then - rootframe:SetStatusText(name..": Expected "..pattern) + validated = name..": Expected "..pattern else - rootframe:SetStatusText(name..": Invalid Value") + validated = name..": Invalid Value" end end + end + + -- show validate message + if rootframe.SetStatusText then + rootframe:SetStatusText(validated) else - -- TODO: do something else + validationErrorPopup(validated) end - PlaySound("igPlayerInviteDecline") + PlaySound(882) -- SOUNDKIT.IG_PLAYER_INVITE_DECLINE || _DECLINE is actually missing from the table del(info) return true else @@ -1015,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 @@ -1092,7 +1112,7 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName) local image, width, height = GetOptionsMemberValue("image",v, options, path, appName) - if type(image) == "string" then + if type(image) == "string" or type(image) == "number" then control = gui:Create("Icon") if not width then width = GetOptionsMemberValue("imageWidth",v, options, path, appName) @@ -1154,7 +1174,7 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin local image = GetOptionsMemberValue("image", v, options, path, appName) local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName) - if type(image) == "string" then + if type(image) == "string" or type(image) == "number" then if type(imageCoords) == "table" then control:SetImage(image, unpack(imageCoords)) else @@ -1207,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 @@ -1269,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 @@ -1305,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 @@ -1354,7 +1380,7 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName) local image, width, height = GetOptionsMemberValue("image",v, options, path, appName) - if type(image) == "string" then + if type(image) == "string" or type(image) == "number" then if not width then width = GetOptionsMemberValue("imageWidth",v, options, path, appName) end @@ -1386,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 @@ -1448,10 +1476,10 @@ local function TreeOnButtonEnter(widget, event, uniquevalue, button) GameTooltip:SetPoint("LEFT",button,"RIGHT") end - GameTooltip:SetText(name, 1, .82, 0, 1) + GameTooltip:SetText(name, 1, .82, 0, true) if type(desc) == "string" then - GameTooltip:AddLine(desc, 1, 1, 1, 1) + GameTooltip:AddLine(desc, 1, 1, 1, true) end GameTooltip:Show() diff --git a/libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml b/libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml index 86ce057..8e1e606 100755 --- a/libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml +++ b/libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml @@ -1,4 +1,4 @@ <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <Script file="AceConfigDialog-3.0.lua"/> -</Ui> \ No newline at end of file +</Ui> 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 d684d66..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,16 +8,16 @@ -- :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 1105 2013-12-08 22:11:58Z nevcairiel $ -local MAJOR, MINOR = "AceConfigRegistry-3.0", 15 +-- @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", 18 local AceConfigRegistry = LibStub:NewLibrary(MAJOR, MINOR) if not AceConfigRegistry then return end AceConfigRegistry.tables = AceConfigRegistry.tables or {} -local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") - if not AceConfigRegistry.callbacks then AceConfigRegistry.callbacks = CallbackHandler:New(AceConfigRegistry) end @@ -57,6 +57,7 @@ local istable={["table"]=true, _="table"} local ismethodtable={["table"]=true,["string"]=true,["function"]=true, _="methodname, funcref or table"} local optstring={["nil"]=true,["string"]=true, _="string"} local optstringfunc={["nil"]=true,["string"]=true,["function"]=true, _="string or funcref"} +local optstringnumberfunc={["nil"]=true,["string"]=true,["number"]=true,["function"]=true, _="string, number or funcref"} local optnumber={["nil"]=true,["number"]=true, _="number"} local optmethod={["nil"]=true,["string"]=true,["function"]=true, _="methodname or funcref"} local optmethodfalse={["nil"]=true,["string"]=true,["function"]=true,["boolean"]={[false]=true}, _="methodname, funcref or false"} @@ -66,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, @@ -82,20 +84,20 @@ local basekeys={ dialogHidden=optmethodbool, dropdownHidden=optmethodbool, cmdHidden=optmethodbool, - icon=optstringfunc, + icon=optstringnumberfunc, iconCoords=optmethodtable, handler=opttable, get=optmethodfalse, set=optmethodfalse, func=optmethodfalse, arg={["*"]=true}, - width=optstring, + width=optstringnumber, } local typedkeys={ header={}, description={ - image=optstringfunc, + image=optstringnumberfunc, imageCoords=optmethodtable, imageHeight=optnumber, imageWidth=optnumber, @@ -112,7 +114,7 @@ local typedkeys={ childGroups=optstring, }, execute={ - image=optstringfunc, + image=optstringnumberfunc, imageCoords=optmethodtable, imageHeight=optnumber, imageWidth=optnumber, @@ -127,7 +129,7 @@ local typedkeys={ }, toggle={ tristate=optbool, - image=optstringfunc, + image=optstringnumberfunc, imageCoords=optmethodtable, }, tristate={ diff --git a/libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml b/libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml index 101bfda..4ea69ca 100755 --- a/libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml +++ b/libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml @@ -1,4 +1,4 @@ <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <Script file="AceConfigRegistry-3.0.lua"/> -</Ui> \ No newline at end of file +</Ui> diff --git a/libs/AceDB-3.0/AceDB-3.0.lua b/libs/AceDB-3.0/AceDB-3.0.lua index 79f4b39..b42b442 100755 --- a/libs/AceDB-3.0/AceDB-3.0.lua +++ b/libs/AceDB-3.0/AceDB-3.0.lua @@ -10,6 +10,7 @@ -- * **race** Race-specific data. All of the players characters of the same race share this database. -- * **faction** Faction-specific data. All of the players characters of the same faction share this database. -- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database. +-- * **locale** Locale specific data, based on the locale of the players game client. -- * **global** Global Data. All characters on the same account share this database. -- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used. -- @@ -39,8 +40,8 @@ -- end -- @class file -- @name AceDB-3.0.lua --- @release $Id: AceDB-3.0.lua 1115 2014-09-21 11:52:35Z kaelten $ -local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 25 +-- @release $Id: AceDB-3.0.lua 1142 2016-07-11 08:36:19Z nevcairiel $ +local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 26 local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) if not AceDB then return end -- No upgrade needed @@ -263,7 +264,7 @@ local factionrealmKey = factionKey .. " - " .. realmKey local localeKey = GetLocale():lower() local regionTable = { "US", "KR", "EU", "TW", "CN" } -local regionKey = _G["GetCurrentRegion"] and regionTable[GetCurrentRegion()] or string.sub(GetCVar("realmList"), 1, 2):upper() +local regionKey = regionTable[GetCurrentRegion()] local factionrealmregionKey = factionrealmKey .. " - " .. regionKey -- Actual database initialization function @@ -303,7 +304,7 @@ local function initdb(sv, defaults, defaultProfile, olddb, parent) ["factionrealm"] = factionrealmKey, ["factionrealmregion"] = factionrealmregionKey, ["profile"] = profileKey, - ["locale"] = localeKey, + ["locale"] = localeKey, ["global"] = true, ["profiles"] = true, } diff --git a/libs/AceDB-3.0/AceDB-3.0.xml b/libs/AceDB-3.0/AceDB-3.0.xml index 46b20ba..108fc70 100755 --- a/libs/AceDB-3.0/AceDB-3.0.xml +++ b/libs/AceDB-3.0/AceDB-3.0.xml @@ -1,4 +1,4 @@ <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <Script file="AceDB-3.0.lua"/> -</Ui> \ No newline at end of file +</Ui> diff --git a/libs/AceDBOptions-3.0/AceDBOptions-3.0.lua b/libs/AceDBOptions-3.0/AceDBOptions-3.0.lua index 616f35e..5028fef 100755 --- a/libs/AceDBOptions-3.0/AceDBOptions-3.0.lua +++ b/libs/AceDBOptions-3.0/AceDBOptions-3.0.lua @@ -1,8 +1,8 @@ --- AceDBOptions-3.0 provides a universal AceConfig options screen for managing AceDB-3.0 profiles. -- @class file -- @name AceDBOptions-3.0 --- @release $Id: AceDBOptions-3.0.lua 1066 2012-09-18 14:36:49Z nevcairiel $ -local ACEDBO_MAJOR, ACEDBO_MINOR = "AceDBOptions-3.0", 14 +-- @release $Id: AceDBOptions-3.0.lua 1140 2016-07-03 07:53:29Z nevcairiel $ +local ACEDBO_MAJOR, ACEDBO_MINOR = "AceDBOptions-3.0", 15 local AceDBOptions, oldminor = LibStub:NewLibrary(ACEDBO_MAJOR, ACEDBO_MINOR) if not AceDBOptions then return end -- No upgrade needed @@ -53,19 +53,19 @@ if LOCALE == "deDE" then L["choose_sub"] = "Wählt ein bereits vorhandenes Profil aus." L["copy"] = "Kopieren von..." L["copy_desc"] = "Kopiere die Einstellungen von einem vorhandenen Profil in das aktive Profil." - -- L["current"] = "Current Profile:" + L["current"] = "Aktuelles Profil:" L["default"] = "Standard" L["delete"] = "Profil löschen" L["delete_confirm"] = "Willst du das ausgewählte Profil wirklich löschen?" - L["delete_desc"] = "Lösche vorhandene oder unbenutzte Profile aus der Datenbank um Platz zu sparen und um die SavedVariables Datei 'sauber' zu halten." + L["delete_desc"] = "Lösche vorhandene oder unbenutzte Profile aus der Datenbank, um Platz zu sparen und die SavedVariables-Datei 'sauber' zu halten." L["delete_sub"] = "Löscht ein Profil aus der Datenbank." - L["intro"] = "Hier kannst du das aktive Datenbankprofile ändern, damit du verschiedene Einstellungen für jeden Charakter erstellen kannst, wodurch eine sehr flexible Konfiguration möglich wird." + L["intro"] = "Hier kannst du das aktive Datenbankprofil ändern, damit du verschiedene Einstellungen für jeden Charakter erstellen kannst, wodurch eine sehr flexible Konfiguration möglich wird." L["new"] = "Neu" L["new_sub"] = "Ein neues Profil erstellen." L["profiles"] = "Profile" L["profiles_sub"] = "Profile verwalten" L["reset"] = "Profil zurücksetzen" - L["reset_desc"] = "Setzt das momentane Profil auf Standardwerte zurück, für den Fall das mit der Konfiguration etwas schief lief oder weil du einfach neu starten willst." + L["reset_desc"] = "Setzt das momentane Profil auf Standardwerte zurück, für den Fall, dass mit der Konfiguration etwas schief lief oder weil du einfach neu starten willst." L["reset_sub"] = "Das aktuelle Profil auf Standard zurücksetzen." elseif LOCALE == "frFR" then L["choose"] = "Profils existants" @@ -73,7 +73,7 @@ elseif LOCALE == "frFR" then L["choose_sub"] = "Permet de choisir un des profils déjà disponibles." L["copy"] = "Copier à partir de" L["copy_desc"] = "Copie les paramètres d'un profil déjà existant dans le profil actuellement actif." - -- L["current"] = "Current Profile:" + L["current"] = "Profil actuel :" L["default"] = "Défaut" L["delete"] = "Supprimer un profil" L["delete_confirm"] = "Etes-vous sûr de vouloir supprimer le profil sélectionné ?" @@ -88,32 +88,32 @@ elseif LOCALE == "frFR" then L["reset_desc"] = "Réinitialise le profil actuel au cas où votre configuration est corrompue ou si vous voulez tout simplement faire table rase." L["reset_sub"] = "Réinitialise le profil actuel avec les paramètres par défaut." elseif LOCALE == "koKR" then - L["choose"] = "프로필 선택" - L["choose_desc"] = "새로운 이름을 입력하거나, 이미 있는 프로필중 하나를 선택하여 새로운 프로필을 만들 수 있습니다." - L["choose_sub"] = "당신이 현재 이용할수 있는 프로필을 선택합니다." - L["copy"] = "복사" - L["copy_desc"] = "현재 사용중인 프로필에, 선택한 프로필의 설정을 복사합니다." - -- L["current"] = "Current Profile:" + L["choose"] = "저장 중인 프로필" + L["choose_desc"] = "입력창에 새로운 이름을 입력하거나 저장 중인 프로필 중 하나를 선택하여 새로운 프로필을 만들 수 있습니다." + L["choose_sub"] = "현재 이용할 수 있는 프로필 중 하나를 선택합니다." + L["copy"] = "복사해오기" + L["copy_desc"] = "현재 사용 중인 프로필에 선택한 프로필의 설정을 복사합니다." + L["current"] = "현재 프로필:" L["default"] = "기본값" L["delete"] = "프로필 삭제" - L["delete_confirm"] = "정말로 선택한 프로필의 삭제를 원하십니까?" - L["delete_desc"] = "데이터베이스에 사용중이거나 저장된 프로파일 삭제로 SavedVariables 파일의 정리와 공간 절약이 됩니다." + L["delete_confirm"] = "정말로 선택한 프로필을 삭제할까요?" + L["delete_desc"] = "저장 공간 절약과 SavedVariables 파일의 정리를 위해 데이터베이스에서 사용하지 않는 프로필을 삭제하세요." L["delete_sub"] = "데이터베이스의 프로필을 삭제합니다." - L["intro"] = "모든 캐릭터의 다양한 설정과 사용중인 데이터베이스 프로필, 어느것이던지 매우 다루기 쉽게 바꿀수 있습니다." + L["intro"] = "활성 데이터베이스 프로필을 변경할 수 있고, 각 캐릭터 별로 다른 설정을 할 수 있습니다." L["new"] = "새로운 프로필" L["new_sub"] = "새로운 프로필을 만듭니다." L["profiles"] = "프로필" - L["profiles_sub"] = "프로필 설정" + L["profiles_sub"] = "프로필 관리" L["reset"] = "프로필 초기화" - L["reset_desc"] = "단순히 다시 새롭게 구성을 원하는 경우, 현재 프로필을 기본값으로 초기화 합니다." - L["reset_sub"] = "현재의 프로필을 기본값으로 초기화 합니다" + L["reset_desc"] = "설정이 깨졌거나 처음부터 다시 설정을 원하는 경우, 현재 프로필을 기본값으로 초기화하세요." + L["reset_sub"] = "현재 프로필을 기본값으로 초기화합니다" elseif LOCALE == "esES" or LOCALE == "esMX" then L["choose"] = "Perfiles existentes" L["choose_desc"] = "Puedes crear un nuevo perfil introduciendo un nombre en el recuadro o puedes seleccionar un perfil de los ya existentes." L["choose_sub"] = "Selecciona uno de los perfiles disponibles." L["copy"] = "Copiar de" L["copy_desc"] = "Copia los ajustes de un perfil existente al perfil actual." - -- L["current"] = "Current Profile:" + L["current"] = "Perfil actual:" L["default"] = "Por defecto" L["delete"] = "Borrar un Perfil" L["delete_confirm"] = "¿Estas seguro que quieres borrar el perfil seleccionado?" @@ -129,31 +129,31 @@ elseif LOCALE == "esES" or LOCALE == "esMX" then L["reset_sub"] = "Reinicar el perfil actual al de por defecto" elseif LOCALE == "zhTW" then L["choose"] = "現有的設定檔" - L["choose_desc"] = "你可以通過在文本框內輸入一個名字創立一個新的設定檔,也可以選擇一個已經存在的設定檔。" - L["choose_sub"] = "從當前可用的設定檔裏面選擇一個。" + L["choose_desc"] = "您可以在文字方塊內輸入名字以建立新的設定檔,或是選擇一個現有的設定檔使用。" + L["choose_sub"] = "從當前可用的設定檔裡面選擇一個。" L["copy"] = "複製自" - L["copy_desc"] = "從當前某個已保存的設定檔複製到當前正使用的設定檔。" - -- L["current"] = "Current Profile:" + L["copy_desc"] = "從一個現有的設定檔,將設定複製到現在使用中的設定檔。" + L["current"] = "目前設定檔:" L["default"] = "預設" L["delete"] = "刪除一個設定檔" - L["delete_confirm"] = "你確定要刪除所選擇的設定檔嗎?" - L["delete_desc"] = "從資料庫裏刪除不再使用的設定檔,以節省空間,並且清理SavedVariables檔。" - L["delete_sub"] = "從資料庫裏刪除一個設定檔。" - L["intro"] = "你可以選擇一個活動的資料設定檔,這樣你的每個角色就可以擁有不同的設定值,可以給你的插件設定帶來極大的靈活性。" + L["delete_confirm"] = "確定要刪除所選擇的設定檔嗎?" + L["delete_desc"] = "從資料庫裡刪除不再使用的設定檔,以節省空間,並且清理 SavedVariables 檔案。" + L["delete_sub"] = "從資料庫裡刪除一個設定檔。" + L["intro"] = "您可以從資料庫中選擇一個設定檔來使用,如此就可以讓每個角色使用不同的設定。" L["new"] = "新建" L["new_sub"] = "新建一個空的設定檔。" L["profiles"] = "設定檔" L["profiles_sub"] = "管理設定檔" L["reset"] = "重置設定檔" - L["reset_desc"] = "將當前的設定檔恢復到它的預設值,用於你的設定檔損壞,或者你只是想重來的情況。" - L["reset_sub"] = "將當前的設定檔恢復為預設值" + L["reset_desc"] = "將現用的設定檔重置為預設值;用於設定檔損壞,或者單純想要重來的情況。" + L["reset_sub"] = "將目前的設定檔重置為預設值" elseif LOCALE == "zhCN" then L["choose"] = "现有的配置文件" L["choose_desc"] = "你可以通过在文本框内输入一个名字创立一个新的配置文件,也可以选择一个已经存在的配置文件。" L["choose_sub"] = "从当前可用的配置文件里面选择一个。" L["copy"] = "复制自" L["copy_desc"] = "从当前某个已保存的配置文件复制到当前正使用的配置文件。" - -- L["current"] = "Current Profile:" + L["current"] = "当前配置文件:" L["default"] = "默认" L["delete"] = "删除一个配置文件" L["delete_confirm"] = "你确定要删除所选择的配置文件么?" @@ -173,7 +173,7 @@ elseif LOCALE == "ruRU" then L["choose_sub"] = "Выбор одиного из уже доступных профилей" L["copy"] = "Скопировать из" L["copy_desc"] = "Скопировать настройки из выбранного профиля в активный." - -- L["current"] = "Current Profile:" + L["current"] = "Текущий профиль:" L["default"] = "По умолчанию" L["delete"] = "Удалить профиль" L["delete_confirm"] = "Вы уверены, что вы хотите удалить выбранный профиль?" @@ -185,17 +185,17 @@ elseif LOCALE == "ruRU" then L["profiles"] = "Профили" L["profiles_sub"] = "Управление профилями" L["reset"] = "Сброс профиля" - L["reset_desc"] = "Если ваша конфигурации испорчена или если вы хотите настроить всё заново - сбросьте текущий профиль на стандартные значения." + L["reset_desc"] = "Сбросить текущий профиль к стандартным настройкам, если ваша конфигурация испорчена или вы хотите настроить всё заново." L["reset_sub"] = "Сброс текущего профиля на стандартный" elseif LOCALE == "itIT" then - L["choose"] = "Profili esistenti" - L["choose_desc"] = "Puoi creare un nuovo profilo digitando il nome della casella di testo, oppure scegliendone uno tra i profili gia' esistenti." - L["choose_sub"] = "Seleziona uno dei profili disponibili." + L["choose"] = "Profili Esistenti" + L["choose_desc"] = "Puoi creare un nuovo profilo digitando il nome della casella di testo, oppure scegliendone uno tra i profili già esistenti." + L["choose_sub"] = "Seleziona uno dei profili attualmente disponibili." L["copy"] = "Copia Da" L["copy_desc"] = "Copia le impostazioni da un profilo esistente, nel profilo attivo in questo momento." L["current"] = "Profilo Attivo:" L["default"] = "Standard" - L["delete"] = "Cancella un profilo" + L["delete"] = "Cancella un Profilo" L["delete_confirm"] = "Sei sicuro di voler cancellare il profilo selezionato?" L["delete_desc"] = "Cancella i profili non utilizzati dal database per risparmiare spazio e mantenere puliti i file di configurazione SavedVariables." L["delete_sub"] = "Cancella un profilo dal Database." @@ -205,8 +205,28 @@ elseif LOCALE == "itIT" then L["profiles"] = "Profili" L["profiles_sub"] = "Gestisci Profili" L["reset"] = "Reimposta Profilo" - L["reset_desc"] = "Riporta il tuo profilo attivo alle sue impostazioni di default, nel caso in cui la tua configurazione si sia corrotta, o semplicemente tu voglia re-inizializzarla." - L["reset_sub"] = "Reimposta il profilo ai suoi valori di default." + L["reset_desc"] = "Riporta il tuo profilo attivo alle sue impostazioni predefinite, nel caso in cui la tua configurazione si sia corrotta, o semplicemente tu voglia re-inizializzarla." + L["reset_sub"] = "Reimposta il profilo ai suoi valori predefiniti." +elseif LOCALE == "ptBR" then + L["choose"] = "Perfis Existentes" + L["choose_desc"] = "Você pode tanto criar um perfil novo tanto digitando um nome na caixa de texto, quanto escolher um dos perfis já existentes." + L["choose_sub"] = "Selecione um de seus perfis atualmente disponíveis." + L["copy"] = "Copiar De" + L["copy_desc"] = "Copia as definições de um perfil existente no perfil atualmente ativo." + L["current"] = "Perfil Autal:" + L["default"] = "Padrão" + L["delete"] = "Remover um Perfil" + L["delete_confirm"] = "Tem certeza que deseja remover o perfil selecionado?" + L["delete_desc"] = "Remove perfis existentes e inutilizados do banco de dados para economizar espaço, e limpar o arquivo SavedVariables." + L["delete_sub"] = "Remove um perfil do banco de dados." + L["intro"] = "Você pode alterar o perfil do banco de dados ativo, para que possa ter definições diferentes para cada personagem." + L["new"] = "Novo" + L["new_sub"] = "Cria um novo perfil vazio." + L["profiles"] = "Perfis" + L["profiles_sub"] = "Gerenciar Perfis" + L["reset"] = "Resetar Perfil" + L["reset_desc"] = "Reseta o perfil atual para os valores padrões, no caso de sua configuração estar quebrada, ou simplesmente se deseja começar novamente." + L["reset_sub"] = "Resetar o perfil atual ao padrão" end local defaultProfiles diff --git a/libs/AceDBOptions-3.0/AceDBOptions-3.0.xml b/libs/AceDBOptions-3.0/AceDBOptions-3.0.xml index 2668fb0..51305f9 100755 --- a/libs/AceDBOptions-3.0/AceDBOptions-3.0.xml +++ b/libs/AceDBOptions-3.0/AceDBOptions-3.0.xml @@ -1,4 +1,4 @@ <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <Script file="AceDBOptions-3.0.lua"/> -</Ui> \ No newline at end of file +</Ui> diff --git a/libs/AceEvent-3.0/AceEvent-3.0.lua b/libs/AceEvent-3.0/AceEvent-3.0.lua index 578ae25..bbf55c2 100755 --- a/libs/AceEvent-3.0/AceEvent-3.0.lua +++ b/libs/AceEvent-3.0/AceEvent-3.0.lua @@ -9,8 +9,10 @@ -- make into AceEvent. -- @class file -- @name AceEvent-3.0 --- @release $Id: AceEvent-3.0.lua 975 2010-10-23 11:26:18Z nevcairiel $ -local MAJOR, MINOR = "AceEvent-3.0", 3 +-- @release $Id: AceEvent-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $ +local CallbackHandler = LibStub("CallbackHandler-1.0") + +local MAJOR, MINOR = "AceEvent-3.0", 4 local AceEvent = LibStub:NewLibrary(MAJOR, MINOR) if not AceEvent then return end @@ -18,8 +20,6 @@ if not AceEvent then return end -- Lua APIs local pairs = pairs -local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") - AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib diff --git a/libs/AceEvent-3.0/AceEvent-3.0.xml b/libs/AceEvent-3.0/AceEvent-3.0.xml index 313ef4d..41ef791 100755 --- a/libs/AceEvent-3.0/AceEvent-3.0.xml +++ b/libs/AceEvent-3.0/AceEvent-3.0.xml @@ -1,4 +1,4 @@ <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <Script file="AceEvent-3.0.lua"/> -</Ui> \ No newline at end of file +</Ui> diff --git a/libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua b/libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua index 0dae68c..80fd582 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua @@ -1,7 +1,7 @@ --[[----------------------------------------------------------------------------- Frame Container -------------------------------------------------------------------------------]] -local Type, Version = "Frame", 24 +local Type, Version = "Frame", 26 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end @@ -21,10 +21,14 @@ local CreateFrame, UIParent = CreateFrame, UIParent Scripts -------------------------------------------------------------------------------]] local function Button_OnClick(frame) - PlaySound("gsTitleOptionExit") + PlaySound(799) -- SOUNDKIT.GS_TITLE_OPTION_EXIT frame.obj:Hide() end +local function Frame_OnShow(frame) + frame.obj:Fire("OnShow") +end + local function Frame_OnClose(frame) frame.obj:Fire("OnClose") end @@ -186,6 +190,7 @@ local function Constructor() frame:SetBackdropColor(0, 0, 0, 1) frame:SetMinResize(400, 200) frame:SetToplevel(true) + frame:SetScript("OnShow", Frame_OnShow) frame:SetScript("OnHide", Frame_OnClose) frame:SetScript("OnMouseDown", Frame_OnMouseDown) diff --git a/libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua b/libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua index 6dd0c4d..9afb54b 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua @@ -6,8 +6,6 @@ local Type, Version = "ScrollFrame", 24 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end -local IsLegion = select(4, GetBuildInfo()) >= 70000 - -- Lua APIs local pairs, assert, type = pairs, assert, type local min, max, floor, abs = math.min, math.max, math.floor, math.abs @@ -178,11 +176,7 @@ local function Constructor() local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND") scrollbg:SetAllPoints(scrollbar) - if IsLegion then - scrollbg:SetColorTexture(0, 0, 0, 0.4) - else - scrollbg:SetTexture(0, 0, 0, 0.4) - end + scrollbg:SetColorTexture(0, 0, 0, 0.4) --Container Support local content = CreateFrame("Frame", nil, scrollframe) diff --git a/libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua b/libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua index 00be129..95544c5 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua @@ -2,7 +2,7 @@ TabGroup Container Container that uses tabs on top to switch between groups. -------------------------------------------------------------------------------]] -local Type, Version = "TabGroup", 35 +local Type, Version = "TabGroup", 36 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end @@ -63,7 +63,7 @@ Scripts -------------------------------------------------------------------------------]] local function Tab_OnClick(frame) if not (frame.selected or frame.disabled) then - PlaySound("igCharacterInfoTab") + 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 9bf17d8..236f633 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua @@ -2,11 +2,11 @@ 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 IsLegion = select(4, GetBuildInfo()) >= 70000 +local WoW80 = select(4, GetBuildInfo()) >= 80000 -- Lua APIs local next, pairs, ipairs, assert, type = next, pairs, ipairs, assert, type @@ -164,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(...) @@ -302,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 @@ -390,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 @@ -422,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 @@ -672,12 +680,7 @@ local function Constructor() local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND") scrollbg:SetAllPoints(scrollbar) - - if IsLegion then - scrollbg:SetColorTexture(0,0,0,0.4) - else - scrollbg:SetTexture(0,0,0,0.4) - end + scrollbg:SetColorTexture(0,0,0,0.4) local border = CreateFrame("Frame",nil,frame) border:SetPoint("TOPLEFT", treeframe, "TOPRIGHT") diff --git a/libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua b/libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua index bb0a2a2..6825420 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua @@ -21,14 +21,18 @@ local CreateFrame, UIParent = CreateFrame, UIParent ]] do local Type = "Window" - local Version = 4 + local Version = 6 + + local function frameOnShow(this) + this.obj:Fire("OnShow") + end local function frameOnClose(this) this.obj:Fire("OnClose") end local function closeOnClick(this) - PlaySound("gsTitleOptionExit") + PlaySound(799) -- SOUNDKIT.GS_TITLE_OPTION_EXIT this.obj:Hide() end @@ -180,6 +184,7 @@ do frame:SetFrameStrata("FULLSCREEN_DIALOG") frame:SetScript("OnMouseDown", frameOnMouseDown) + frame:SetScript("OnShow",frameOnShow) frame:SetScript("OnHide",frameOnClose) frame:SetMinResize(240,240) frame:SetToplevel(true) diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua index c7c72c1..0a23be4 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua @@ -2,7 +2,7 @@ Button Widget Graphical Button. -------------------------------------------------------------------------------]] -local Type, Version = "Button", 23 +local Type, Version = "Button", 24 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end @@ -18,7 +18,7 @@ Scripts -------------------------------------------------------------------------------]] local function Button_OnClick(frame, ...) AceGUI:ClearFocus() - PlaySound("igMainMenuOption") + 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 8847ebc..b96ac59 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua @@ -1,7 +1,7 @@ --[[----------------------------------------------------------------------------- Checkbox Widget -------------------------------------------------------------------------------]] -local Type, Version = "CheckBox", 22 +local Type, Version = "CheckBox", 23 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end @@ -60,9 +60,9 @@ local function CheckBox_OnMouseUp(frame) self:ToggleChecked() if self.checked then - PlaySound("igMainMenuOptionCheckBoxOn") + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON else -- for both nil and false (tristate) - PlaySound("igMainMenuOptionCheckBoxOff") + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF end self:Fire("OnValueChanged", self.checked) diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua index 740a467..05e2b57 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua @@ -5,8 +5,6 @@ local Type, Version = "ColorPicker", 23 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end -local IsLegion = select(4, GetBuildInfo()) >= 70000 - -- Lua APIs local pairs = pairs @@ -148,11 +146,7 @@ local function Constructor() local texture = frame:CreateTexture(nil, "BACKGROUND") texture:SetWidth(16) texture:SetHeight(16) - if IsLegion then - texture:SetColorTexture(1, 1, 1) - else - texture:SetTexture(1, 1, 1) - end + texture:SetColorTexture(1, 1, 1) texture:SetPoint("CENTER", colorSwatch) texture:Show() diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua index 5ea840f..5748e4f 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua @@ -1,9 +1,7 @@ ---[[ $Id: AceGUIWidget-DropDown-Items.lua 1137 2016-05-15 10:57:36Z nevcairiel $ ]]-- +--[[ $Id: AceGUIWidget-DropDown-Items.lua 1167 2017-08-29 22:08:48Z funkydude $ ]]-- local AceGUI = LibStub("AceGUI-3.0") -local IsLegion = select(4, GetBuildInfo()) >= 70000 - -- Lua APIs local select, assert = select, assert @@ -325,7 +323,7 @@ end -- Does not close the pullout on click. do local widgetType = "Dropdown-Item-Toggle" - local widgetVersion = 3 + local widgetVersion = 4 local function UpdateToggle(self) if self.value then @@ -345,9 +343,9 @@ do if self.disabled then return end self.value = not self.value if self.value then - PlaySound("igMainMenuOptionCheckBoxOn") + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON else - PlaySound("igMainMenuOptionCheckBoxOff") + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF end UpdateToggle(self) self:Fire("OnValueChanged", self.value) @@ -457,11 +455,7 @@ do local line = self.frame:CreateTexture(nil, "OVERLAY") line:SetHeight(1) - if IsLegion then - line:SetColorTexture(.5, .5, .5) - else - line:SetTexture(.5, .5, .5) - end + line:SetColorTexture(.5, .5, .5) line:SetPoint("LEFT", self.frame, "LEFT", 10, 0) line:SetPoint("RIGHT", self.frame, "RIGHT", -10, 0) diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua index 0dd3bff..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 1116 2014-10-12 08:15:46Z nevcairiel $ ]]-- +--[[ $Id: AceGUIWidget-DropDown.lua 1167 2017-08-29 22:08:48Z funkydude $ ]]-- local AceGUI = LibStub("AceGUI-3.0") -- Lua APIs @@ -356,7 +356,7 @@ end do local widgetType = "Dropdown" - local widgetVersion = 30 + local widgetVersion = 31 --[[ Static data ]]-- @@ -381,7 +381,7 @@ do local function Dropdown_TogglePullout(this) local self = this.obj - PlaySound("igMainMenuOptionCheckBoxOn") -- missleading name, but the Blizzard code uses this sound + 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 d039026..b0b00f9 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", 26 +local Type, Version = "EditBox", 27 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("igMainMenuOptionCheckBoxOn") + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON HideButton(self) end end diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-InteractiveLabel.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-InteractiveLabel.lua index 9e06049..036efee 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-InteractiveLabel.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-InteractiveLabel.lua @@ -1,7 +1,7 @@ --[[----------------------------------------------------------------------------- InteractiveLabel Widget -------------------------------------------------------------------------------]] -local Type, Version = "InteractiveLabel", 20 +local Type, Version = "InteractiveLabel", 21 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-Label.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-Label.lua index 23897d5..75817a0 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-Label.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-Label.lua @@ -2,7 +2,7 @@ Label Widget Displays text and optionally an icon. -------------------------------------------------------------------------------]] -local Type, Version = "Label", 23 +local Type, Version = "Label", 24 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end @@ -78,6 +78,8 @@ local methods = { self:SetImageSize(16, 16) self:SetColor() self:SetFontObject() + self:SetJustifyH("LEFT") + self:SetJustifyV("TOP") -- reset the flag self.resizing = nil @@ -134,6 +136,14 @@ local methods = { self.image:SetHeight(height) UpdateImageAnchor(self) end, + + ["SetJustifyH"] = function(self, justifyH) + self.label:SetJustifyH(justifyH) + end, + + ["SetJustifyV"] = function(self, justifyV) + self.label:SetJustifyV(justifyV) + end, } --[[----------------------------------------------------------------------------- @@ -144,9 +154,6 @@ local function Constructor() frame:Hide() local label = frame:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall") - label:SetJustifyH("LEFT") - label:SetJustifyV("TOP") - local image = frame:CreateTexture(nil, "BACKGROUND") -- create widget diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua index 583f29d..20d0887 100755 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua @@ -2,7 +2,7 @@ Slider Widget Graphical Slider, like, for Range values. -------------------------------------------------------------------------------]] -local Type, Version = "Slider", 21 +local Type, Version = "Slider", 22 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end @@ -108,7 +108,7 @@ local function EditBox_OnEnterPressed(frame) end if value then - PlaySound("igMainMenuOptionCheckBoxOn") + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON self.slider:SetValue(value) self:Fire("OnMouseUp", value) end diff --git a/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua b/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua index a127301..675d7b0 100755 --- a/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua +++ b/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua @@ -1,4 +1,4 @@ ---[[ $Id: CallbackHandler-1.0.lua 965 2010-08-09 00:47:52Z mikk $ ]] +--[[ $Id: CallbackHandler-1.0.lua 1131 2015-06-04 07:29:24Z nevcairiel $ ]] local MAJOR, MINOR = "CallbackHandler-1.0", 6 local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) @@ -65,9 +65,7 @@ end}) -- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" -- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. -function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) - -- TODO: Remove this after beta has gone out - assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") +function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName) RegisterName = RegisterName or "RegisterCallback" UnregisterName = UnregisterName or "UnregisterCallback" diff --git a/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml b/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml index 876df83..c107f88 100755 --- a/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml +++ b/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml @@ -1,4 +1,4 @@ <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <Script file="CallbackHandler-1.0.lua"/> -</Ui> \ No newline at end of file +</Ui> diff --git a/libs/HereBeDragons/CHANGES.txt b/libs/HereBeDragons/CHANGES.txt index 1be2f23..3ffe873 100755 --- a/libs/HereBeDragons/CHANGES.txt +++ b/libs/HereBeDragons/CHANGES.txt @@ -1,8 +1,8 @@ Changes since tag 1.91-beta -commit af699f6637a2d07ba3000273534116aefaffc679 -Author: Hendrik Leppkes <h.leppkes@gmail.com> -Date: Sat May 26 19:15:44 2018 +0200 - - Optimize transform storage for faster lookups - +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 index 97e54ff..78faf34 100755 --- a/libs/HereBeDragons/HereBeDragons-1.0.lua +++ b/libs/HereBeDragons/HereBeDragons-1.0.lua @@ -1,776 +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 +-- 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 index bee78ea..b9e333e 100755 --- a/libs/HereBeDragons/HereBeDragons-2.0.lua +++ b/libs/HereBeDragons/HereBeDragons-2.0.lua @@ -1,496 +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", 4 -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} - else - mapData[id] = {0, 0, 0, 0, instance = instance or -1, name = data.name, mapType = data.mapType} - 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 +-- 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", 4 +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} + else + mapData[id] = {0, 0, 0, 0, instance = instance or -1, name = data.name, mapType = data.mapType} + 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 index e56d9e9..b6efc1b 100755 --- a/libs/HereBeDragons/HereBeDragons-Migrate.lua +++ b/libs/HereBeDragons/HereBeDragons-Migrate.lua @@ -1,529 +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 +-- 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 index d92c2b5..d0a79c5 100755 --- a/libs/HereBeDragons/HereBeDragons-Pins-1.0.lua +++ b/libs/HereBeDragons/HereBeDragons-Pins-1.0.lua @@ -1,651 +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 +-- 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 index 381dfc7..7aee844 100755 --- a/libs/HereBeDragons/HereBeDragons-Pins-2.0.lua +++ b/libs/HereBeDragons/HereBeDragons-Pins-2.0.lua @@ -1,716 +1,716 @@ --- 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", 1 -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 - --- 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 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 - -------------------------------------------------------------------------------------------- --- 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 - --- 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 == WORLDMAP_AZEROTH_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 info = C_Map.GetMapInfo(data.uiMapID) - while info and info.parentMapID do - if info.parentMapID == uiMapID then - local mapType = HBD.mapData[info.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 - info = C_Map.GetMapInfo(info.parentMapID) - 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") -end - -function worldmapProviderPin:OnAcquired(icon, x, y) - self:SetPosition(x, y) - - self.icon = icon - icon:SetParent(self) - icon:ClearAllPoints() - icon:SetPoint("CENTER", self, "CENTER") -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 +-- 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", 1 +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 + +-- 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 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 + +------------------------------------------------------------------------------------------- +-- 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 + +-- 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 == WORLDMAP_AZEROTH_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 info = C_Map.GetMapInfo(data.uiMapID) + while info and info.parentMapID do + if info.parentMapID == uiMapID then + local mapType = HBD.mapData[info.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 + info = C_Map.GetMapInfo(info.parentMapID) + 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") +end + +function worldmapProviderPin:OnAcquired(icon, x, y) + self:SetPosition(x, y) + + self.icon = icon + icon:SetParent(self) + icon:ClearAllPoints() + icon:SetPoint("CENTER", self, "CENTER") +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