view WorldPlan.lua @ 113:03e4a8b93012 v7.3.0-2

7.3 Updates - Added some map frame adjustments to keep the filter bar from obstructing edge of zone clicks KNOWN ISSUES: - Argus quests can only be filtered when viewing Argus maps - Main Argus map in the WorldMapFrame has misaligned pins; this is due to it using a different map texture from what is used in the teleporter map, so all the POI coordinates are based on that map in the client info
author Nenue
date Tue, 05 Sep 2017 03:14:34 -0400
parents e8b6c5433128
children a4dfdd4f1cf3
line wrap: on
line source
-- WorldPlan.lua
-- Created: 8/16/2016 8:19 AM
-- %file-revision%
local addonFileName, db = ...
local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
local WP_VERSION = "1.4"
local tinsert, pairs, floor = tinsert, pairs, floor
local tremove, ipairs, wipe, unpack = tremove, ipairs, wipe, unpack
local select, type, tostring, tonumber = select, type, tostring, tonumber
local ITEM_QUALITY_COLORS = ITEM_QUALITY_COLORS
local BROKEN_ISLES_ID = 1007
local GetCurrentMapAreaID = GetCurrentMapAreaID
local GetTime, IsLoggedIn = GetTime, IsLoggedIn
local DEBUG_HISTORY = {}
local ofunc = {}

-- Define tables here so the pointers match up
WorldPlanCore = { defaults = {}, modules = {}, TaskQueue = {}, }
WorldPlanPOIMixin = {}
WorldPlanSummaryMixin = {}
db.filtersDirty = true
db.questsDirty = true
db.OrderedModules = {}
db.LoadedModules = {}
db.UsedFilters = {}
db.QuestsByZone = {}
db.QuestsByID = {}
db.TasksByID = {}
db.FreePins = {}
db.UsedPins = {}
db.UpdatedPins = {}
db.ReportChunks = {}
db.Bounties = {}
db.BountiesByQuestID = {}
db.BountiesByFactionID = {}
db.IgnoreTimers = {}
db.CLTriggers = {
  wq = function(arg2, extraArgs)
    if arg2 and WorldPlanQuests[arg2] then
      self:print('WorldPlanQuests:'..arg2..'()')
      WorldPlanQuests[arg2](WorldPlanQuests)
    else
      self:print('WorldPlanQuests:Refresh(true)')
      WorldPlanQuests:Refresh(true)
    end
  end,
  flightmap = function(arg2, extraArgs)
    if not extraArgs then
      return
    end

    local val1, val2, val3 = extraArgs:match("(%S+)%s*(%S*)%s*(%S*)")
    if arg2 == 'scale' then
      if tonumber(val1) and tonumber(val2) and tonumber(val3) then
        db.Config.FlightMapScalingLimits = {tonumber(val1), tonumber(val2), tonumber(val3)}
        self:print('FlightMapFrame scaling limits updated:', unpack(db.Config.FlightMapScalingLimits))
      else
        self:print('FlightMapFrame scaling limits:', unpack(db.Config.FlightMapScalingLimits))
      end
    elseif arg2 == 'alpha' then

      if tonumber(val1) and tonumber(val2) and tonumber(val3) then
        db.Config.FlightMapAlphaLimits = {tonumber(val1), tonumber(val2), tonumber(val3)}
        self:print('FlightMapFrame alpha limits updated:', unpack(db.Config.FlightMapAlphaLimits))
      else
        self:print('FlightMapFrame alpha limits:', unpack(db.Config.FlightMapAlphaLimits))
      end
    end

  end,
  filter = function(arg2)
    if arg2 and WorldPlanSummary[arg2] then
      self:print('WorldPlanSummary:'..arg2..'()')
      WorldPlanSummary[arg2](WorldPlanSummary)
    else
      self:print('WorldPlanSummary:Refresh(true)')
      WorldPlanSummary:Refresh(true)
    end
  end,
  log = function()
    if WorldPlanDebug:IsShown() then
      WorldPlanDebug:SetShown(false)
    else
      WorldPlanDebug:SetShown(true)
    end

  end,
  debug = function()

    if WorldPlanData then
      WorldPlanData.DebugEnabled = (not WorldPlanData.DebugEnabled)
      self:print(WorldPlanData.DebugEnabled and "Debugger on." or "Debugger off.")
    end
  end
}


  -- default color templates
