view WorldPlan.lua @ 31:d0114b51cdea

WorldPlan: - Reworking filters to utilize newly added CVars - Fleshed out POI tooltip for WQ's associated with Family Familiars - Filter button tooltip includes reward icons - Respond to tracking menu clicks when they change filter options
author Nenue
date Fri, 28 Oct 2016 19:54:00 -0400
parents 8cb750e79952
children e8679ecb48d8
line wrap: on
line source
-- WorldPlan.lua
-- Created: 8/16/2016 8:19 AM
-- %file-revision%

local addonName, db = ...

local ICON_UNKNOWN = "Interface\\ICONS\\inv_misc_questionmark"
local ICON_MONEY = "Interface\\Buttons\\UI-GroupLoot-Coin-Up"

local POI_BORDER_MASK = "Interface\\Minimap\\UI-Minimap-Background"
local POI_BORDER_FILL = "Interface\\BUTTONS\\YELLOWORANGE64"
local POI_BORDER_BLUE = "Interface\\BUTTONS\\GRADBLUE"
local POI_BORDER_RED = "Interface\\BUTTONS\\RedGrad64"
local POI_BORDER_YELLOW = "Interface\\BUTTONS\\YELLOWORANGE64"
local POI_BORDER_GREEN = "Interface\\BUTTONS\\GREENGRAD64"

WorldPlanCore = {
  defaults = {},
  modules = {},
}
WorldPlanQuestsMixin = {
  QuestsByZone = {},
  QuestsByID = {},
  freePins = {},
}
WorldPlanPOIMixin = {}
WorldPlanFilterPinMixin = setmetatable({ QuestsByID = {}, freePins = {} }, {__tostring = function() return 'QuestHandler' end})
local WorldPlanFlightMapMixin = setmetatable({}, {__tostring = function() return 'FlightMapHandler' end})
local WorldQuests = WorldPlanQuestsMixin


local WorldPlan = WorldPlanCore
local QuestPOI = WorldPlanPOIMixin
local FilterPin = WorldPlanFilterPinMixin
local WP_VERSION = "1.0"

local db
local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or function() end
local iprint = DEVIAN_WORKSPACE and function(...) _G.print('ItemScan', ...) end or function() end
local wqprint = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or function() end
local fbprint = DEVIAN_WORKSPACE and function(...) _G.print('FilterBar', ...) end or function() end

local wipe, tremove, tinsert, pairs, floor, tContains = table.wipe, table.remove, table.insert, pairs, floor, tContains
local TQ_GetQuestsForPlayerByMapID = C_TaskQuest.GetQuestsForPlayerByMapID -- This function is not yet documented
local TQ_GetQuestZoneID = C_TaskQuest.GetQuestZoneID
local TQ_IsActive = C_TaskQuest.IsActive
local ITEM_QUALITY_COLORS = ITEM_QUALITY_COLORS
local WorldMap_DoesWorldQuestInfoPassFilters = WorldMap_DoesWorldQuestInfoPassFilters
local QuestMapFrame_IsQuestWorldQuest = QuestMapFrame_IsQuestWorldQuest
local GameTooltip = GameTooltip
local GetItemIcon = GetItemIcon


local GetMapInfo, QuestPOIGetIconInfo = GetMapInfo, QuestPOIGetIconInfo
local GetQuestTagInfo, HaveQuestData =  GetQuestTagInfo, HaveQuestData
local GetNumQuestLogRewards, GetNumQuestLogRewardCurrencies, GetQuestLogRewardMoney = GetNumQuestLogRewards, GetNumQuestLogRewardCurrencies, GetQuestLogRewardMoney
local GetQuestLogRewardInfo, GetQuestLogRewardCurrencyInfo, GetMoneyString = GetQuestLogRewardInfo, GetQuestLogRewardCurrencyInfo, GetMoneyString

local GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID = GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID
local MC_GetNumZones, MC_GetZoneInfo = C_MapCanvas.GetNumZones, C_MapCanvas.GetZoneInfo


