--[[------------------------------------------------------------------- -- TomTomLite - Copyright 2010 - James N. Whitehead II -------------------------------------------------------------------]]-- local addonName, addon = ... local L = addon.L addon.callbacks = LibStub("CallbackHandler-1.0"):New(addon) addon.libwindow = LibStub("LibWindow-1.1") -- Set up some tables to track waypoints do local cache = {} addon.waypoints = setmetatable({}, { __newindex = function(t, k, v) if type(v) == "nil" then -- A waypoint is being removed local oldwaypoint = rawget(t, k) rawset(t, k, nil) local map = oldwaypoint[1] if map then cache[map] = nil end else -- A waypoint is being added rawset(t, k, v) local map = v[1] if map then cache[map] = nil end end end, }) addon.waypointsByMap = setmetatable({}, { __index = function(t, k) local cached = cache[k] if cached then return cached end -- Look up all the waypoints for a given mapId local set = {} for idx, waypoint in ipairs(addon.waypoints) do if waypoint[1] == k then table.insert(set, waypoint) end end rawset(cache, k, set) return set end }) end function addon:Initialize() self.db = LibStub("AceDB-3.0"):New("TomTomLiteDB", self.defaults) self.arrow = self:CreateCrazyArrow("TomTomLiteArrow") self.arrow:SetPoint("CENTER", 0, 0) self.arrow:Hide() -- Events for objective tracking self:RegisterEvent("QUEST_POI_UPDATE", "OBJECTIVES_CHANGED") self:RegisterEvent("QUEST_LOG_UPDATE", "OBJECTIVES_CHANGED") self:RegisterMessage("OBJECTIVES_CHANGED") hooksecurefunc("WatchFrame_Update", function(self) addon:FireMessage("OBJECTIVES_CHANGED") end) self:RegisterMessage("TOMTOMLITE_WAYPOINT_ADDED") -- Events for world map overlays self:RegisterEvent("WORLD_MAP_UPDATE") end function addon:CreateCrazyArrow(name, parent) parent = parent or UIParent local frame = CreateFrame("Button", name, parent) frame:SetSize(128, 128) frame.arrow = frame:CreateTexture("OVERLAY") frame.arrow:SetAllPoints() frame.arrow:SetTexture("Interface\\Addons\\TomTomLite\\images\\BevArrow") frame.title = frame:CreateFontString("OVERLAY", name .. "Title", "GameFontHighlight") frame.info = frame:CreateFontString("OVERLAY", name .. "Info", "GameFontHighlight") frame.subtitle = frame:CreateFontString("OVERLAY", name .. "Subtitle", "GameFontHighlight") frame.title:SetPoint("TOP", frame, "BOTTOM", 0, 0) frame.info:SetPoint("TOP", frame.title, "BOTTOM", 0, 0) frame.subtitle:SetPoint("TOP", frame.info, "BOTTOM", 0, 0) frame:Hide() local PI2 = math.pi * 2 -- Set up the OnUpdate handler frame:SetScript("OnUpdate", function(self, elapsed) local map, floor, x, y = unpack(self.waypoint) local distance, angle = addon:GetVectorFromCurrent(map, floor, x, y) local facing = GetPlayerFacing() local faceangle = angle - facing local perc = math.abs((math.pi - math.abs(faceangle)) / math.pi) local gr,gg,gb = unpack(addon.db.profile.goodcolor) local mr,mg,mb = unpack(addon.db.profile.middlecolor) local br,bg,bb = unpack(addon.db.profile.badcolor) local r,g,b = addon:ColorGradient(perc, br, bg, bb, mr, mg, mb, gr, gg, gb) self.arrow:SetVertexColor(r,g,b) self.arrow:SetRotation(faceangle) self.subtitle:SetFormattedText("%.1f yards", distance) end) self.libwindow.RegisterConfig(frame, self.db.profile.positions) self.libwindow.RestorePosition(frame) self.libwindow.MakeDraggable(frame) self.libwindow.EnableMouseOnAlt(frame) self.libwindow.EnableMouseWheelScaling(frame) return frame end --[[------------------------------------------------------------------------- -- External API -------------------------------------------------------------------------]]-- function addon:AddWaypoint(map, floor, x, y, opt) assert(type(map) == "number") assert(type(floor) == "number") assert(type(x) == "number") assert(type(y) == "number") local waypoint = {map, floor, x, y} if type(opt) == "table" then for k, v in pairs(opt) do if type(k) ~= "number" then waypoint[k] = v end end end table.insert(self.waypoints, waypoint) self:FireMessage("TOMTOMLITE_WAYPOINT_ADDED", waypoint) return waypoint end function addon:DeleteWaypoint(waypoint) for idx, entry in ipairs(self.waypoints) do if entry == waypoint then table.remove(self.waypoints, waypoint) break end end self:FireMessage("TOMTOMLITE_WAYPOINT_DELETED", waypoint) end --[[------------------------------------------------------------------------- -- Private implementation -------------------------------------------------------------------------]]-- function addon:TOMTOMLITE_WAYPOINT_ADDED(msg, waypoint, ...) self:UpdateArrow() end function addon:OBJECTIVES_CHANGED() self:UpdateQuestObjectives() self:UpdateArrow() end function addon:UpdateArrow() -- local cmap, cfloor = GetCurrentMapAreaID() -- local cx, cy = GetPlayerMapPosition("player") -- -- Scan the current waypoints and determine which one is closest, regardless -- -- of which zone the waypoint is in. This could be altered to only consider -- -- waypoints in the current zone, quite easily. -- local mindist = math.huge -- local closest -- for idx, waypoint in ipairs(self.waypoints) do -- local map, floor, x, y = unpack(waypoint) -- local distance = addon.mapdata:DistanceWithinContinent(cmap, cfloor or 0, cx, cy, map, floor, x, y) -- if distance < mindist then -- mindist = distance -- closest = waypoint -- end -- end local closest = self.arrow.waypoint if closest then -- Set the crazy arrow to display this waypoint local zone, floor, x, y = unpack(closest) local lzone = self:GetMapDisplayName(zone) self.arrow.waypoint = closest self.arrow.title:SetText(closest.title or L["Unknown waypoint"]) self.arrow.info:SetFormattedText("%.2f, %.2f - %s", x * 100, y * 100, lzone) self.arrow:Show() else self.arrow.waypoint = nil self.arrow:Hide() end end local objectiveWaypoints = {} function addon:UpdateQuestObjectives() local map, floor = GetCurrentMapAreaID() floor = floor or 0 -- Do not set any NEW waypoints if we're on the continent map if self:IsContinentMap(map) then return end local cvar = GetCVarBool("questPOI") SetCVar("questPOI", 1) -- Only do an objective scan if the option is enabled if not self.db.profile.trackQuestObjectives then return end QuestPOIUpdateIcons() -- Scan through every quest that is being tracked, and create a waypoint -- for each of the objectives that are being tracked. These waypoints will -- be unordered, and will be sorted or ordered by the user/algorithm local closest local watchIndex = 1 while true do local questIndex = GetQuestIndexForWatch(watchIndex) if not questIndex then break end local title = GetQuestLogTitle(questIndex) local qid = select(9, GetQuestLogTitle(questIndex)) local completed, x, y, objective = QuestPOIGetIconInfo(qid) if x and y then -- For two waypoints to be equal, their map, floor, x, y should all be -- the same, as well as the title and completion set. local key = qid + (x * 100) * (y * 100) * map * (floor + 1) if not objectiveWaypoints[key] then if completed then title = "Turn in: " .. title end local waypoint = self:AddWaypoint(map, floor, x, y, {title = title}) objectiveWaypoints[key] = waypoint if not closest then closest = objectiveWaypoints[key] end else if not closest then closest = objectiveWaypoints[key] end end end watchIndex = watchIndex + 1 end self.arrow.waypoint = closest SetCVar("questPOI", cvar and 1 or 0) end --[[------------------------------------------------------------------------- -- World map support, displaying waypoint overlays -------------------------------------------------------------------------]]-- -- Create an overlay that we can use to parent our world map icons addon.overlay = CreateFrame("Frame", addonName .. "MapOverlay", WorldMapButton) addon.overlay:SetAllPoints() addon.overlay:Show() -- Metatable that stores world map icons indexed by number, in array form local worldmapIcons = setmetatable({}, { __index = function(t, k) local name = addonName .. "MapIcon" .. tostring(k) local button = CreateFrame("Button", name, addon.overlay) button:SetSize(64, 64) button:SetHitRectInsets(12, 12, 5, 2) button.icon = button:CreateTexture("BACKGROUND") button.icon:SetTexture("Interface\\AddOns\\TomTomLite\\images\\MapPointer") button.icon:SetVertexColor(0.3, 1.0, 0.3) button.icon:SetAllPoints() button.glow = button:CreateTexture("BACKGROUND") button.glow:SetTexture("Interface\\AddOns\\TomTomLite\\images\\MapPointerGlow") button.glow:SetAllPoints() button.glow:Hide() button.number = button:CreateTexture("OVERLAY", name .. "Number") button.number:SetSize(50, 50) button.number:SetTexture("Interface\\WorldMap\\UI-QuestPoi-NumberIcons") button.number:SetPoint("CENTER", button, "CENTER", 0, 8) button.number:SetDrawLayer("OVERLAY", 7) rawset(t, k, button) return button end, }) function addon:WORLD_MAP_UPDATE() -- Display any waypoints overlaid on the world map. Specifically, if -- the map zoom is set to a continent or cosmic map, then display all -- waypoints, otherwise display only the waypoints for the currently -- displayed zone. If there are waypoints on a floor other than the -- one the player is currently on, they will be displayed as well, and -- will be distinguishable from waypoints on the current floor. -- If the map isn't shown, do nothing if not addon.overlay:IsVisible() then return end -- Check the options to see what should be displayed if not self.db.profile.showMapIconsZone then return end -- TODO: Handle 'showMapIconsContinent' option local continent = GetCurrentMapContinent() local map, floor = GetCurrentMapAreaID() local waypoints = addon.waypointsByMap[map] for idx = 1, math.max(#waypoints, #worldmapIcons), 1 do local icon = worldmapIcons[idx] local waypoint = waypoints[idx] if waypoint then local width, height = addon.overlay:GetSize() icon:ClearAllPoints() local x = waypoint[3] * width local y = waypoint[4] * height -- Set the number to be displayed local buttonIndex = idx - 1 local yOffset = 0.5 + math.floor(buttonIndex / QUEST_POI_ICONS_PER_ROW) * QUEST_POI_ICON_SIZE; local xOffset = mod(buttonIndex, QUEST_POI_ICONS_PER_ROW) * QUEST_POI_ICON_SIZE icon.number:SetTexCoord(xOffset, xOffset + QUEST_POI_ICON_SIZE, yOffset, yOffset + QUEST_POI_ICON_SIZE) -- Nudge position so arrow appears centered on POI icon:SetPoint("BOTTOM", addon.overlay, "TOPLEFT", x + 1, -y - 5) if (floor or 0) == waypoint[2] then icon:SetAlpha(1.0) else icon:SetAlpha(0.6) end icon:Show() else icon:Hide() end end end