db.DefaultType = {
  a = 1,
  r = 1, g = 1, b = 1,
  x = 0, y = 0,
  desaturated = true,
  pinMask = "Interface\\Minimap\\UI-Minimap-Background",
  rewardMask = "Interface\\Minimap\\UI-Minimap-Background",
  texture = "Interface\\BUTTONS\\YELLOWORANGE64",
  continent = {
    iconWidth = 14,
    borderWidth = 2,
    highlightWidth = 1,
    TagSize = 8,
    maxAlertLevel = 0,
    showNumber = true,
    numberFontObject = 'WorldPlanFont'
  },
  zone = {
    iconWidth = 18,
    borderWidth = 2,
    highlightWidth = 2,
    TagSize = 12,
    maxAlertLevel = 3,
    showNumber = true,
    numberFontObject = 'WorldPlanNumberFontThin'
  },
  minimized = {
    r = 0, g = 0, b = 0, a = 0.1,
    iconWidth = 8,
    borderWidth = 0,
    alpha = 0.5,
    highlightWidth = 0,
    maxAlertLevel = 0,
    NoIcon = true,

    TagSize = 8,
    TimeleftStage = 1,
    showNumber = false,
    alpha = 0.1,
  }
}


db.DefaultConfig = {
  ShowAllProfessionQuests = false,
  DisplayContinentSummary = true,
  DisplayContinentPins = true,
  NotifyWhenNewQuests = true,
  EnablePins = true,
  FadeWhileGrouped = false,
  FlightMapAlphaLimits = {1, .7, 1},
  FlightMapScalingLimits = {1, 1, 1.5},
  --UntrackedColor = {},
  --TrackedColor = {},
  --CriteriaColor = {},
  --RewardColorGold = {},
  --RewardColorReagent = {},
  --RewardColorArtifactPower = {},
  --RewardColorCurrency = {},
  IgnoreTimers = {},
}



-- tracking menu toggler
local DropDown_OnClick = function(self)
  local key = self.value
  if key then
    if WorldPlanData[key] then
      WorldPlanData[key] = nil
    else
      WorldPlanData[key] = true
    end
  end
  _G.WorldPlan:OnConfigUpdate()
end

