Nenue@0: -- WorldPlan.lua Nenue@0: -- Created: 8/16/2016 8:19 AM Nenue@0: -- %file-revision% Nenue@0: Nenue@29: WorldPlanCore = { Nenue@29: defaults = {}, Nenue@30: modules = {}, Nenue@33: FilterOptions = {}, Nenue@33: UsedFilters = {}, Nenue@30: QuestsByZone = {}, Nenue@30: QuestsByID = {}, Nenue@33: TaskQueue = {}, Nenue@29: } Nenue@33: local WorldPlan = WorldPlanCore Nenue@30: Nenue@33: local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end Nenue@0: local WP_VERSION = "1.0" Nenue@33: local tinsert, pairs, floor = table.insert, pairs, floor Nenue@0: local ITEM_QUALITY_COLORS = ITEM_QUALITY_COLORS Nenue@33: local BROKEN_ISLES_ID = 1007 Nenue@0: local GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID = GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID Nenue@0: Nenue@0: -- default color templates Nenue@29: local DEFAULT_TYPE = { Nenue@0: a = 1, Nenue@0: r = 1, g = 1, b = 1, Nenue@0: x = 0, y = 0, Nenue@0: desaturated = true, Nenue@33: pinMask = "Interface\\Minimap\\UI-Minimap-Background", Nenue@33: rewardMask = "Interface\\Minimap\\UI-Minimap-Background", Nenue@33: texture = "Interface\\BUTTONS\\YELLOWORANGE64", Nenue@0: continent = { Nenue@30: PinSize = 14, Nenue@30: Border = 2, Nenue@30: TrackingBorder = 1, Nenue@0: TagSize = 6, Nenue@30: TimeleftStage = 0, Nenue@27: showNumber = true, Nenue@30: numberFontObject = 'WorldPlanFont' Nenue@0: }, Nenue@0: zone = { Nenue@0: PinSize = 22, Nenue@0: Border = 3, Nenue@0: TrackingBorder = 2, Nenue@0: TagSize = 12, Nenue@0: TimeleftStage = 3, Nenue@27: showNumber = true, Nenue@30: numberFontObject = 'WorldPlanNumberFontThin' Nenue@0: }, Nenue@0: minimized = { Nenue@31: PinSize = 6, Nenue@30: Border = 0, Nenue@30: TrackingBorder = 1, Nenue@0: NoIcon = true, Nenue@0: TimeleftStage = 1, Nenue@27: showNumber = false, Nenue@0: } Nenue@0: } Nenue@29: Nenue@29: Nenue@0: Nenue@9: Nenue@9: local defaults = { Nenue@27: ShowAllProfessionQuests = false, Nenue@9: DisplayContinentSummary = true, Nenue@9: DisplayContinentPins = true, Nenue@9: NotifyWhenNewQuests = true, Nenue@9: EnablePins = true, Nenue@9: FadeWhileGrouped = true, Nenue@9: } Nenue@9: Nenue@9: -- operating flags Nenue@9: local superTrackedID Nenue@9: local currentMapName Nenue@9: local hasNewQuestPins Nenue@9: local isContinentMap Nenue@0: local hasPendingQuestData Nenue@0: local notifyPlayed Nenue@0: local scanner, wmtt, WorldMapPOIFrame Nenue@0: Nenue@0: Nenue@0: -- tracking menu toggler Nenue@0: local DropDown_OnClick = function(self) Nenue@0: local key = self.value Nenue@0: if key then Nenue@0: if WorldPlanData[key] then Nenue@0: WorldPlanData[key] = nil Nenue@0: else Nenue@0: WorldPlanData[key] = true Nenue@0: end Nenue@0: end Nenue@33: _G.WorldPlan:Refresh() Nenue@0: end Nenue@0: Nenue@0: function WorldPlan:print(...) Nenue@0: local msg Nenue@0: for i = 1, select('#', ...) do Nenue@0: msg = (msg and (msg .. ' ') or '') .. tostring(select(i, ...)) Nenue@0: end Nenue@0: DEFAULT_CHAT_FRAME:AddMessage("|cFF0088FFWorldPlan|r: " .. msg) Nenue@0: end Nenue@0: Nenue@30: local current_type_owner Nenue@30: function WorldPlan:AddHandler (frame, defaults) Nenue@30: print('|cFFFFFF00'..self:GetName()..':AddHandler()', frame:GetName()) Nenue@30: tinsert(self.modules, frame) Nenue@30: self.defaults[frame] = defaults Nenue@30: frame.GetTypeInfo = function(frame, typeID) Nenue@30: return self:GetTypeInfo(frame, typeID) Nenue@30: end Nenue@30: end Nenue@30: Nenue@0: function WorldPlan:OnLoad () Nenue@29: Nenue@29: self.Types = setmetatable({}, { Nenue@29: __newindex = function(t, k, v) Nenue@29: if type(v) == 'table' then Nenue@30: print('adding owner', k) Nenue@30: v = setmetatable(v, { Nenue@30: __newindex = function(t2,k2,v2) Nenue@30: if type(v2) == 'table' then Nenue@30: print('adding type', k2) Nenue@30: v2 = setmetatable(v2, {__index = function(t3,k3) Nenue@30: --print('##deferring to default key', k3) Nenue@30: return DEFAULT_TYPE[k3] Nenue@30: end}) Nenue@30: end Nenue@30: rawset(t2,k2,v2) Nenue@29: end}) Nenue@29: end Nenue@29: rawset(t,k,v) Nenue@29: end Nenue@29: }) Nenue@29: Nenue@30: self.Types[self] = {} Nenue@29: Nenue@29: for index, color in pairs(ITEM_QUALITY_COLORS) do Nenue@30: self:AddTypeInfo(self, index, { r = color.r, g = color.g, b = color.b, hex = color.hex, }) Nenue@29: end Nenue@29: Nenue@0: WorldPlan = self Nenue@0: Nenue@0: WorldPlan:print('v'..WP_VERSION) Nenue@0: Nenue@0: self:RegisterEvent("QUESTLINE_UPDATE") Nenue@0: self:RegisterEvent("QUEST_LOG_UPDATE") Nenue@0: self:RegisterEvent("WORLD_MAP_UPDATE") Nenue@0: self:RegisterEvent("WORLD_QUEST_COMPLETED_BY_SPELL") Nenue@0: self:RegisterEvent("SUPER_TRACKED_QUEST_CHANGED") Nenue@0: self:RegisterEvent("SKILL_LINES_CHANGED") Nenue@0: self:RegisterEvent("ARTIFACT_XP_UPDATE") Nenue@0: self:RegisterEvent("ADDON_LOADED") Nenue@27: self:SetParent(WorldMapFrame) Nenue@0: end Nenue@0: Nenue@27: function WorldPlan:OnShow() Nenue@27: print(self:GetName()..':OnShow()') Nenue@27: if self.isStale then Nenue@30: self:Refresh() Nenue@27: end Nenue@27: end Nenue@27: Nenue@0: function WorldPlan:OnEvent (event, ...) Nenue@0: print() Nenue@33: print(event, 'init:', self.initialized) Nenue@0: if event == 'ADDON_LOADED' then Nenue@0: local addon = ... Nenue@0: if addon == "Blizzard_FlightMap" then Nenue@0: print('do mixin junk') Nenue@0: self.OnFlightMapLoaded() Nenue@0: Nenue@0: end Nenue@0: if IsLoggedIn() and not self.initialized then Nenue@0: self:Setup() Nenue@0: end Nenue@27: else Nenue@33: if event == 'WORLD_MAP_UPDATE' then Nenue@33: self.currentMapID = GetCurrentMapAreaID() Nenue@33: self.isContinentMap = (self.currentMapID == BROKEN_ISLES_ID) Nenue@33: print('|cFFFF4400currentMapID =', self.currentMapID) Nenue@33: --self.isStale = true Nenue@33: end Nenue@33: Nenue@27: for i, module in ipairs(self.modules) do Nenue@27: if module.OnEvent then Nenue@33: print(' |cFF0088FF'..module:GetName() .. ':OnEvent()|r') Nenue@27: module:OnEvent(event, ...) Nenue@27: end Nenue@0: end Nenue@0: end Nenue@0: end Nenue@0: Nenue@33: function WorldPlanCore:OnNext(func) Nenue@33: tinsert(self.TaskQueue, func) Nenue@33: end Nenue@33: Nenue@33: function WorldPlanCore:OnUpdate() Nenue@33: if #self.TaskQueue >= 1 then Nenue@33: local func = tremove(self.TaskQueue, 1) Nenue@33: if func then Nenue@33: func() Nenue@33: end Nenue@33: Nenue@33: end Nenue@33: Nenue@33: if self.isStale then Nenue@33: print('|cFF00FF00pushing global update') Nenue@33: self.isStale = nil Nenue@33: self:Refresh() Nenue@33: else Nenue@33: for i, module in ipairs(self.modules) do Nenue@33: if module.isStale then Nenue@33: print('|cFF00FF00internal '..module:GetName()..':Refresh()|r') Nenue@33: module:Refresh() Nenue@33: end Nenue@33: end Nenue@33: end Nenue@33: end Nenue@0: Nenue@0: function WorldPlan:Setup () Nenue@0: if not WorldPlanData then Nenue@0: WorldPlanData = {key = 0 } Nenue@0: end Nenue@0: WorldPlanData.key = (WorldPlanData.key or 0) + 1 Nenue@0: self.db = WorldPlanData Nenue@9: self.db.WorldQuests = self.db.WorldQuests or {} Nenue@9: db = self.db Nenue@9: for k,v in pairs(defaults) do Nenue@18: --[===[@non-debug@ Nenue@18: if not db[k] then Nenue@18: db[k] = v Nenue@18: end Nenue@18: Nenue@18: --@end-non-debug@]===] Nenue@18: --@debug@ Nenue@9: db[k] = v Nenue@18: --@end-debug@ Nenue@9: end Nenue@9: Nenue@9: self.currentMapID = GetCurrentMapAreaID() Nenue@0: Nenue@0: for i, module in ipairs(self.modules) do Nenue@9: module.db = self.db Nenue@0: if module.Setup then module:Setup() end Nenue@0: if not module.RegisterEvent then Nenue@0: module.RegisterEvent = self.RegisterEvent Nenue@0: end Nenue@0: end Nenue@0: self.initialized = true Nenue@0: Nenue@9: hooksecurefunc("UIDropDownMenu_Initialize", self.OnDropDownInitialize) Nenue@33: Nenue@33: hooksecurefunc("WorldMapTrackingOptionsDropDown_OnClick", function(button) Nenue@33: print("|cFF0088FFWorldMapTrackingOptionsDropDown_OnClick|r") Nenue@33: local value = button.value Nenue@33: if (value == "worldQuestFilterOrderResources" or value == "worldQuestFilterArtifactPower" or Nenue@33: value == "worldQuestFilterProfessionMaterials" or value == "worldQuestFilterGold" or Nenue@33: value == "worldQuestFilterEquipment") then Nenue@33: self:Refresh(true) Nenue@33: end Nenue@33: end) Nenue@0: end Nenue@0: Nenue@30: function WorldPlan:AddTypeInfo(owner, id, info) Nenue@30: self.Types[owner] = self.Types[owner] or {} Nenue@30: self.Types[owner][id] = info Nenue@30: print('Type('..owner:GetName()..')('..id..') = '.. tostring(info)) Nenue@30: end Nenue@30: Nenue@30: function WorldPlan:GetTypeInfo(owner, typeID) Nenue@29: local info, extraInfo Nenue@30: if not owner then Nenue@30: --print('## deferring to default type list') Nenue@30: else Nenue@30: --print('## pulling for', owner:GetName(), 'id =', typeID) Nenue@30: end Nenue@30: Nenue@30: owner = owner or self Nenue@30: if (not typeID) or (not self.Types[owner][typeID]) then Nenue@30: --print('## sending list default') Nenue@29: info = DEFAULT_TYPE Nenue@29: else Nenue@30: --print('## sent list definition', typeID) Nenue@30: info = self.Types[owner][typeID] Nenue@29: end Nenue@29: Nenue@29: if isContinentMap then Nenue@29: extraInfo = info.continent Nenue@30: --print('### continent subtype', extraInfo) Nenue@29: else Nenue@29: extraInfo = info.zone Nenue@29: Nenue@30: --print('### zone subtype', extraInfo) Nenue@29: end Nenue@29: return info, extraInfo Nenue@29: end Nenue@29: Nenue@29: do Nenue@29: local timeStates = { Nenue@29: {maxSeconds = 60, Nenue@29: r=1, g=0.25, b =0, format = function (minutes) return '|cFFFF4400'.. minutes .. 'm' end, Nenue@29: }, Nenue@29: {maxSeconds = 240, Nenue@29: r=1, g=.5, b=0, format = function(minutes) return '|cFFFF4400'.. floor(minutes/60) .. 'h' end, Nenue@29: }, Nenue@29: {maxSeconds = 1440, Nenue@29: r=1, g=1, b=0, format = function(minutes) return '|cFFFFFF00'.. floor(minutes/60) .. 'h' end, Nenue@29: }, Nenue@29: {maxSeconds = 10081, Nenue@29: r=0, g=1, b=0, Nenue@29: }, -- 7 days + 1 minute Nenue@29: } Nenue@29: -- Generates a timeleft string Nenue@29: function WorldPlan:GetTimeInfo(timeLeft, limit) Nenue@29: limit = limit or #timeStates Nenue@29: for index = 1, limit do Nenue@29: local state = timeStates[index] Nenue@29: if timeLeft <= state.maxSeconds then Nenue@29: local text Nenue@29: if state.format then Nenue@29: text = state.format(timeLeft) Nenue@29: end Nenue@29: return text, index Nenue@29: end Nenue@29: end Nenue@29: return nil, nil Nenue@29: end Nenue@29: end Nenue@29: Nenue@30: function WorldPlan:Refresh (forced) Nenue@30: print('|cFFFFFF00'..self:GetName()..':Refresh()|r forced:', forced, 'init:', self.initialized) Nenue@9: if not self.initialized then Nenue@9: return Nenue@9: end Nenue@9: Nenue@9: for i, module in ipairs(self.modules) do Nenue@0: if module.Refresh then Nenue@33: print('|cFF00FF00external '..module:GetName()..':Refresh()|r') Nenue@33: module:Refresh(forced) Nenue@0: end Nenue@0: end Nenue@0: end Nenue@0: Nenue@0: -- insert visual options into the tracking button menu Nenue@0: WorldPlan.OnDropDownInitialize = function (self, callback, dropType) Nenue@0: if self ~= WorldMapFrameDropDown then Nenue@0: return Nenue@0: end Nenue@9: local db = WorldPlan.db Nenue@0: Nenue@0: local info = UIDropDownMenu_CreateInfo() Nenue@0: info.text = "" Nenue@0: info.isTitle = true Nenue@0: UIDropDownMenu_AddButton(info) Nenue@0: info.text = "|cFF00AAFFWorldPlan|r" Nenue@0: info.isTitle = true Nenue@0: UIDropDownMenu_AddButton(info) Nenue@0: info.isTitle = nil Nenue@0: info.disabled = nil Nenue@0: info.keepShownOnClick = true Nenue@0: info.tooltipOnButton = 1 Nenue@0: Nenue@9: info.text = "Enable" Nenue@9: info.isNotRadio = true Nenue@9: info.value = "EnablePins" Nenue@9: info.checked = db.EnablePins Nenue@9: info.tooltipTitle = "Enable World Quest Overlays" Nenue@9: info.tooltipText = "Toggle the detail layers here." Nenue@9: info.func = DropDown_OnClick Nenue@9: UIDropDownMenu_AddButton(info) Nenue@9: Nenue@9: info.text = "Display All Profession Quests" Nenue@0: info.isNotRadio = true Nenue@0: info.value = "ShowAllProfessionQuests" Nenue@9: info.checked = db.ShowAllProfessionQuests Nenue@0: info.tooltipTitle = "Hidden Quests" Nenue@0: info.tooltipText = "Display work order and profession-related quests that are skipped by the default UI." Nenue@0: info.func = DropDown_OnClick Nenue@0: UIDropDownMenu_AddButton(info) Nenue@0: Nenue@0: info.text = "Show Continent Pins" Nenue@0: info.isNotRadio = true Nenue@0: info.value = "DisplayContinentPins" Nenue@9: info.checked = db.DisplayContinentPins Nenue@0: info.tooltipTitle = "Continent Pins" Nenue@0: info.tooltipText = "Display quest pins on the continent map (may get cramped)." Nenue@0: info.func = DropDown_OnClick Nenue@0: UIDropDownMenu_AddButton(info) Nenue@0: Nenue@0: info.text = "Show Summary" Nenue@0: info.isNotRadio = true Nenue@0: info.value = "DisplayContinentSummary" Nenue@0: info.tooltipTitle = "Summary Bar" Nenue@0: info.tooltipText = "Display a summary of active world quests. Note: requires directly viewing Broken Isle and Dalaran maps to gain complete info." Nenue@9: info.checked = db.DisplayContinentSummary Nenue@9: info.func = DropDown_OnClick Nenue@9: UIDropDownMenu_AddButton(info) Nenue@9: Nenue@9: info.text = "Fade In Groups" Nenue@9: info.isNotRadio = true Nenue@9: info.value = "FadeWhileGrouped" Nenue@9: info.tooltipTitle = "Group Fade" Nenue@9: info.tooltipText = "Reduce pin alpha when grouped, so player dots are easier to see." Nenue@9: info.checked = db.DisplayContinentSummary Nenue@0: info.func = DropDown_OnClick Nenue@0: UIDropDownMenu_AddButton(info) Nenue@0: end Nenue@0: Nenue@30: -------------------------------------------------------------------------------------------------------------------- Nenue@30: -------------------------------------------------------------------------------------------------------------------- Nenue@30: Nenue@0: Nenue@0: -- data provider manipulations for the taxi map Nenue@0: WorldPlan.OnFlightMapLoaded = function() Nenue@0: if true then return end Nenue@0: -- todo: figure out how to layer inside the map canvas Nenue@0: local res = {} Nenue@0: local t = {} Nenue@0: for k,v in pairs(FlightMapFrame) do Nenue@0: tinsert(res, tostring(k)) Nenue@0: end Nenue@0: Nenue@0: table.sort(res) Nenue@0: for i, k in ipairs(res) do Nenue@0: print(k) Nenue@0: end Nenue@0: hooksecurefunc(FlightMapFrame, 'RefreshAll', function(self) Nenue@0: print('|cFF0088FFWQDP RefreshAllData ', GetTime()) Nenue@0: Nenue@0: WorldPlan:GetPinsForMap(self:GetMapID()) Nenue@0: Nenue@0: for pin in self:EnumerateAllPins() do Nenue@0: if pin.worldQuest then Nenue@0: --print('got pin #', pin.questID) Nenue@30: local wp = self.QuestsByID[pin.questID] Nenue@0: if wp then Nenue@0: wp:ClearAllPoints() Nenue@0: wp:SetParent(FlightMapFrame.ScrollContainer) Nenue@0: wp:SetFrameStrata('MEDIUM') Nenue@0: wp:SetPoint('CENTER', pin, 'CENTER') Nenue@0: wp:Show() Nenue@0: end Nenue@0: end Nenue@0: end Nenue@0: end) Nenue@0: end Nenue@0: Nenue@0: Nenue@0: Nenue@0: Nenue@0: Nenue@0: Nenue@0: Nenue@0: SLASH_WORLDPLAN1 = "/worldplan" Nenue@0: SLASH_WORLDPLAN2 = "/wp" Nenue@0: SlashCmdList.WORLDPLAN = function() Nenue@0: print('command pop') Nenue@0: WorldPlan:GetPinsForMap() Nenue@0: WorldPlan:RefreshPins() Nenue@0: Nenue@0: SetTimedCallbackForAllPins(0, function(self) self.FadeIn:Play() self.FlashIn:Play() end) Nenue@0: SetTimedCallbackForAllPins(5, function(self) self.PendingFade:Play() end) Nenue@0: SetTimedCallbackForAllPins(8, function(self) self.PendingFade:Stop() end) Nenue@0: end Nenue@0: --%end-debug%