-- default color templates
local ARTIFACT_COLOR = ITEM_QUALITY_COLORS[LE_ITEM_QUALITY_ARTIFACT]
local MONEY_COLOR = {hex ='|cFFFFFF00', r=1, g=1, b=0}
local COMMON_COLOR = ITEM_QUALITY_COLORS[LE_ITEM_QUALITY_COMMON]
local DEFAULT_TYPE = {
  a = 1,
  r = 1, g = 1, b = 1,
  x = 0, y = 0,
  desaturated = true,
  pinMask = POI_BORDER_MASK,
  rewardMask = POI_BORDER_MASK,
  texture = POI_BORDER_FILL,
  continent = {
    PinSize = 14,
    Border = 2,
    TrackingBorder = 1,
    TagSize = 6,
    TimeleftStage = 0,
    showNumber = true,
    numberFontObject = 'WorldPlanFont'
  },
  zone = {
    PinSize = 22,
    Border = 3,
    TrackingBorder = 2,
    TagSize = 12,
    TimeleftStage = 3,
    showNumber = true,
    numberFontObject = 'WorldPlanNumberFontThin'
  },
  minimized = {
    PinSize = 6,
    Border = 0,
    TrackingBorder = 1,
    NoIcon = true,
    TimeleftStage = 1,
    showNumber = false,
  }
}




local defaults = {
  ShowAllProfessionQuests = false,
  DisplayContinentSummary = true,
  DisplayContinentPins = true,
  NotifyWhenNewQuests = true,
  EnablePins = true,
  FadeWhileGrouped = true,
}

-- Summary header structure

local REWARD_CASH = WORLD_QUEST_REWARD_TYPE_FLAG_GOLD
local REWARD_ARTIFACT_POWER = WORLD_QUEST_REWARD_TYPE_FLAG_ARTIFACT_POWER
local REWARD_GEAR = WORLD_QUEST_REWARD_TYPE_FLAG_EQUIPMENT
local REWARD_CURRENCY = WORLD_QUEST_REWARD_TYPE_FLAG_ORDER_RESOURCES
local REWARD_REAGENT = WORLD_QUEST_REWARD_TYPE_FLAG_MATERIALS


local LE_QUEST_TAG_TYPE_PVP = LE_QUEST_TAG_TYPE_PVP
local LE_QUEST_TAG_TYPE_PET_BATTLE = LE_QUEST_TAG_TYPE_PET_BATTLE
local LE_QUEST_TAG_TYPE_DUNGEON = LE_QUEST_TAG_TYPE_DUNGEON
local LE_QUEST_TAG_TYPE_PROFESSION = LE_QUEST_TAG_TYPE_PROFESSION
local LE_QUEST_TAG_TYPE_NORMAL = LE_QUEST_TAG_TYPE_NORMAL
WorldPlanCore.BrokenIsleID = BROKEN_ISLES_ID
WorldPlanCore.FilterStyle = POI_FILTER_STYLE

WorldPlanCore.FilterOptions = {}
WorldPlanCore.UsedFilters = {}


-- operating flags
local superTrackedID
local currentMapName
local hasNewQuestPins
local isContinentMap
local numPins = 0
local FilterInclusions = {rewardType = {}, worldQuestType = {}, factionID = {}}
local NotificationTypes = {}
local ZoneInfo = {}
local SummaryHeaders = {}

local FreePins = {}
local NumPinFrames = 1

local hasPendingQuestData
local notifyPlayed
local scanner, wmtt, WorldMapPOIFrame