-- insert visual options into the tracking button menu
local DropDown_Initialize = function  (self, callback, dropType)
  if self ~= WorldMapFrameDropDown then
    return
  end
  local config = WorldPlanData
  local info = UIDropDownMenu_CreateInfo()
  info.text = ""
  info.isTitle = true
  UIDropDownMenu_AddButton(info)
  info.text = "|cFF00AAFFWorldPlan|r"
  info.isTitle = true
  UIDropDownMenu_AddButton(info)
  info.isTitle = nil
  info.disabled = nil
  info.keepShownOnClick = true
  info.tooltipOnButton = 1

  info.text = "Enable"
  info.isNotRadio = true
  info.value = "EnablePins"
  info.checked = config.EnablePins
  info.tooltipTitle = "Enable World Quest Overlays"
  info.tooltipText = "Toggle the detail layers here."
  info.func = DropDown_OnClick
  UIDropDownMenu_AddButton(info)

  info.text = "Display All Profession Quests"
  info.isNotRadio = true
  info.value = "ShowAllProfessionQuests"
  info.checked = config.ShowAllProfessionQuests
  info.tooltipTitle = "Hidden Quests"
  info.tooltipText = "Display work order and profession-related quests that are skipped by the default UI."
  info.func = DropDown_OnClick
  UIDropDownMenu_AddButton(info)

  info.text = "Show Continent Pins"
  info.isNotRadio = true
  info.value = "DisplayContinentPins"
  info.checked = config.DisplayContinentPins
  info.tooltipTitle = "Continent Pins"
  info.tooltipText = "Display quest pins on the continent map (may get cramped)."
  info.func = DropDown_OnClick
  UIDropDownMenu_AddButton(info)

  info.text = "Show Summary"
  info.isNotRadio = true
  info.value = "DisplayContinentSummary"
  info.tooltipTitle = "Summary Bar"
  info.tooltipText = "Display a summary of active world quests. Note: requires directly viewing Broken Isle and Dalaran maps to gain complete info."
  info.checked = config.DisplayContinentSummary
  info.func = DropDown_OnClick
  UIDropDownMenu_AddButton(info)
  --[[

  info.text = "Nudge Pins"
  info.isNotRadio = true
  info.value = "NudgePins"
  info.tooltipTitle = "Pin Nudging"
  info.tooltipText = "Adjust the position of quest pins that overlap."
  info.checked = config.NudgePins
  info.func = DropDown_OnClick
  UIDropDownMenu_AddButton(info)

  info.text = "Fade Whiled Grouped"
  info.isNotRadio = true
  info.value = "FadeWhileGrouped"
  info.tooltipTitle = "Group Fade"
  info.tooltipText = "Reduce pin alpha when grouped, so player dots are easier to see."
  info.checked = config.FadeWhileGrouped
  info.func = DropDown_OnClick
  UIDropDownMenu_AddButton(info)
  --]]
end

local function Handler_UpdateFader(self, sinceLast, isActive)

  if  isActive then
    self.toAlpha = 1
    self.Backdrop:Show()
  else
    self.toAlpha = self.fadeOpacity
    self.Backdrop:Hide()
  end
  local cAlpha = self:GetAlpha()
  if cAlpha ~= self.toAlpha then
    if cAlpha > self.toAlpha then
      cAlpha = cAlpha - sinceLast*4
      if cAlpha <= self.toAlpha then
        cAlpha = self.toAlpha
      end
    else
      cAlpha = cAlpha + sinceLast*4
      if cAlpha >= self.toAlpha then
        cAlpha = self.toAlpha
      end
    end
  end
  self:SetAlpha(cAlpha)
end

function db.print(...)
  for i = 1, select('#', ...) do
    tinsert(db.ReportChunks, tostring(select(i, ...)))
  end
end

function db.log(msg)
  WorldPlanData.Debug = WorldPlanData.Debug or {}
  tinsert(WorldPlanData.Debug, msg)
  tinsert(DEBUG_HISTORY, msg)
  if WorldPlanDebug:IsShown() then
    WorldPlanDebug:Update()
  end
end

WorldPlanDebugMixin = {
  OnLoad = function(self)
    self:SetFont("Interface\\Addons\\Devian\\font\\SourceCodePro-Regular.ttf", 13, 'NORMAL')
    self:SetJustifyH('LEFT')
    self:SetFading(false)
    self:SetMaxLines(2048)
    self.loadedMessages = 0
  end,
  OnShow = function(self)
    if self.loadedMessages < #DEBUG_HISTORY then
      self:Update()
    end
  end,
  Update = function(self)
    for i = self.loadedMessages, #DEBUG_HISTORY do
      self:AddMessage(DEBUG_HISTORY[i])
      self.loadedMessages = i
    end
  end ,
  OnMouseWheel = function(self, delta)

  local up =  delta > 0
  if IsControlKeyDown() then
    if up then self:ScrollToTop()
    else self:ScrollToBottom() end
  elseif IsShiftKeyDown() then
    if up then self:PageUp()
    else self:PageDown() end
  else
    if up then self:ScrollUp()
    else self:ScrollDown() end
  end
end
}


function WorldPlanCore:OnConfigUpdate()
  for _, module in ipairs(db.OrderedModules) do
    if module.OnConfigUpdate then
      module:OnConfigUpdate()
    end
  end
  db.currentMapID = nil
  db.BountyUpdate = true
  self:SetCurrentMap('CONFIG_UPDATE')
  self:Refresh()
