------------------------------------------------------------------------------- ---------------------------------- NAMESPACE ---------------------------------- ------------------------------------------------------------------------------- local ADDON_NAME, ns = ... local Class = ns.Class ------------------------------------------------------------------------------- ------------------------------------- MAP ------------------------------------- ------------------------------------------------------------------------------- --[[ Base class for all maps. id (integer): MapID value for this map intro (Node): An intro node to display when phased phased (boolean): If false, hide all nodes except the intro node. settings (boolean): Create a settings panel for this map (default: false). --]] local Map = Class('Map', nil, { id = 0, intro = nil, phased = true, settings = false }) function Map:Initialize(attrs) for k, v in pairs(attrs) do self[k] = v end self.nodes = {} self.groups = {} self.settings = self.settings or false setmetatable(self.nodes, { __newindex = function (nodes, coord, node) self:AddNode(coord, node) end }) -- auto-register this map if ns.maps[self.id] then error('Map already registered: '..self.id) end ns.maps[self.id] = self end function Map:AddNode(coord, node) if not ns.IsInstance(node, ns.node.Node) then error('All nodes must be instances of the Node() class:', coord, node) end if node.group.name ~= 'intro' then -- Initialize group defaults and UI controls for this map if the group does -- not inherit its settings and defaults from a parent map if self.settings then ns.CreateGroupOptions(self, node.group) end -- Keep track of all groups associated with this map if not self.groups[node.group.name] then self.groups[#self.groups + 1] = node.group self.groups[node.group.name] = true end end rawset(self.nodes, coord, node) end function Map:Prepare() for coord, node in pairs(self.nodes) do -- prepare each node once to ensure its dependent data is loaded if not node._prepared then node:Prepare() node._prepared = true end end end function Map:IsNodeEnabled(node, coord, minimap) local db = ns.addon.db -- Debug option to force display all nodes if ns:GetOpt('force_nodes') or ns.dev_force then return true end -- Check if the zone is still phased if node ~= self.intro and not self.phased then return false end -- Check if we've been hidden by the user if db.char[self.id..'_coord_'..coord] then return false end -- Minimap may be disabled for this node if not node.minimap and minimap then return false end -- Node may be faction restricted if node.faction and node.faction ~= ns.faction then return false end -- Display the intro node! if node == self.intro then return not node:IsCompleted() end -- Check if node's group is disabled if not node.group:IsEnabled() then return false end -- Check for prerequisites and quest (or custom) completion if not node:IsEnabled() then return false end -- Display the node based off the group display setting return node.group:GetDisplay() end function Map:HasEnabledGroups() for i, group in ipairs(self.groups) do if group:IsEnabled() then return true end end return false end ------------------------------------------------------------------------------- ---------------------------- MINIMAP DATA PROVIDER ---------------------------- ------------------------------------------------------------------------------- local HBD = LibStub("HereBeDragons-2.0") local HBDPins = LibStub("HereBeDragons-Pins-2.0") local MinimapPinsKey = ADDON_NAME.."MinimapPins" local MinimapDataProvider = CreateFrame("Frame", ADDON_NAME.."MinimapDP") local MinimapPinTemplate = ADDON_NAME..'MinimapPinTemplate' local MinimapPinMixin = {} _G[ADDON_NAME..'MinimapPinMixin'] = MinimapPinMixin MinimapDataProvider.facing = GetPlayerFacing() MinimapDataProvider.pins = {} MinimapDataProvider.pool = {} MinimapDataProvider.minimap = true function MinimapDataProvider:ReleaseAllPins() for i, pin in ipairs(self.pins) do self.pool[pin] = true pin:OnReleased() pin:Hide() end end function MinimapDataProvider:AcquirePin(template, ...) local pin = next(self.pool) if pin then self.pool[pin] = nil -- remove it from the pool else pin = CreateFrame("Button", ADDON_NAME.."Pin"..(#self.pins + 1), Minimap, template) pin.provider = self pin:OnLoad() pin:Hide() self.pins[#self.pins + 1] = pin end pin:OnAcquired(...) end function MinimapDataProvider:RefreshAllData() -- Skip refresh if rotate minimap is on and we failed to get a facing value if GetCVar('rotateMinimap') == '1' and self.facing == nil then return end HBDPins:RemoveAllMinimapIcons(MinimapPinsKey) self:ReleaseAllPins() local map = ns.maps[HBD:GetPlayerZone()] if not map then return end for coord, node in pairs(map.nodes) do if node._prepared and map:IsNodeEnabled(node, coord, true) then -- If this icon has a glow enabled, render it local glow = node:GetGlow(map) if glow then glow[1] = coord -- update POI coord for this placement glow:Render(self, MinimapPinTemplate) end -- Render any POIs this icon has registered if node.pois and (node._focus or node._hover) then for i, poi in ipairs(node.pois) do poi:Render(self, MinimapPinTemplate) end end end end end function MinimapDataProvider:OnUpdate() local facing = GetPlayerFacing() if facing ~= self.facing then if GetCVar('rotateMinimap') == '1' then self:RefreshAllData() end self.facing = facing end end function MinimapPinMixin:OnLoad() self:SetFrameLevel(Minimap:GetFrameLevel() + 3) self:SetFrameStrata(Minimap:GetFrameStrata()) self.minimap = true end function MinimapPinMixin:OnAcquired(poi, ...) local mapID = HBD:GetPlayerZone() local x, y = poi:Draw(self, ...) if GetCVar('rotateMinimap') == '1' then self.texture:SetRotation(self.texture:GetRotation() + math.pi*2 - self.provider.facing) end HBDPins:AddMinimapIconMap(MinimapPinsKey, self, mapID, x, y, true) end function MinimapPinMixin:OnReleased() if self.ticker then self.ticker:Cancel() self.ticker = nil end end MinimapDataProvider:SetScript('OnUpdate', function () MinimapDataProvider:OnUpdate() end) ns.addon:RegisterEvent('MINIMAP_UPDATE_ZOOM', function (...) MinimapDataProvider:RefreshAllData() end) ns.addon:RegisterEvent('CVAR_UPDATE', function (_, varname) if varname == 'ROTATE_MINIMAP' then MinimapDataProvider:RefreshAllData() end end) ------------------------------------------------------------------------------- --------------------------- WORLD MAP DATA PROVIDER --------------------------- ------------------------------------------------------------------------------- local WorldMapDataProvider = CreateFromMixins(MapCanvasDataProviderMixin) local WorldMapPinTemplate = ADDON_NAME..'WorldMapPinTemplate' local WorldMapPinMixin = CreateFromMixins(MapCanvasPinMixin) _G[ADDON_NAME..'WorldMapPinMixin'] = WorldMapPinMixin function WorldMapDataProvider:RemoveAllData() if self:GetMap() then self:GetMap():RemoveAllPinsByTemplate(WorldMapPinTemplate) end end function WorldMapDataProvider:RefreshAllData(fromOnShow) self:RemoveAllData() if not self:GetMap() then return end local map = ns.maps[self:GetMap():GetMapID()] if not map then return end for coord, node in pairs(map.nodes) do if node._prepared and map:IsNodeEnabled(node, coord, false) then -- If this icon has a glow enabled, render it local glow = node:GetGlow(map) if glow then glow[1] = coord -- update POI coord for this placement glow:Render(self:GetMap(), WorldMapPinTemplate) end -- Render any POIs this icon has registered if node.pois and (node._focus or node._hover) then for i, poi in ipairs(node.pois) do poi:Render(self:GetMap(), WorldMapPinTemplate) end end end end end function WorldMapPinMixin:OnLoad() -- The MAP_HIGHLIGHT frame level is well below the level standard -- HandyNotes pins use, preventing mouseover conflicts self:UseFrameLevelType("PIN_FRAME_LEVEL_MAP_HIGHLIGHT") end function WorldMapPinMixin:OnAcquired(poi, ...) local _, _, w, h = self:GetParent():GetRect() self.parentWidth = w self.parentHeight = h if (w and h) then local x, y = poi:Draw(self, ...) self:ApplyCurrentScale() self:SetPosition(x, y) end end function WorldMapPinMixin:OnReleased() if self.ticker then self.ticker:Cancel() self.ticker = nil end end function WorldMapPinMixin:ApplyFrameLevel() -- Allow frame level adjustments in POIs even if the current frame level -- type has a range of only 1 frame level MapCanvasPinMixin.ApplyFrameLevel(self) self:SetFrameLevel(self:GetFrameLevel() + self.frameOffset) end ------------------------------------------------------------------------------- ------------------------------ HANDYNOTES HOOKS ------------------------------- ------------------------------------------------------------------------------- -- HandyNotes removes its data provider from the world map when the global -- enable/disable checkbox is toggled at the top of its UI window. We need -- to do the same thing here or our paths will still display. local OnEnable = HandyNotes.OnEnable local OnDisable = HandyNotes.OnDisable function HandyNotes:OnEnable() OnEnable(self) if not HandyNotes.db.profile.enabled then return end WorldMapFrame:AddDataProvider(WorldMapDataProvider) end function HandyNotes:OnDisable() OnDisable(self) if WorldMapFrame.dataProviders[WorldMapDataProvider] then WorldMapFrame:RemoveDataProvider(WorldMapDataProvider) end end ------------------------------------------------------------------------------- ns.Map = Map ns.MinimapDataProvider = MinimapDataProvider ns.WorldMapDataProvider = WorldMapDataProvider