local tasksQueue = {}
local function OnNext (func)
  if #tasksQueue == 0 then
    _G.WorldPlan:SetScript('OnUpdate', function()
      local func = tremove(tasksQueue, 1)
      if func then
        func()
      end
      if #tasksQueue == 0 then
        _G.WorldPlan:SetScript('OnUpdate', nil)
      end
    end)
  end
  print('inserting task #', #tasksQueue+1, func)
  tinsert(tasksQueue, func)
end

-- update a masked texture without messing up its blending mask
local SetMaskedTexture = function(region, file, mask)
  mask = mask or POI_BORDER_MASK
  region:SetMask(nil)
  region:SetTexture(file)
  region:SetMask(mask)
end

-- 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
  WorldPlan:RefreshAll()
end

function WorldPlan:print(...)
  local msg
  for i = 1, select('#', ...) do
    msg = (msg and (msg .. ' ') or '') .. tostring(select(i, ...))
  end
  DEFAULT_CHAT_FRAME:AddMessage("|cFF0088FFWorldPlan|r: " .. msg)
end

local current_type_owner
function WorldPlan:AddHandler (frame, defaults)
  print('|cFFFFFF00'..self:GetName()..':AddHandler()', frame:GetName())
  tinsert(self.modules, frame)
  self.defaults[frame] = defaults
  frame.GetTypeInfo = function(frame, typeID)
    return self:GetTypeInfo(frame, typeID)
  end
end

function WorldPlan: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  DEFAULT_TYPE[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

  WorldPlan = self
  scanner = _G.WorldPlanTooltip
  wmtt = _G.WorldMapTooltip
  WorldMapPOIFrame = _G.WorldMapPOIFrame

  WorldPlan:print('v'..WP_VERSION)

  self:RegisterEvent("QUESTLINE_UPDATE")
  self:RegisterEvent("QUEST_LOG_UPDATE")
  self:RegisterEvent("WORLD_MAP_UPDATE")
  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:SetParent(WorldMapFrame)
end

function WorldPlan:OnShow()
  print(self:GetName()..':OnShow()')
  if self.isStale then
    self:Refresh()
  end

end

function WorldPlan:OnEvent (event, ...)
  print()
  print(event, self.initialized)
  if event == 'ADDON_LOADED' then
    local addon = ...
    if addon == "Blizzard_FlightMap" then
      print('do mixin junk')
      self.OnFlightMapLoaded()

    end
    if IsLoggedIn() and not self.initialized then
      self:Setup()
    end
  elseif event == 'WORLD_MAP_UPDATE' then
    self.currentMapID = GetCurrentMapAreaID()
    print('|cFFFF4400currentMapID =', self.currentMapID)
    self:Refresh()
  else
    for i, module in ipairs(self.modules) do
      if module.OnEvent then
        print('forwarding to', tostring(module))
        module:OnEvent(event, ...)
      end
    end
  end
end


function WorldPlan:Setup ()
  if not WorldPlanData then
    WorldPlanData = {key = 0 }
  end
  WorldPlanData.key = (WorldPlanData.key or 0) + 1
  self.db = WorldPlanData
  self.db.WorldQuests = self.db.WorldQuests or {}
  db = self.db
  for k,v in pairs(defaults) do
    --[===[@non-debug@
    if not db[k] then
      db[k] = v
    end

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

  self.currentMapID = GetCurrentMapAreaID()

  for i, module in ipairs(self.modules) do
    module.db = self.db
    if module.Setup then module:Setup() end
    if not module.RegisterEvent then
      module.RegisterEvent = self.RegisterEvent
    end
  end
  self.initialized = true

  hooksecurefunc("UIDropDownMenu_Initialize", self.OnDropDownInitialize)
end

function WorldPlan:AddTypeInfo(owner, id, info)
  self.Types[owner] = self.Types[owner] or {}
  self.Types[owner][id] = info
  print('Type('..owner:GetName()..')('..id..') = '.. tostring(info))
end

function WorldPlan: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 = DEFAULT_TYPE
  else
    --print('## sent list definition', typeID)
    info = self.Types[owner][typeID]
  end

  if isContinentMap then
    extraInfo = info.continent
    --print('### continent subtype', extraInfo)
  else
    extraInfo = info.zone

    --print('### zone subtype', extraInfo)
  end
  return info, extraInfo
end

do
  local timeStates = {
    {maxSeconds = 60,
      r=1, g=0.25, b =0, format = function (minutes) return '|cFFFF4400'.. minutes .. 'm' end,
    },
    {maxSeconds = 240,
      r=1, g=.5, b=0, format = function(minutes) return '|cFFFF4400'.. floor(minutes/60) .. 'h' end,
    },
    {maxSeconds = 1440,
      r=1, g=1, b=0, format = function(minutes) return '|cFFFFFF00'.. floor(minutes/60) .. 'h' end,
    },
    {maxSeconds = 10081,
      r=0, g=1, b=0,
    }, -- 7 days + 1 minute
  }
  -- Generates a timeleft string
  function WorldPlan:GetTimeInfo(timeLeft, limit)
    limit = limit or #timeStates
    for index = 1, limit do
      local state = timeStates[index]
      if timeLeft <= state.maxSeconds then
        local text
        if state.format then
          text = state.format(timeLeft)
        end
        return text, index
      end
    end
    return nil, nil
  end
end

function WorldPlan: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(self.modules) do
    if module.Reset then
      print(module, 'Reset()')
      module:Reset()
    end
  end

  for i, module in ipairs(self.modules) do
    if module.Refresh then
      print(module, 'Refresh()')
      module:Refresh()
    end
  end

  for i, module in ipairs(self.modules) do
    if module.Cleanup then
      print(module, 'Cleanup()')
      module:Cleanup()
    end
  end
end
function WorldPlan:UpdateAnchors ()
  for i, module in ipairs(self.modules) do
    if module.UpdateAnchors then
      module:UpdateAnchors()
    end
  end
end

-- insert visual options into the tracking button menu
WorldPlan.OnDropDownInitialize = function  (self, callback, dropType)
  if self ~= WorldMapFrameDropDown then
    return
  end
  local db = WorldPlan.db

  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 = db.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 = db.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 = db.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 = db.DisplayContinentSummary
  info.func = DropDown_OnClick
  UIDropDownMenu_AddButton(info)

  info.text = "Fade In Groups"
  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 = db.DisplayContinentSummary
  info.func = DropDown_OnClick
  UIDropDownMenu_AddButton(info)
end

--------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------

local PinBaseIndex = 1600
local BROKEN_ISLES_ID, DALARAN_ID, AZSUNA_ID, VALSHARAH_ID, HIGHMOUNTAIN_ID, STORMHEIM_ID, SURAMAR_ID, EOA_ID = 1007, 1014, 1015,1018, 1024, 1017, 1033, 1096

-- maps where we do our own anchors
local CONTINENT_MAPS = { [BROKEN_ISLES_ID] = BROKEN_ISLES_ID, }
local WORLD_QUEST_MAPS = { [DALARAN_ID] = 'Dalaran70',  [AZSUNA_ID] = 'Azsuna',  [VALSHARAH_ID] = "Val'sharah",
  [HIGHMOUNTAIN_ID] = 'Highmountain', [STORMHEIM_ID] = 'Stormheim',  [SURAMAR_ID] = 'Suramar',  [EOA_ID] = 'EyeOfAszhara', }

function WorldQuests:Setup()


  for mapID, mapName in pairs(WORLD_QUEST_MAPS) do
    self.QuestsByZone[mapID] = {}
  end


  -- refresh positions any time blizzard does so (i.e. mousewheel zoom)
  hooksecurefunc("WorldMapScrollFrame_ReanchorQuestPOIs", function()
    self:Refresh(true)
  end)

  -- hide the original world quest POIs
  hooksecurefunc("WorldMap_UpdateQuestBonusObjectives", function()
    for i = 1, NUM_WORLDMAP_TASK_POIS do
      local button = _G['WorldMapFrameTaskPOI'..i]
      if button and button.worldQuest then
        button:Hide()
      end
    end
  end)

  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)
end

local defaults = {}
function WorldQuests:OnLoad()
  print('|cFF00FF88'..self:GetName()..':OnLoad')

  WorldPlan:AddHandler(self, defaults)

  local rgbWhite = {1, 1, 1}
  WorldPlan:AddTypeInfo(self, REWARD_REAGENT, { r = 0, g = 1, b = 1 })
  WorldPlan:AddTypeInfo(self, REWARD_ARTIFACT_POWER, { r = 1, g = .25, b = .5, hasNumeric = true, numberRGB = rgbWhite })
  WorldPlan:AddTypeInfo(self, REWARD_GEAR, { r = .1, g = .2, b = 1 })
  WorldPlan:AddTypeInfo(self, REWARD_CURRENCY, { r = 1, g = 1, b = 0, hasNumeric = true, numberRGB = {1,1,0}, })
  WorldPlan:AddTypeInfo(self, REWARD_CASH, { r = 0, g = 0, b = 0, })

  for areaID, fileName in pairs(WORLD_QUEST_MAPS) do
    self.QuestsByZone[areaID] = {}
  end

  self:RegisterEvent('QUEST_LOG_UPDATE')
  self:RegisterEvent('WORLD_QUEST_COMPLETED_BY_SPELL')
  self:RegisterEvent('SKILL_LINES_CHANGED')


end

function WorldQuests:OnEvent (event, ...)
  local print = wqprint
  print('|cFFFFFF00'..self:GetName()..':OnEvent()'..event..'|r', GetTime(), ...)
  if event == 'QUEST_LOG_UPDATE' then
    local questID, added = ...
    if questID and added then
      local questPOI = self:AcquirePin(questID)
      self.hasUpdate, self.isPending = questPOI:RefreshData()
    else
      self:RefreshData()
    end
    print('WorldMapFrame', WorldMapFrame:IsVisible(), 'hasUpdates:', self.hasUpdate)
  elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then
    local questID = ...
    if questID and self.QuestsByID[questID] then
      self:ReleasePin(self.QuestsByID[questID])
    end
  elseif event == 'SKILL_LINES_CHANGED' then
    self.hasUpdate = true
  end
end

function WorldQuests:OnUpdate()
  if self.hasUpdate then
    wqprint('|cFF00FF00pushing update')
    self:Refresh(true)
  end
end

local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation
function WorldQuests:AcquirePin (questID, mapID)
  local pin = self.QuestsByID[questID]
  local isNew = false
  if not pin then
    isNew = true
    local numFree = #self.freePins
    if numFree >= 1 then
      pin = tremove(self.freePins, numFree)
      --print('|cFF00FF00Re-using', pin:GetName())
    else
      local name = 'WorldPlanQuestMarker' .. NumPinFrames
      --print('|cFF00FF00Creating', name)
      pin = CreateFrame('Frame', name, WorldMapPOIFrame, 'WorldPlanQuestPin')

      pin:SetFrameStrata('HIGH')
      pin.GetTypeInfo = function(frame, typeID)
        return self:GetTypeInfo(typeID)
      end
      NumPinFrames = NumPinFrames + 1
      --pin.iconBorder:SetVertexColor(0,0,0,1)
    end
    pin:SetID(questID)
    pin.isNew = true
    pin.currentWidth = nil

    -- used by TaskPOI_x scripts
    pin.questID = questID
    pin.worldQuest = true

    self.QuestsByID[questID] = pin
  else
    --print('|cFF00FF00Using', pin:GetName())
  end
  mapID = mapID or TQ_GetQuestZoneID(questID)
  self.QuestsByZone[mapID][questID] = pin

  return pin, isNew
end

-- remove from index and add it to the recycling heap
function WorldQuests:ReleasePin (pin)

  local id = pin.questId
  if id then
    self.QuestsByID[id] = nil
    for i, zone in pairs(self.QuestsByZone) do
      print('-', i, zone[i])
      zone[id] = nil
    end
  end
  pin:Hide()
  pin:ClearAllPoints()
  tinsert(self.freePins, pin)
  print('|cFFFF4400Clearing out', pin:GetName(),id)
end

-- create of update quest pins for a map and its underlying zones
function WorldQuests:RefreshData (mapID)
  local print = wqprint
  mapID = mapID or GetCurrentMapAreaID()
  superTrackedID = GetSuperTrackedQuestID()
  if not mapID then
    -- info not available yet
    return
  end

  print('|cFF00FF88'..self:GetName()..':RefreshData()|r', 'map:', mapID, 'realMap:', GetCurrentMapAreaID())

  if mapID == BROKEN_ISLES_ID then
    self.hasUpdate = false
    print('|cFF00FFFFContinent:|r', mapID, GetMapNameByID(mapID), superTrackedID)
    self.fullSearch = true
    for i = 1, MC_GetNumZones(mapID) do
      local submapID, name, depth = MC_GetZoneInfo(mapID, i)
      self:RefreshData(submapID)
    end
    self.fullSearch = nil
  elseif self.QuestsByZone[mapID] then
    local taskInfo = TQ_GetQuestsForPlayerByMapID(mapID)
    local numQuests = 0
    if taskInfo and #taskInfo >= 1 then
      print('|cFF00FFFF  Zone:|r', mapID, GetMapNameByID(mapID), #taskInfo)
      wipe(self.QuestsByZone[mapID])
      ZoneInfo[mapID] = taskInfo
      qprint('|cFFFF4400START of', GetMapNameByID(mapID))
      for taskID, info in pairs(taskInfo) do
        local questID = info.questId
        info.mapID = mapID
        local questPOI = self:AcquirePin(questID, mapID)
        local hasUpdate, isPending = questPOI:RefreshData(info)
        self.hasUpdate = (self.hasUpdate or hasUpdate)
        self.isPending = (self.isPending or isPending)
        numQuests = numQuests + 1
      end
      qprint('|cFFFF4400END of', GetMapNameByID(mapID))
    end
  end

  if not self.fullSearch then
    print('  hasUpdate:', self.hasUpdate, 'isPending:', self.isPending, 'timer:', (self.OnNext and 'waiting' or ''))

  end

end

function WorldQuests:Refresh(forced)
  local print = wqprint
  print('|cFF00FF88'..self:GetName()..':Refresh()|r')
  if not WorldMapPOIFrame:IsVisible() then
    return
  end
  if forced then
    self:Reset()
  end
  self:UpdateAnchors()

  if forced then
    self:Cleanup ()
  end
end

-- prepares elements for a map update
function WorldQuests:Reset ()
  local print = wqprint
  print('|cFF00FF88'..self:GetName()..':Reset()|r')
  for questID, pin in pairs(self.QuestsByID) do
    pin.used = nil
  end
end

-- update visibility states of all pins
function WorldQuests:UpdateAnchors (submapID)
  local print = wqprint
  local db = WorldPlan.db
  local mapFileName, textureHeight, textureWidth, isMicroDungeon, microDungeonMapName = GetMapInfo()
  if isMicroDungeon then
    return
  end

  local currentMap = GetCurrentMapAreaID()
  local submapID = submapID or currentMap

  if submapID == BROKEN_ISLES_ID and (not db.DisplayContinentPins) then
    print('not updating map for reasons')
    return
  end
  print('|cFF88FF00'..self:GetName()..':UpdateAnchors|r', submapID, GetMapNameByID(submapID), 'pin count:', numPins)
  local numZones = MC_GetNumZones(submapID)
  if numZones then
    for i = 1, numZones do
      local subMapID = MC_GetZoneInfo(submapID, i)
      self:UpdateAnchors(subMapID)
    end
  end
  local pins = self.QuestsByZone[submapID]

  if pins then
    local hostFrame = WorldMapPOIFrame
    local mapWidth, mapHeight = hostFrame:GetSize()
    for questID, pin in pairs(pins) do
      pin:IsShowable()
      if pin.used then
        pin.hasUpdate = true
        pin:SetFrameLevel(PinBaseIndex+ (pin.whiteListed and 200 or 0) +numPins)
        print('level', PinBaseIndex+ (pin.whiteListed and 200 or 0) +numPins)
        pin:SetAnchor(WorldMapPOIFrame, currentMap, mapWidth, mapHeight)
        numPins = numPins + 1
      end
    end
  end
end

-- shows, animates, or hides pins based on their current visibility flags
local debug_show = {}
local debug_animate = {}
local debug_hide = {}
function WorldQuests:Cleanup ()
  local print = wqprint
  local showQuestPOI = db.EnablePins
  print('|cFFFFFF00'..tostring(self)..':Cleanup()|r')
  local mapID = GetCurrentMapAreaID()
  isContinentMap = (mapID == BROKEN_ISLES_ID)

  wipe(debug_show)
  wipe(debug_animate)
  wipe(debug_hide)
  -- continent or zone sizing
  local fadeGrouped = (db.FadeWhileGrouped and IsInGroup())

  numPins = 0
  for questID, pin in pairs(self.QuestsByID) do
    -- can we show it?
    if showQuestPOI and (pin.used) then

      pin.hasUpdate = true
      if fadeGrouped then
        pin:SetAlpha(0.25)
      else
        pin:SetAlpha(1)
      end
      -- is it a new quest?
      if pin.isNew then
        if not pin.isAnimating then
          pin.isAnimating = true
          OnNext(function()
            pin:ShowNew()
          end)
          if not notifyPlayed then
            for k,v in pairs(NotificationTypes) do
              if v[pin[k]] then
                notifyPlayed = true
                PlaySoundKitID(23404)
              end
            end
          end
          tinsert(debug_animate,questID)
        else

          print('animating? ', questID, 'filtered:', pin.filtered)
        end
        -- trap new but animating pins here
      else
        -- hard show existing pin
        print('refresh #', questID, 'filtered:', pin.filtered, 'hasUpdate', pin.hasUpdate)
        pin:Show()
        tinsert(debug_show,questID)
      end
    else
      if pin:IsShown() then
        tinsert(debug_hide,questID)
      end
      pin.isAnimating = nil
      pin.FadeIn:Stop()
      pin:Hide()
    end
  end
  print('   adding:', table.concat(debug_animate, ',' ))
  print('  refresh:', table.concat(debug_show, ',' ))
  print('  hiding:', table.concat(debug_hide, ',' ))
  hasNewQuestPins = nil
  notifyPlayed = nil
  self.hasUpdate = nil
end

-- data provider manipulations for the taxi map
WorldPlan.OnFlightMapLoaded = function()
  if true then return end
  -- todo: figure out how to layer inside the map canvas
  local res = {}
  local t = {}
  for k,v in pairs(FlightMapFrame) do
    tinsert(res, tostring(k))
  end

  table.sort(res)
  for i, k in ipairs(res) do
    print(k)
  end
  hooksecurefunc(FlightMapFrame, 'RefreshAll', function(self)
    print('|cFF0088FFWQDP RefreshAllData ', GetTime())

    WorldPlan:GetPinsForMap(self:GetMapID())

    for pin in self:EnumerateAllPins() do
      if pin.worldQuest then
        --print('got pin #', pin.questID)
        local wp = self.QuestsByID[pin.questID]
        if wp then
          wp:ClearAllPoints()
          wp:SetParent(FlightMapFrame.ScrollContainer)
          wp:SetFrameStrata('MEDIUM')
          wp:SetPoint('CENTER', pin, 'CENTER')
          wp:Show()
        end
      end
    end
  end)
end



local throttle = 0
local tooltip = CreateFrame ("GameTooltip", "VeneerWorldQuestsScanner", nil, "GameTooltipTemplate")
local tooltipLine1 = _G['VeneerWorldQuestsScannerTextLeft1']
local tooltipLine3 = _G['VeneerWorldQuestsScannerTextLeft3']
local GetTime, mod = GetTime, mod




function WorldQuests:FilterCheckByID(questID)
  local pin = WorldQuests:GetPinByQuestID(questID)
  return pin:IsShowable()
end


function QuestPOI:IsShowable ()
  local print = wqprint
  local db = WorldPlan.db
  local qType = self.worldQuestType
  local rType = self.rewardType
  self.filtered = nil
  self.used = true

  print('  |cFFFF4400IsShowable()|r', self.title)

  self.questId  = self:GetID()
  if not (WorldMap_DoesWorldQuestInfoPassFilters(self)) then
    self.filtered = true
  end

  for filterKey, value in pairs(WorldPlan.UsedFilters) do
    if self[filterKey] ~= value then
      self.filtered = true
    end
  end

  if not TQ_IsActive(self.questID) then
    self.used = nil
  elseif qType == LE_QUEST_TAG_TYPE_PROFESSION then
    if not (db.ShowAllProfessionQuests or (self.tradeskillLineIndex and GetProfessionInfo(self.tradeskillLineIndex))) then
      self.used = nil
    end
  end
  return self.used, self.filtered
end

function QuestPOI:UpdateTimer (timeLeft, timeType)
  print('|cFF0088FFUpdatePinTimer()|r')
end

--- Fixes icons upon size update
function QuestPOI:UpdateSize (style, subStyle)
  style = style or self.style
  subStyle = subStyle or self.subStyle

  --qprint('|cFF00FF88'..self:GetName()..'|r:UpdateSize()', style, subStyle)

  self.currentWidth = subStyle.PinSize
  self.borderSize = subStyle.Border
  self.trackingBorderSize = subStyle.TrackingBorder
  self.tagSize = subStyle.TagSize
  self.TimeleftStage = subStyle.TimeleftStage
  self.NoIcon = subStyle.NoIcon


  self:SetSize(self.currentWidth, self.currentWidth)

  local icon = self.icon
  local iconBorder = self.iconBorder
  local trackingBorder = self.supertrackBorder
  local tag = self.tagIcon
  local pinMask = style.pinMask
  local rewardMask = style.rewardMask

  if self.NoIcon then
    self.icon:Hide()
  else
    self.icon:Show()
    icon:SetMask(nil)
    icon:SetMask(rewardMask)
    icon:SetTexture(self.icon:GetTexture())
  end
  iconBorder:SetMask(nil)
  trackingBorder:SetMask(nil)


  local borderWidth = self.borderSize
  local trackingWidth = self.trackingBorderSize

  iconBorder:ClearAllPoints()
  iconBorder:SetPoint('BOTTOMLEFT', self, 'BOTTOMLEFT', -borderWidth + (style.x or 0), -borderWidth + (style.y or 0))
  iconBorder:SetPoint('TOPRIGHT', self, 'TOPRIGHT', borderWidth + (style.x or 0), borderWidth + (style.y or 0))

  trackingBorder:ClearAllPoints()
  trackingBorder:SetPoint('BOTTOMLEFT', iconBorder, 'BOTTOMLEFT', -trackingWidth, -trackingWidth)
  trackingBorder:SetPoint('TOPRIGHT', iconBorder, 'TOPRIGHT', trackingWidth, trackingWidth)

  if self.tagSize then
    tag:Show()
    tag:ClearAllPoints()
    tag:SetPoint('BOTTOMRIGHT', self, 'BOTTOMRIGHT', borderWidth, -borderWidth)
  else
    tag:Hide()
  end

  --qprint('using mask:', mask, self.name )
  iconBorder:SetMask(pinMask)
  trackingBorder:SetMask(pinMask)


end



--%debug%
local SetTimedCallbackForAllPins = function(seconds, callback)
  C_Timer.After(seconds, function()
    for id, pin in pairs(WorldPlanQuests.QuestsByID) do
      callback(pin)
    end
  end)
end

SLASH_WORLDPLAN1 = "/worldplan"
SLASH_WORLDPLAN2 = "/wp"
SlashCmdList.WORLDPLAN = function()
  print('command pop')
  WorldPlan:GetPinsForMap()
  WorldPlan:RefreshPins()

  SetTimedCallbackForAllPins(0, function(self) self.FadeIn:Play() self.FlashIn:Play()  end)
  SetTimedCallbackForAllPins(5, function(self) self.PendingFade:Play() end)
  SetTimedCallbackForAllPins(8, function(self) self.PendingFade:Stop() end)
end
--%end-debug%