end

function WorldPlanCore:print(...) db.print(...) end

function WorldPlanCore:AddHandler (frame)
  if not db.LoadedModules[frame] then
    print('|cFFFFFF00'..self:GetName()..':AddHandler()', frame:GetName(), self.initialized)
    db.LoadedModules[frame] = true
    tinsert(db.OrderedModules, frame)

    if frame.defaults then
      db.DefaultConfig[frame:GetName()] = frame.defaults
    end
    frame.UpdateAlpha = Handler_UpdateFader
    frame.owningFrame = self
  else

    print('|cFFFF4400'..self:GetName()..':AddHandler()', frame:GetName())
  end
end

function WorldPlanCore:OnLoad ()

  self.Types = setmetatable({}, {
    __newindex = function(t, k, v)
      if type(v) == 'table' then
        print('adding owner', k)
        v = setmetatable(v, {
          __newindex = function(t2,k2,v2)
          if type(v2) == 'table' then
            --print('adding type', k2)
            v2 = setmetatable(v2, {__index = function(t3,k3)
              --print('##deferring to default key', k3)
              return db.DefaultType[k3]
            end})
          end
          rawset(t2,k2,v2)
        end})
      end
      rawset(t,k,v)
    end
  })

  self.Types[self] = {}

  for index, color in pairs(ITEM_QUALITY_COLORS) do
    self:AddTypeInfo(self, index, { r = color.r, g = color.g, b = color.b, hex = color.hex, })
  end


  db.print('v'..WP_VERSION)


  self:RegisterEvent("QUESTLINE_UPDATE")
  self:RegisterEvent("QUEST_LOG_UPDATE")
  self:RegisterEvent("WORLD_MAP_UPDATE")
  self:RegisterEvent("SPELLS_CHANGED")
  self:RegisterEvent('PLAYER_ENTERING_WORLD')
  self:RegisterEvent("WORLD_QUEST_COMPLETED_BY_SPELL")
  self:RegisterEvent("SUPER_TRACKED_QUEST_CHANGED")
  self:RegisterEvent("SKILL_LINES_CHANGED")
  self:RegisterEvent("ARTIFACT_XP_UPDATE")
  self:RegisterEvent("ADDON_LOADED")
  self:RegisterEvent("PLAYER_LOGIN")
  --self:SetParent(WorldMapFrame)


  --ofunc[WorldMap_SetupWorldQuestButton] = WorldMap_SetupWorldQuestButton
  --WorldMap_SetupWorldQuestButton = nop
end

function WorldPlanCore:OnShow()
  --print(self:GetName()..':OnShow()')
  --hooksecurefunc(self, 'SetScript', function(...) self:print('|cFFFFFF00'..self:GetName()..':SetScript()|r', ...) end)
end

local BROKEN_ISLE_MAPS = {
  [1007] = true, -- Broken Isle
  [1014] = true, -- Dalaran
  [1021] = true, -- Broken Shoree
  [1024] = true, -- Highmountain
  [1015] = true, -- Azsuna
  [1017] = true, -- Azsuna
  [1018] = true, -- Val'Sharah
  [1033] = true, -- Suramar
  [1077] = true, -- Dreamgrove
  [1096] = true, -- Eye of Azshara
  [1080] = true, -- Thunder Totem
  [1072] = true, -- True Shot Lodge,

}

