changeset 0:3830a592cb0f

initial commmit
author Nenue
date Tue, 11 Oct 2016 17:20:37 -0400
parents
children 232617b8bcd5
files WorldPlan.lua WorldPlan.toc WorldPlan.xml
diffstat 3 files changed, 1587 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WorldPlan.lua	Tue Oct 11 17:20:37 2016 -0400
@@ -0,0 +1,1410 @@
+-- Veneer
+-- WorldPlan.lua
+-- Created: 8/16/2016 8:19 AM
+-- %file-revision%
+--[[
+ Summary:
+ Adds reward icons to the world quest POI markers, and adds said markers to the continent map.
+
+ Issues:
+ Dalaran quests aren't visible until that map has been specifically viewed by the player.
+--]]
+
+WorldPlanCore = {}
+WorldPlanPOIMixin = {}
+WorldPlanFilterPinMixin = {}
+local WorldPlanFlightMapMixin = setmetatable({}, {__tostring = function() return 'FlightMapHandler' end})
+local WorldQuests = setmetatable({ QuestsByID = {}, freePins = {} }, {__tostring = function() return 'QuestHandler' end})
+local FilterBar = setmetatable({ SummaryHeaders = {} },  {__tostring = function() return 'FilterBar' end})
+
+local WorldPlan = WorldPlanCore
+local QuestPOI = WorldPlanPOIMixin
+local FilterPin = WorldPlanFilterPinMixin
+local WP_VERSION = "1.0"
+
+local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
+local wipe, tremove, tinsert, pairs, floor, tContains = table.wipe, table.remove, table.insert, pairs, floor, tContains
+local TQ_GetQuestInfoByQuestID = C_TaskQuest.GetQuestInfoByQuestID -- Return the name of a quest with a given ID
+local TQ_GetQuestsForPlayerByMapID = C_TaskQuest.GetQuestsForPlayerByMapID -- This function is not yet documented
+local TQ_GetQuestTimeLeftMinutes = C_TaskQuest.GetQuestTimeLeftMinutes
+local TQ_RequestPreloadRewardData = C_TaskQuest.RequestPreloadRewardData
+local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation
+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
+
+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 SearchFaction
+local PinBaseIndex = 1600
+local ShowAllProfessionQuests = false
+local DisplayContinentSummary = true
+local DisplayContinentPins = true
+local NotifyWhenNewQuests = true
+local PinRewardFilter, PinTagFilter
+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', }
+-- 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]
+
+-- operating flags
+local superTrackedID
+local currentMapName
+local hasNewQuestPins
+local isContinentMap
+
+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"
+
+local REWARD_CASH = 1001
+local REWARD_ARTIFACT_POWER = 1002
+local REWARD_GEAR = 1003
+local REWARD_CURRENCY = 1004
+local REWARD_ITEM = 1005
+local REWARD_REAGENT = 1006
+
+local POI_DEFAULT_TYPE = {
+  a = 1,
+  r = 1, g = 1, b = 1,
+  x = 0, y = 0,
+  desaturated = true,
+  mask = POI_BORDER_MASK,
+  texture = POI_BORDER_FILL,
+  continent = {
+    PinSize = 18,
+    Border = 3,
+    TrackingBorder = 2,
+    TagSize = 6,
+    TimeleftStage = 3,
+  },
+  zone = {
+    PinSize = 22,
+    Border = 3,
+    TrackingBorder = 2,
+    TagSize = 12,
+    TimeleftStage = 3,
+  },
+  minimized = {
+    PinSize = 4,
+    Border = 1,
+    TrackingBorder = 2,
+    NoIcon = true,
+    TimeleftStage = 1,
+  }
+}
+local POI_REWARD_TYPE =  setmetatable({}, {
+  __newindex = function(t, k, v)
+    if type(v) == 'table' then
+      setmetatable(v, {__index = POI_DEFAULT_TYPE})
+    end
+    rawset(t,k,v)
+  end
+})
+local POI_FILTER_STYLE = setmetatable({
+  continentBorder = 2,
+  zoneBorder = 2,
+}, {__index = POI_DEFAULT_TYPE})
+
+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
+
+-- Pin color/display variables
+POI_REWARD_TYPE[REWARD_ITEM] = {
+  r = 1, g = 1, b = 1,
+}
+POI_REWARD_TYPE[REWARD_REAGENT] = {
+  r = 0, g = 1, b = 1,
+}
+POI_REWARD_TYPE[REWARD_ARTIFACT_POWER] = {
+  r = 1, g = .25, b = .5,
+  showNumber = true,
+}
+POI_REWARD_TYPE[REWARD_GEAR] = {
+  r = .1, g = .2, b = 1,
+}
+POI_REWARD_TYPE[REWARD_CURRENCY] = {
+  r = 1, g = 1, b = 0,
+}
+POI_REWARD_TYPE[REWARD_CASH] = {
+  r = 0, g = 0, b = 0,
+  --x = 0, y = -1,
+  --mask = ICON_MONEY,
+  --continentBorder = 1,
+  --zoneBorder = 1,
+}
+
+
+-- Summary header structure
+local POI_FILTER_OPTIONS = {
+  { label = 'Filters', texture = "Interface\\WorldMap\\WorldMap-Icon" },
+  { filterKey= 'rewardType', filterValue = REWARD_ARTIFACT_POWER, label = 'Artifact Power', texture = "Interface\\ICONS\\inv_7xp_inscription_talenttome01" },
+  { filterKey= 'rewardType', filterValue = REWARD_CURRENCY,label = 'Currency', texture = "Interface\\ICONS\\inv_misc_elvencoins" },
+  { filterKey= 'rewardType', filterValue = REWARD_ITEM, label = 'Item', texture = "Interface\\ICONS\\inv_crate_01" },
+  { filterKey= 'rewardType', filterValue = REWARD_GEAR, label = 'Equipment', texture = "Interface\\ICONS\\garrison_bluearmorupgrade" },
+  { filterKey= 'rewardType', filterValue = REWARD_REAGENT, label = 'Reagents', texture = 1417744 },
+  { filterKey= 'rewardType', filterValue = REWARD_CASH, label = 'Reagents', texture = ICON_MONEY },
+  { filterKey= 'worldQuestType', filterValue = LE_QUEST_TAG_TYPE_PVP, label = 'PvP', texture = "Interface\\Icons\\Ability_PVP_GladiatorMedallion", spacing = 10 },
+  { filterKey= 'worldQuestType', filterValue = LE_QUEST_TAG_TYPE_PET_BATTLE, label = 'Pet Battle', texture = "Interface\\Icons\\PetJournalPortrait", },
+  { filterKey= 'worldQuestType', filterValue = LE_QUEST_TAG_TYPE_DUNGEON, label = 'Dungeon', texture = "Interface\\LFGFRAME\\UI-LFR-PORTRAIT", },
+}
+
+local PIN_TIME_CONTEXT = {
+  {max = 60,
+    r=1, g=0.25, b =0, format = function (minutes) return '|cFFFF4400'.. minutes .. 'm' end,
+    continentAlpha = 1, swipeTime = 1440,
+  },
+  {max = 240,
+    r=1, g=.5, b=0, format = function(minutes) return '|cFFFF4400'.. floor(minutes/60) .. 'h' end,
+    continentAlpha = 1, swipeTime = 1440,
+  },
+  {max = 1440,
+    r=1, g=1, b=0, format = function(minutes) return '|cFFFFFF00'.. floor(minutes/60) .. 'h' end,
+    continentAlpha = .55, swipeTime = 1440
+  },
+  {max = 10081,
+    r=0, g=1, b=0,
+    continentAlpha = .3,
+  }, -- 7 days + 1 minute
+}
+
+local numPins = 0
+local QuestsByZone = {}
+local QuestsByFaction = {}
+local QuestsByReward = {}
+local QuestsByTag = {}
+local QuestsByID = {}
+local QuestPositions = {}
+local FilterInclusions = {rewardType = {}, worldQuestType = {}}
+local FilterExclusions = {rewardType = {}, worldQuestType = {}}
+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
+  tinsert(tasksQueue, func)
+end
+
+-- combines templates
+local function DoMixins(frame,...)
+  for i = 1, select('#', ...) do
+    for k,v in pairs(select(i,...)) do
+      frame[k] = v
+    end
+  end
+  return frame
+end
+
+-- use tooltip object to extract item details
+local ParseItemReward = function(questID)
+  local rewardType = REWARD_ITEM
+  local name, icon, quantity, quality, _, itemID = GetQuestLogRewardInfo(1, questID)
+  if not itemID then
+    return REWARD_ITEM
+  end
+
+  scanner:SetOwner(WorldPlan, "ANCHOR_NONE")
+  scanner:SetItemByID(itemID)
+  local ttl1 = _G['WorldPlanTooltipTextLeft1']
+  local ttl2 = _G['WorldPlanTooltipTextLeft2']
+  local ttl3 = _G['WorldPlanTooltipTextLeft3']
+  local ttl4 = _G['WorldPlanTooltipTextLeft4']
+  if ttl2 then
+    local text = ttl2:GetText()
+    -- Artifact Power
+    if text and text:match("|cFFE6CC80") then
+      --print('AP token!', text)
+      local power
+      if ttl4 then
+        local text = ttl4:GetText()
+        --print('tip line 4', text)
+        if text then
+          power = text:gsub("%p", ""):match("%d+")
+          power = tonumber(power)
+        end
+
+      end
+      rewardType = REWARD_ARTIFACT_POWER
+      icon = "Interface\\ICONS\\inv_7xp_inscription_talenttome01"
+      quantity = power
+    elseif text and text:match("Item Level") then
+      --print('equipment!', text)
+      rewardType = REWARD_GEAR
+      quantity = text:match("Item Level ([%d\+]+)")
+    end
+  end
+  if ttl3 then
+    local text = ttl3:GetText()
+    -- Crafting Reagent
+    if text and text:match("Crafting Reagent") then
+      --print('reagent', text)
+      rewardType = REWARD_REAGENT
+    end
+  end
+  iprint('  item:', name, rewardType, icon, quantity)
+  return rewardType, icon, quantity, name, itemID
+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
+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
+
+function WorldPlan:OnLoad ()
+  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")
+
+  WorldPlan.modules = {
+    WorldQuests, FilterBar, WorldPlanFlightMapMixin,
+  }
+  hooksecurefunc("UIDropDownMenu_Initialize", self.OnDropDownInitialize)
+end
+
+function WorldPlan:OnEvent (event, ...)
+  print()
+  print(event, ...)
+  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:RefreshAll()
+  end
+  for i, module in ipairs(self.modules) do
+    if module.OnEvent then
+      print('forwarding to', tostring(module))
+      module:OnEvent(event, ...)
+    end
+  end
+end
+
+
+function WorldPlan:Setup ()
+  if not WorldPlanData then
+    WorldPlanData = {key = 0 }
+  end
+  WorldPlanData.key = (WorldPlanData.key or 0) + 1
+  self.db = WorldPlanData
+
+  for i, module in ipairs(self.modules) do
+    if module.Setup then module:Setup() end
+    if not module.RegisterEvent then
+      module.RegisterEvent = self.RegisterEvent
+    end
+  end
+  self.initialized = true
+
+end
+
+function WorldPlan:RefreshAll (forced)
+  for i, module in ipairs(self.modules) do
+    if module.Refresh then
+      module:Refresh()
+    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 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 = "Hidden World Quests"
+  info.isNotRadio = true
+  info.value = "ShowAllProfessionQuests"
+  info.checked = 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 = 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 = DisplayContinentSummary
+  info.func = DropDown_OnClick
+  UIDropDownMenu_AddButton(info)
+end
+
+function WorldQuests:Setup()
+  -- refresh positions any time blizzard does so (i.e. mousewheel zoom)
+  hooksecurefunc("WorldMapScrollFrame_ReanchorQuestPOIs", function()
+    self:Refresh()
+  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)
+
+
+end
+
+function WorldQuests:OnEvent (event, ...)
+  print('|cFFFFFF00'..tostring(self)..':OnEvent()'..event..'|r', ...)
+  if event == 'QUEST_LOG_UPDATE' then
+    local questID, added = ...
+    if questID and added then
+      self:GetPinByQuestID(questID)
+    else
+      self:GetPinsForMap()
+    end
+  elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then
+    local questID = ...
+    if questID and QuestsByID[questID] then
+      self:ReleasePin(QuestsByID[questID])
+    end
+  elseif event == 'SKILL_LINES_CHANGED' then
+    self:Refresh()
+  end
+end
+
+function WorldQuests:AcquirePin (questID, pinTable)
+  local pin = 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:SetScript('OnEnter', function(self)
+        TaskPOI_OnEnter(self)
+      end)
+      pin:SetScript('OnLeave', function(self)
+        TaskPOI_OnLeave(self)
+      end)
+      pin:SetScript('OnMouseDown', TaskPOI_OnClick)
+
+      NumPinFrames = NumPinFrames + 1
+
+      --pin.iconBorder:SetVertexColor(0,0,0,1)
+
+    end
+    QuestsByID[questID] = pin
+    pin.isNew = true
+    pin.currentWidth = nil
+
+    -- used by TaskPOI_x scripts
+    pin.questID = questID
+    pin.worldQuest = true
+  else
+    --print('|cFF00FF00Using', pin:GetName())
+  end
+
+  -- set display flags accordingly
+  if pinTable then
+    for k,v in pairs(pinTable) do
+      pin[k] = v
+    end
+  end
+  pin.throttle = nil
+  pin.timeThreschold = nil
+  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
+    QuestsByID[id] = nil
+    for i, zone in pairs(QuestsByZone) do
+      print('-', i, zone[i])
+      zone[id] = nil
+    end
+  end
+  if pin.factionID then
+    QuestsByFaction[pin.factionID][id] = nil
+  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:GetPinsForMap (mapID)
+  mapID = mapID or GetCurrentMapAreaID()
+  superTrackedID = GetSuperTrackedQuestID()
+  if not mapID then
+    -- info not available yet
+    return
+  end
+  if mapID == BROKEN_ISLES_ID then
+    hasPendingQuestData = nil
+    print('|cFF00FFFFRefreshQuestsForMap|r', mapID, GetMapNameByID(mapID), superTrackedID)
+    self.fullSearch = true
+    for i = 1, MC_GetNumZones(mapID) do
+      local submapID, name, depth = MC_GetZoneInfo(mapID, i)
+      self:GetPinsForMap(submapID)
+    end
+    self.fullSearch = nil
+  elseif QuestsByZone[mapID] then
+    local taskInfo = TQ_GetQuestsForPlayerByMapID(mapID)
+    local quest = QuestsByZone[mapID]
+    local numQuests = 0
+    if taskInfo and #taskInfo >= 1 then
+      print('|cFF00FFFFRefreshQuestsForMap|r', mapID, GetMapNameByID(mapID), #taskInfo)
+      wipe(QuestsByZone[mapID])
+      ZoneInfo[mapID] = taskInfo
+      for taskID, info in pairs(taskInfo) do
+        local questID = info.questId
+
+        info.mapID = mapID
+        QuestsByZone[mapID][questID] = self:GetPinByQuestID(questID, info)
+        numQuests = numQuests + 1
+      end
+    end
+  end
+  if hasNewQuestPins and not self.fullSearch then
+    print('|cFF00FF00NEW PINS DO ANCHOR THINGS')
+    self:Refresh ()
+    hasNewQuestPins = nil
+  end
+end
+
+-- create or update the pin using the given questID and C_TaskQuest results
+function WorldQuests:GetPinByQuestID (questID, taskInfo)
+  if (QuestMapFrame_IsQuestWorldQuest (questID)) then
+    local questTitle, rewardIcon, rewardName, rewardCount, rewardStyle, rewardType, itemID, quantity, quality, _
+    local pin = self:AcquirePin(questID, taskInfo)
+
+    if pin.isNew then
+      if not hasNewQuestPins then
+        print('triggering new quest pins event')
+      end
+
+      hasNewQuestPins = true
+    end
+
+    if not HaveQuestData(questID) then
+      print('|cFFFF4400Retrieval failed.')
+      TQ_RequestPreloadRewardData(questID)
+      hasPendingQuestData = true
+    else
+      print('|cFF00FF88HaveQuestData|r')
+      pin.mapID = pin.mapID or C_TaskQuest.GetQuestZoneID(questID)
+
+      -- set reward category
+      local numRewards = GetNumQuestLogRewards(questID)
+      local numCurrency = GetNumQuestLogRewardCurrencies(questID)
+      local money = GetQuestLogRewardMoney(questID)
+      if numRewards >= 1 then
+        rewardType, rewardIcon, rewardCount, rewardName, itemID = ParseItemReward(questID)
+      elseif numCurrency >= 1 then
+        rewardName, rewardIcon, rewardCount = GetQuestLogRewardCurrencyInfo(1, questID)
+        rewardType = REWARD_CURRENCY
+      elseif money >= 1 then
+        rewardIcon = ICON_MONEY
+        rewardName = GetMoneyString(money)
+        rewardType = REWARD_CASH
+      end
+      rewardStyle = POI_REWARD_TYPE[rewardType] or POI_DEFAULT_TYPE
+
+      pin.itemNumber = rewardCount or pin.itemNumber
+      pin.rewardType = rewardType or REWARD_ITEM
+      pin.style = rewardStyle
+      QuestsByID[questID] = pin
+
+      -- title, faction, capped state
+      local questTitle, factionID, capped = TQ_GetQuestInfoByQuestID(questID)
+      if factionID then
+        QuestsByFaction[factionID] = QuestsByFaction[factionID] or {}
+        QuestsByFaction[factionID][questID] = pin
+      end
+      pin.title = questTitle or "|cFFFF0000Retrieving..."
+      pin.factionID = factionID
+      pin.capped = capped
+
+      -- set tag details
+      local tagID, tagName, worldQuestType, rarity, isElite, tradeskillLineIndex = GetQuestTagInfo(questID);
+      local tagAtlas
+      if worldQuestType == LE_QUEST_TAG_TYPE_PET_BATTLE then
+        tagAtlas = "worldquest-icon-petbattle"
+      elseif worldQuestType == LE_QUEST_TAG_TYPE_PVP then
+        tagAtlas = "worldquest-icon-pvp-ffa"
+      elseif worldQuestType == LE_QUEST_TAG_TYPE_PROFESSION then
+        local id = tradeskillLineIndex and select(7, GetProfessionInfo(tradeskillLineIndex))
+        if id then
+          tagAtlas = WORLD_QUEST_ICONS_BY_PROFESSION[id]
+        end
+      elseif worldQuestType == LE_QUEST_TAG_TYPE_DUNGEON then
+        tagAtlas = "worldquest-icon-dungeon"
+      end
+      pin.tagID = tagID
+      pin.tagName = tagName
+      pin.worldQuestType = worldQuestType
+      pin.isElite = isElite
+      pin.tradeskillLineIndex = tradeskillLineIndex
+      pin.rarity = rarity
+      pin.tagAtlas = tagAtlas
+    end
+
+    -- flag unresolved info
+    if not (rewardIcon and rewardName) then
+      if not pin.isPending then
+        pin.isPending = true
+        TQ_RequestPreloadRewardData (questID)
+        pin.rewardType = pin.rewardType or REWARD_ITEM
+        pin.style = pin.style or POI_REWARD_TYPE[REWARD_ITEM]
+
+        if not hasPendingQuestData then
+          hasPendingQuestData = true
+          PlaySoundKitID(229)
+        end
+        --WorldPlan:print('|cFFFFFF00'..tostring(pin.title)..'|r waiting on texture info')
+      end
+    else
+      if (rewardIcon and rewardName) then
+        --WorldPlan:print('|cFF00FF00'..tostring(pin.title)..'|r has info', rewardIcon, rewardName)
+        pin.hasUpdate = true
+      end
+      pin.isPending = nil
+    end
+    pin.itemTexture = rewardIcon or pin.itemTexture
+    pin.itemName = rewardName or pin.itemName
+
+    qprint('  |cFF00FFFF'..questID..'|r:->', (HaveQuestData(questID) and "|cFF00FF00HaveQuestData" or "-"), (C_TaskQuest.IsActive(questID) and "|cFF88FF00IsActive|r" or ""))
+    qprint(' ', pin.title, pin.itemTexture, 'rewardType:', pin.rewardType, 'tag:', pin.tagID, 'style', pin.style )
+  end
+  return QuestsByID[questID]
+end
+
+function WorldQuests:Refresh(forced)
+  print('pushing |cFF00FF88'..tostring(self)..':Refresh()|r')
+  if not WorldMapPOIFrame:IsVisible() then
+    return
+  end
+  self:Reset()
+  self:UpdateAnchors()
+  self:Cleanup ()
+end
+
+-- prepares elements for a map update
+function WorldQuests:Reset ()
+  wipe(QuestPositions)
+  wipe(QuestsByReward)
+  wipe(QuestsByTag)
+  for questID, pin in pairs(QuestsByID) do
+    pin.used = nil
+  end
+  qprint(tostring(self)..':|cFFFFFF00Reset()|r')
+end
+
+-- update visibility states of all pins
+function WorldQuests:UpdateAnchors (submapID)
+  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 DisplayContinentPins) then
+    print('not updating map for reasons')
+    return
+  end
+  print('|cFF88FF00'..tostring(self)..':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 = QuestsByZone[submapID]
+
+  if pins then
+    local hostFrame = WorldMapPOIFrame
+    local mapWidth, mapHeight = hostFrame:GetSize()
+    for questID, pin in pairs(pins) do
+      if pin:IsShowable(true) then
+        pin:SetFrameLevel(PinBaseIndex+numPins)
+        pin:SetAnchor(WorldMapPOIFrame, currentMap, mapWidth, mapHeight)
+        pin.used = true
+        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 showQuestPOI = GetCVarBool("questPOI")
+  print('|cFFFFFF00'..tostring(self)..':Cleanup()|r')
+  wipe(QuestsByReward)
+  wipe(QuestsByTag)
+  wipe(debug_show)
+  wipe(debug_animate)
+  wipe(debug_hide)
+  local mapID = GetCurrentMapAreaID()
+  isContinentMap = (mapID == BROKEN_ISLES_ID)
+
+  -- continent or zone sizing
+  local subStyle = isContinentMap and 'continent' or 'zone'
+
+  numPins = 0
+  for questID, pin in pairs(QuestsByID) do
+    -- can we show it?
+    if showQuestPOI and (pin.used) then
+      pin.subStyle = pin.filtered and 'minimized' or subStyle
+      pin:GetSizeMetrics()
+
+
+      -- is it a new quest?
+      if pin.isNew then
+        if not pin.isAnimating then
+          pin.isAnimating = true
+          OnNext(function()
+            pin.isNew = nil
+            pin:Show()
+            pin.FadeIn:Play()
+          end)
+          if not notifyPlayed then
+            notifyPlayed = true
+            PlaySoundKitID(23404)
+          end
+          tinsert(debug_animate,questID)
+        end
+      else
+        -- hard show existing pin
+        pin:Show()
+        tinsert(debug_show,questID)
+      end
+    else
+      pin:Hide()
+      tinsert(debug_hide,questID)
+    end
+
+    -- is it part of the current map?
+    if pin.used then
+      local rewardType = pin.rewardType
+      local tagType = pin.worldQuestType
+      if rewardType then
+        QuestsByReward[rewardType] = QuestsByReward[rewardType] or {}
+        tinsert(QuestsByReward[rewardType], pin)
+      end
+      if tagType then
+        QuestsByTag[tagType] = QuestsByTag[tagType] or {}
+        tinsert(QuestsByTag[tagType], pin)
+      end
+    end
+  end
+  print('   adding:', table.concat(debug_animate, ',' ))
+  print('  showing:', table.concat(debug_show, ',' ))
+  hasNewQuestPins = 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 = 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 QuestPOI:OnLoad()
+  self:RegisterEvent('SUPER_TRACKED_QUEST_CHANGED')
+end
+
+function QuestPOI:OnEvent(event, ...)
+  if event == 'SUPER_TRACKED_QUEST_CHANGED' then
+    if self:IsVisible() then
+      self:Refresh()
+    end
+  end
+end
+
+local PIN_UPDATE_DELAY = .016
+local TOP_PIN_ID
+function QuestPOI:OnUpdate (sinceLast)
+  -- control update check intervals
+  self.throttle = (self.throttle or PIN_UPDATE_DELAY) - sinceLast
+  if self.throttle <= 0 then
+    -- factor overtime into the throttle timer
+    self.throttle = PIN_UPDATE_DELAY - self.throttle
+  else
+    return
+  end
+
+  -- query for reward data if it wasn't found in the original scan
+  local questID = self.questID
+  if self.isPending then
+    WorldQuests:GetPinByQuestID(questID)
+    if not (self.PendingFade:IsPlaying() or self.isAnimating) then
+      self.PendingFade:Play()
+    end
+    return
+  else
+    if self.PendingFade:IsPlaying() then
+      self.PendingFade:Stop()
+    end
+  end
+
+  if self.hasUpdate then
+    self:Refresh()
+    self.hasUpdate = nil
+  end
+
+  -- update time elements
+  local tl = self.timeThreschold
+  local timeLeft = TQ_GetQuestTimeLeftMinutes(questID)
+  if timeLeft > 0 then
+    for i, context in ipairs(PIN_TIME_CONTEXT) do
+      if i > self.TimeleftStage then
+        self.timeLabel:SetText(nil)
+        break
+      end
+
+
+      if timeLeft <= context.max then
+        if tl ~= i then
+          tl = i
+        end
+
+        if context.format then
+          self.timeLabel:SetText(context.format(timeLeft))
+        else
+          self.timeLabel:SetText(nil)
+        end
+        break
+      end
+    end
+  else
+    -- remove self in a timely manner
+    if not self.isPending then
+      self:Hide()
+    end
+  end
+  self.timeThreschold = tl
+
+  if self:IsMouseOver() then
+    self.MouseGlow:Show()
+  else
+    self.MouseGlow:Hide()
+  end
+end
+
+function QuestPOI:OnShow ()
+  qprint('|cFFFFFF00["'..tostring(self.title)..'"]|r:OnShow()')
+  -- pop this on principle
+  self:Refresh()
+end
+function QuestPOI:OnHide()
+  --qprint('|cFFFFFF00["'..tostring(self.title)..'"]|r:OnHide()')
+end
+function QuestPOI:Refresh ()
+  print('|cFF00FF88["'..tostring(self.title)..'"]|r:Refresh()', tostring(self.title), "|T"..tostring(self.itemTexture)..":12:12|t", tostring(self.itemName))
+  qprint(self.style)
+
+  local questID = self.questId
+  local style = self.style
+  local borderMask = style.mask
+  local borderFill = style.texture
+  local iconBorder = self.iconBorder
+  local icon = self.icon
+  local count = self.count
+
+  --WorldPlan:print(tostring(self.title), "|T"..tostring(self.itemTexture)..":16:16|t", tostring(self.itemName))
+  SetMaskedTexture(icon, self.itemTexture or ICON_UNKNOWN, borderMask)
+  icon:SetAllPoints(self)
+
+  if self.itemName then
+    local color = self.rewardColor or COMMON_COLOR
+    if self.itemNumber and  self.target then
+      self.count:SetText(color.hex .. tostring(self.itemNumber))
+    else
+      self.count:SetText(nil)
+    end
+  end
+
+  SetMaskedTexture(iconBorder, borderFill, borderMask)
+  local border = (self.rewardType and POI_REWARD_TYPE[self.rewardType]) or (WORLD_QUEST_QUALITY_COLORS[self.rarity] or COMMON_COLOR)
+  iconBorder:SetVertexColor(border.r, border.g, border.b, border.a)
+  iconBorder:SetDesaturated(true)
+
+  local trackingBorder = self.supertrackBorder
+
+  self.highlight:SetMask(nil)
+  if questID == GetSuperTrackedQuestID() then
+    trackingBorder:SetVertexColor(0,0,0,1)
+  else
+    trackingBorder:SetVertexColor(0,0,0,0.5)
+  end
+  self.highlight:SetAllPoints(trackingBorder)
+
+  SetMaskedTexture(trackingBorder, borderFill, borderMask)
+  self.highlight:SetMask(borderMask)
+
+  local qType = self.worldQuestType
+  self.tagIcon:SetAtlas(self.tagAtlas)
+  self.tagIcon:SetTexCoord(0,1,0,1)
+
+
+  if self.isElite then
+    self.EliteDecal:Show()
+  else
+    self.EliteDecal:Hide()
+  end
+
+  if style.showNumber then
+    self.label:SetText(self.itemNumber)
+  else
+    self.label:SetText(nil)
+  end
+  qprint('|cFF88FF00updated', questID, self.title, self.rewardType, (style.showNumber and self.itemNumber) or '')
+end
+
+function QuestPOI:SetAnchor(frame, mapID, mapWidth, mapHeight)
+  self:ClearAllPoints()
+  local dX, dY = TQ_GetQuestLocation(self.questID, mapID)
+  if not dX or dX == 0 then
+    local _, x, y = QuestPOIGetIconInfo(self.questID)
+    if x and floor(x) ~= 0 then
+      dX, dY = x, y
+    else
+      dX, dY = self.x, self.y
+    end
+  end
+  self.x = dX
+  self.y = dY
+
+  print('  |cFF00FF00'..self.questID..':|r', format("%0.2f %0.2f", dX, dY))
+
+  local pX = (dX * mapWidth)
+  local pY = (-dY * mapHeight)
+
+  self:SetParent(WorldMapPOIFrame)
+  self:SetPoint('CENTER', frame, 'TOPLEFT', pX, pY)
+end
+
+
+function QuestPOI:IsShowable (ignoreFilters)
+  local qType = self.worldQuestType
+  local rType = self.rewardType
+  self.filtered = nil
+  print('  |cFFFF4400IsShowable()|r', self.tradeskillLineIndex, self.title)
+
+  local whiteListed, blackListed
+  for filterKey, includes in pairs(FilterInclusions) do
+    local controlValue = self[filterKey]
+    if FilterInclusions[filterKey][controlValue] then
+      whiteListed = true
+      break
+    end
+    if FilterExclusions[filterKey][controlValue] then
+      blackListed = true
+    end
+  end
+  print(blackListed, whiteListed)
+  self.filtered = (blackListed and (not whiteListed))
+
+  if not TQ_IsActive(self.questID) then
+    print('  quest is dead')
+    return false
+  end
+  if qType == LE_QUEST_TAG_TYPE_PROFESSION then
+
+    if not (ShowAllProfessionQuests or (self.tradeskillLineIndex and GetProfessionInfo(self.tradeskillLineIndex))) then
+      print('  non-profession')
+      return false
+    end
+  end
+  return true
+end
+
+function QuestPOI:UpdateTimer (timeLeft, timeType)
+  print('|cFF0088FFUpdatePinTimer()|r')
+end
+
+--- Re-acquires size information and triggers things
+function QuestPOI:GetSizeMetrics (style, subStyle)
+  self.style = self.style or POI_DEFAULT_TYPE
+  self.subStyle = self.subStyle or 'continent'
+
+  style = style or self.style
+  subStyle = style[subStyle or self.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)
+end
+
+-- triggered by OnSizeChanged script
+function QuestPOI:OnSizeChanged ()
+  local iconBorder = self.iconBorder
+  local trackingBorder = self.supertrackBorder
+  local tag = self.tagIcon
+  local style = self.style or POI_DEFAULT_TYPE
+  local mask = style.mask or POI_BORDER_FILL
+
+  self.icon:SetMask(nil)
+  self.iconBorder:SetMask(nil)
+  self.supertrackBorder: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
+
+  self.icon:SetMask(mask)
+  self.iconBorder:SetMask(mask)
+  self.supertrackBorder:SetMask(mask)
+
+  if self.NoIcon then
+    self.icon:Hide()
+  else
+    self.icon:Show()
+  end
+
+end
+
+
+
+function FilterBar:OnEvent(event)
+  if event == 'QUEST_LOG_UPDATE' then
+    self:Refresh()
+  end
+end
+
+function FilterBar:PassesFilterSet(filterKey, pin)
+  local passesFilter = true
+  for filterKey, filters in pairs(QuestFilters) do
+    for rewardType, value in pairs(QuestFilters[filterKey]) do
+      if value == 1 and rewardType == pin[filterKey] then
+        passesFilter = true
+      elseif value == -1 and rewardType == pin[filterKey] then
+        passesFilter = false
+      end
+    end
+  end
+  return passesFilter
+end
+
+local debug_headers = {}
+function FilterBar:Refresh()
+  local mapID = GetCurrentMapAreaID()
+  local blocks = self.SummaryHeaders
+  local numHeaders = 0
+  local lastButton
+  local pinSize, borderSize, trackingSize = 20, 2, 2
+  print('|cFF00FF88'..tostring(self)..':Refresh()|r')
+  for index, info in pairs(POI_FILTER_OPTIONS) do
+    local numQuests = 0
+    local quests = QuestsByZone[mapID] or QuestsByID
+
+    for questID, pin in pairs(quests) do
+      if pin.used then
+        if not info.filterKey then
+          numQuests = numQuests + 1
+        elseif pin[info.filterKey] == info.filterValue then
+          numQuests = numQuests + 1
+        end
+      end
+    end
+    print(tostring(index).. ' ("'..tostring(info.label)..'"'.. tostring(rewardFilter).. ', '..tostring(tagFilter) .. ', '..tostring(numQuests)..')')
+
+    if numQuests >= 1 then
+      numHeaders = numHeaders + 1
+      local button = blocks[numHeaders]
+      if not blocks[numHeaders] then
+        button = CreateFrame('Button', 'WorldPlanFilterButton'..numHeaders, WorldMapScrollFrame, 'WorldPlanFilterPin')
+        button.iconBorder:SetTexture(info.fill or POI_BORDER_FILL)
+        button.iconBorder:SetMask(info.mask or POI_BORDER_MASK)
+        button.iconBorder:SetDesaturated(info.desaturated)
+        button.supertrackBorder:SetTexture(info.fill or POI_BORDER_FILL)
+        button.supertrackBorder:SetMask(info.mask or POI_BORDER_MASK)
+        button.supertrackBorder:SetDesaturated(true)
+        blocks[numHeaders] = button
+      end
+
+      button:SetID(index)
+      button.lastButton = lastButton
+      button:Refresh(info, (numHeaders == 1), numQuests)
+      button:Show()
+      lastButton = button
+    end
+
+  end
+  -- hide trailing buttons
+  for i = numHeaders + 1, #POI_FILTER_OPTIONS do
+    if blocks[i] then
+      blocks[i]:Hide()
+    end
+  end
+end
+
+
+function FilterPin:Refresh(info, isFirst, numQuests)
+  info = info or POI_FILTER_OPTIONS[self:GetID()]
+  isFirst = isFirst or self.isFirst
+  numQuests = numQuests or self.numQuests
+
+  self.isFirst = isFirst
+  self.numQuests = numQuests
+  self.filterKey = info.filterKey
+  self.filterValue = info.filterValue
+  self.tagID = info.tagID
+
+  self.icon:ClearAllPoints()
+  self.icon:SetTexture(info.texture)
+  self.icon:SetAllPoints(self)
+  self.supertrackBorder:Hide()
+  self.label:SetText(numQuests)
+  self:Show()
+
+
+  if isFirst then
+    self:SetPoint('TOP', WorldMapFrame.UIElementsFrame.TrackingOptionsButton, 'BOTTOM', 0, -5)
+  else
+    self:SetPoint('TOPRIGHT', self.lastButton, 'BOTTOMRIGHT', 0, -(3*2 + 1 + (info.spacing or 0)))
+  end
+
+  print(self.filterKey, self.filterValue)
+  local r, g, b, a = 1,1,1,1
+  if self.filterKey then
+    if FilterInclusions[self.filterKey][self.filterValue] == true then
+      r, g, b = 0, 1, 0
+    elseif FilterExclusions[self.filterKey][self.filterValue] then
+      r, g, b = 1, 0, 0
+    end
+  end
+  self.iconBorder:SetVertexColor(r, g, b, a)
+
+
+  self:GetSizeMetrics()
+end
+
+function FilterPin:OnLoad()
+  self:RegisterForClicks('AnyUp')
+  self:SetFrameStrata('HIGH')
+  self:SetFrameLevel(151)
+  self:SetScript('OnUpdate', nil)
+end
+
+function FilterPin:OnUpdate ()
+
+end
+
+function FilterPin:OnEnter ()
+  local filter = POI_FILTER_OPTIONS[self:GetID()]
+  local mapID = GetCurrentMapAreaID()
+  local quests = (mapID == BROKEN_ISLES_ID) and QuestsByID or QuestsByZone[mapID]
+  if quests then
+    GameTooltip:SetOwner(self, 'ANCHOR_RIGHT')
+    GameTooltip:AddLine(filter.label)
+    local filterKey = self.filterKey
+    local filterValue = self.filterValue
+    if filterKey then
+      for questID, pin in pairs(quests) do
+        if pin.used and not pin.filtered then
+          if  pin[filterKey] == filterValue then
+            local style = pin.style or POI_FILTER_STYLE
+            GameTooltip:AddLine(pin.title)
+          end
+        end
+      end
+    else
+      GameTooltip:AddLine('Reset all filters')
+    end
+    GameTooltip:Show()
+  end
+end
+function FilterPin:OnLeave ()
+  if GameTooltip:IsOwned(self) then
+    GameTooltip:Hide()
+  end
+end
+
+-- shift-click: reset filter
+-- click: rotate through include(1), exclude(-1), ignore(nil)
+function FilterPin:OnClick (button)
+  local filterKey = self.filterKey
+  local filterValue = self.filterValue
+
+  print('click', filterKey, filterValue)
+
+  if not filterKey then
+    -- resetting
+    for k,v in pairs(FilterInclusions) do
+      wipe(v)
+    end
+    for k,v in pairs(FilterExclusions) do
+      wipe(v)
+    end
+  else
+    local setExclude = (button == 'RightButton')
+    if IsControlKeyDown() then
+      if setExclude then
+        if FilterExclusions[filterKey][filterValue] then
+          FilterExclusions[filterKey][filterValue] = nil
+        else
+          FilterExclusions[filterKey][filterValue] = true
+        end
+        FilterInclusions[filterKey][filterValue] = nil
+      else
+        if FilterInclusions[filterKey][filterValue] == true then
+          FilterInclusions[filterKey][filterValue] = nil
+        else
+          FilterInclusions[filterKey][filterValue] = true
+        end
+        FilterExclusions[filterKey][filterValue] = nil
+      end
+    elseif IsShiftKeyDown() then
+      FilterInclusions[filterKey][filterValue] = nil
+      FilterExclusions[filterKey][filterValue] = nil
+    else
+      print('limit to', filterKey, filterValue)
+      if setExclude then
+        FilterExclusions[filterKey][filterValue] = true
+        FilterInclusions[filterKey][filterValue] = nil
+      else
+        FilterInclusions[filterKey][filterValue] = true
+        FilterExclusions[filterKey][filterValue] = nil
+      end
+
+      for k, info  in ipairs(POI_FILTER_OPTIONS) do
+        if info.filterKey and ((info.filterKey ~= filterKey) or (info.filterValue ~= filterValue)) then
+          if setExclude then
+            FilterExclusions[info.filterKey][info.filterValue] = nil
+            FilterInclusions[info.filterKey][info.filterValue] = true
+          else
+            FilterExclusions[info.filterKey][info.filterValue] = true
+            FilterInclusions[info.filterKey][info.filterValue] = nil
+          end
+
+        end
+      end
+
+    end
+  end
+
+
+  WorldPlan:RefreshAll()
+end
+
+--%debug%
+local SetTimedCallbackForAllPins = function(seconds, callback)
+  C_Timer.After(seconds, function()
+    for id, pin in pairs(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%
+
+for mapID, mapName in pairs(WORLD_QUEST_MAPS) do
+  QuestsByZone[mapID] = {}
+end
+for index, color in pairs(ITEM_QUALITY_COLORS) do
+  POI_REWARD_TYPE[index] = {
+    r = color.r, g = color.g, b = color.b,
+    hex = color.hex,
+  }
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WorldPlan.toc	Tue Oct 11 17:20:37 2016 -0400
@@ -0,0 +1,12 @@
+## Interface: 70000
+## Title: WorldPlan
+## Notes: World Quest planning helper
+## Author: Krakyn
+## Version: 1.0-@project-revision@
+## SavedVariables: WorldPlanData
+## X-Category: Interface Enhancements
+## DefaultState: Enabled
+## LoadOnDemand: 0
+## Dependencies: Veneer
+
+WorldPlan.xml
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WorldPlan.xml	Tue Oct 11 17:20:37 2016 -0400
@@ -0,0 +1,165 @@
+<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
+..\FrameXML\UI.xsd">
+
+  <Script file="WorldPlan.lua" />
+
+  <Font name="VeneerPinFont" font="Interface\AddOns\Veneer\Font\ArchivoNarrow-Bold.ttf" height="13" outline="NORMAL" virtual="true" />
+  <Font name="VeneerTimeFont" font="Interface\AddOns\Veneer\Font\ArchivoNarrow-Bold.ttf" height="10" outline="NORMAL" virtual="true" />
+
+  <GameTooltip name="WorldPlanTooltip" parent="UIParent" inherits="GameTooltipTemplate">
+
+  </GameTooltip>
+
+
+  <Button name="VeneerWQCompassTemplate" virtual="true" hidden="true">
+    <Size x="50" y="36" />
+    <Anchors>
+      <Anchor point="TOP" />
+    </Anchors>
+    <Layers>
+      <Layer level="ARTWORK">
+        <Texture parentKey="icon" name="$parentIcon" setAllPoints="false">
+          <Anchors>
+            <Anchor point="TOPLEFT" />
+          </Anchors>
+        </Texture>
+      </Layer>
+      <Layer level="OVERLAY">
+        <FontString inherits="VeneerFont" parentKey="label">
+          <Anchors>
+            <Anchor point="LEFT" relativePoint="RIGHT" relativeKey="$parent.icon" />
+          </Anchors>
+        </FontString>
+      </Layer>
+    </Layers>
+  </Button>
+
+  <Button name="WorldPlanQuestPin" virtual="true" hidden="true" frameStrata="TOOLTIP" mixin="WorldPlanPOIMixin">
+    <Animations>
+      <AnimationGroup parentKey="FadeIn" setToFinalAlpha="true" looping="NONE">
+        <Alpha duration="0.15" fromAlpha="0" toAlpha="1" order="1" />
+        <Scripts>
+          <OnPlay>
+            self:GetParent().highlight:Show()
+          </OnPlay>
+          <OnStop>
+            self:GetParent().highlight:Hide()
+            self:GetParent().isAnimating = nil
+          </OnStop>
+          <OnFinished>
+            self:GetParent().highlight:Hide()
+            self:GetParent().isAnimating = nil
+          </OnFinished>
+        </Scripts>
+      </AnimationGroup>
+      <AnimationGroup parentKey="PendingFade" setToFinalAlpha="true" looping="REPEAT">
+        <Alpha duration="0.72" fromAlpha="1" toAlpha="0" order="1" />
+        <Alpha duration="0.72" fromAlpha="0" toAlpha="1" order="2" />
+      </AnimationGroup>
+    </Animations>
+    <Layers>
+      <Layer level="ARTWORK">
+
+        <Texture parentKey="icon" setAllPoints="true">
+          <Anchors>
+            <Anchor point="TOPLEFT" />
+          </Anchors>
+        </Texture>
+      </Layer>
+      <Layer level="BORDER">
+        <Texture parentKey="iconBorder" file="Interface\BUTTONS\GREENGRAD64" desaturated="true" />
+      </Layer>
+      <Layer level="BACKGROUND">
+        <Texture parentKey="MouseGlow" file="Interface\WorldMap\UI-QuestPoi-IconGlow" alphaMode="ADD" hidden="true">
+          <Anchors>
+            <Anchor point="TOPRIGHT" x="14" y="14" />
+            <Anchor point="BOTTOMLEFT" x="-14" y="-14" />
+          </Anchors>
+        </Texture>
+        <Texture parentKey="supertrackBorder" file="Interface\BUTTONS\GREENGRAD64" desaturated="true" />
+        <Texture parentKey="EliteDecal" atlas="worldquest-questmarker-dragon" hidden="true">
+          <Anchors>
+            <Anchor point="TOPLEFT" relativeKey="$parent.iconBorder" x="-6" y="6"/>
+            <Anchor point="BOTTOMRIGHT" relativeKey="$parent.iconBorder" x="6" y="-6"/>
+          </Anchors>
+        </Texture>
+      </Layer>
+      <Layer level="OVERLAY">
+        <FontString inherits="GameFontNormal" parentKey="count">
+          <Anchors>
+            <Anchor point="BOTTOMRIGHT" relativeKey="$parent.icon" x="0" y="0" />
+          </Anchors>
+        </FontString>
+        <FontString inherits="VeneerPinFont" parentKey="label">
+          <Anchors>
+            <Anchor point="BOTTOM" x="0" y="0" />
+          </Anchors>
+        </FontString>
+        <FontString inherits="VeneerTimeFont" parentKey="timeLabel">
+          <Anchors>
+            <Anchor point="BOTTOM" relativePoint="TOP" relativeKey="$parent.label" x="0" y="0" />
+          </Anchors>
+        </FontString>
+        <Texture parentKey="dot" hidden="true" setAllPoints="false">
+          <Size x="6" y="6" />
+          <Color a="1" r="1" g="0" b="0" />
+        </Texture>
+        <Texture parentKey="dotH" hidden="true">
+          <Size y="4" />
+          <Color a="1" r="0" g="0" b="0" />
+        </Texture>
+        <Texture parentKey="tagIcon" setAllPoints="false">
+          <Size x="12" y="12" />
+          <Anchors>
+            <Anchor point="BOTTOMRIGHT" x="1" y="-1" />
+          </Anchors>
+        </Texture>
+
+        <Texture parentKey="highlight" setAllPoints="true" alphaMode="ADD" alpha="0" file="Interface\Tooltips\UI-Tooltip-Background" desaturated="true" />
+
+      </Layer>
+    </Layers>
+    <Frames>
+      <Cooldown parentKey="Swipe" hideCountdownNumbers="true" hidden="true">
+        <Anchors>
+          <Anchor point="CENTER" />
+        </Anchors>
+        <SwipeTexture file="Interface\Garrison\GarrLanding-TradeskillTimerFill" setAllPoints="true" />
+        <Layers>
+          <Layer level="BACKGROUND">
+            <Texture parentKey="bg" setAllPoints="true" atlas="GarrLanding-TradeskillTimer-Border"  alpha="1"/>
+          </Layer>
+        </Layers>
+      </Cooldown>
+    </Frames>
+    <Scripts>
+      <OnSizeChanged method="OnSizeChanged" />
+      <OnLoad method="OnLoad" />
+      <OnEvent method="OnEvent" />
+      <OnUpdate method="OnUpdate" />
+      <OnShow method="OnShow" />
+      <OnHide method="OnHide" />
+    </Scripts>
+  </Button>
+  <Button name="WorldPlanFilterPin" virtual="true" inherits="WorldPlanQuestPin" mixin="WorldPlanFilterPinMixin">
+    <Scripts>
+      <OnClick method="OnClick" />
+      <OnLoad method="OnLoad" />
+    </Scripts>
+  </Button>
+
+  <Frame name="WorldPlan" mixin="WorldPlanCore" parent="UIParent">
+    <KeyValues>
+      <KeyValue key="DEVIAN_PNAME" value="WorldPlan"/>
+    </KeyValues>
+    <Size x="600" y="24" />
+    <Anchors>
+      <Anchor point="TOP" />
+    </Anchors>
+    <Scripts>
+      <OnLoad method="OnLoad" />
+      <OnEvent method="OnEvent" />
+    </Scripts>
+  </Frame>
+
+</Ui>
\ No newline at end of file