annotate WorldPlan.lua @ 33:be4db60219ca

WorldPlan: - Toggling a reward filter cancels out other types by default. Use right mouse to clear. - Fixed filter bar info falling out of sync after player-triggered world map updates. ClassPlan: - Available missions are now recorded; the mission list can be toggled between in-progress and available by clicking the heading.
author Nenue
date Wed, 02 Nov 2016 17:25:07 -0400
parents e8679ecb48d8
children 0100d923d8c3
rev   line source
Nenue@0 1 -- WorldPlan.lua
Nenue@0 2 -- Created: 8/16/2016 8:19 AM
Nenue@0 3 -- %file-revision%
Nenue@0 4
Nenue@29 5 WorldPlanCore = {
Nenue@29 6 defaults = {},
Nenue@30 7 modules = {},
Nenue@33 8 FilterOptions = {},
Nenue@33 9 UsedFilters = {},
Nenue@30 10 QuestsByZone = {},
Nenue@30 11 QuestsByID = {},
Nenue@33 12 TaskQueue = {},
Nenue@29 13 }
Nenue@33 14 local WorldPlan = WorldPlanCore
Nenue@30 15
Nenue@33 16 local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
Nenue@0 17 local WP_VERSION = "1.0"
Nenue@33 18 local tinsert, pairs, floor = table.insert, pairs, floor
Nenue@0 19 local ITEM_QUALITY_COLORS = ITEM_QUALITY_COLORS
Nenue@33 20 local BROKEN_ISLES_ID = 1007
Nenue@0 21 local GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID = GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID
Nenue@0 22
Nenue@0 23 -- default color templates
Nenue@29 24 local DEFAULT_TYPE = {
Nenue@0 25 a = 1,
Nenue@0 26 r = 1, g = 1, b = 1,
Nenue@0 27 x = 0, y = 0,
Nenue@0 28 desaturated = true,
Nenue@33 29 pinMask = "Interface\\Minimap\\UI-Minimap-Background",
Nenue@33 30 rewardMask = "Interface\\Minimap\\UI-Minimap-Background",
Nenue@33 31 texture = "Interface\\BUTTONS\\YELLOWORANGE64",
Nenue@0 32 continent = {
Nenue@30 33 PinSize = 14,
Nenue@30 34 Border = 2,
Nenue@30 35 TrackingBorder = 1,
Nenue@0 36 TagSize = 6,
Nenue@30 37 TimeleftStage = 0,
Nenue@27 38 showNumber = true,
Nenue@30 39 numberFontObject = 'WorldPlanFont'
Nenue@0 40 },
Nenue@0 41 zone = {
Nenue@0 42 PinSize = 22,
Nenue@0 43 Border = 3,
Nenue@0 44 TrackingBorder = 2,
Nenue@0 45 TagSize = 12,
Nenue@0 46 TimeleftStage = 3,
Nenue@27 47 showNumber = true,
Nenue@30 48 numberFontObject = 'WorldPlanNumberFontThin'
Nenue@0 49 },
Nenue@0 50 minimized = {
Nenue@31 51 PinSize = 6,
Nenue@30 52 Border = 0,
Nenue@30 53 TrackingBorder = 1,
Nenue@0 54 NoIcon = true,
Nenue@0 55 TimeleftStage = 1,
Nenue@27 56 showNumber = false,
Nenue@0 57 }
Nenue@0 58 }
Nenue@29 59
Nenue@29 60
Nenue@0 61
Nenue@9 62
Nenue@9 63 local defaults = {
Nenue@27 64 ShowAllProfessionQuests = false,
Nenue@9 65 DisplayContinentSummary = true,
Nenue@9 66 DisplayContinentPins = true,
Nenue@9 67 NotifyWhenNewQuests = true,
Nenue@9 68 EnablePins = true,
Nenue@9 69 FadeWhileGrouped = true,
Nenue@9 70 }
Nenue@9 71
Nenue@9 72 -- operating flags
Nenue@9 73 local superTrackedID
Nenue@9 74 local currentMapName
Nenue@9 75 local hasNewQuestPins
Nenue@9 76 local isContinentMap
Nenue@0 77 local hasPendingQuestData
Nenue@0 78 local notifyPlayed
Nenue@0 79 local scanner, wmtt, WorldMapPOIFrame
Nenue@0 80
Nenue@0 81
Nenue@0 82 -- tracking menu toggler
Nenue@0 83 local DropDown_OnClick = function(self)
Nenue@0 84 local key = self.value
Nenue@0 85 if key then
Nenue@0 86 if WorldPlanData[key] then
Nenue@0 87 WorldPlanData[key] = nil
Nenue@0 88 else
Nenue@0 89 WorldPlanData[key] = true
Nenue@0 90 end
Nenue@0 91 end
Nenue@33 92 _G.WorldPlan:Refresh()
Nenue@0 93 end
Nenue@0 94
Nenue@0 95 function WorldPlan:print(...)
Nenue@0 96 local msg
Nenue@0 97 for i = 1, select('#', ...) do
Nenue@0 98 msg = (msg and (msg .. ' ') or '') .. tostring(select(i, ...))
Nenue@0 99 end
Nenue@0 100 DEFAULT_CHAT_FRAME:AddMessage("|cFF0088FFWorldPlan|r: " .. msg)
Nenue@0 101 end
Nenue@0 102
Nenue@30 103 local current_type_owner
Nenue@30 104 function WorldPlan:AddHandler (frame, defaults)
Nenue@30 105 print('|cFFFFFF00'..self:GetName()..':AddHandler()', frame:GetName())
Nenue@30 106 tinsert(self.modules, frame)
Nenue@30 107 self.defaults[frame] = defaults
Nenue@30 108 frame.GetTypeInfo = function(frame, typeID)
Nenue@30 109 return self:GetTypeInfo(frame, typeID)
Nenue@30 110 end
Nenue@30 111 end
Nenue@30 112
Nenue@0 113 function WorldPlan:OnLoad ()
Nenue@29 114
Nenue@29 115 self.Types = setmetatable({}, {
Nenue@29 116 __newindex = function(t, k, v)
Nenue@29 117 if type(v) == 'table' then
Nenue@30 118 print('adding owner', k)
Nenue@30 119 v = setmetatable(v, {
Nenue@30 120 __newindex = function(t2,k2,v2)
Nenue@30 121 if type(v2) == 'table' then
Nenue@30 122 print('adding type', k2)
Nenue@30 123 v2 = setmetatable(v2, {__index = function(t3,k3)
Nenue@30 124 --print('##deferring to default key', k3)
Nenue@30 125 return DEFAULT_TYPE[k3]
Nenue@30 126 end})
Nenue@30 127 end
Nenue@30 128 rawset(t2,k2,v2)
Nenue@29 129 end})
Nenue@29 130 end
Nenue@29 131 rawset(t,k,v)
Nenue@29 132 end
Nenue@29 133 })
Nenue@29 134
Nenue@30 135 self.Types[self] = {}
Nenue@29 136
Nenue@29 137 for index, color in pairs(ITEM_QUALITY_COLORS) do
Nenue@30 138 self:AddTypeInfo(self, index, { r = color.r, g = color.g, b = color.b, hex = color.hex, })
Nenue@29 139 end
Nenue@29 140
Nenue@0 141 WorldPlan = self
Nenue@0 142
Nenue@0 143 WorldPlan:print('v'..WP_VERSION)
Nenue@0 144
Nenue@0 145 self:RegisterEvent("QUESTLINE_UPDATE")
Nenue@0 146 self:RegisterEvent("QUEST_LOG_UPDATE")
Nenue@0 147 self:RegisterEvent("WORLD_MAP_UPDATE")
Nenue@0 148 self:RegisterEvent("WORLD_QUEST_COMPLETED_BY_SPELL")
Nenue@0 149 self:RegisterEvent("SUPER_TRACKED_QUEST_CHANGED")
Nenue@0 150 self:RegisterEvent("SKILL_LINES_CHANGED")
Nenue@0 151 self:RegisterEvent("ARTIFACT_XP_UPDATE")
Nenue@0 152 self:RegisterEvent("ADDON_LOADED")
Nenue@27 153 self:SetParent(WorldMapFrame)
Nenue@0 154 end
Nenue@0 155
Nenue@27 156 function WorldPlan:OnShow()
Nenue@27 157 print(self:GetName()..':OnShow()')
Nenue@27 158 if self.isStale then
Nenue@30 159 self:Refresh()
Nenue@27 160 end
Nenue@27 161 end
Nenue@27 162
Nenue@0 163 function WorldPlan:OnEvent (event, ...)
Nenue@0 164 print()
Nenue@33 165 print(event, 'init:', self.initialized)
Nenue@0 166 if event == 'ADDON_LOADED' then
Nenue@0 167 local addon = ...
Nenue@0 168 if addon == "Blizzard_FlightMap" then
Nenue@0 169 print('do mixin junk')
Nenue@0 170 self.OnFlightMapLoaded()
Nenue@0 171
Nenue@0 172 end
Nenue@0 173 if IsLoggedIn() and not self.initialized then
Nenue@0 174 self:Setup()
Nenue@0 175 end
Nenue@27 176 else
Nenue@33 177 if event == 'WORLD_MAP_UPDATE' then
Nenue@33 178 self.currentMapID = GetCurrentMapAreaID()
Nenue@33 179 self.isContinentMap = (self.currentMapID == BROKEN_ISLES_ID)
Nenue@33 180 print('|cFFFF4400currentMapID =', self.currentMapID)
Nenue@33 181 --self.isStale = true
Nenue@33 182 end
Nenue@33 183
Nenue@27 184 for i, module in ipairs(self.modules) do
Nenue@27 185 if module.OnEvent then
Nenue@33 186 print(' |cFF0088FF'..module:GetName() .. ':OnEvent()|r')
Nenue@27 187 module:OnEvent(event, ...)
Nenue@27 188 end
Nenue@0 189 end
Nenue@0 190 end
Nenue@0 191 end
Nenue@0 192
Nenue@33 193 function WorldPlanCore:OnNext(func)
Nenue@33 194 tinsert(self.TaskQueue, func)
Nenue@33 195 end
Nenue@33 196
Nenue@33 197 function WorldPlanCore:OnUpdate()
Nenue@33 198 if #self.TaskQueue >= 1 then
Nenue@33 199 local func = tremove(self.TaskQueue, 1)
Nenue@33 200 if func then
Nenue@33 201 func()
Nenue@33 202 end
Nenue@33 203
Nenue@33 204 end
Nenue@33 205
Nenue@33 206 if self.isStale then
Nenue@33 207 print('|cFF00FF00pushing global update')
Nenue@33 208 self.isStale = nil
Nenue@33 209 self:Refresh()
Nenue@33 210 else
Nenue@33 211 for i, module in ipairs(self.modules) do
Nenue@33 212 if module.isStale then
Nenue@33 213 print('|cFF00FF00internal '..module:GetName()..':Refresh()|r')
Nenue@33 214 module:Refresh()
Nenue@33 215 end
Nenue@33 216 end
Nenue@33 217 end
Nenue@33 218 end
Nenue@0 219
Nenue@0 220 function WorldPlan:Setup ()
Nenue@0 221 if not WorldPlanData then
Nenue@0 222 WorldPlanData = {key = 0 }
Nenue@0 223 end
Nenue@0 224 WorldPlanData.key = (WorldPlanData.key or 0) + 1
Nenue@0 225 self.db = WorldPlanData
Nenue@9 226 self.db.WorldQuests = self.db.WorldQuests or {}
Nenue@9 227 db = self.db
Nenue@9 228 for k,v in pairs(defaults) do
Nenue@18 229 --[===[@non-debug@
Nenue@18 230 if not db[k] then
Nenue@18 231 db[k] = v
Nenue@18 232 end
Nenue@18 233
Nenue@18 234 --@end-non-debug@]===]
Nenue@18 235 --@debug@
Nenue@9 236 db[k] = v
Nenue@18 237 --@end-debug@
Nenue@9 238 end
Nenue@9 239
Nenue@9 240 self.currentMapID = GetCurrentMapAreaID()
Nenue@0 241
Nenue@0 242 for i, module in ipairs(self.modules) do
Nenue@9 243 module.db = self.db
Nenue@0 244 if module.Setup then module:Setup() end
Nenue@0 245 if not module.RegisterEvent then
Nenue@0 246 module.RegisterEvent = self.RegisterEvent
Nenue@0 247 end
Nenue@0 248 end
Nenue@0 249 self.initialized = true
Nenue@0 250
Nenue@9 251 hooksecurefunc("UIDropDownMenu_Initialize", self.OnDropDownInitialize)
Nenue@33 252
Nenue@33 253 hooksecurefunc("WorldMapTrackingOptionsDropDown_OnClick", function(button)
Nenue@33 254 print("|cFF0088FFWorldMapTrackingOptionsDropDown_OnClick|r")
Nenue@33 255 local value = button.value
Nenue@33 256 if (value == "worldQuestFilterOrderResources" or value == "worldQuestFilterArtifactPower" or
Nenue@33 257 value == "worldQuestFilterProfessionMaterials" or value == "worldQuestFilterGold" or
Nenue@33 258 value == "worldQuestFilterEquipment") then
Nenue@33 259 self:Refresh(true)
Nenue@33 260 end
Nenue@33 261 end)
Nenue@0 262 end
Nenue@0 263
Nenue@30 264 function WorldPlan:AddTypeInfo(owner, id, info)
Nenue@30 265 self.Types[owner] = self.Types[owner] or {}
Nenue@30 266 self.Types[owner][id] = info
Nenue@30 267 print('Type('..owner:GetName()..')('..id..') = '.. tostring(info))
Nenue@30 268 end
Nenue@30 269
Nenue@30 270 function WorldPlan:GetTypeInfo(owner, typeID)
Nenue@29 271 local info, extraInfo
Nenue@30 272 if not owner then
Nenue@30 273 --print('## deferring to default type list')
Nenue@30 274 else
Nenue@30 275 --print('## pulling for', owner:GetName(), 'id =', typeID)
Nenue@30 276 end
Nenue@30 277
Nenue@30 278 owner = owner or self
Nenue@30 279 if (not typeID) or (not self.Types[owner][typeID]) then
Nenue@30 280 --print('## sending list default')
Nenue@29 281 info = DEFAULT_TYPE
Nenue@29 282 else
Nenue@30 283 --print('## sent list definition', typeID)
Nenue@30 284 info = self.Types[owner][typeID]
Nenue@29 285 end
Nenue@29 286
Nenue@29 287 if isContinentMap then
Nenue@29 288 extraInfo = info.continent
Nenue@30 289 --print('### continent subtype', extraInfo)
Nenue@29 290 else
Nenue@29 291 extraInfo = info.zone
Nenue@29 292
Nenue@30 293 --print('### zone subtype', extraInfo)
Nenue@29 294 end
Nenue@29 295 return info, extraInfo
Nenue@29 296 end
Nenue@29 297
Nenue@29 298 do
Nenue@29 299 local timeStates = {
Nenue@29 300 {maxSeconds = 60,
Nenue@29 301 r=1, g=0.25, b =0, format = function (minutes) return '|cFFFF4400'.. minutes .. 'm' end,
Nenue@29 302 },
Nenue@29 303 {maxSeconds = 240,
Nenue@29 304 r=1, g=.5, b=0, format = function(minutes) return '|cFFFF4400'.. floor(minutes/60) .. 'h' end,
Nenue@29 305 },
Nenue@29 306 {maxSeconds = 1440,
Nenue@29 307 r=1, g=1, b=0, format = function(minutes) return '|cFFFFFF00'.. floor(minutes/60) .. 'h' end,
Nenue@29 308 },
Nenue@29 309 {maxSeconds = 10081,
Nenue@29 310 r=0, g=1, b=0,
Nenue@29 311 }, -- 7 days + 1 minute
Nenue@29 312 }
Nenue@29 313 -- Generates a timeleft string
Nenue@29 314 function WorldPlan:GetTimeInfo(timeLeft, limit)
Nenue@29 315 limit = limit or #timeStates
Nenue@29 316 for index = 1, limit do
Nenue@29 317 local state = timeStates[index]
Nenue@29 318 if timeLeft <= state.maxSeconds then
Nenue@29 319 local text
Nenue@29 320 if state.format then
Nenue@29 321 text = state.format(timeLeft)
Nenue@29 322 end
Nenue@29 323 return text, index
Nenue@29 324 end
Nenue@29 325 end
Nenue@29 326 return nil, nil
Nenue@29 327 end
Nenue@29 328 end
Nenue@29 329
Nenue@30 330 function WorldPlan:Refresh (forced)
Nenue@30 331 print('|cFFFFFF00'..self:GetName()..':Refresh()|r forced:', forced, 'init:', self.initialized)
Nenue@9 332 if not self.initialized then
Nenue@9 333 return
Nenue@9 334 end
Nenue@9 335
Nenue@9 336 for i, module in ipairs(self.modules) do
Nenue@0 337 if module.Refresh then
Nenue@33 338 print('|cFF00FF00external '..module:GetName()..':Refresh()|r')
Nenue@33 339 module:Refresh(forced)
Nenue@0 340 end
Nenue@0 341 end
Nenue@0 342 end
Nenue@0 343
Nenue@0 344 -- insert visual options into the tracking button menu
Nenue@0 345 WorldPlan.OnDropDownInitialize = function (self, callback, dropType)
Nenue@0 346 if self ~= WorldMapFrameDropDown then
Nenue@0 347 return
Nenue@0 348 end
Nenue@9 349 local db = WorldPlan.db
Nenue@0 350
Nenue@0 351 local info = UIDropDownMenu_CreateInfo()
Nenue@0 352 info.text = ""
Nenue@0 353 info.isTitle = true
Nenue@0 354 UIDropDownMenu_AddButton(info)
Nenue@0 355 info.text = "|cFF00AAFFWorldPlan|r"
Nenue@0 356 info.isTitle = true
Nenue@0 357 UIDropDownMenu_AddButton(info)
Nenue@0 358 info.isTitle = nil
Nenue@0 359 info.disabled = nil
Nenue@0 360 info.keepShownOnClick = true
Nenue@0 361 info.tooltipOnButton = 1
Nenue@0 362
Nenue@9 363 info.text = "Enable"
Nenue@9 364 info.isNotRadio = true
Nenue@9 365 info.value = "EnablePins"
Nenue@9 366 info.checked = db.EnablePins
Nenue@9 367 info.tooltipTitle = "Enable World Quest Overlays"
Nenue@9 368 info.tooltipText = "Toggle the detail layers here."
Nenue@9 369 info.func = DropDown_OnClick
Nenue@9 370 UIDropDownMenu_AddButton(info)
Nenue@9 371
Nenue@9 372 info.text = "Display All Profession Quests"
Nenue@0 373 info.isNotRadio = true
Nenue@0 374 info.value = "ShowAllProfessionQuests"
Nenue@9 375 info.checked = db.ShowAllProfessionQuests
Nenue@0 376 info.tooltipTitle = "Hidden Quests"
Nenue@0 377 info.tooltipText = "Display work order and profession-related quests that are skipped by the default UI."
Nenue@0 378 info.func = DropDown_OnClick
Nenue@0 379 UIDropDownMenu_AddButton(info)
Nenue@0 380
Nenue@0 381 info.text = "Show Continent Pins"
Nenue@0 382 info.isNotRadio = true
Nenue@0 383 info.value = "DisplayContinentPins"
Nenue@9 384 info.checked = db.DisplayContinentPins
Nenue@0 385 info.tooltipTitle = "Continent Pins"
Nenue@0 386 info.tooltipText = "Display quest pins on the continent map (may get cramped)."
Nenue@0 387 info.func = DropDown_OnClick
Nenue@0 388 UIDropDownMenu_AddButton(info)
Nenue@0 389
Nenue@0 390 info.text = "Show Summary"
Nenue@0 391 info.isNotRadio = true
Nenue@0 392 info.value = "DisplayContinentSummary"
Nenue@0 393 info.tooltipTitle = "Summary Bar"
Nenue@0 394 info.tooltipText = "Display a summary of active world quests. Note: requires directly viewing Broken Isle and Dalaran maps to gain complete info."
Nenue@9 395 info.checked = db.DisplayContinentSummary
Nenue@9 396 info.func = DropDown_OnClick
Nenue@9 397 UIDropDownMenu_AddButton(info)
Nenue@9 398
Nenue@9 399 info.text = "Fade In Groups"
Nenue@9 400 info.isNotRadio = true
Nenue@9 401 info.value = "FadeWhileGrouped"
Nenue@9 402 info.tooltipTitle = "Group Fade"
Nenue@9 403 info.tooltipText = "Reduce pin alpha when grouped, so player dots are easier to see."
Nenue@9 404 info.checked = db.DisplayContinentSummary
Nenue@0 405 info.func = DropDown_OnClick
Nenue@0 406 UIDropDownMenu_AddButton(info)
Nenue@0 407 end
Nenue@0 408
Nenue@30 409 --------------------------------------------------------------------------------------------------------------------
Nenue@30 410 --------------------------------------------------------------------------------------------------------------------
Nenue@30 411
Nenue@0 412
Nenue@0 413 -- data provider manipulations for the taxi map
Nenue@0 414 WorldPlan.OnFlightMapLoaded = function()
Nenue@0 415 if true then return end
Nenue@0 416 -- todo: figure out how to layer inside the map canvas
Nenue@0 417 local res = {}
Nenue@0 418 local t = {}
Nenue@0 419 for k,v in pairs(FlightMapFrame) do
Nenue@0 420 tinsert(res, tostring(k))
Nenue@0 421 end
Nenue@0 422
Nenue@0 423 table.sort(res)
Nenue@0 424 for i, k in ipairs(res) do
Nenue@0 425 print(k)
Nenue@0 426 end
Nenue@0 427 hooksecurefunc(FlightMapFrame, 'RefreshAll', function(self)
Nenue@0 428 print('|cFF0088FFWQDP RefreshAllData ', GetTime())
Nenue@0 429
Nenue@0 430 WorldPlan:GetPinsForMap(self:GetMapID())
Nenue@0 431
Nenue@0 432 for pin in self:EnumerateAllPins() do
Nenue@0 433 if pin.worldQuest then
Nenue@0 434 --print('got pin #', pin.questID)
Nenue@30 435 local wp = self.QuestsByID[pin.questID]
Nenue@0 436 if wp then
Nenue@0 437 wp:ClearAllPoints()
Nenue@0 438 wp:SetParent(FlightMapFrame.ScrollContainer)
Nenue@0 439 wp:SetFrameStrata('MEDIUM')
Nenue@0 440 wp:SetPoint('CENTER', pin, 'CENTER')
Nenue@0 441 wp:Show()
Nenue@0 442 end
Nenue@0 443 end
Nenue@0 444 end
Nenue@0 445 end)
Nenue@0 446 end
Nenue@0 447
Nenue@0 448
Nenue@0 449
Nenue@0 450
Nenue@0 451
Nenue@0 452
Nenue@0 453
Nenue@0 454 SLASH_WORLDPLAN1 = "/worldplan"
Nenue@0 455 SLASH_WORLDPLAN2 = "/wp"
Nenue@0 456 SlashCmdList.WORLDPLAN = function()
Nenue@0 457 print('command pop')
Nenue@0 458 WorldPlan:GetPinsForMap()
Nenue@0 459 WorldPlan:RefreshPins()
Nenue@0 460
Nenue@0 461 SetTimedCallbackForAllPins(0, function(self) self.FadeIn:Play() self.FlashIn:Play() end)
Nenue@0 462 SetTimedCallbackForAllPins(5, function(self) self.PendingFade:Play() end)
Nenue@0 463 SetTimedCallbackForAllPins(8, function(self) self.PendingFade:Stop() end)
Nenue@0 464 end
Nenue@0 465 --%end-debug%