function WorldPlanCore:SetCurrentMap(event)
  local mapAreaID, isContinent = GetCurrentMapAreaID()
  if not mapAreaID then
    return
  end
  print('SetCurrentMap()', event, mapAreaID)
  local isBrokenIsle = BROKEN_ISLE_MAPS[mapAreaID]

  local mapFileName, textureHeight, textureWidth, isMicroDungeon, microDungeonMapName = GetMapInfo()

  local isMapOpen = WorldMapFrame:IsShown()
  local isNewMap = (mapAreaID ~= db.currentMapID) or (isMapOpen ~= db.isMapOpen) or (db.isMicroDungeon ~= isMicroDungeon) or (db.isContinentMap ~= isContinent)

  db.isMicroDungeon = isMicroDungeon
  db.isMapOpen = isMapOpen
  db.currentMapID = mapAreaID
  db.isContinentMap = isContinent
  db.isBrokenIsle = isBrokenIsle

  for _, module in ipairs(db.OrderedModules) do
    if module.OnMapInfo then
      if module.Debug then
        module:Debug(event)
      end
      print('  |cFF00FFFF'..module:GetName() .. ':OnMapInfo()|r')
      module:OnMapInfo(isBrokenIsle, isContinent, mapAreaID, isNewMap, isMapOpen)
    end
  end
end

function WorldPlanCore:OnEvent (event, ...)

  print('|cFF00FF88'..self:GetName().. ':OnEvent()|r', event, GetTime(), 'init:', self.initialized)
  if event == 'ADDON_LOADED' then

    if IsLoggedIn() and not self.initialized then
      self:Setup()
    end
  else
    if (event == 'WORLD_MAP_UPDATE') or (event == 'PLAYER_ENTERING_WORLD') or (event == 'PLAYER_LOGIN') then
      print('|cFFFF4400currentMapID =', db.currentMapID, ...)
      if event == 'PLAYER_ENTERING_WORLD' then
        -- start from scratch
        db.isMicroDungeon = nil
        db.isMapOpen = nil
        db.currentMapID = nil
        db.isContinentMap = nil
        db.isBrokenIsle = nil
      end


      self:SetCurrentMap(event .. ' ' .. GetTime())
    end
  end
end

function WorldPlanCore:OnNext(func)


  tinsert(self.TaskQueue, func)
  --self:print('|cFF00FF00adding scheduled task #', #self.TaskQueue)
end

function WorldPlanCore:OnUpdate()
  if #self.TaskQueue >= 1 then
    local func = tremove(self.TaskQueue, 1)
    --self:print('|cFF00FF00running scheduled task #', #self.TaskQueue)
    func()
  end

  if self.isStale then
    -- these need to happen in load order
    for i, module in ipairs(db.OrderedModules) do
      if module:IsVisible() and module.isStale then
        print('|cFF00FF00internal '..module:GetName()..':Refresh()|r')
        module:Refresh()
      end
    end
    self.isStale = nil
  end

  if #db.ReportChunks >= 1 then

    DEFAULT_CHAT_FRAME:AddMessage("|cFF0088FF"..addonFileName.."|r: " .. table.concat(db.ReportChunks, ', '))
    wipe(db.ReportChunks)
  end

  if self.dataFlush then
    self:FireCallbacks()
  end

end

function WorldPlanCore:Setup ()
  print('|cFFFFFF00'..self:GetName()..':Setup()|r')

  if not WorldPlanData then
    WorldPlanData = {key = 0}
  end

  -- debug info
  WorldPlanData.key = (WorldPlanData.key or 0) + 1
  WorldPlanData.Debug = WorldPlanData.Debug or {}
  local guid = UnitGUID('player')
  WorldPlanData.IgnoreTimers = WorldPlanData.IgnoreTimers or {}
  WorldPlanData.IgnoreTimers[guid] = WorldPlanData.IgnoreTimers[guid] or {}

  for _, msg in ipairs(WorldPlanData.Debug) do
    tinsert(DEBUG_HISTORY, msg)
  end
  tinsert(DEBUG_HISTORY, '--SESSION BREAK--')
  wipe(WorldPlanData.Debug)

  db.IgnoreTimers = WorldPlanData.IgnoreTimers
  db.Config = WorldPlanData
  for k,v in pairs(db.DefaultConfig) do
    --[===[@non-debug@
    if not db.Config[k] then
      db.Config[k] = v
    end

    --@end-non-debug@]===]
    --@debug@
    db.Config[k] = v
    --@end-debug@
  end


  db.currentMapID = GetCurrentMapAreaID()

  for i, module in ipairs(db.OrderedModules) do
    db.Config[module:GetName()] = db.Config[module:GetName()] or {}
    if module.Setup then module:Setup() end
    if not module.RegisterEvent then
      module.RegisterEvent = self.RegisterEvent
    end
    if module.OnConfigUpdate then
      module:OnConfigUpdate()
    end
  end


  self.initialized = true

  hooksecurefunc("UIDropDownMenu_Initialize", DropDown_Initialize)

  hooksecurefunc("WorldMapTrackingOptionsDropDown_OnClick", function(button)
    print("|cFF0088FFWorldMapTrackingOptionsDropDown_OnClick|r")
    local value = button.value
    if (value == "worldQuestFilterOrderResources" or value == "worldQuestFilterArtifactPower" or
        value == "worldQuestFilterProfessionMaterials" or value == "worldQuestFilterGold" or
        value == "worldQuestFilterEquipment") then
      self:Refresh(true)
    end
  end)


  hooksecurefunc("WorldMapFrame_Update", function()
    print('|cFFFF4400WorldMapFrame_Update|r')
    for _,module in ipairs(db.OrderedModules) do
      if module.OnWorldMapFrameUpdate then
        print('  |cFFFF4400'..module:GetName()..'|r')
        module:OnWorldMapFrameUpdate()
      end
    end
  end)

  SLASH_WORLDPLAN1 = "/worldplan"
  SLASH_WORLDPLAN2 = "/wp"




  SlashCmdList.WORLDPLAN = function(args)
    local arg1, arg2, extraArgs = args:match("(%S+)%s*(%S*)%s*(.*)")

    if db.CLTriggers[arg1] then
      db.CLTriggers[arg1](arg2, extraArgs)
    else
      self:print('Refreshing data.')
      self:Refresh(true)
    end

  end
end

-- registers a template table
function WorldPlanCore:AddTypeInfo(owner, id, info)
  self.Types[owner] = self.Types[owner] or {}
  self.Types[owner][id] = info
  print('Type('..owner:GetName()..')('..id..') = '.. tostring(info))
end

-- recall a template table, with situational details filled in
function WorldPlanCore:GetTypeInfo(owner, typeID)
  local info, extraInfo
  if not owner then
    --print('## deferring to default type list')
  else
    --print('## pulling for', owner:GetName(), 'id =', typeID)
  end

  owner = owner or self
  if (not typeID) or (not self.Types[owner][typeID]) then
    --print('## sending list default')
    info = db.DefaultType
  else
    --print('## sent list definition', typeID)
    info = self.Types[owner][typeID]
  end

  local subType = 'continent'
  if (
    FlightMapFrame
    and FlightMapFrame:IsVisible()
    and FlightMapFrame:IsZoomedIn()
  ) or (
    not db.isContinentMap
  ) or (
    db.useContinentType == false
  ) then
    subType = 'zone'
  end

  return info, info[subType] or db.DefaultType[subType]
end


function WorldPlanCore:Refresh (forced)
  print('|cFFFFFF00'..self:GetName()..':Refresh()|r forced:', forced, 'init:', self.initialized)
  if not self.initialized then
    return
  end

  for i, module in ipairs(db.OrderedModules) do
    if module.Refresh then
      print('|cFF00FF00external '..module:GetName()..':Refresh()|r')
      module:Refresh('WORLDPLAN_REFRESH')
    end
  end

  self.isStale = nil
end


--------------------------------------------------------------------------------------------------------------------
-------------------
function WorldPlanCore:GetQuestPins(zoneID)
  return db.UsedPins
end
function WorldPlanCore:RegisterDataCallback(func)
  self.callbacks = self.callbacks or {}
  self.callbacks[func] = func
end
function WorldPlanCore:FireCallbacks()
  self.callbacks = self.callbacks or {}
  for func in pairs(self.callbacks) do
    func()
  end
  self.dataFlush = nil
end

function WorldPlanCore:SetHook(base, arg1, arg2)
  if type(base) == 'table' then
  end

end




--%end-debug%