changeset 40:589c444d4837

WowAce/Curseforge migration push
author Nenue
date Sun, 25 Dec 2016 13:04:57 -0500
parents 89ddef0594bc
children 79e5e96e5f18
files ClassPlan.lua ClassPlan.xml ClassPlanFollowers.lua ClassPlanFollowers.xml ClassPlanMissions.lua ClassPlanMissions.xml ClassPlanShipments.lua ClassPlanShipments.xml FilterBar.lua FilterBar.xml FlightMap.lua FlightMap.xml QuestPOI.lua WorldPlan.lua WorldPlan.xml WorldQuests.lua
diffstat 16 files changed, 2168 insertions(+), 1336 deletions(-) [+]
line wrap: on
line diff
--- a/ClassPlan.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/ClassPlan.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -1,3 +1,4 @@
+local _, db = ...
 local wipe, tinsert, sort = table.wipe, tinsert, table.sort
 local pairs, ipairs = pairs, ipairs
 local floor, mod, time = floor, mod, time
@@ -6,20 +7,25 @@
 local GI_currentTime = time()
 local print = DEVIAN_WORKSPACE and function(...) print('ClassPlan', ...) end or nop
 
-local CG_GetBuildings = C_Garrison.GetBuildings
-local CG_GetFollowerShipments = C_Garrison.GetFollowerShipments
-local CG_GetLooseShipments = C_Garrison.GetLooseShipments
-local CG_GetTalentTrees = C_Garrison.GetTalentTrees
-local CG_GetCompleteTalent = C_Garrison.GetCompleteTalent
-local CG_GetLandingPageShipmentInfo = C_Garrison.GetLandingPageShipmentInfo
-local CG_GetLandingPageShipmentInfoByContainerID = C_Garrison.GetLandingPageShipmentInfoByContainerID
-
 local CP_REPLACE_LANDINGPAGE = true
 local CP_HEADER_SIZE = 24
-local CP_BACKGROUND_COLOR = {
-  inProgress = {0, 0, 0, 0.5},
-  shipmentsReady = {0, 0, 0, 0.25},
-  complete = {0.5, 0.5, 0.5, 0.5}
+db.ClassPlanDefaultType = {
+
+  backgroundColor = {0, 0, 0, 0.5},
+  textColor = {1,1,1,1}
+}
+db.ClassPlanTypes = setmetatable({}, {__index = db.ClassPlanDefaultType})
+db.ClassPlanTypes.inProgress = {
+  backgroundColor = {0, 0, 0, 0.5},
+  textColor = {1,1,1}
+}
+db.ClassPlanTypes.shipmentsReady = {
+  backgroundColor = {1, 1, 0, 0.25 },
+  textColor = {1, 1, 0}
+}
+db.ClassPlanTypes.complete = {
+  backgroundColor = {0, 1, 0, 0.25 },
+  textColor = {0, 1, 0}
 }
 
 local GetTimeLeftString = function(timeLeft)
@@ -34,7 +40,6 @@
   end
 end
 
-
 ClassOrderPlanCore = {
   events = {},
   freeBlocks = {},
@@ -45,61 +50,72 @@
   shipments = {},
   playerFirst = false,
   prototypes = {},
-  Queued = {}
+  Queued = {},
+  Timers = {},
+  ReportChunks = {},
 }
-local MissionList = {
-  templateName = 'ClassPlanMissionEntry',
-  listKey = {'missions', 'available'},
-  listTitle = {'In Progress', 'Available'},
-
-  point = 'TOPLEFT',
-  relativePoint ='TOPLEFT',
-  events = {
-    'GARRISON_MISSION_LIST_UPDATE',
-    'GARRISON_LANDINGPAGE_SHIPMENTS'},
-}
-local ShipmentList = {
-  templateName = 'ClassPlanShipmentEntry',
-  listKey = {'shipments'},
-  listTitle = {'Work Orders'},
-  events = {
-    'GARRISON_MISSION_LIST_UPDATE',
-    'GARRISON_LANDINGPAGE_SHIPMENTS',
-    'GARRISON_TALENT_UPDATE',
-    "GARRISON_TALENT_COMPLETE",
-    "GARRISON_SHIPMENT_RECEIVED",
-    'GARRISON_FOLLOWER_LIST_UPDATE',
-    'GARRISON_SHOW_LANDING_PAGE'},
-}
-local SharedHandlers = {
+ClassPlanHandlerBase = {
   numBlocks = 0,
   isStale = true,
   maxItems = 10
 }
-local SharedEntry = {}
-local ShipmentEntry = {}
-local MissionEntry = {}
+ClassPlanEntryBase = {}
 
 local ClassPlan = ClassOrderPlanCore
+local Embed = function(object, ...)
+  for i = 1, select('#', ...) do
+    local src = select(i, ...)
+    for k,v in pairs(src) do
+      if not object[k] then
+        object[k] = v
+      end
+    end
+  end
+  return object
+end
 
 function ClassPlan:OnLoad ()
   self:RegisterEvent('PLAYER_LOGIN')
   self:RegisterEvent('ADDON_LOADED')
   self:RegisterEvent('PLAYER_REGEN_ENABLED')
   self:RegisterEvent('PLAYER_REGEN_DISABLED')
+  self:RegisterEvent('GARRISON_SHOW_LANDING_PAGE')
   self:RegisterForDrag('LeftButton')
   self:SetMovable(true)
   self:SetToplevel(true)
 
-
   SLASH_CLASSPLAN1 = "/classplan"
   SLASH_CLASSPLAN2 = "/cp"
   SlashCmdList.CLASSPLAN = function(args)
     self:Toggle()
   end
 
+  local originalScript = GarrisonLandingPageMinimapButton:GetScript('OnClick')
+  GarrisonLandingPageMinimapButton:SetScript("OnClick", function(minimap, button)
+    if button == 'LeftButton' and (not IsShiftKeyDown()) then
+      self:Toggle()
+    else
+      originalScript(minimap, button)
+    end
+  end)
+  self.FadeOut:SetScript('OnFinished', function()
+    self:Hide()
+    self.data.IsShown = nil
+    self.isAnimating = nil
+  end)
+  self.FadeIn:SetScript('OnPlay', function()
+    self.isAnimating = true
+    self:SetShown(true)
+  end)
+
+  --hooksecurefunc(C_Garrison, 'RequestLandingPageShipmentInfo', function()
+  --  WorldPlan:print("Requesting shipments data.")
+  --end)
+  C_Garrison.RequestLandingPageShipmentInfo();
+  self.isStale = true
 end
 
+
 function ClassPlan:GetCurrentProfile()
   WorldPlanData.OrderHall = WorldPlanData.OrderHall or {}
   local db = WorldPlanData.OrderHall
@@ -110,38 +126,49 @@
 
   local name, realm = UnitName('player')
   realm  = realm or GetRealmName()
+
   local profileName = name .. '-' .. realm
-
-  self.profile = characters[profileName] or {}
+  self.profile = characters[profileName] or {
+    showItems = true
+  }
   self.characters = characters
   characters[profileName] = self.profile
 
-
   local classColor = RAID_CLASS_COLORS[select(2, UnitClass('player'))]
   local className = UnitClass('player')
-
   print('|cFFFFFF00Loaded:|r', classColor.hex, className, profileName)
-  self.Background:SetColorTexture(classColor.r, classColor.g, classColor.b, 0.5)
   self.profile.classColor = classColor
   self.profile.className = className
   self.profile.characterName = name
   self.profile.characterRealm = realm
+  -- flip it on
+  self.profile.showItems = true
+
+  self.HeaderInset:SetHeight(CP_HEADER_SIZE)
+  self.ClassStripe:SetColorTexture(classColor.r, classColor.g, classColor.b, 1)
+  self.ClassStripe:SetPoint('TOPLEFT', self.HeaderInset, 'BOTTOMLEFT')
+
   return self.profile
 end
 
-function ClassPlan:SetupHandler(handler)
-  print('|cFF00FF00'..handler:GetName()..' loaded')
-  for i, event in ipairs(handler.events) do
+function ClassPlan:AddHandler(frame)
+  print('|cFF00FF00'..frame:GetName()..' loaded')
+  for i, event in ipairs(frame.events) do
     print('|cFF00FF00  event', event)
-    handler:RegisterEvent(event)
+    frame:RegisterEvent(event)
   end
-  for index, listKey in ipairs(handler.listKey) do
+  frame.sortedItems = {}
+  for index, listKey in ipairs(frame.listKey) do
+    frame.profile = self.profile
+    frame.data = self.data
     self.profile[listKey] = self.profile[listKey] or {}
-    local listTitle = handler.listTitle[index]
-    setmetatable(self.profile[listKey], { __tostring = listTitle })
+    local listTitle = frame.listTitle[index]
+    setmetatable(self.profile[listKey], { __tostring = function() return listTitle end })
+    frame.sortedItems[listKey] = {}
+
   end
-  handler:SetList(1)
-  handler.sortedItems = {}
+  frame.owningFrame = self
+  frame:SetList(1)
 end
 
 function ClassPlan:OnEvent (event, arg)
@@ -165,6 +192,8 @@
     if not self.initialized then
       self:Setup()
     end
+  elseif event == 'GARRISON_SHOW_LANDING_PAGE' then
+    self:RefreshData()
   end
 end
 
@@ -174,120 +203,124 @@
 
     self:GetCurrentProfile()
     for _, handler in ipairs(self.Handlers) do
-      self:SetupHandler(handler)
+      self:AddHandler(handler)
+      handler.initialized = true
     end
     self.initialized = true
     self:SetShown(self.data.IsShown)
+
+
   end
 end
 
 
 --- Update space
 
-  local max = math.max
-  function ClassPlan:Update()
-    print('|cFF00FFFFRefresh()|r')
-    self.currentHeight = 0
-    for index, handler in pairs(self.Handlers) do
-      if handler.isStale then
-        print('  |cFF00FF00'..index..' '..handler:GetName()..'|r')
-        local sortedItems = handler.sortedItems
-        local activeKey = handler.activeKey
+local max = math.max
+function ClassPlan:RefreshData()
+  local detailsFailed
+  for index, handler in pairs(self.Handlers) do
+    print(' |cFF00FF00'..handler:GetName()..' data update|r')
+    handler:RefreshData()
+  end
+  if detailsFailed then
+    db.print('Unable to obtain player details. Trying again later.')
+  else
+    self.requestingData = nil
+  end
+end
 
-        handler.profile = self.profile[handler.activeKey]
-        handler.currentTime = GI_currentTime
-        handler:GetPlayerData(self.profile)
-        wipe(sortedItems)
-        for key, profile in pairs(self.data.characters) do
-          print('profile', key, activeKey)
-          local profileList = profile[activeKey]
-          if profileList and #profileList >= 1 then
-            local classColor = profile.classColor or RAID_CLASS_COLORS['HUNTER']
-            local isMine = (profile == self.profile)
-            for index, data in ipairs(profileList) do
-              data.classColor = classColor
-              data.profileKey = key
-              data.isMine = isMine
-              if handler.OnGetItem then
-                handler:OnGetItem(data)
-              end
-              tinsert(sortedItems, data)
-            end
-          end
-        end
+function ClassPlan:Update()
+  print('|cFF00FFFF'..self:GetName()..'Refresh()|r')
 
-        if handler.SortHandler then
-          sort(sortedItems, handler.SortHandler)
-        end
+  self.currentHeight = 0
+  for index, handler in ipairs(self.Handlers) do
+    local itemsHeight = handler:UpdateItems()
+    if itemsHeight then
+      self.currentHeight = max(itemsHeight, self.currentHeight)
+    end
+  end
 
-      end
-      handler.isStale = nil
-      local itemsHeight = handler:UpdateItems()
-      self.currentHeight = max(itemsHeight, self.currentHeight)
-
+  local index = 1
+  for key, info in pairs(self.data.characters) do
+    print('cbutton', key)
+    if self.data[key] then
+      print('|cFFFF4400remove legacy value', key)
+      self.data[key] = nil
     end
 
-    local index = 1
-    for id, profile in pairs(self.data.characters) do
-      local button = self.characterButtons[index]
-      if not button then
-        button = CreateFrame('Button', nil, self, 'ClassOrderPlanCharacterButton')
-        button:SetID(index)
-        self.characterButtons[index] = button
 
-        if not self.lastButton then
-          button:SetPoint('BOTTOMLEFT', self, 'TOPLEFT', 0, 0)
-        else
-          button:SetPoint('BOTTOMLEFT', self.lastButton, 'BOTTOMRIGHT', 2, 0)
-        end
-        self.lastButton = button
+    local button = self.characterButtons[index]
+    if not button then
+      button = CreateFrame('Button', nil, self, 'ClassOrderPlanCharacterButton')
+      button:SetID(index)
+      self.characterButtons[index] = button
+
+      if not self.lastButton then
+        button:SetPoint('TOPLEFT', self.HeaderInset, 'TOPLEFT', 0, 0)
+      else
+        button:SetPoint('TOPLEFT', self.lastButton, 'TOPRIGHT', 2, 0)
       end
-      if not profile.characterName then
-        profile.characterName, profile.characterRealm = id:match("%(.+)%-(.+)^")
-      end
-
-      button.className = profile.className
-      button.classColor = profile.classColor
-      button.characterName = profile.characterName
-      button.characterRealm = profile.characterRealm
-      button.hideItems = (profile.showItems == false) and (profile ~= self.profile)
-      button.isMine = (profile == self.profile)
-      button:Update()
-      button:Show()
-      index = index + 1
+      self.lastButton = button
+    end
+    if not info.characterName then
+      info.characterName, info.characterRealm = key:match("%(.+)%-(.+)^")
     end
 
+    print(info.characterName)
 
-    self.isStale = nil
-    self:Reanchor()
-    self:SetHeight(self.currentHeight + CP_HEADER_SIZE)
+    button:SetSize(CP_HEADER_SIZE, CP_HEADER_SIZE)
+    button.profileKey = key
+    button.className = info.className
+    button.classColor = info.classColor
+    button.characterName = info.characterName
+    button.characterRealm = info.characterRealm
+    button.showItems = info.showItems
+    button.isMine = (info == self.profile)
+    button:Update()
+    button:Show()
+    index = index + 1
   end
 
+  self.HeaderInset:SetHeight(CP_HEADER_SIZE)
+  self.HeaderInset:ClearAllPoints()
+  self.HeaderInset:SetPoint('TOPLEFT' ,self, 'TOPLEFT')
+  self.HeaderInset:SetPoint('RIGHT' ,self, 'RIGHT')
+  self.ClassStripe:ClearAllPoints()
+  self.ClassStripe:SetPoint('TOPLEFT', self.HeaderInset, 'BOTTOMLEFT', 0, 0)
+  self.ClassStripe:SetPoint('RIGHT')
+  self:Reanchor()
+  self.isStale = nil
+end
+
 
 function ClassPlan:Toggle()
   if self:IsShown() then
-    self:Hide()
+    self.FadeOut:Play()
   else
-    self:Show()
-  end
-
-  if self.data then
-    self.data.IsShown = self:IsShown()
+    self.data.IsShown = true
+    self.FadeIn:Play()
   end
 end
 
+
 function ClassPlan:OnUpdate()
-  if self.isStale then
-    print('|cFFFF4400An illusion! What are you hiding?|r')
+  if self.requestingData then
+    self:RefreshData()
+  elseif self.isStale then
+    -- shouldn't happen, usually
     self:Update()
   end
+  if #self.ReportChunks >= 1 then
+    db.print(table.concat(self.ReportChunks, ', '))
+    wipe(self.ReportChunks)
+  end
+
 end
 
 function ClassPlan:OnShow()
   print('|cFF00FFFFShow()')
-  if self.isStale then
-    self:Update()
-  end
+  self.isStale = true
   self:Reanchor()
 end
 
@@ -296,30 +329,34 @@
 end
 
 function ClassPlan:Reanchor()
+  if not (self.data.positionX and self.data.positionY) then
+    self.data.positionX = 0
+    self.data.positionY = -148
+  end
   self:ClearAllPoints()
-  self:SetPoint('CENTER', self.data.positionX, self.data.positionY)
-
+  self:SetPoint('TOP', self.data.positionX, self.data.positionY)
+  self.currentHeight = 0
   for index, frame in ipairs(self.Handlers) do
     frame:Reanchor()
-
     local ListTab = frame.ListTab
     if ListTab then
       ListTab:ClearAllPoints()
-      ListTab:SetPoint('TOPLEFT', frame, 'TOPLEFT', 0, CP_HEADER_SIZE)
-      ListTab:SetPoint('BOTTOMRIGHT', frame, 'TOPRIGHT', 0, 0)
+      ListTab:SetPoint('TOPLEFT', frame, 'TOPLEFT', 0, 0)
+      ListTab:SetPoint('BOTTOMRIGHT', frame, 'TOPRIGHT', 0, -CP_HEADER_SIZE)
       ListTab.Label:SetText(frame.listTitle[frame.currentListIndex])
       ListTab:Show()
       print(ListTab:GetSize())
     end
+    self.currentHeight = max(self.currentHeight, frame.currentHeight or 0)
+  end
+  self:SetHeight(self.currentHeight + self.HeaderInset:GetHeight() + self.ClassStripe:GetHeight())
 
-  end
 end
 
 function ClassPlan:OnDragStart()
   self:StartMoving()
 end
 function ClassPlan:OnDragStop()
-
   self:StopMovingOrSizing()
   local x,y = self:GetCenter()
   if x and y then
@@ -330,7 +367,23 @@
   end
 end
 
-function SharedHandlers:SetList(index)
+function ClassPlanHandlerBase:ScheduleUpdate(expires)
+  -- value will occasionally lag so check here
+  local duration = expires - time()
+  self.Timers = self.Timers or {}
+  if (duration > 0) and  (not self.Timers[expires]) then
+    print('  adding timer at', expires, 'c', duration)
+    self.Timers[expires] = true
+    C_Timer.After(duration, function()
+      self.isStale = true
+      self:UpdateItems()
+      self.Timers[expires] = nil
+    end)
+  end
+end
+
+function ClassPlanHandlerBase:SetList(index)
+  local prevIndex = self.currentListIndex
   if not index then
     if self.currentListIndex == #self.listKey then
       index = 1
@@ -339,57 +392,93 @@
     end
   end
 
-  print('|cFF0088FF'..self:GetName()..'|r:SetList()', index)
+  print('|cFF0088FF'..self:GetName()..'|r:SetList()', index, self.listKey[index])
   self.currentListIndex = index
   self.activeKey = self.listKey[index]
   self.activeTitle = self.listTitle[index]
 
+  self.ListTab.Label:SetText(self.listTitle[index])
   self.isStale = true
+
+  if self.OnSetList then
+    self:OnSetList(self.currentListIndex, prevIndex)
+  end
 end
 
-function SharedHandlers:OnMouseWheel(delta)
+function ClassPlanHandlerBase:OnMouseWheel(delta)
   self.scrollOffset = (self.scrollOffset or 0) - ((delta > 0) and 1 or -1)
   self:UpdateItems()
 end
 
-function SharedHandlers:RequestData()
-  print('|cFF0088FF'..self:GetName()..':RequestData()')
+function ClassPlanHandlerBase:RefreshData()
+  print('|cFF0088FF'..self:GetName()..':RefreshData()')
+  local activeKey = self.activeKey
+  local detailsFailed
+  self.truncatedItems = 0
+  self.currentTime = time()
+  if self:GetParent().profile then
+    self:GetPlayerData()
+  else
+    detailsFailed  = true
+  end
+  for _, listKey in ipairs(self.listKey) do
+    local sortedItems = self.sortedItems[listKey]
+    wipe(sortedItems)
+    for key, profile in pairs(self.data.characters) do
+      local isMine = (profile == self.profile)
+      print(key, listKey, isMine, profile.showItems)
+      local profileList = profile[listKey]
+      if profileList and #profileList >= 1 then
+        local classColor = profile.classColor or RAID_CLASS_COLORS['HUNTER']
+        if profile.showItems then
+          for index, data in ipairs(profileList) do
+            data.classColor = classColor
+            data.profileKey = key
+            data.isMine = isMine
+            if self.OnGetItem then
+              self:OnGetItem(data)
+            end
+            tinsert(sortedItems, data)
+          end
+        else
+          self.truncatedItems = self.truncatedItems + 1
+        end
+      end
+    end
+
+    if self.SortHandler then
+      sort(sortedItems, self.SortHandler)
+    end
+  end
+
+  for k,v in pairs(self.sortedItems) do
+    print('  ', k)
+  end
+
   self.isStale = true
 end
 
-function SharedHandlers:OnEvent(event, arg)
-  if (event == 'GARRISON_MISSION_LIST_UPDATE') and (arg ~= LE_FOLLOWER_TYPE_GARRISON_7_0) then
-    -- ignore non-OrderHall updates
-    return
-  end
-  print('|cFF00FF88'..self:GetName()..':OnEvent()|r', event, arg)
-  if self:IsVisible() then
-    print('|cFF88FF00  frame visible; get busy')
-    self:RequestData()
+function ClassPlanHandlerBase:OnEvent(event, id)
+  if (event == 'GARRISON_MISSION_LIST_UPDATE') then
+    if (id == LE_FOLLOWER_TYPE_GARRISON_7_0) then
+      print('|cFF00FF88'..self:GetName()..':OnEvent()|r', event, id)
+      self:RefreshData()
+    end
   else
-    if not self.NextData then
-      print('|cFF88FF00  setting timer')
-      self.NextData = C_Timer.NewTimer(0.25, function()
-        if self.initialized then
-          self:RequestData()
-          self.NextData:Cancel()
-          self.NextData = nil
-          print('|cFF88FF00'..self:GetName()..' clearing timer')
-        end
-
-      end)
-    end
-  end
-end
-function SharedHandlers:OnUpdate()
-  if self.isStale then
-    self:GetParent():Update()
+    print('|cFF00FF88'..self:GetName()..':OnEvent()|r', event, id)
   end
 end
 
+function ClassPlanHandlerBase:OnUpdate()
+  if self.isStale then
+    print('|cFF00FF00'..self:GetName()..':OnUpdate()|r')
+
+    self:UpdateItems()
+  end
+end
 
 -- Stuff set on every list item
-function SharedHandlers:SetOwnerData (self, data)
+function ClassPlanHandlerBase:SetOwnerData (self, data)
   local name, realm = string.match(data.profileKey, "(.+)%-(.+)")
   local ownerText = '|c'.. data.classColor.colorStr .. name .. '|r'
   self.Owner:SetText(ownerText)
@@ -397,17 +486,16 @@
   self.Name:SetTextColor(data.classColor.r, data.classColor.g, data.classColor.b)
 end
 
-function SharedHandlers:Acquire(id)
+function ClassPlanHandlerBase:Acquire(id)
 end
-function SharedHandlers:FreeBlock (block)
+function ClassPlanHandlerBase:FreeBlock (block)
 end
 
-function SharedHandlers:UpdateItems()
-
+function ClassPlanHandlerBase:UpdateItems()
+  print('|cFF0088FF '..self:GetName()..':UpdateItems()|r', self.activeKey)
   self.MoreItemsUp:Hide()
   self.MoreItemsDown:Hide()
-
-  local sortedItems = self.sortedItems
+  local sortedItems = self.sortedItems[self.activeKey]
   local scrollOffset = self.scrollOffset or 0
   local numItems = #sortedItems
   if (not sortedItems[scrollOffset+1]) or (numItems <= self.maxItems) then
@@ -416,41 +504,41 @@
     scrollOffset = (numItems - self.maxItems)
   end
 
-
+  self.ListTab.Count:SetText(numItems)
   self.blocks = self.blocks or  {}
   local blocks = self.blocks
-
   local lastProfile
-  local totalHeight = 0
+  local totalHeight = (self.ListTab:GetHeight() or 0)
   self.lastBlock = nil
   self.numActive = 0
   for i = 1, self.maxItems do
     local index = scrollOffset + i
     local data = sortedItems[index]
     if not data then
+      print('|cFFFF4400end of data')
       break
     end
 
-
     local block = blocks[i]
     if not block then
-      block = CreateFrame('Button', nil, self, self.templateName)
+      block = CreateFrame('Button', self:GetName()..'ListItem'..i , self, self.templateName)
       block.listType = self.activeKey
+
+      block.doAnimation = true
+
       block.handler = self
       self.numBlocks = self.numBlocks + 1
       blocks[i] = block
     end
     block:SetID(index)
-
-    print('RefreshItem', block)
     self.numActive = self.numActive + 1
 
     if self.lastBlock then
       block:SetPoint('TOPLEFT', self.lastBlock, 'BOTTOMLEFT', 0, 0)
-      print('--', index, data.isComplete, data.missionEndTime, data.name)
+      --print('--', index, data.isComplete, data.missionEndTime, data.name)
     else
-      block:SetPoint('TOPLEFT', 0, 0)
-      print('--top')
+      block:SetPoint('TOPLEFT', self.ListTab, 'BOTTOMLEFT', 0, 0)
+      --print('--top')
     end
     self.lastBlock = block
 
@@ -491,104 +579,33 @@
     end
   end
 
+  self:Reanchor()
+  if totalHeight ~= self.currentHeight then
+    self.currentHeight = totalHeight
+    self:SetHeight(self.currentHeight)
+    self:GetParent():Reanchor()
+  end
+
+
   self.scrollOffset = scrollOffset
-  self:Reanchor()
-
+  self.isStale = nil
   return totalHeight
 end
 
+function ClassPlanEntryBase:OnAnimFinished()
+end
+function ClassPlanEntryBase:OnShow()
 
-function ShipmentList:Reanchor()
-  print('|cFF00FFFF'..self:GetName()..':Reanchor|r')
-  self:SetPoint('TOPLEFT', 0, -24)
-  self:SetPoint('BOTTOMRIGHT', -ClassOrderPlan:GetWidth()/2, 0)
-end
-
-
-
-do
-  local ShipmentsInfo = {}
-  local AddShipmentInfo = function(shipmentType, name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID, followerID)
-    -- early login queries may return empty tables, causing the sorter to compare nil
-    if not creationTime then
-      return
+  print('|cFF44FF00'..self:GetName()..':OnShow()')
+  if self.doAnimation then
+    self.doAnimation = nil
+    if not ClassOrderPlan.isAnimating then
+      self.NewBlockFade:Play()
     end
-    --print(shipmentType, name, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString)
-    tinsert(ShipmentsInfo,
-      {
-        shipmentType = shipmentType,
-        name = name,
-        icon = texture,
-        shipmentCapacity = shipmentCapacity,
-        shipmentsReady = shipmentsReady,
-        shipmentsTotal = shipmentsTotal,
-        creationTime = creationTime,
-        duration = duration,
-        timeleftString = timeleftString,
-        itemName = itemName,
-        itemIcon = itemIcon,
-        itemQuality = itemQuality,
-        itemID = itemID,
-        followerID = followerID,
-      })
-  end
-  function ShipmentList:GetPlayerData (profile)
-    if not profile then
-      return false
-    end
-    local profileList = profile.shipments
-    wipe(ShipmentsInfo)
-
-    local garrisonType = LE_GARRISON_TYPE_7_0
-    local buildings = CG_GetBuildings(garrisonType);
-    local shipmentIndex = 0
-    --print('Buildings:')
-    for i = 1, #buildings do
-      local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID = CG_GetLandingPageShipmentInfo(buildingID);
-      AddShipmentInfo('Building', name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID)
-    end
-
-    --print('Follower:')
-    local followerShipments = CG_GetFollowerShipments(garrisonType);
-    for i = 1, #followerShipments do
-      local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, _, _, _, _, followerID = CG_GetLandingPageShipmentInfoByContainerID(followerShipments[i]);
-      AddShipmentInfo('Follower', name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, nil, nil, nil, nil, followerID)
-    end
-
-    --print('Loose:')
-    local looseShipments = CG_GetLooseShipments(garrisonType)
-    for i = 1, #looseShipments do
-      local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString = CG_GetLandingPageShipmentInfoByContainerID(looseShipments[i]);
-      AddShipmentInfo('Misc', name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString)
-    end
-
-    local talentTrees = CG_GetTalentTrees(garrisonType, select(3, UnitClass("player")));
-    -- this is a talent that has completed, but has not been seen in the talent UI yet.
-    local completeTalentID = CG_GetCompleteTalent(garrisonType);
-    --print('Talents:')
-    if (talentTrees) then
-      for treeIndex, tree in ipairs(talentTrees) do
-        for talentIndex, talent in ipairs(tree) do
-          local showTalent = false;
-          if (talent.isBeingResearched) or (talent.id == completeTalentID) then
-            AddShipmentInfo('Talent', talent.name, talent.icon, 1, (talent.isBeingResearched and 0 or 1), 1, talent.researchStartTime, talent.researchDuration, talent.timeleftString)
-          end
-        end
-      end
-    end
-
-    wipe(profileList)
-    for index, data in ipairs(ShipmentsInfo) do
-      --DEFAULT_CHAT_FRAME:AddMessage(data.shipmentType ..' '.. tostring(data.name) ..' '.. tostring(data.creationTime) ..' '.. tostring(data.duration))
-      tinsert(profileList, data)
-    end
-    self.isStale = true
-    return true
   end
 end
 
-
-function SharedEntry:SetTimeLeft(expires, duration)
+function ClassPlanEntryBase:SetTimeLeft(expires, duration)
   self.ProgressBG:Hide()
   self.ProgressBar:Hide()
   if not expires then
@@ -596,10 +613,14 @@
   end
 
   -- calculate here since time isn't available
-  local timeLeft = expires - GI_currentTime
+  local timeLeft = expires - time()
   if timeLeft < 0 then
     -- handle being complete
-
+    if self.shipmentsReady and (self.shipmentsReady < self.shipmentsTotal) then
+      self.TimeLeft:SetText('Ready')
+    else
+      self.TimeLeft:SetText('Complete!')
+    end
   else
     self.TimeLeft:SetText(GetTimeLeftString(timeLeft))
   end
@@ -610,188 +631,85 @@
     local g = ((progress <= .5) and (progress*2)) or 1
     self.ProgressBG:Show()
     self.ProgressBar:Show()
-
     self.ProgressBG:SetColorTexture(r,g,0,0.25)
     self.ProgressBar:SetColorTexture(r,g,0,0.5)
-    self.ProgressBar:SetWidth(self:GetWidth() * progress)
+    self.ProgressBar:SetWidth(self.ProgressBG:GetWidth() * progress)
   end
 end
 
--- Update shipment flags data
-local SetActualShipmentTime = function(self)
 
-  if self.isComplete then
-    return nil, nil
+
+ClassPlanHeaderMixin = {}
+function ClassPlanHeaderMixin:OnLoad()
+    self:EnableMouse((#self:GetParent().listKey > 1))
+    self:RegisterForClicks('AnyUp')
   end
-
-  local timestamp = time()
-  local timeLeft = self.creationTime + self.duration - timestamp
-  local duration = self.duration * self.shipmentsTotal
-  local justFinished = false
-  while (self.shipmentsReady < self.shipmentsTotal) and (timeLeft <= 0) do
-    if not self.originalReady then
-      self.originalReady = self.shipmentsReady
-      self.originalCreationTime = self.creationTime
-    end
-
-
-    self.shipmentsReady = self.shipmentsReady + 1
-    self.creationTime = self.creationTime + self.duration
-    timeLeft = timeLeft + self.duration
-    print('|cFF00FF88udpating '..self.name..'|r', 'timeLeft:', timeLeft, 'shipments:', self.shipmentsReady, self.shipmentsTotal)
-  end
-
-  if (timeLeft <= 0) and (not self.isBeingResearched) then
-    self.isComplete = true
-    self.isStale = true
-  end
-
-
-  local expires = (self.originalCreationTime or self.creationTime) + duration
-
-  return expires, duration
-end
-
-function ShipmentList:OnGetItem (data)
-  print('OnGetItem()')
-  if data.shipmentsTotal then
-    SetActualShipmentTime(data)
+function ClassPlanHeaderMixin:OnClick ()
+  local frame = self:GetParent()
+  frame:SetList()
+  if frame.OnHeaderClick then
+    frame.OnHeaderClick(frame)
   end
 end
 
-ShipmentList.SortHandler = function(a, b)
-  if b.isComplete ~= a.isComplete then
-    return a.isComplete and true or false
-  elseif a.shipmentsReady or b.shipmentsReady then
-    return (a.shipmentsReady or 0) > (b.shipmentsReady or 0)
-  else
-    return (a.creationTime) < (b.creationTime)
+ClassPlanCharacterButtonMixin = {
+}
+function ClassPlanCharacterButtonMixin:Update ()
+  --print(CLASS_ICON_TCOORDS[self.className:upper()])
+  if self.className and CLASS_ICON_TCOORDS[self.className:upper()] then
+    self.Icon:SetTexCoord(unpack(CLASS_ICON_TCOORDS[self.className:upper()]))
   end
+  self.Icon:SetDesaturated((not self.showItems))
+  self.SelectGlow:SetShown(self.isMine)
 end
 
-function ShipmentList:OnLoad()
-  C_Garrison.RequestLandingPageShipmentInfo();
-end
-function  ShipmentList:OnShow()
-  print('|cFF00FF88'..self:GetName()..':OnShow()|r')
-  C_Garrison.RequestLandingPageShipmentInfo()
-end
-
-function ShipmentEntry:OnLoad()
-  MissionEntry.OnLoad(self)
-end
-
-
-function ShipmentEntry:Update()
-  print('|cFF0088FF'.. self.name..'|r:Update()')
-  self.Icon:SetTexture(self.icon)
-  self.Count:SetText(self.shipmentsReady)
-  self.Done:SetShown(self.shipmentsReady and (self.shipmentsReady >= 1))
-
-  -- flag as complete
-
-  local bgColor = CP_BACKGROUND_COLOR.inProgress
-  if ( self.shipmentsReady >= self.shipmentsTotal ) and (not self.isBeingResearched) then
-    self.Swipe:SetCooldownUNIX(0, 0);
-    self.Done:Show();
-    bgColor = CP_BACKGROUND_COLOR.complete
-  else
-    if (self.shipmentsReady >= 1) and (self.shipmentsReady < self.shipmentsTotal) then
-      bgColor = CP_BACKGROUND_COLOR.shipmentsReady
-    end
-    self.Swipe:SetCooldownUNIX(self.creationTime or 0 , self.duration or 0);
-  end
-  self.Background:SetColorTexture(unpack(bgColor))
-
-  SetActualShipmentTime(self)
-
-  if self.originalReady then
-    print('|cFF00FF88'..self.name..'|r', 'starting ready:', self.originalReady, 'starting time:', self.originalCreationTime)
-  end
-end
-
-function ShipmentEntry:OnUpdate(sinceLast)
-  self.throttle = (self.throttle or 1) + sinceLast
-  if self.throttle >= 1 then
-    self.throttle = self.throttle - 1
-  else
+function ClassPlanCharacterButtonMixin:OnEnter()
+  if not self.profileKey then
     return
   end
 
-
-  if (self.shipmentsReady and self.shipmentsTotal) and (self.shipmentsReady < self.shipmentsTotal) then
-    local expires, duration = SetActualShipmentTime(self)
-
-    if self.isComplete then
-      self.TimeLeft:SetText('Complete!')
-      self.TimeLeft:SetTextColor(0,1,1)
-    elseif self.shipmentsReady >= 1 then
-      self:SetTimeLeft(expires, duration)
-      self.TimeLeft:SetTextColor(0,1,0)
-    else
-      self:SetTimeLeft(expires, duration)
-      self.TimeLeft:SetTextColor(1,1,1)
-    end
-
-  elseif self.isBeingResearched then
-    self:SetTimeLeft(self.researchStartTime + self.researchDuration - time(), self.researchDuration)
-    self.TimeLeft:SetTextColor(1,1,1)
-  else
-    self.TimeLeft:SetText('Complete!')
-    self.TimeLeft:SetTextColor(0,1,0)
+  GameTooltip:SetOwner(self, 'ANCHOR_RIGHT')
+  local info = ClassOrderPlan.data.characters[self.profileKey]
+  GameTooltip:AddLine(self.characterName, self.classColor.r, self.classColor.g, self.classColor.b)
+  local numItems = 0
+  if info.missions then
+    GameTooltip:AddLine(#info.missions .. ' mission'..((#info.missions == 1) and '' or 's')..' in progress')
   end
-
+  if info.shipments then
+    GameTooltip:AddLine(#info.shipments .. ' work order' .. ((#info.shipments == 1) and '' or 's'))
+  end
+  if info.available then
+    GameTooltip:AddLine(#info.available .. ' mission'..((#info.available == 1) and '' or 's')..' available')
+  end
+  GameTooltip:Show()
 end
 
-function ShipmentEntry:OnEnter()
-  if ( self.shipmentsReady and self.shipmentsTotal ) then
-    GameTooltip:SetOwner(self, 'ANCHOR_LEFT')
-    GameTooltip:AddLine(self.Owner:GetText(), self.Owner:GetTextColor())
-    GameTooltip:AddLine(self.shipmentType)
-    GameTooltip:AddLine(self.shipmentsReady .. ' of '.. self.shipmentsTotal)
-    GameTooltip:Show()
-  end
-end
-
-function ShipmentEntry:OnLeave()
+function ClassPlanCharacterButtonMixin:OnLeave()
   if GameTooltip:IsOwned(self) then
     GameTooltip:Hide()
   end
 end
 
-function ShipmentEntry:OnClick(button)
+function ClassPlanCharacterButtonMixin:OnClick(button, down)
+  print('OnClick', self.profileKey)
+  local clist = ClassOrderPlan.data.characters
+
   if button == 'RightButton' then
-    self.handler:FreeBlock(self)
+    for _, profile in pairs(clist) do
+      profile.showItems = true
+    end
+  else
+    if clist[self.profileKey].showItems then
+      clist[self.profileKey].showItems = nil
+    else
+      clist[self.profileKey].showItems = true
+    end
   end
+  for i, handler in ipairs(ClassOrderPlan.Handlers) do
+    handler.isStale = true
+  end
+
+  ClassOrderPlan:RefreshData()
+  ClassOrderPlan:Update()
+  print(clist[self.profileKey].showItems)
 end
-
-
-
-
-ClassPlanMissionHandler = Mixin(MissionList, SharedHandlers)
-ClassPlanMissionEntryMixin = Mixin(MissionEntry, SharedEntry)
-ClassPlanShipmentHandler = Mixin(ShipmentList, SharedHandlers)
-ClassPlanShipmentEntryMixin = Mixin(ShipmentEntry,SharedEntry)
-
-ClassPlanHeaderMixin = {
-  OnClick = function(self)
-    self:GetParent():SetList()
-    self:GetParent().isStale = true
-    ClassOrderPlan:Update()
-  end
-}
-
-ClassPlanCharacterButtonMixin = {
-  Update = function(self)
-    print(CLASS_ICON_TCOORDS[self.className:upper()])
-    if self.className and CLASS_ICON_TCOORDS[self.className:upper()] then
-      self.Icon:SetTexCoord(unpack(CLASS_ICON_TCOORDS[self.className:upper()]))
-    end
-    self.Icon:SetDesaturated(self.showItems)
-    self.SelectGlow:SetShown(self.isMine)
-  end
-}
-
-function ClassPlanCharacterButtonMixin:OnEnter() end
-function ClassPlanCharacterButtonMixin:OnLeave() end
-function ClassPlanCharacterButtonMixin:OnClick() end
\ No newline at end of file
--- a/ClassPlan.xml	Fri Nov 04 02:54:32 2016 -0400
+++ b/ClassPlan.xml	Sun Dec 25 13:04:57 2016 -0500
@@ -18,20 +18,45 @@
       <OnShow method="OnShow" />
       <OnHide method="OnHide" />
       <OnUpdate method="OnUpdate" />
+      <OnDragStart method="OnDragStart" />
+      <OnDragStop method="OnDragStop" />
     </Scripts>
+    <Animations>
+      <AnimationGroup parentKey="FadeIn" setToFinalAlpha="true">
+        <Alpha parentKey="AlphaFade" fromAlpha="0" toAlpha="1" duration=".3" order="1" />
+      </AnimationGroup>
+      <AnimationGroup parentKey="FadeOut">
+
+        <Alpha parentKey="AlphaFade" fromAlpha="1" toAlpha="0" duration=".3" order="1" />
+      </AnimationGroup>
+    </Animations>
     <Layers>
       <Layer level="BACKGROUND">
-        <Texture parentKey="Background" setAllPoints="true">
-          <Color a="0.5" r="0" b="0" g="0" />
+        <Texture parentKey="HeaderInset">
+          <Size y="24" />
+          <Anchors>
+            <Anchor point="TOPLEFT" x="0" y="0" />
+            <Anchor point="RIGHT" x="0" y="0" />
+          </Anchors>
+          <Color r="0" g="0" b="0" a="0.5" />
         </Texture>
       </Layer>
       <Layer level="ARTWORK">
-        <Texture parentKey="portrait">
-          <Size x="40" y="40" />
+        <Texture parentKey="ClassStripe">
+          <Size y="4" />
           <Anchors>
-            <Anchor point="TOPLEFT" />
+            <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeKey="$parent.HeaderInset" x="0" y="-24" />
+            <Anchor point="RIGHT" />
+          </Anchors>
+          <Color a="1" r="0" b="0" g="0" />
+        </Texture>
+        <Texture parentKey="BackgroundInset">
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeKey="$parent.ClassStripe" x="0" y="0" />
 
+            <Anchor point="BOTTOMRIGHT" />
           </Anchors>
+          <Color a="0.65" r="0" g="0" b="0" />
         </Texture>
       </Layer>
     </Layers>
@@ -49,6 +74,41 @@
       <OnMouseWheel method="OnMouseWheel" />
     </Scripts>
     <Frames>
+      <Button name="$parentTab" parentKey="ListTab" mixin="ClassPlanHeaderMixin">
+        <Anchors>
+          <Anchor point="TOPLEFT" />
+          <Anchor point="RIGHT" />
+        </Anchors>
+        <Scripts>
+          <OnLoad method="OnLoad" />
+          <OnClick method="OnClick" />
+        </Scripts>
+        <Size y="24" />
+        <Layers>
+          <Layer level="BACKGROUND">
+            <Texture parentKey="Background">
+              <Color a="1" r="0" g="0" b="0" />
+            </Texture>
+          </Layer>
+          <Layer level="OVERLAY">
+            <FontString parentKey="Label" inherits="ClassPlanFont">
+              <Anchors>
+                <Anchor point="CENTER" />
+              </Anchors>
+            </FontString>
+            <FontString parentKey="Count" inherits="ClassPlanFont">
+              <Anchors>
+                <Anchor point="LEFT" x="3" y="0" />
+              </Anchors>
+            </FontString>
+          </Layer>
+          <Layer level="HIGHLIGHT">
+            <Texture parentKey="Highlight">
+              <Color a="0.5" r="1" g="1" b="1" />
+            </Texture>
+          </Layer>
+        </Layers>
+      </Button>
       <Frame parentKey="MoreItemsUp" frameStrata="HIGH" hidden="true">
         <Size y="24" />
         <Anchors>
@@ -87,11 +147,31 @@
         </Layers>
       </Frame>
     </Frames>
+    <Layers>
+      <Layer level="OVERLAY">
+        <FontString parentKey="Count" inherits="ClassPlanFont">
+          <Anchors>
+            <Anchor point="BOTTOMLEFT" relativePoint="TOPLEFT" x="0" y="3" />
+          </Anchors>
+        </FontString>
+      </Layer>
+    </Layers>
   </Frame>
 
-
-  <Frame name="$parentShipments" parentKey="Shipments" mixin="ClassPlanShipmentHandler" inherits="ClassPlanPanelTemplate" />
   <Button name="ClassPlanListEntryTemplate" virtual="true" hidden="true">
+    <Animations>
+      <AnimationGroup parentKey="NewBlockFade">
+        <Scripts>
+          <OnStop>
+            self:GetParent():OnAnimFinished()
+          </OnStop>
+          <OnFinished>
+            self:GetParent():OnAnimFinished()
+          </OnFinished>
+        </Scripts>
+        <Alpha parentKey="AlphaFade" duration="0.5" fromAlpha="0" toAlpha="1" order="1" />
+      </AnimationGroup>
+    </Animations>
     <Scripts>
       <OnLoad method="OnLoad" />
       <OnClick method="OnClick" />
@@ -138,13 +218,19 @@
         </Texture>
       </Layer>
       <Layer level="ARTWORK">
-        <Texture parentKey="Done" atlas="GarrLanding-ShipmentCompleteGlow">
+        <Texture parentKey="Done" atlas="GarrLanding-ShipmentCompleteGlow" hidden="true">
           <Size x="24" y="24" />
           <Anchors>
             <Anchor point="BOTTOMLEFT"/>
           </Anchors>
         </Texture>
       </Layer>
+      <Layer level="HIGHLIGHT">
+
+        <Texture setAllPoints="true">
+          <Color a="0.25" r="1" g="1" b="1" />
+        </Texture>
+      </Layer>
     </Layers>
     <Frames>
       <Cooldown parentKey="Swipe" inherits="CooldownFrameTemplate" reverse="true" hideCountdownNumbers="true">
@@ -167,20 +253,21 @@
         <Layers>
           <Layer level="OVERLAY">
 
-            <FontString parentKey="Name" inherits="ClassPlanFont" justifyV="TOP" justifyH="CENTER">
-              <Anchors>
-                <Anchor point="LEFT" x="26" y="0"/>
-              </Anchors>
-              <Color r=".75" g=".75" b=".73"/>
-            </FontString>
             <FontString parentKey="TimeLeft" inherits="ClassPlanFont" justifyH="CENTER">
               <Anchors>
                 <Anchor point="RIGHT" x="-2" y="0"/>
               </Anchors>
             </FontString>
+            <FontString parentKey="Name" inherits="ClassPlanFont" justifyV="TOP" wordwrap="false" justifyH="LEFT">
+              <Anchors>
+                <Anchor point="LEFT" x="26" y="0"/>
+                <Anchor point="RIGHT" relativePoint="LEFT" relativeKey="$parent.TimeLeft" x="-4" y="0"/>
+              </Anchors>
+              <Color r=".75" g=".75" b=".73"/>
+            </FontString>
             <FontString parentKey="Count" inherits="WorldPlanFont" justifyH="CENTER">
               <Anchors>
-                <Anchor point="CENTER" relativePoint="LEFT" x="12" y="0"/>
+                <Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT" x="2" y="1" relativeKey="$parent.$parent.Icon"/>
               </Anchors>
             </FontString>
           </Layer>
@@ -216,6 +303,11 @@
           <TexCoords left="0.5" right="1" top="0" bottom="0.5" />
         </Texture>
       </Layer>
+      <Layer level="HIGHLIGHT">
+        <Texture parentKey="HighlightGlow" file="Interface\Glues\CHARACTERCREATE\UI-CharacterCreate-Highlights" alphaMode="ADD" alpha="0.5">
+          <TexCoords left="0" right="0.5" top="0" bottom="0.5" />
+        </Texture>
+      </Layer>
     </Layers>
   </Button>
 
--- a/ClassPlanFollowers.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/ClassPlanFollowers.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -1,11 +1,50 @@
-ClassPlanFollowerMixin = {}
-local c = ClassPlanFollowerMixin
-function c:OnEvent(event, arg)
+
+local print = DEVIAN_WORKSPACE and function(...) print('ClassPlan', ...) end or nop
+local c  = {
+
+  templateName = 'ClassPlanShipmentEntry',
+  listKey = {'followers'},
+  listTitle = {'Followers'},
+  events = {
+    'GARRISON_FOLLOWER_LIST_UPDATE',
+    'GARRISON_FOLLOWER_XP_CHANGED',
+    'GARRISON_FOLLOWER_DURABILITY_CHANGED',
+    'GARRISON_FOLLOWER_UPGRADED',
+    'GARRISON_FOLLOWER_REMOVED'
+  }
+}
+
+
+function c:OnLoad()
+  self.followerType = 4
+  -- follower type, versus garrison_type
+end
+
+function c:OnEvent(event, ...)
+  print('|cFF00FF00'..self:GetName()..':OnEvent()|r', event, ...)
+  self:GetPlayerData()
+end
+
+function c:OnUpdate() end
+
+function c:GetPlayerData()
+
+  print('|cFF0088FF'..self:GetName()..':GetPlayerData()|r')
+  local profileList = self:GetParent().profile.followers
+  local followerInfo = C_Garrison.GetFollowers(self.followerType)
+  if followerInfo then
+    table.wipe(profileList)
+    for followerID, follower in pairs(followerInfo) do
+      profileList[followerID] = follower
+    end
+  end
 
 end
 
-function c:GetPlayerData(event, arg) end
+function c:UpdateItems()
+end
+function c:Reanchor()
+end
 
-function c:UpdateItems() end
 
-function c:Reanchor() end
\ No newline at end of file
+ClassPlanFollowerMixin = CreateFromMixins(ClassPlanHandlerBase, c)
\ No newline at end of file
--- a/ClassPlanFollowers.xml	Fri Nov 04 02:54:32 2016 -0400
+++ b/ClassPlanFollowers.xml	Sun Dec 25 13:04:57 2016 -0500
@@ -1,9 +1,9 @@
 <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="FollowerList.lua" />
-  <Frame name="$parentFollowerList" parent="ClassOrderPlan"parentKey="FollowerList"  parentArray="Handlers" mixin="ClassPlanFollowerMixin">
+  <Script file="ClassPlanFollowers.lua" />
+  <Frame name="$parentFollowers" parent="ClassOrderPlan" parentKey="FollowerList"  parentArray="Handlers" mixin="ClassPlanFollowerMixin" inherits="ClassPlanPanelTemplate">
     <Scripts>
-      <OnEvent method="OnEvent" />
+      <OnEvent method="OnEvent"/>
       <OnUpdate method="OnUpdate" />
       <OnShow method="OnShow" />
     </Scripts>
--- a/ClassPlanMissions.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/ClassPlanMissions.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -1,19 +1,37 @@
-local MissionList = ClassPlanMissionHandler
-local ListEntry = ClassPlanMissionEntryMixin
+local _, db= ...
 local ORDERHALL_TYPE = LE_GARRISON_TYPE_7_0
-function ClassPlanMissionHandler:Reanchor()
-  self:SetPoint('TOPRIGHT', 0, -24)
-  self:SetPoint('BOTTOMLEFT', ClassOrderPlan:GetWidth()/2, 0)
-
-end
+local UnitClass = UnitClass
+local time, max, min = time, max, min
+local print = DEVIAN_WORKSPACE and function(...) print('ClassPlan', ...) end or nop
 local GetPrimaryGarrisonFollowerType = GetPrimaryGarrisonFollowerType
 local C_Garrison = C_Garrison
 
 local wipe, tinsert, date, ipairs = table.wipe, table.insert, date, ipairs
 local GetItemIcon = GetItemIcon
-local print = DEVIAN_WORKSPACE and function(...) print('ClassPlan', ...) end or nop
 
-function ClassPlanMissionHandler:GetPlayerData (profile)
+
+local MissionList = {
+  templateName = 'ClassPlanMissionEntry',
+  listKey = {'missions', 'available'},
+  listTitle = {'In Progress', 'Available'},
+
+  point = 'TOPLEFT',
+  relativePoint ='TOPLEFT',
+  events = {
+    'GARRISON_MISSION_LIST_UPDATE'},
+}
+local ListEntry = {}
+
+function MissionList:Reanchor()
+  self:SetPoint('TOPRIGHT', ClassOrderPlan.BackgroundInset, 'TOPRIGHT', 0, 0)
+  self:SetPoint('BOTTOMLEFT', ClassOrderPlan:GetWidth()/2, 0)
+end
+
+
+
+
+function MissionList:GetPlayerData ()
+  local profile = self:GetParent().profile
   local items = C_Garrison.GetAvailableMissions(GetPrimaryGarrisonFollowerType(ORDERHALL_TYPE));
   if not items then
     return
@@ -38,60 +56,83 @@
   return true
 end
 
-function ClassPlanMissionHandler:OnGetItem (data)
-  if data.missionEndTime and (data.missionEndTime < self.currentTime) then
-    data.isComplete = true
+function MissionList:OnGetItem (data)
+  if data.missionEndTime then
+    if (data.missionEndTime < self.currentTime) then
+      data.isComplete = true
+    else
+      self:ScheduleUpdate(data.missionEndTime)
+    end
   end
-  if data.offerEndTime and (data.offerEndTime < self.currentTime) then
-    data.isExpired = true
-    data.timeToKeep = self.currentTime + 300
+
+  if data.offerEndTime then
+    if (data.offerEndTime < self.currentTime) then
+      data.isExpired = true
+      data.timeToKeep = self.currentTime + 300
+    else
+      self:ScheduleUpdate(data.offerEndTime)
+    end
   end
+
 end
 
 MissionList.SortHandler = function (a,b)
   local result = false
-  --if not a or not b then
-  --  return true
-  --else
-  --if (a.isMine ~= b.isMine) then
-  --  result = a.isMine
-  --else
-  --if (not b.missionEndTime) or (not a.missionEndTime) then
-  --  print('missing article', b.missionEndTime, a.missionEndTime)
-  --end
   if b.isComplete ~= a.isComplete then
     return a.isComplete
-  elseif b.isMine ~= a.isMine then
-    return a.isMine
-  elseif b.missionEndTime then
-    return ( b.missionEndTime > a.missionEndTime)
   else
-    return  ((b.offerEndTime or 0) > (a.offerEndTime or 0))
+    if b.isMine ~= a.isMine then
+      return a.isMine
+    else
+      if a.isComplete then
+        if a.profileKey ~= b.profileKey then
+          return (a.profileKey < b.profileKey)
+        else
+          return (a.name < b.name)
+        end
+      else
+        if b.missionEndTime then
+          return ( b.missionEndTime > a.missionEndTime)
+        else
+          return ((b.offerEndTime or 0) > (a.offerEndTime or 0))
+        end
+      end
+    end
+  end
+end
+
+function MissionList:OnEvent(event, ...)
+  self:RefreshData()
+  if event == 'GARRISON_MISSION_FINISHED' then
+    local followerTypeID, missionID = ...;
+  elseif event == 'GARRISON_TALENT_COMPLETE' then
+    local garrisonType = ...;
+    elseif event == '' then
   end
 
-  --end
-  --end
 end
 
-
 function MissionList:OnShow()
   print('|cFF00FF88'..self:GetName()..':OnShow()|r')
 end
 
-
-
-function ClassPlanMissionEntryMixin:OnComplete()
+function ListEntry:OnComplete()
   print('flagging complete', self.name)
   self:Update()
 end
 
-function ClassPlanMissionEntryMixin:OnUpdate(sinceLast)
+function ListEntry:OnUpdate(sinceLast)
+  if self.doAnimation then
+    self.doAnimation = nil
+    self.NewBlockFade:Play()
+  end
   self.throttle = (self.throttle or .5) + sinceLast
   if self.throttle < .5 then
     return
   else
     self.throttle = self.throttle - .5
   end
+
   if self.offerEndTime then
     self:SetTimeLeft(self.offerEndTime)
   elseif self.missionEndTime then
@@ -99,8 +140,12 @@
   end
 end
 
-function ClassPlanMissionEntryMixin:OnLoad()
-  print('|cFFFF4400',self:GetName() or tostring(self), 'onload')
+function ListEntry:OnHide(...)
+  print(self:GetName().. ':Hide()', ...)
+end
+
+function ListEntry:OnLoad()
+  print('|cFFFF4400'..self:GetName()..':OnLoad()')
   self.Count = self.Overlay.Count
   self.Name = self.Overlay.Name
   self.TimeLeft = self.Overlay.TimeLeft
@@ -109,9 +154,17 @@
 
   self.Icon:SetDesaturated(false)
   self.Done:Hide()
+  --[[
+  hooksecurefunc(self.Background, 'SetColorTexture', function(self, ...)
+    print(self:GetName(), ...)
+  end)
+  hooksecurefunc(self.Background, 'SetTexture', function(self, ...)
+    --print(self:GetName(), ...)
+  end)
+  --]]
 end
 
-function ClassPlanMissionEntryMixin:Update()
+function ListEntry:Update()
   local r,g,b = 1, 1, 1
   if self.isRare then
     r,g,b = 0.1, 0.4, 1
@@ -127,26 +180,31 @@
     self.Icon:SetAtlas(self.typeAtlas, false)
   end
 
+  local itemType = db.ClassPlanTypes.inProgress
+  if self.isComplete then
+    itemType = db.ClassPlanTypes.complete
+  end
 
-
-  if self.isComplete then
-    self.TimeLeft:SetText('Complete!')
-    self.Background:SetColorTexture(.25,.25,.25,1)
-  else
-    self.Background:SetColorTexture(0,0,0,0.5)
-  end
+  self.Background:SetColorTexture(unpack(itemType.backgroundColor))
 end
 
 function ListEntry:OnEnter()
+  WorldMap_HijackTooltip(self)
   if self.rewardInfo and self.rewardInfo.itemID then
-    GameTooltip:SetOwner(self, 'ANCHOR_LEFT')
-    GameTooltip:SetItemByID(self.rewardInfo.itemID)
-    GameTooltip:Show()
+    WorldMapTooltip:SetOwner(self, 'ANCHOR_LEFT')
+    WorldMapTooltip:AddLine(self.name)
+    EmbeddedItemTooltip_SetItemByID(WorldMapTooltip.ItemTooltip, self.rewardInfo.itemID)
+    WorldMapTooltip:Show()
   end
 end
 
 function ListEntry:OnLeave()
-  if GameTooltip:IsOwned(self) then
-    GameTooltip:Hide()
+  WorldMap_RestoreTooltip()
+  if WorldMapTooltip:IsOwned(self) then
+    WorldMapTooltip:Hide()
   end
-end
\ No newline at end of file
+end
+
+
+ClassPlanMissionHandler = CreateFromMixins(ClassPlanHandlerBase, MissionList)
+ClassPlanMissionEntryMixin = CreateFromMixins(ClassPlanEntryBase, ListEntry)
\ No newline at end of file
--- a/ClassPlanMissions.xml	Fri Nov 04 02:54:32 2016 -0400
+++ b/ClassPlanMissions.xml	Sun Dec 25 13:04:57 2016 -0500
@@ -7,6 +7,11 @@
     <Anchors>
       <Anchor point="RIGHT" />
     </Anchors>
+    <Scripts>
+
+      <OnShow method="OnShow" />
+      <OnHide method="OnHide" />
+    </Scripts>
   </Button>
   <Frame name="ClassPlanMissionList" parentKey="MissionList" mixin="ClassPlanMissionHandler" inherits="ClassPlanPanelTemplate">
     <Scripts>
@@ -14,34 +19,7 @@
       <OnUpdate method="OnUpdate" />
       <OnEvent method="OnEvent" />
       <OnShow method="OnShow" />
+      <OnHide method="OnHide" />
     </Scripts>
-    <Frames>
-      <Button name="$parentTab" parentKey="ListTab" mixin="ClassPlanHeaderMixin">
-        <Scripts>
-          <OnClick method="OnClick" />
-        </Scripts>
-        <Size y="24" />
-        <Layers>
-          <Layer level="BACKGROUND">
-            <Texture parentKey="Background">
-              <Color a="1" r="0" g="0" b="0" />
-            </Texture>
-          </Layer>
-          <Layer level="OVERLAY">
-            <FontString parentKey="Label" inherits="ClassPlanFont">
-              <Anchors>
-                <Anchor point="CENTER" />
-              </Anchors>
-            </FontString>
-          </Layer>
-          <Layer level="HIGHLIGHT">
-            <Texture parentKey="Highlight">
-              <Color a="0.5" r="1" g="1" b="1" />
-            </Texture>
-          </Layer>
-        </Layers>
-
-      </Button>
-    </Frames>
   </Frame>
   </Ui>
\ No newline at end of file
--- a/ClassPlanShipments.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/ClassPlanShipments.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -0,0 +1,329 @@
+local _, db = ...
+
+local wipe, tinsert, ipairs, select = table.wipe, table.insert, ipairs, select
+local UnitClass = UnitClass
+local time, max, min = time, max, min
+local print = DEVIAN_WORKSPACE and function(...) print('ClassPlan', ...) end or nop
+
+
+local CG_GetBuildings = C_Garrison.GetBuildings
+local CG_GetFollowerShipments = C_Garrison.GetFollowerShipments
+local CG_GetLooseShipments = C_Garrison.GetLooseShipments
+local CG_GetTalentTrees = C_Garrison.GetTalentTrees
+local CG_GetCompleteTalent = C_Garrison.GetCompleteTalent
+local CG_GetLandingPageShipmentInfo = C_Garrison.GetLandingPageShipmentInfo
+local CG_GetLandingPageShipmentInfoByContainerID = C_Garrison.GetLandingPageShipmentInfoByContainerID
+
+local AK_NOTES, RECRUIT_MAJOR, RECRUIT_MINOR, OH_TALENT, NOMI = 2, 4, 8, 16, 32
+
+local FollowerTypes = {
+  ['Pathfinders'] = true,
+  ['Acolytes'] = true,
+}
+local ShipmentOrder = {
+  [AK_NOTES] = 2,
+  [RECRUIT_MAJOR] = 3,
+  [RECRUIT_MINOR] = 4,
+  [NOMI] = 5,
+  [OH_TALENT] = 6,
+}
+local SortKey = 'shipmentType'
+local SortTable = ShipmentOrder
+
+local ShipmentList = {
+  templateName = 'ClassPlanShipmentEntry',
+  listKey = {'shipments'},
+  listTitle = {'Work Orders'},
+  events = {
+    'GARRISON_MISSION_LIST_UPDATE',
+    'GARRISON_LANDINGPAGE_SHIPMENTS',
+    'GARRISON_TALENT_UPDATE',
+    "GARRISON_TALENT_COMPLETE",
+    "GARRISON_SHIPMENT_RECEIVED",
+    'GARRISON_FOLLOWER_LIST_UPDATE',
+    'SHIPMENT_CRAFTER_INFO',
+    'ITEM_PUSH'},
+}
+
+local ShipmentEntry = {}
+
+function ShipmentList:Reanchor()
+  print('|cFF00FFFF'..self:GetName()..':Reanchor|r')
+  self:SetPoint('TOPLEFT', ClassOrderPlan.BackgroundInset, 'TOPLEFT')
+  self:SetPoint('BOTTOMRIGHT', -ClassOrderPlan.BackgroundInset:GetWidth()/2, 0)
+end
+
+do
+  local ShipmentsInfo = {}
+  local AddShipmentInfo = function(shipmentType, name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID, followerID)
+    -- early login queries may return empty tables, causing the sorter to compare nil
+    if not creationTime then
+      return
+    end
+    --print(shipmentType, name, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString)
+    tinsert(ShipmentsInfo,
+      {
+        shipmentType = shipmentType,
+        name = name,
+        icon = texture,
+        shipmentCapacity = shipmentCapacity,
+        shipmentsReady = shipmentsReady,
+        shipmentsTotal = shipmentsTotal,
+        creationTime = creationTime,
+        duration = duration,
+        timeleftString = timeleftString,
+        itemName = itemName,
+        itemIcon = itemIcon,
+        itemQuality = itemQuality,
+        itemID = itemID,
+        followerID = followerID,
+      })
+    print('  ', name, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration)
+  end
+  function ShipmentList:GetPlayerData ()
+    local profileList = self:GetParent().profile.shipments
+    wipe(ShipmentsInfo)
+
+    local garrisonType = LE_GARRISON_TYPE_7_0
+    local buildings = CG_GetBuildings(garrisonType);
+    local shipmentIndex = 0
+    --print('Buildings:')
+    for i = 1, #buildings do
+      local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID = CG_GetLandingPageShipmentInfo(i);
+      AddShipmentInfo(RECRUIT_MAJOR, name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID)
+    end
+
+    --print('Follower:')
+    local followerShipments = CG_GetFollowerShipments(garrisonType);
+    for i = 1, #followerShipments do
+      local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, _, _, _, _, followerID = CG_GetLandingPageShipmentInfoByContainerID(followerShipments[i]);
+      AddShipmentInfo(RECRUIT_MINOR, name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, nil, nil, nil, nil, followerID)
+    end
+
+    --print('Loose:')
+    local looseShipments = CG_GetLooseShipments(garrisonType)
+    for i = 1, #looseShipments do
+      local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString = CG_GetLandingPageShipmentInfoByContainerID(looseShipments[i]);
+      AddShipmentInfo(AK_NOTES, name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString)
+    end
+
+    local talentTrees = CG_GetTalentTrees(garrisonType, select(3, UnitClass("player")));
+    -- this is a talent that has completed, but has not been seen in the talent UI yet.
+    local completeTalentID = CG_GetCompleteTalent(garrisonType);
+    --print('Talents:')
+    if (talentTrees) then
+      for treeIndex, tree in ipairs(talentTrees) do
+        for talentIndex, talent in ipairs(tree) do
+          local showTalent = false;
+          if (talent.isBeingResearched) or (talent.id == completeTalentID) then
+            AddShipmentInfo(OH_TALENT, talent.name, talent.icon, 1, (talent.isBeingResearched and 0 or 1), 1, talent.researchStartTime, talent.researchDuration, talent.timeleftString)
+          end
+        end
+      end
+    end
+
+    wipe(profileList)
+    for index, data in ipairs(ShipmentsInfo) do
+      --DEFAULT_CHAT_FRAME:AddMessage(data.shipmentType ..' '.. tostring(data.name) ..' '.. tostring(data.creationTime) ..' '.. tostring(data.duration))
+      tinsert(profileList, data)
+    end
+    self.isStale = true
+    return true
+  end
+end
+-- Update shipment flags data
+local SetActualShipmentTime = function(self)
+
+  if self.isComplete then
+    return nil, nil
+  end
+
+  local timestamp = time()
+  local timeLeft = self.creationTime + self.duration - timestamp
+  local duration = self.duration * (self.fullDuration and (self.shipmentsTotal - self.shipmentsReady) or 1)
+  local justFinished = false
+  while (self.shipmentsReady < self.shipmentsTotal) and (timeLeft <= 0) do
+    if not self.originalReady then
+      self.originalReady = self.shipmentsReady
+      self.originalCreationTime = self.creationTime
+    end
+
+    self.shipmentsReady = self.shipmentsReady + 1
+    self.creationTime = self.creationTime + self.duration
+    timeLeft = timeLeft + self.duration
+    print('|cFF00FF88  pre-parse "'..self.name..'"|r', 'timeLeft:', timeLeft, 'shipments:', self.shipmentsReady, self.shipmentsTotal)
+  end
+
+  if (timeLeft <= 0) and (not self.isBeingResearched) then
+    self.isComplete = true
+    self.isStale = true
+  end
+
+  local expires = (self.originalCreationTime or self.creationTime) + duration
+  return expires, duration
+end
+
+function ShipmentList:OnEvent(event, ...)
+  if (event == 'SHIPMENT_CRAFTER_INFO' or event == 'ITEM_PUSH' or event == 'GARRISON_FOLLOWER_LIST_UPDATE') then
+    C_Garrison.RequestLandingPageShipmentInfo()
+  elseif event == 'SHIPMENT_UPDATE' then
+    local shipmentStarted = ...
+    if shipmentStarted then
+      C_Garrison.RequestLandingPageShipmentInfo()
+    end
+  elseif event == 'GARRISON_LANDINGPAGE_SHIPMENTS' then
+    --WorldPlan:print("New shipments data received.")
+    self:RefreshData()
+  else
+    ClassPlanHandlerBase.OnEvent(self, event, ...)
+  end
+end
+
+
+function ShipmentList:OnGetItem (data)
+  if data.shipmentsTotal then
+    local expires = SetActualShipmentTime(data)
+    if expires and (expires > time()) then
+      self:ScheduleUpdate(expires)
+    end
+  end
+
+  if data.shipmentType == 255 or type(data.shipmentType == 'string') then
+    if data.name == 'Artifact Research Notes' then
+      data.shipmentType = AK_NOTES
+    elseif string.match(data.name, 'Recipes') then
+      data.shipmentType = NOMI
+    elseif FollowerTypes[data.name] then
+      data.shipmentType = RECRUIT_MINOR
+    else
+      data.shipmentType = OH_TALENT
+    end
+  end
+
+
+end
+
+function ShipmentList:OnHeaderClick()
+  -- flip sort table and key, push a sort and refresh
+end
+
+
+ShipmentList.SortHandler = function(a, b)
+  if a[SortKey]  then
+    if b[SortKey] then
+      return SortTable[a[SortKey]] < SortTable[b[SortKey]]
+    else
+      return true
+    end
+  else
+    if b.isComplete ~= a.isComplete then
+      return a.isComplete and true or false
+    else
+      if a.profileKey ~= b.profileKey then
+        return a.profileKey < b.profileKey
+      else
+        if a.shipmentsReady and b.shipmentsReady then
+          return (a.shipmentsReady) > (b.shipmentsReady)
+        elseif a.shipmentsReady or b.shipmentsReady then
+          return (a.shipmentsReady) or true or false
+        else
+
+          if (a.creationTime ~= b.creationTime) then
+            return (a.creationTime) < (b.creationTime)
+          else
+            return (a.name) < (b.name)
+          end
+        end
+
+      end
+    end
+  end
+end
+
+function ShipmentList:OnLoad()
+end
+function  ShipmentList:OnShow()
+  print('|cFF00FF88'..self:GetName()..':OnShow()|r')
+end
+
+function ShipmentEntry:OnLoad()
+  ClassPlanMissionEntryMixin.OnLoad(self)
+end
+
+
+function ShipmentEntry:Update()
+  --print('  |cFF00FF88"'.. self.name..'":Update()|r')
+  self.Icon:SetTexture(self.icon)
+  self.Count:SetText(self.shipmentsReady ..'/'.. self.shipmentsTotal)
+
+  -- flag as complete
+  local itemType = db.ClassPlanTypes.inProgress
+  if ( self.shipmentsReady >= self.shipmentsTotal ) and (not self.isBeingResearched) then
+    self.Swipe:SetCooldownUNIX(0, 0);
+    itemType = db.ClassPlanTypes.complete
+  else
+    if (self.shipmentsReady >= 1) and (self.shipmentsReady < self.shipmentsTotal) then
+      itemType = db.ClassPlanTypes.shipmentsReady
+    end
+    self.Swipe:SetCooldownUNIX(self.creationTime or 0 , self.duration or 0);
+  end
+  self.Background:SetColorTexture(unpack(itemType.backgroundColor))
+
+  SetActualShipmentTime(self)
+  self.throttle = 2
+end
+
+
+
+function ShipmentEntry:OnUpdate(sinceLast)
+  self.throttle = (self.throttle or 1) + sinceLast
+  if self.throttle >= 1 then
+    self.throttle = self.throttle - 1
+  else
+    return
+  end
+
+  if (self.shipmentsReady and self.shipmentsTotal) and (self.shipmentsReady < self.shipmentsTotal) then
+    local expires, duration = SetActualShipmentTime(self)
+    if self.isComplete then
+      self.TimeLeft:SetText('Complete!')
+      self.TimeLeft:SetTextColor(0,1,1)
+    elseif self.shipmentsReady >= 1 then
+      self:SetTimeLeft(expires, duration)
+      self.TimeLeft:SetTextColor(1,1,0)
+    else
+      self:SetTimeLeft(expires, duration)
+      self.TimeLeft:SetTextColor(1,1,1)
+    end
+  elseif self.isBeingResearched then
+    self:SetTimeLeft(self.researchStartTime + self.researchDuration - time(), self.researchDuration)
+    self.TimeLeft:SetTextColor(1,1,1)
+  else
+    self.TimeLeft:SetText('Complete!')
+    self.TimeLeft:SetTextColor(0,1,0)
+  end
+end
+
+function ShipmentEntry:OnEnter()
+  if ( self.shipmentsReady and self.shipmentsTotal ) then
+    GameTooltip:SetOwner(self, 'ANCHOR_LEFT')
+    GameTooltip:AddLine(self.Owner:GetText(), self.Owner:GetTextColor())
+    GameTooltip:AddLine(self.shipmentsReady .. ' of '.. self.shipmentsTotal)
+    GameTooltip:Show()
+  end
+end
+
+
+function ShipmentEntry:OnLeave()
+  if GameTooltip:IsOwned(self) then
+    GameTooltip:Hide()
+  end
+end
+
+function ShipmentEntry:OnClick(button)
+  self.fullDuration = not self.fullDuration
+  self:Update()
+end
+
+ClassPlanShipmentHandler = CreateFromMixins(ClassPlanHandlerBase, ShipmentList)
+ClassPlanShipmentEntryMixin = CreateFromMixins(ClassPlanEntryBase, ShipmentEntry)
\ No newline at end of file
--- a/ClassPlanShipments.xml	Fri Nov 04 02:54:32 2016 -0400
+++ b/ClassPlanShipments.xml	Sun Dec 25 13:04:57 2016 -0500
@@ -0,0 +1,14 @@
+<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="ClassPlanShipments.lua" />
+
+  <Frame name="$parentShipments" parent="ClassOrderPlan" parentKey="Shipments" mixin="ClassPlanShipmentHandler" inherits="ClassPlanPanelTemplate">
+
+    <Scripts>
+      <OnLoad method="OnLoad" />
+      <OnUpdate method="OnUpdate" />
+      <OnEvent method="OnEvent" />
+      <OnShow method="OnShow" />
+    </Scripts>
+  </Frame>
+</Ui>
\ No newline at end of file
--- a/FilterBar.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/FilterBar.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -3,7 +3,7 @@
 -- Created: 10/27/2016 8:55 PM
 -- %file-revision%
 --
-
+local _, db = ...
 local print = DEVIAN_WORKSPACE and function(...) _G.print('FilterBar', ...) end or nop
 local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
 local wipe, ipairs, pairs = table.wipe, ipairs, pairs
@@ -39,8 +39,9 @@
 }
 local familiars_id = 9696
 
-local DEFAULT_FILTER_LAYOUT = {
+db.DefaultFilterType = {
   iconWidth = 24,
+  iconHeight = 18,
   borderWidth = 3,
   highlightWidth = 2,
   TagSize = 12,
@@ -48,7 +49,7 @@
   showNumber = true,
   numberFontObject = 'WorldPlanNumberFontThin'
 }
-local DEFAULT_FILTER_LIST = {
+db.DefaultFilters = {
   { label = 'Filters', texture = "Interface\\WorldMap\\WorldMap-Icon" },
   { filterKey= 'rewardType', cVar = 'worldQuestFilterArtifactPower', filterValue = REWARD_ARTIFACT_POWER, label = 'Artifact Power', texture = "Interface\\ICONS\\inv_7xp_inscription_talenttome01" },
   { filterKey= 'rewardType', cVar = 'worldQuestFilterOrderResources', filterValue = REWARD_CURRENCY,label = 'Order Resources', texture = "Interface\\Icons\\inv_orderhall_orderresources" },
@@ -62,21 +63,21 @@
 }
 local defaults = {}
 
-WorldPlanSummaryMixin = {
-  selectedBountyIndex = {},
-  bounties = {},
-  filterList = {},
-  buttons = {},
-}
+WorldPlanSummaryMixin = WorldPlanSummaryMixin or {}
+WorldPlanSummaryMixin.selectedBountyIndex = {}
+WorldPlanSummaryMixin.bounties = {}
+WorldPlanSummaryMixin.filterList = {}
+WorldPlanSummaryMixin.buttons = {}
+WorldPlanSummaryMixin.cvarFiltersDirty = false
 WorldPlanFilterPinMixin = {}
 
 function WorldPlanSummaryMixin:OnLoad()
-  WorldPlan:AddHandler(self, defaults)
-  for index, info in ipairs(DEFAULT_FILTER_LIST) do
-    info.zone = DEFAULT_FILTER_LAYOUT
-    info.continent = DEFAULT_FILTER_LAYOUT
+  self:SetParent(WorldMapFrame)
+  WorldPlan:AddHandler(self)
+  for index, info in ipairs(db.DefaultFilters) do
+    info.zone = db.DefaultFilterType
+    info.continent = db.DefaultFilterType
     info.pinMask =  "Interface\\Minimap\\UI-Minimap-Background"
-
     WorldPlan:AddTypeInfo(self,index, info)
   end
 
@@ -93,7 +94,6 @@
 
 function WorldPlanSummaryMixin:Setup()
   print('|cFF00FF88'..self:GetName()..':Setup()')
-  self:GetFilters()
 end
 
 
@@ -117,10 +117,10 @@
 
   wipe(self.filterList)
 
-  for index, info in ipairs(DEFAULT_FILTER_LIST) do
+  for index, info in ipairs(db.DefaultFilters) do
     tinsert(self.filterList, info)
   end
-  self.bounties, self.numBounties = GetQuestBountyInfoForMapID(WorldPlan.currentMapID)
+  self.bounties, self.numBounties = GetQuestBountyInfoForMapID(db.currentMapID)
   self.BountyFilters = {}
   for index, data in ipairs(self.bounties) do
     local info = self.BountyFilters[index]
@@ -148,6 +148,7 @@
 end
 
 function WorldPlanSummaryMixin:Refresh()
+  self:GetFilters()
   self:Update()
 end
 
@@ -157,59 +158,85 @@
 local questResults = {{}}
 function WorldPlanSummaryMixin:Update()
   local blocks = self.buttons
+
   local relativeFrame = WorldMapFrame.UIElementsFrame.TrackingOptionsButton
-  local numHeaders = 0
+  if FlightMapFrame and FlightMapFrame:IsVisible() then
+    relativeFrame = FlightMapFrame
+  end
+  self:SetParent(relativeFrame)
+
+    local numHeaders = 0
   print('|cFF00FF88'..self:GetName()..':Update()|r', 'currentMap=',WorldPlan.currentMapID)
 
-  local layout = DEFAULT_FILTER_LAYOUT
+
+  local layout = db.DefaultFilterType
   local borderWidth = layout.iconWidth + (layout.borderWidth * 2)
   local highlightWidth = borderWidth + (layout.highlightWidth * 2)
-  local quests = WorldPlanQuests.QuestsByZone[WorldPlan.currentMapID] or WorldPlanQuests.QuestsByID
-  local foundQuests = questResults[1]
+  local mapQuests = db.QuestsByZone[db.currentMapID] or db.QuestsByID
+    local firstCvar, lastCvar
   for index, info in ipairs(self.filterList) do
-    local numQuests = 0
-    local resultIndex = numHeaders + 1
-    questResults[resultIndex] = questResults[resultIndex] or  {}
-    wipe(questResults[resultIndex])
-    for questID, pin in pairs(quests) do
-      if pin.used then
+    local numQuestsHere = 0
+    local numQuestsTotal = 0
+    info.questList = info.questList or {}
+    wipe(info.questList)
+
+    for questID, pin in pairs(db.QuestsByID) do
+      print(pin.worldQuestType ~= LE_QUEST_TAG_TYPE_PROFESSION, (db.Config.ShowAllProfessionQuests or pin.isKnownProfession))
+      if (pin.worldQuestType ~= LE_QUEST_TAG_TYPE_PROFESSION) or (db.Config.ShowAllProfessionQuests or pin.isKnownProfession) then
+        print(pin.title)
         if not info.filterKey then
-          numQuests = numQuests + 1
+          if mapQuests[questID] then
+              numQuestsHere = numQuestsHere + 1
+          end
+          numQuestsTotal = numQuestsTotal + 1
         elseif pin[info.filterKey] == info.filterValue then
-          numQuests = numQuests + 1
-          tinsert(questResults[resultIndex], pin)
+          if mapQuests[questID] then
+            numQuestsHere = numQuestsHere + 1
+            tinsert(info.questList, pin)
+          end
+          numQuestsTotal = numQuestsTotal + 1
         end
       end
     end
-    print(tostring(index).. ' ("'..tostring(info.label)..'" f('.. tostring(info.filterKey).. '='..tostring(info.filterValue) .. '), '..tostring(numQuests)..')')
+    --print('num here', numQuestsHere, numQuestsTotal)
+    info.totalQuests = maxQuests
 
-    if numQuests >= 1 then
+    --print(tostring(index).. ' ("'..tostring(info.label)..'" f('.. tostring(info.filterKey).. '='..tostring(info.filterValue) .. '), '..tostring(numQuests)..')')
+
+    if numQuestsTotal >= 1 then
       numHeaders = numHeaders + 1
       local button = blocks[numHeaders]
       if not blocks[numHeaders] then
-        button = CreateFrame('Button', 'WorldPlanFilterButton'..numHeaders, WorldMapScrollFrame, 'WorldPlanFilterPin')
-        button:SetSize(borderWidth, borderWidth)
+        button = CreateFrame('Button', 'WorldPlanFilterButton'..numHeaders, self, 'WorldPlanFilterPin')
+        button:SetSize(32,20)
+        button.icon:SetTexCoord(0.1,.9,.1,(1 * (20/32)))
 
-        button.icon:SetSize(layout.iconWidth, layout.iconWidth)
-        button.RewardBorder:SetSize(borderWidth, borderWidth)
-        button.HighlightBorder:SetSize(highlightWidth, highlightWidth)
-        button.RewardBorder:SetMask(filterMask)
-        button.RewardBorder:SetDesaturated(true)
+        button.RewardBorder:ClearAllPoints()
+        button.RewardBorder:SetPoint('TOPLEFT', button, 'TOPLEFT')
+        button.RewardBorder:SetPoint('BOTTOMRIGHT', button, 'BOTTOMRIGHT')
 
-        button.HighlightBorder:Hide()
+
         blocks[numHeaders] = button
       end
 
       button.info = info
-      button.questList = questResults[resultIndex]
+      button.numQuestsTotal = numQuestsTotal
+      button.numQuestsHere = numQuestsHere
+      button.questList = info.questList
+      button.isFirst = (numHeaders == 1)
       button:SetID(index)
-      button.spacing = ((relativeFrame.cVar and (not info.cVar)) or (relativeFrame.filterKey ~= info.filterKey)) and 5 or 0
+      button.spacing = ((relativeFrame.cVar and (not info.cVar)) or (relativeFrame.filterKey ~= info.filterKey)) and 5 or 1
       button.relativeFrame = relativeFrame
-      button:Refresh((numHeaders == 1), numQuests)
+      button:Refresh()
       button:Show()
       relativeFrame = button
+
+      if info.cVar then
+        firstCvar = firstCvar or button
+        lastCvar = button
+      end
+
     end
-
   end
 
   self.numHeaders = numHeaders
@@ -221,6 +248,16 @@
       wipe(questResults[i])
     end
   end
+
+
+  if firstCvar and lastCvar then
+    self.CVarsHighlight:ClearAllPoints()
+    self.CVarsHighlight:SetPoint('TOPLEFT', firstCvar, 'TOPLEFT', -1, 1)
+    self.CVarsHighlight:SetPoint('BOTTOMRIGHT', lastCvar, 'BOTTOMRIGHT', 1, -1)
+  end
+  self.CVarsHighlight:SetShown(self.cvarFiltersDirty or false)
+
+
   self.isStale = nil
 end
 
@@ -230,13 +267,14 @@
 
 local rgbWhite = {r = 1, g= 1, b= 1, hex = '|cFFFFFFFF'}
 function WorldPlanFilterPinMixin:OnEnter()
-  if self.questList and #self.questList >= 1 then
+  if #self.questList >= 1 then
     GameTooltip:SetOwner(self, 'ANCHOR_LEFT')
     GameTooltip:AddLine(self.info.label)
     for index, pin in ipairs(self.questList) do
       local colorInfo = (pin.quality and ITEM_QUALITY_COLORS[pin.quality]) or rgbWhite
-      GameTooltip:AddLine('|T'.. tostring(pin.itemTexture)..':16:16|t ' .. pin.title ..(pin.cheevos and " |cFFFFFF00!|R" or ''), colorInfo.r, colorInfo.g, colorInfo.b)
+      GameTooltip:AddLine('|T'.. tostring(pin.itemTexture)..':16:16|t ' .. tostring(pin.title) ..(pin.cheevos and " |cFFFFFF00!|R" or ''), colorInfo.r, colorInfo.g, colorInfo.b)
     end
+    GameTooltip:AddLine(self.numQuestsTotal .. ' total')
     GameTooltip:Show()
   end
 end
@@ -247,63 +285,66 @@
   end
 end
 
-function WorldPlanFilterPinMixin:Refresh(isFirst, numQuests)
-  isFirst = isFirst or self.isFirst
-  numQuests = numQuests or self.numQuests
-
+function WorldPlanFilterPinMixin:Refresh()
   local info = self.info
-
-  self.isFirst = isFirst
-  self.numQuests = numQuests
   self.filterKey = info.filterKey
   self.filterValue = info.filterValue
   self.tagID = info.tagID
 
-  self.icon:SetMask(filterMask)
   self.icon:SetTexture(info.texture)
-  self.count:SetText(numQuests)
+  self.count:SetText(self.numQuestsHere)
   self.cVar = info.cVar
 
   self.itemTexture = self.texture
 
-  if isFirst then
-    self:SetPoint('TOP', self.relativeFrame, 'BOTTOM', 0, -5)
+  if self.isFirst then
+    if FlightMapFrame and FlightMapFrame:IsVisible() then
+      self:SetPoint('TOPRIGHT', FlightMapFrame, 'TOPRIGHT', -4, -25)
+    else
+      self:SetPoint('TOP', self.relativeFrame, 'BOTTOM', 0, -5)
+    end
+
   else
     self:SetPoint('TOPRIGHT', self.relativeFrame, 'BOTTOMRIGHT', 0, -(self.spacing or 0))
   end
   print('anchor to', self.relativeFrame:GetName(), info.mask)
 
-  local r, g, b, a = 1,1,1,1
+  self.icon:SetDesaturated(self.numQuestsHere == 0)
+
+  local r, g, b, a = 0,0,0,1
   local desaturated = false
-  if self.cVar then
-    self.RewardBorder:SetVertexColor(1, 1, 1, 1)
-    if GetCVarBool(self.cVar) then
-      self.icon:SetVertexColor(1,1,1,1)
-      self:SetAlpha(1)
-    else
-      self.icon:SetVertexColor(.5, .5, .5, 1)
-      self:SetAlpha(0.5)
-    end
-  else
-    self:SetAlpha(1)
-    if WorldPlan.UsedFilters[self.filterKey] then
-      if WorldPlan.UsedFilters[self.filterKey] == self.filterValue then
-        self.RewardBorder:SetVertexColor(0, 1, 0)
+  if (self.numQuestsHere > 0) then
+    if self.cVar then
+      if GetCVarBool(self.cVar) then
+        self.count:SetTextColor(1,1,1)
+        r,g,b = 0, 1, 0
       else
-        self.RewardBorder:SetVertexColor(1, 0, 0)
+        self:GetParent().cvarFiltersDirty = true
+        self.count:SetTextColor(1,0,0)
+        self.icon:SetDesaturated(true)
+        r,g,b = 1, 0, 0
       end
     else
-      if self.filterKey == 'worldQuestType' then
-        self.RewardBorder:SetVertexColor(0, 1, 1)
-      elseif self.filterKey == 'factionID' then
-        self.RewardBorder:SetVertexColor(1, 1, 0)
+      if db.UsedFilters[self.filterKey] then
+        if db.UsedFilters[self.filterKey] == self.filterValue then
+          self.count:SetTextColor(0,1,0)
+          r,g,b = 0, 1, 0
+        else
+          self.count:SetTextColor(1,0,0)
+          r, g, b = 1, 0, 0
+        end
       else
 
-        self.RewardBorder:SetVertexColor(0.5, 0.5, 0.5)
+        self.count:SetTextColor(1,1,1)
+        if self.filterKey == 'worldQuestType' then
+          r, g, b = 0, 0, 1
+        elseif self.filterKey == 'factionID' then
+          r, g, b = 1, 1, 0
+        end
       end
-
     end
   end
+  self.RewardBorder:SetColorTexture(r, g, b, a)
 
   --self:UpdateSize()
 end
@@ -313,84 +354,102 @@
   self:SetFrameStrata('HIGH')
   self:SetFrameLevel(151)
   self:SetScript('OnUpdate', nil)
-  WorldPlanPOIMixin.OnLoad(self)
+  self.questList = {}
 end
 
 function WorldPlanFilterPinMixin:OnUpdate ()
 end
 
-
-function WorldPlanFilterPinMixin:OnLeave ()
-  if GameTooltip:IsOwned(self) then
-    GameTooltip:Hide()
-  end
-end
-
 -- shift-click: reset filter
 -- click: rotate through include(1), exclude(-1), ignore(nil)
 local filtered_report = {}
+local RESET_FILTER = "|cFFFFFFFF+%s|r"
+local FILTER_EXCLUDE_TYPE = '|cFFFF0000-%s|r'
+local FILTER_INCLUDE_TYPE = '|cFF00FF00+%s|r'
 function WorldPlanFilterPinMixin:OnClick (button)
 
-  print('|cFF00FF88'..self:GetName()..':OnClick()|r', filterKey, filterValue, operation)
   local filterKey = self.filterKey
   local filterValue = self.filterValue
   local cVar = self.cVar
   local parent = self:GetParent()
-  local operation = opPrefix
-  local setDirty = false
+  local setDirty
+
+  print('|cFF00FF88'..self:GetName()..':OnClick()|r', filterKey, filterValue, cVar, parent)
 
   local resetMode = (button == 'RightButton')
   wipe(filtered_report)
   if not (filterKey or cVar) then
-    wipe(WorldPlan.UsedFilters)
-    for i, info in ipairs(DEFAULT_FILTER_LIST) do
+    for i, info in ipairs(db.DefaultFilters) do
       if info.cVar then
-        if GetCVar(info.cVar) ~= 1 then
-          tinsert(filtered_report, '|cFF888888'.. tostring(info.label) ..'|r')
+        SetCVar(info.cVar, 1)
+      elseif info.filterKey then
+        if db.UsedFilters[info.filterKey] then
+          db.UsedFilters[info.filterKey] = nil
         end
-        SetCVar(info.cVar, 1)
       end
     end
+    parent.cvarFiltersDirty = false
 
+    --WorldPlan:print('All filters reset.')
   elseif cVar then
-    WorldPlan:print('Toggling cvar filter:', cVar)
-    if (not parent.isDirty) or resetMode then
-      for i, info in ipairs(DEFAULT_FILTER_LIST) do
+    if resetMode then
+      print('|cFFFF4400cleaning dirty')
+      for i, info in ipairs(db.DefaultFilters) do
         if info.cVar then
-          local value = GetCVar(info.cVar)
-          if resetMode then
-            value = 1
-            parent.isDirty = nil
-          else
-
-              if (cVar ~= info.cVar) then
-                value = 0
-              else
-                value = 1
-              end
-              setDirty = true
-
-          end
-          SetCVar(info.cVar, value)
+          parent.cvarFiltersDirty = false
+          SetCVar(info.cVar, 1)
         end
       end
-      if setDirty then
-        parent.isDirty = true
+      --WorldPlan:print('Reward filters reset.')
+    elseif parent.cvarFiltersDirty == true then
+      if GetCVarBool(cVar) then
+        tinsert(filtered_report, FILTER_EXCLUDE_TYPE:format(tostring(self.info.label)))
+        SetCVar(cVar, 0)
+      else
+
+        tinsert(filtered_report, FILTER_INCLUDE_TYPE:format(tostring(self.info.label)))
+        SetCVar(cVar, 1)
+      end
+
+      -- check the visible filters and consider it clean if they're all lit
+      parent.cvarFiltersDirty = false
+      for i, info in ipairs(parent.filterList) do
+        if info.cVar and (#info.questList >= 1) then
+          print(info.cVar, GetCVarBool(info.cVar))
+          if GetCVarBool(info.cVar) == false then
+            parent.cvarFiltersDirty = true
+            print('|cFFFF4400still dirty')
+            break
+          end
+        end
       end
     else
-      SetCVar(cVar, (GetCVarBool(cVar) and 0) or 1)
+      print('|cFFFF4400making dirty')
+      for i, info in ipairs(db.DefaultFilters) do
+        if info.cVar then
+          local msgType = (cVar == info.cVar) and FILTER_INCLUDE_TYPE or FILTER_EXCLUDE_TYPE
+          SetCVar(info.cVar, ((cVar == info.cVar) and 1) or 0)
+          tinsert(filtered_report, msgType:format(info.label))
+        end
+      end
+      parent.cvarFiltersDirty = true
     end
   else
-    local flushValue
-    print('')
-    if WorldPlan.UsedFilters[filterKey] == filterValue then
-      WorldPlan.UsedFilters[filterKey] = nil
-      tinsert(filtered_report,  '|cFFFF0000'.. tostring(filterKey) ..'|r')
+    if resetMode then
+      wipe(db.UsedFilters)
+      --WorldPlan:print('Type filters reset.')
     else
-      WorldPlan.UsedFilters[filterKey] = filterValue
-      tinsert(filtered_report,  '|cFF00FF00'.. tostring(filterKey) ..'|r')
+      if (db.UsedFilters[filterKey] == filterValue) or resetMode then
+        db.UsedFilters[filterKey] = nil
+        tinsert(filtered_report,  FILTER_INCLUDE_TYPE:format(tostring(filterKey)))
+      else
+        db.UsedFilters[filterKey] = filterValue
+        tinsert(filtered_report,  FILTER_EXCLUDE_TYPE:format(tostring(filterKey)))
+      end
     end
   end
-  WorldPlan:print('Changed:', table.concat(filtered_report, ', '))
+  if #filtered_report >= 1 then
+    --WorldPlan:print('Setting filter(s):', table.concat(filtered_report, ', '))
+  end
   WorldPlan:Refresh(true)
 end
\ No newline at end of file
--- a/FilterBar.xml	Fri Nov 04 02:54:32 2016 -0400
+++ b/FilterBar.xml	Sun Dec 25 13:04:57 2016 -0500
@@ -8,14 +8,45 @@
       <OnShow method="OnShow" />
     </Scripts>
 
+    <Layers>
+      <Layer level="OVERLAY">
+        <Texture parentKey="CVarsHighlight">
+          <Color a=".5" r="1" g="1" b="1" />
+        </Texture>
+      </Layer>
+    </Layers>
   </Frame>
-  <Button name="WorldPlanFilterPin" virtual="true" inherits="WorldPlanQuestPin" mixin="WorldPlanFilterPinMixin">
+  <Button name="WorldPlanFilterPin" virtual="true" mixin="WorldPlanFilterPinMixin">
     <Scripts>
-      <OnMouseDown method="OnMouseDown" />
       <OnClick method="OnClick" />
       <OnLoad method="OnLoad" />
+      <OnEvent method="OnEvent" />
+      <OnUpdate method="OnUpdate" />
+      <OnShow method="OnShow" />
+      <OnHide method="OnHide" />
+      <OnMouseDown method="OnMouseDown" />
       <OnEnter method="OnEnter" />
       <OnLeave method="OnLeave" />
     </Scripts>
+    <Layers>
+      <Layer level="BACKGROUND">
+        <Texture parentKey="RewardBorder" setAllPoints="true" />
+      </Layer>
+      <Layer level="ARTWORK">
+        <Texture parentKey="icon">
+          <Anchors>
+            <Anchor point="BOTTOMLEFT" x="1" y="1" />
+            <Anchor point="TOPRIGHT" x="-1" y="-1" />
+          </Anchors>
+        </Texture>
+      </Layer>
+      <Layer level="OVERLAY">
+        <FontString parentKey="count" inherits="WorldPlanNumberFontThin">
+          <Anchors>
+            <Anchor point="RIGHT" x="-3" />
+          </Anchors>
+        </FontString>
+      </Layer>
+    </Layers>
   </Button>
 </Ui>
\ No newline at end of file
--- a/FlightMap.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/FlightMap.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -1,4 +1,4 @@
-
+local _, db = ...
 local print = DEVIAN_WORKSPACE and function(...) _G.print('Canvas', ...) end or function() end
 local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
 local wipe, pairs, ipairs = wipe, pairs, ipairs
@@ -36,6 +36,13 @@
   end
 end
 
+function WorldPlanDataProvider:OnLoad()
+
+  self:SetNudgeTargetFactor(0.015);
+  self:SetNudgeZoomedOutFactor(1.0);
+  self:SetNudgeZoomedInFactor(0.25);
+end
+
 function WorldPlanDataProvider:OnShow()
   assert(self.ticker == nil);
   self.ticker = C_Timer.NewTicker(10, function() self:RefreshAllData() end);
@@ -69,7 +76,9 @@
   for questId in pairs(self.activePins) do
     pinsToRemove[questId] = true;
   end
-
+  print(unpack(db.Config.FlightMapAlphaLimits))
+  local alpha1, alpha2, alpha3 = unpack(db.Config.FlightMapAlphaLimits)
+  local scale1, scale2, scale3 = unpack(db.Config.FlightMapScalingLimits)
 
   local mapAreaID = self:GetMap():GetMapID();
   for zoneIndex = 1, C_MapCanvas.GetNumZones(mapAreaID) do
@@ -82,8 +91,11 @@
       for i, info in ipairs(taskInfo) do
         if HaveQuestData(info.questId) then
           if QuestUtils_IsQuestWorldQuest(info.questId) then
-            local pin = WorldPlanQuests:AcquirePin(info.questId, zoneMapID)
-            pin:RefreshData(info)
+            local pin = db.QuestsByID[info.questId]
+            if not db.QuestsByID[info.questId] then
+              pin = WorldPlanQuests:AcquirePin(info, zoneMapID)
+            end
+
             pin:IsShowable()
             if pin.used then
               print(i, pin.x, pin.y, pin.used, pin.isNew, pin.isStale, pin:IsShown(), pin:GetAlpha())
@@ -92,8 +104,10 @@
               local frame = self.activePins[info.questId]
               if not frame then
                 frame = self:GetMap():AcquirePin("WorldPlanFlightPin")
-                frame:SetAlphaLimits(1, 0.7, 1)
-                frame:SetScalingLimits(1, 3, 1.5);
+                print(alpha1, alpha2, alpha3)
+                print(scale1, scale2, scale3)
+                frame:SetAlphaLimits(alpha1, alpha2, alpha3)
+                frame:SetScalingLimits(scale1, scale2, scale3);
                 frame:SetFrameLevel(1000 + self:GetMap():GetNumActivePinsByTemplate("WorldPlanFlightPin"));
                 frame:Show()
                 self.activePins[info.questId] = frame
@@ -101,7 +115,7 @@
               frame:SetPosition(info.x, info.y)
               frame.pin = pin
 
-              pin.owningFrame = self:GetMap()
+              pin.owningFrame = frame:GetMap()
               pin.isStale = true
               pin:SetParent(frame)
               pin:ClearAllPoints()
@@ -127,21 +141,15 @@
 
 end
 function WorldPlanDataProvider:OnEvent()
-  for pin in self:GetMap():EnumeratePinsByTemplate("WorldQuestPinTemplate") do
-    pin:Hide()
-  end
 end
 
 function WorldPlanDataPinMixin:OnShow()
-  print('|cFFFFFF00'..tostring(self:GetName())..':OnShow()|r')
 end
 
 function WorldPlanDataPinMixin:OnMouseEnter ()
-
 end
 
 function WorldPlanDataPinMixin:OnMouseLeave ()
-
 end
 
 function WorldPlanDataPinMixin:RemoveAllData()
--- a/FlightMap.xml	Fri Nov 04 02:54:32 2016 -0400
+++ b/FlightMap.xml	Sun Dec 25 13:04:57 2016 -0500
@@ -12,7 +12,7 @@
     </Scripts>
   </Frame>
 
-  <Frame name="WorldPlanFlightPin" mixin="WorldPlanDataPinMixin" hidden="true" flattenRenderLayers="true" frameStrata="MEDIUM" enableMouseMotion="true" virtual="true">
+  <Frame name="WorldPlanFlightPin" mixin="WorldPlanDataPinMixin" hidden="true" flattenRenderLayers="true" frameStrata="MEDIUM" enableMouse="false" enableMouseMotion="true" virtual="true">
     <Size x="50" y="50"/>
   </Frame>
 </Ui>
\ No newline at end of file
--- a/QuestPOI.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/QuestPOI.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -3,6 +3,7 @@
 -- Created: 10/1/2016 7:21 PM
 -- %file-revision%
 --
+local _, db = ...
 
 local TQ_GetQuestInfoByQuestID = C_TaskQuest.GetQuestInfoByQuestID -- Return the name of a quest with a given ID
 local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation
@@ -12,6 +13,9 @@
 local QuestPOIGetIconInfo, WorldMapPOIFrame =  QuestPOIGetIconInfo, WorldMapPOIFrame
 local WorldMap_DoesWorldQuestInfoPassFilters = WorldMap_DoesWorldQuestInfoPassFilters
 local QuestMapFrame_IsQuestWorldQuest = QuestMapFrame_IsQuestWorldQuest
+local GetAchievementNumCriteria, GetAchievementCriteriaInfo, GetAchievementInfo = GetAchievementNumCriteria, GetAchievementCriteriaInfo, GetAchievementInfo
+local pairs, ipairs, tinsert, unpack, select = pairs, ipairs, tinsert, unpack, select
+local floor, mod, tostring, tonumber, GetSuperTrackedQuestID = floor, mod, tostring, tonumber, GetSuperTrackedQuestID
 local GameTooltip = GameTooltip
 local GetItemIcon = GetItemIcon
 
@@ -20,17 +24,18 @@
 local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
 local wqprint = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or function() end
 local iprint = DEVIAN_WORKSPACE and function(...) _G.print('ItemScan', ...) end or function() end
+local rprint = DEVIAN_WORKSPACE and function(...) _G.print('WQRefresh', ...) end or function() end
 local QuestPOI = WorldPlanPOIMixin
 
+
+local PIN_REFRESH_DELAY = .5
+local PIN_REQUEST_DELAY = .2
 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 WORLD_QUEST_BORDER = "Interface\\UNITPOWERBARALT\\Generic1Target_Circular_Frame"
+local PENDING_BORDER
+local PENDING_ICON = "Interface\\BUTTONS\\YELLOWORANGE64"
 
 local REWARD_CASH = WORLD_QUEST_REWARD_TYPE_FLAG_GOLD
 local REWARD_ARTIFACT_POWER = WORLD_QUEST_REWARD_TYPE_FLAG_ARTIFACT_POWER
@@ -38,7 +43,6 @@
 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
@@ -51,226 +55,294 @@
 local LE_QUEST_TAG_TYPE_PROFESSION = LE_QUEST_TAG_TYPE_PROFESSION
 local LE_QUEST_TAG_TYPE_NORMAL = LE_QUEST_TAG_TYPE_NORMAL
 
-local subStyles = {
-  continent = {
-    iconWidth = 14,
-    borderWidth = 2,
-    highlightWidth = 1,
-    TagSize = 6,
-    maxAlertLevel = 0,
-    showNumber = false,
-    numberFontObject = 'WorldPlanFont'
-  },
-  zone = {
-    iconWidth = 22,
-    borderWidth = 3,
-    highlightWidth = 2,
-    TagSize = 12,
-    maxAlertLevel = 3,
-    showNumber = true,
-    numberFontObject = 'WorldPlanNumberFontThin'
-  },
-  minimized = {
-    iconWidth = 4,
-    borderWidth = 0,
-    highlightWidth = 1,
-    NoIcon = true,
-    maxAlertLevel = 1,
-    showNumber = false,
-  }
-}
+local STYLE_TYPE_PENDING = 768
+
 
 -- Pin color/display variables
+db.TooltipExtras = db.TooltipExtras or {} -- idiot-proofing
+
 
 local familiars = {
-    [42159] = {npc = 106552, name = 'Nightwatcher Merayl'},
-    [40277] = {npc = 97804, name = 'Tiffany Nelson'},
-    [40298] = {npc = 99182, name = 'Sir Galveston'},
-    [40282] = {npc=  99150, name = 'Grixis Tinypop'},
-    [40278] = {npc = 98270, name = 'Robert Craig'},
-    [48195] = {npc = 105250, name = 'Aulier'},
-    [41990] = {npc = 105674, name = 'Varenne'},
-    [41860] = {npc = 104970, name = 'Xorvasc'},
-    [40299] = {npc = 99210, name = 'Bodhi Sunwayver'},
-    [42442] = {npc = 107489, name = 'Amalia'},
-    [40280] = {npc = 99077, name = 'Bredda Tenderhide'},
-    [41687] = {npc = 104553, name = 'Odrogg'},
-    [41944] = {npc = 105455, name = 'Trapper Jarrun'},
-    [40337] = {npc = 97709, name = 'Master Tamer Flummox'},
-    [40279] = {npc = 99035, name = 'Durian Strongfruit'}
+    [42159] = 'Nightwatcher Merayl',
+    [40277] = 'Tiffany Nelson',
+    [40298] = 'Sir Galveston',
+    [40282] = 'Grixis Tinypop',
+    [40278] = 'Robert Craig',
+    [48195] = 'Aulier',
+    [41990] = 'Varenne',
+    [41860] = 'Xorvasc',
+    [40299] = 'Bodhi Sunwayver',
+    [42442] = 'Amalia',
+    [40280] = 'Bredda Tenderhide',
+    [41687] = 'Odrogg',
+    [41944] = 'Trapper Jarrun',
+    [40337] = 'Master Tamer Flummox',
+    [40279] = 'Durian Strongfruit'
+}
+local falcosaurs = {
+  [44895] = {44881, 'Sharptalon Hatchling', 115786},
+  [44894] = {44882, 'Bloodgazer Hatchling', 115787},
+  [44893] = {44880, 'Direbeak Hatchling', 115785},
+  [44892] = {44879, 'Snowfeather Hatchling', 115784},
 }
 local familiars_id = 9696
+for questID, name in pairs(familiars) do
+  db.TooltipExtras[questID] = {{
+    achievementID = familiars_id,
+    name = name
+  }}
+end
+for questID, info in pairs(falcosaurs) do
+  local trackingQuestID, petName, petID = unpack(info)
 
-
--- update a masked texture without messing up its blending mask
-
-
--- update a masked texture without messing up its blending mask
-local SetMaskedTexture = function(region, file, mask)
-  mask = mask or "Interface\\Minimap\\UI-Minimap-Background"
-  region:SetMask(nil)
-  region:SetTexture(file)
-  region:SetMask(mask)
+  db.TooltipExtras[questID] = {{
+    questID = trackingQuestID,
+    pet = petName,
+    petID = petID
+  }}
 end
 
+local GetAchievementTooltipExtras = function(info)
 
--- use tooltip object to extract item details
-local ParseItemReward = function(questID)
-  local name, icon, quantity, quality, _, itemID = GetQuestLogRewardInfo(1, questID)
-  local scanner = _G.WorldPlanTooltip
-  if not itemID then
-    return
+  local hasInfo
+  local achievementID = info.achievementID
+  local _, name, _, completed, _, _, _, _, _, icon = GetAchievementInfo(achievementID)
+  if not completed then
+
+    WorldMapTooltip:AddLine(" ")
+    WorldMapTooltip:AddLine("Achievements:")
+    WorldMapTooltip:AddLine(' |T'..icon..':20:20|t '..name)
+
+    local numItems = GetAchievementNumCriteria(achievementID)
+    local numNeeded = 0
+    local tooltipLines = {}
+    for i =1, numItems do
+      local criteriaName, criteriaType, completed, _, _, _, _, subAchievementID = GetAchievementCriteriaInfo(achievementID, i)
+      print(GetAchievementCriteriaInfo(achievementID, i))
+
+      if not completed then
+        print('::', criteriaName, completed, subAchievementID)
+        if criteriaType == 8 then
+          local _, _, _, completed, _, _, _, _, _, subIcon = GetAchievementInfo(subAchievementID)
+          print(' -', criteriaName, completed, subIcon)
+          if not completed then
+            local numCompleted = 0
+            local numSubItems = GetAchievementNumCriteria(subAchievementID)
+            local subCriteriaLine
+            for j = 1, numSubItems do
+              local subName, _, completed = GetAchievementCriteriaInfo(subAchievementID, j)
+
+              print('   -',subName, completed)
+              if completed then
+                numCompleted = numCompleted + 1
+              else
+                numNeeded = numNeeded + 1
+                if subName:match(info.name) then
+                  hasInfo = true
+                  subCriteriaLine = '  |T'..subIcon..':16:16|t ' .. criteriaName
+                end
+              end
+
+            end
+            if subCriteriaLine then
+              tinsert(tooltipLines, subCriteriaLine .. ' ('..numCompleted..'/'..numSubItems..')')
+            end
+          end
+        elseif criteriaName:match(info.name) and (not completed) then
+          numNeeded = numNeeded + 1
+          tinsert(tooltipLines, criteriaName)
+        end
+      end
+    end
+    if numNeeded >= 1 then
+      for i, line in ipairs(tooltipLines) do
+        WorldMapTooltip:AddLine(line)
+      end
+    else
+      WorldMapTooltip:AddLine('Criteria completed!', 0, 1, 0)
+    end
+
+  end
+  return hasInfo
+end
+
+local GetQuestTooltipExtras = function(info)
+  local questID = info.questID
+  local hasInfo
+
+  if info.pet then
+    local index, guid = C_PetJournal.FindPetIDByName(info.pet)
+    if not index then
+      hasInfo = true
+      WorldMapTooltip:AddLine('Pets:')
+      WorldMapTooltip:AddLine(' - ' .. info.petName)
+
+      if not IsQuestFlaggedCompleted(questID) then
+        WorldMapTooltip:AddLine('  Required Quest Flags', 1, 1, 0)
+      else
+        WorldMapTooltip:AddLine('  Quest Flags Complete!', 0, 1, 0)
+      end
+
+    end
   end
 
-  scanner:SetOwner(WorldPlan, "ANCHOR_NONE")
-  scanner:SetItemByID(itemID)
-  scanner:Show()
-  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 then
-      if 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
-        return REWARD_ARTIFACT_POWER, "Interface\\ICONS\\inv_7xp_inscription_talenttome01", power, name, itemID, quality
-      elseif text:match("Item Level") then
-        --print('equipment!', text)
-        quantity = text:match("Item Level ([%d\+]+)")
-        return REWARD_GEAR, icon, quantity, name, itemID, quality
-      elseif  text:match("Crafting Reagent") then
-        --print('|cFFFF4400it is a reagent', text)
-        return REWARD_REAGENT, icon, quantity, name, itemID, quality
-      end
-    end
-
-  elseif ttl3 then
-    local text = ttl3:GetText()
-    if  text:match("Crafting Reagent") then
-      --print('|cFFFF4400it is a reagent', text)
-      return REWARD_REAGENT, icon, quantity, name, itemID, quality
-    end
-  end
-  return 128, icon, quantity, name, itemID, quality
 end
 
-function WorldPlanPOIMixin:OnEnter()
-  if WorldMapFrame:IsVisible() then
-    WorldMapTooltip:SetOwner(self, 'ANCHOR_RIGHT')
+local GetFactionInfoByID, GetQuestObjectiveInfo = GetFactionInfoByID, GetQuestObjectiveInfo
+
+function QuestPOI:OnEnter()
+  if not WorldMapFrame:IsVisible() then
+    WorldMap_HijackTooltip(self.owningFrame)
   else
-    WorldMap_HijackTooltip(self.owningFrame)
-  end
-
-  local completed = select(4,GetAchievementInfo(familiars_id))
-  if not completed then
-    if self.worldQuestType == LE_QUEST_TAG_TYPE_PET_BATTLE and familiars[self.questID] then
-
-      WorldMapTooltip:AddLine(self.title, 1, 1, 1)
-      if self.quality then
-        local c = ITEM_QUALITY_COLORS[self.quality]
-        WorldMapTooltip:AddLine(" ")
-        WorldMapTooltip:AddLine('Rewards')
-        WorldMapTooltip:AddLine(self.itemName .. (self.quantity and (' x'..self.quantity) or ''), c.r, c.g, c.b)
-        WorldMapTooltip:AddTexture(self.itemTexture)
-
-        local cLine = WorldMapTooltip:NumLines()
-        local line = _G['WorldMapTooltipTextLeft'..cLine]
-        local pline = _G['WorldMapTooltipTextLeft'..(cLine-1)]
-        local icon = _G['WorldMapTooltipTexture'..(cLine-3)]
-        icon:SetSize(24,24)
-        icon:ClearAllPoints()
-        icon:SetPoint('TOPLEFT', pline, 'BOTTOMLEFT', 0, -2)
-        line:ClearAllPoints()
-        line:SetPoint('TOPLEFT', icon, 'TOPRIGHT', 7, 0)
-
-        --- voodoo workaround for IDs getting coerced to string
-        if type(self.itemTexture) == 'number' then
-          icon:Show()
-          icon:SetTexture(self.itemTexture)
-        end
-      end
-
-
-      WorldMapTooltip:AddLine(" ") -- causes crash for some reason
-      WorldMapTooltip:AddLine('Family Familiars')
-      local trainer = familiars[self.questID].name
-      local numCheevs = GetAchievementNumCriteria(familiars_id)
-      for index = 1, numCheevs do
-        local cheevName, cType, cCompleted, quantity, requiredQuantity, charName, flags, cheevID, quantityString, criteriaID = GetAchievementCriteriaInfo(familiars_id, index)
-        local numTrainers = GetAchievementNumCriteria(cheevID)
-        for subIndex = 1, numTrainers do
-          local desc, cType, partCompleted = GetAchievementCriteriaInfo(cheevID, subIndex)
-          if desc == trainer then
-            if not partCompleted then
-              local iconPath = select(10, GetAchievementInfo(cheevID))
-              WorldMapTooltip:AddLine(cheevName)
-              WorldMapTooltip:AddTexture(iconPath)
-            end
-          end
-        end
-      end
-      WorldMapTooltip:Show()
+    if self.filtered then
       return
     end
   end
-  TaskPOI_OnEnter(self)
+  WorldMapTooltip:SetOwner(self, "ANCHOR_RIGHT");
+  print('doing tooltip stuff')
+
+  -- Can't add stuff after, so most of the blizzard tooltip hook is simply copied over
+  local questID = self.questID
+  local color = WORLD_QUEST_QUALITY_COLORS[self.rarity] or NORMAL_FONT_COLOR;
+
+
+  WorldMapTooltip:SetText(self.title, color.r, color.g, color.b);
+  QuestUtils_AddQuestTypeToTooltip(WorldMapTooltip, questID, NORMAL_FONT_COLOR);
+
+  if ( self.factionID ) then
+    local factionName = GetFactionInfoByID(self.factionID);
+    if ( factionName ) then
+      if (self.capped) then
+        WorldMapTooltip:AddLine(factionName, GRAY_FONT_COLOR:GetRGB());
+      else
+        WorldMapTooltip:AddLine(factionName);
+      end
+    end
+  end
+
+  if self.worldQuest then
+    WorldMap_AddQuestTimeToTooltip(questID);
+  end
+
+
+  for objectiveIndex = 1, self.numObjectives do
+    local objectiveText, objectiveType, finished = GetQuestObjectiveInfo(questID, objectiveIndex, false);
+    if ( objectiveText and #objectiveText > 0 ) then
+      local color = finished and GRAY_FONT_COLOR or HIGHLIGHT_FONT_COLOR;
+      WorldMapTooltip:AddLine(QUEST_DASH .. objectiveText, color.r, color.g, color.b, true);
+    end
+  end
+
+  local percent = C_TaskQuest.GetQuestProgressBarInfo(self.questID);
+  if ( percent ) then
+    GameTooltip_InsertFrame(WorldMapTooltip, WorldMapTaskTooltipStatusBar);
+    WorldMapTaskTooltipStatusBar.Bar:SetValue(percent);
+    WorldMapTaskTooltipStatusBar.Bar.Label:SetFormattedText(PERCENTAGE_STRING, percent);
+  end
+
+  if db.TooltipExtras[self.questID] then
+    for index, info in pairs(db.TooltipExtras[questID]) do
+      if info.achievementID then
+        GetAchievementTooltipExtras(info)
+      end
+      if info.questID then
+        GetQuestTooltipExtras(info)
+      end
+    end
+  end
+  WorldMap_AddQuestRewardsToTooltip(questID)
+
+  self.MouseGlow:Show()
+  WorldMapTooltip:Show()
+  --WorldMapTooltip.recalculatePadding = true;
+  --print(WorldMapTooltip:GetParent())
+  --print(WorldMapTooltip:IsVisible())
 end
-function WorldPlanPOIMixin:OnLeave()
+function QuestPOI:OnLeave()
   WorldMap_RestoreTooltip()
-  TaskPOI_OnLeave(self)
+  self.MouseGlow:Hide()
+  WorldMapTooltip:Hide();
 end
-function WorldPlanPOIMixin:OnMouseDown()
+function QuestPOI:OnMouseDown()
   TaskPOI_OnClick(self)
 end
 
-
-
--- create or update the pin using the given questID and C_TaskQuest results
-function WorldPlanPOIMixin:RefreshData (info)
-
-  qprint('|cFF00FF88'..self:GetName()..':RefreshData()|r')
-
-  if info then
-    self.inProgress = info.inProgress
-    self.floor = info.floor
-    self.numObjectives = info.numObjectives or 0
-    if info.x and info.y then
-      self.x = info.x or self.x
-      self.y = info.y or self.y
-      --qprint('|cFFFF4400applying taskInfo coords:', info.x, info.y)
-    end
-
+-- attempt to pull pin data
+local GetQuestTagInfo, GetProfessionInfo = GetQuestTagInfo, GetProfessionInfo
+function QuestPOI:GetData ()
+  qprint('|cFF00FF88'..self:GetName()..':GetWorldQuestInfo()|r')
+  local questID = self.questID
+  if not questID then
+    rprint('|cFFFF4400bad pin|r', self:GetName())
+    return nil
   end
 
+  local questTitle, factionID, capped = TQ_GetQuestInfoByQuestID(questID)
+  -- if the title is nil, then wait and try later
+  if not questTitle then
+    self.isPending = true
+    rprint('|cFFBB8844nodata|r|cFF00FFFF', self.questId)
+  else
+    self.title, self.factionID, self.capped = questTitle, factionID, capped
+    rprint('|cFFBB8844  data|r|cFF00FFFF', (self.isPending and 'late|r' or 'jit|r'), self.title, '|r', self.factionID)
+    -- set tag details
+    local worldQuestType
+    self.tagID, self.tagName, worldQuestType, self.rarity, self.isElite, self.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
+      self.isKnownProfession = nil
+      local id = self.tradeskillLineIndex and select(7, GetProfessionInfo(self.tradeskillLineIndex))
+      if id then
+        self.isKnownProfession = true
+        qprint('profession' , self.title, id)
+        tagAtlas = WORLD_QUEST_ICONS_BY_PROFESSION[id]
+      end
+    elseif worldQuestType == LE_QUEST_TAG_TYPE_DUNGEON then
+      tagAtlas = "worldquest-icon-dungeon"
+    end
+    self.worldQuestType = worldQuestType
 
-  local questID = self:GetID()
-  local questTitle, rewardIcon, rewardName, rewardCount, rewardStyle, rewardType, itemID, quantity, quality, _
-  local hasUpdate, isPending = (self.isStale or self.isNew), self.isPending
+    self.tagAtlas = tagAtlas
 
 
+    self:SetRewardInfo()
+
+    -- force throttle on success
+    --qprint('  |cFF00FFFF'..questID..'|r hasUpdate:', hasUpdate, 'isPending:', isPending, 'isShown', self:IsShown())
+    --qprint('  ', 'rewardType:', self.rewardType, 'tag:', self.tagID)
+    qprint('  ', tostring(self.title), " |T"..tostring(self.itemTexture)..":12:12|t", tostring(self.itemName))
+
+    if self.itemTexture and self.itemName and self.title then
+      self.isPending = nil
+      self.throttle = 1
+      self.updateRate = PIN_REFRESH_DELAY
+    end
+  end
+
+  self.isCriteria = WorldMapFrame.UIElementsFrame.BountyBoard:IsWorldQuestCriteriaForSelectedBounty(questID)
+
+  return self.isStale, self.isPending
+end
+
+local GetNumQuestLogRewards, GetNumQuestLogRewardCurrencies, HaveQuestData = GetNumQuestLogRewards, GetNumQuestLogRewardCurrencies, HaveQuestData
+local GetQuestLogRewardMoney, GetQuestLogRewardCurrencyInfo, GetMoneyString = GetQuestLogRewardMoney, GetQuestLogRewardCurrencyInfo, GetMoneyString
+function QuestPOI:SetRewardInfo()
+  local questID = self.questID
   if not HaveQuestData(questID) then
-    TQ_RequestPreloadRewardData(questID)
-    isPending = true
+    self.isPending = true
   else
 
+    local rewardIcon, rewardName, rewardCount, rewardStyle, rewardType, itemID, quantity, quality
     -- set reward category
     local numRewards = GetNumQuestLogRewards(questID)
     local numCurrency = GetNumQuestLogRewardCurrencies(questID)
     local money = GetQuestLogRewardMoney(questID)
     if numRewards >= 1 then
-      rewardType, rewardIcon, rewardCount, rewardName, itemID, quality = ParseItemReward(questID)
+      rewardType, rewardIcon, rewardCount, rewardName, itemID, quality = WorldPlanQuests:GetRewardHeader(questID)
     elseif numCurrency >= 1 then
       rewardName, rewardIcon, rewardCount = GetQuestLogRewardCurrencyInfo(1, questID)
       rewardType = REWARD_CURRENCY
@@ -279,84 +351,40 @@
       rewardName = GetMoneyString(money)
       rewardType = REWARD_CASH
     end
-    rewardStyle = self:GetTypeInfo(rewardType)
 
-    self.itemNumber = rewardCount or self.itemNumber
-    self.rewardType = rewardType or REWARD_ITEM
-    self.style = rewardStyle
+  print('  '..self.questID..':|cFFFFFF00SetRewardInfo():', rewardType)
+    self.itemNumber = tonumber(rewardCount or self.itemNumber)
+    self.rewardType = rewardType or REWARD_GEAR
     self.quality = quality
 
-    -- title, faction, capped state
-    local questTitle, factionID, capped = TQ_GetQuestInfoByQuestID(questID)
-    self.factionID = factionID
-    self.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
-
-    self.tagID = tagID
-    self.tagName = tagName
-    self.worldQuestType = worldQuestType
-    self.isElite = isElite
-    self.tradeskillLineIndex = tradeskillLineIndex
-    self.rarity = rarity
-    self.tagAtlas = tagAtlas
+    self.itemTexture = rewardIcon or self.itemTexture
+    self.itemName = rewardName or self.itemName
 
     -- flag unresolved info
     if not (rewardIcon and rewardName) then
-      isPending = true
-      qprint('because not have icon')
-      TQ_RequestPreloadRewardData (questID)
+      self.isPending = true
+      return true, nil
       --WorldPlan:print('|cFFFFFF00'..tostring(self.title)..'|r waiting on texture info')
     else
-      if (rewardIcon and rewardName) and isPending then
+      if (rewardIcon and rewardName) and self.isPending then
         --WorldPlan:print('|cFF00FF00'..tostring(self.title)..'|r has info', rewardIcon, rewardName)
-        hasUpdate = true
+        self.isStale = true
       end
-      isPending = false
+      self.isPending = nil
     end
 
-    self.title = questTitle or "|cFFFF0000Retrieving..."
-    self.itemTexture = rewardIcon or self.itemTexture
-    self.itemName = rewardName or self.itemName
-    self.isStale = hasUpdate
-    self.isPending = isPending
+  end
+  return self.isStale, self.isPending
 
-
-    --qprint('  |cFF00FFFF'..questID..'|r hasUpdate:', hasUpdate, 'isPending:', isPending, 'isShown', self:IsShown())
-    --qprint('  ', 'rewardType:', self.rewardType, 'tag:', self.tagID)
-    --qprint('  ', tostring(self.title), " |T"..tostring(self.itemTexture)..":12:12|t", tostring(self.itemName))
-  end
-  self.cheevos = familiars[self.questID]
-
-  return hasUpdate, isPending
-end
-
-
-function WorldPlanPOIMixin:SetAchievementProgressTooltip()
-  print('cheevos')
 end
 
 -- run from OnShow if .isNew is set
-function WorldPlanPOIMixin:OnNew()
+function QuestPOI:OnNew()
 
   if not self.isAnimating then
     --qprint('|cFFFFFF00'.. self.title .. ' added to quest log.')
     self:SetAlpha(0)
-    if WorldPlan.db.FadeWhileGrouped then
+    if db.Config.FadeWhileGrouped then
       self.FadeIn.FadeIn:SetToAlpha(0.15)
       self.PendingFade.FadeIn:SetToAlpha(0.15)
       self.PendingFade.FadeOut:SetFromAlpha(0.15)
@@ -367,11 +395,12 @@
     end
     self.isAnimating = true
     self.isNew = nil
+    self.isStale = true
     self.FadeIn:Play()
   end
 end
 
-function WorldPlanPOIMixin:OnShow ()
+function QuestPOI:OnShow ()
   -- pop this on principle
 
   if self.isNew or self.isStale then
@@ -392,7 +421,7 @@
   --WorldPlan:print(self:GetAlpha())
 
 end
-function WorldPlanPOIMixin:OnHide()
+function QuestPOI:OnHide()
   --qprint('|cFFFFFF00["'..tostring(self.title)..'"]|r:OnHide()')
   if not self:IsShown() then
     self.isAnimating = nil
@@ -402,8 +431,8 @@
   self.Overlay:SetShown(false)
 end
 
-function WorldPlanPOIMixin:SetAnchor(frame, mapID, mapWidth, mapHeight)
-  --qprint('  |cFF00FF00'..self:GetName()..':SetAnchor()|r', self.questID, mapID, mapWidth)
+function QuestPOI:SetAnchor(frame, mapID, mapWidth, mapHeight)
+  qprint('  |cFF00FF00'..self:GetName()..':SetAnchor()|r', self.questID, mapID)
   self:ClearAllPoints()
   local dX, dY = TQ_GetQuestLocation(self.questID)
   if not dX or dX == 0 then
@@ -414,50 +443,53 @@
       dX, dY = self.x, self.y
     end
   end
+  local oX, oY = self.x, self.y
   self.x = dX
   self.y = dY
 
-  --qprint('  |cFF00FF00'..self.questID..':|r', format("%0.2f %0.2f", dX, dY))
 
   local pX = (dX * mapWidth)
   local pY = (-dY * mapHeight)
 
+  if oX ~= dX then
+    wqprint('  |cFF00FF00'..self.questID..':|r', oX, dX, format("%0.2f %0.2f", pX, pY))
+  end
+
   self:SetParent(WorldMapPOIFrame)
   self:SetPoint('CENTER', frame, 'TOPLEFT', pX, pY)
 end
 
-
-function WorldPlanPOIMixin:OnLoad()
-  qprint('|cFF00FF88'..self:GetName()..':OnLoad()|r',WorldPlan.db)
+function QuestPOI:OnLoad()
+  qprint('|cFF00FF88'..self:GetName()..':OnLoad()|r',db.Config)
   self:RegisterEvent('SUPER_TRACKED_QUEST_CHANGED')
 
+  self.title = '|cFF0088FF' .. RETRIEVING_DATA..'|r'
+  self.isPending = true
   self.count = self.Overlay.count
   self.timeLabel = self.Overlay.timeLabel
+  self.updateRate = PIN_REQUEST_DELAY
+  self.itemName = '|cFF0088FF' .. RETRIEVING_DATA..'|r'
 end
 
-function WorldPlanPOIMixin:OnEvent(event, ...)
+function QuestPOI:OnEvent(event, ...)
   if event == 'SUPER_TRACKED_QUEST_CHANGED' then
     self.isStale = true
   end
 end
 
-
-local PIN_UPDATE_DELAY = .016
-local TOP_PIN_ID
-function WorldPlanPOIMixin:OnUpdate (sinceLast)
+function QuestPOI:OnUpdate (sinceLast)
+  -- control update check intervals
+  self.throttle = (self.throttle or self.updateRate) + sinceLast
+  if self.throttle >= self.updateRate then
+    -- factor overtime into the throttle timer
+    self.throttle = self.throttle - self.updateRate
+  else
+    return
+  end
   if self.isNew then
     print('|cFFFFFF00push new poi stuff')
     self:OnNew()
-  end
-  -- 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
-  if self.isStale and not self.isAnimating then
+  elseif (self.isStale or (not self.title)) and not self.isAnimating then
     wprint('|cFFFFFF00push poi update')
     self:Refresh()
     return
@@ -466,7 +498,7 @@
   -- query for reward data if it wasn't found in the original scan
   local questID = self.questID
   if self.isPending then
-    self:RefreshData()
+      self:GetData()
     if not (self.PendingFade:IsPlaying() or self.isAnimating) then
       self.PendingFade:Play()
     end
@@ -479,67 +511,100 @@
 
 
   -- update time elements
-  local tl = self.alertLevel
-  local timeLeft = TQ_GetQuestTimeLeftMinutes(questID)
-  if timeLeft > 0 then
-    local text, timeState = WorldPlan:GetTimeInfo(timeLeft, self.maxAlertLevel)
-    if tl ~= timeState then
-      tl = timeState
-      self.timeLabel:SetText(text)
-    end
-  else
-    -- remove self in a timely manner
-    if not TQ_IsActive(self.questID) then
-      print('|cFFFF4400'..self:GetName()..' pin hard timeout')
+  if TQ_IsActive(self.questID) then
+
+    local tl = self.alertLevel
+    local timeLeft = TQ_GetQuestTimeLeftMinutes(questID)
+    if timeLeft > 0 then
+
+      local text, timeState = WorldPlan:GetTimeInfo(timeLeft, self.maxAlertLevel)
+      if tl ~= timeState then
+        tl = timeState
+        self.timeLabel:SetText(text)
+      end
+    else
       if self.worldQuestType ~= LE_QUEST_TAG_TYPE_PROFESSION then
-        self:Hide()
+        self:SetShown(false)
       end
     end
+    self.alertLevel = tl
   end
-  self.alertLevel = tl
-
-  if self:IsMouseOver() then
-    self.MouseGlow:Show()
-  else
-    self.MouseGlow:Hide()
-  end
+  self.timeLabel:SetShown(self.worldQuest and (self.maxAlertLevel >= 1))
 end
 
 
 
-function WorldPlanPOIMixin:Refresh ()
-  local db = WorldPlan.db
+function QuestPOI:Refresh ()
   print('|cFF00FF88'..self:GetName()..'|r:Refresh()')
 
-
+  local styleType = (self.isPending and STYLE_TYPE_PENDING) or self.rewardType
+  local style,subStyle =  self:GetTypeInfo(self.rewardType)
+  if self.filtered then
+    subStyle = style.minimized
+  end
+  self.style = style
+  self.subStyle = subStyle
+  --print(style, subStyle)
+  self.currentWidth = subStyle.iconWidth
+  self.borderWidth = subStyle.borderWidth
+  self.highlightWidth = subStyle.highlightWidth
+  self.tagSize = subStyle.TagSize
+  self.maxAlertLevel = subStyle.maxAlertLevel
+  self.NoIcon = subStyle.NoIcon
 
   local questID = self:GetID()
   local iconBorder = self.RewardBorder
   local trackingBorder = self.HighlightBorder
   local icon = self.icon
   local count = self.count
-
-
-
-
   --WorldPlan:print(tostring(self.title), "|T"..tostring(self.itemTexture)..":16:16|t", tostring(self.itemName))
 
-
-
   if self.itemName then
       --wqprint('filtered:', self.filtered, 'showNumber:', self.showNumber)
+      if self.itemNumber and self.itemNumber >= 1000 then
+        local numeral = floor(self.itemNumber/1000)
+        local decimal = mod(self.itemNumber, 1000)
+        local numberString = numeral
+        if decimal > 100 then
+          numberString = numberString .. '.' .. tostring(floor(decimal/100))
+        end
+        numberString = numberString .. 'k'
+        self.count:SetText(numberString)
+      else
+        self.count:SetText(self.itemNumber)
+      end
 
-      self.count:SetText(self.itemNumber)
+
   end
+  icon:SetMask("Interface\\Minimap\\UI-Minimap-Background")
   if self.itemTexture then
-    icon:SetTexture(self.itemTexture)
-    icon:SetMask("Interface\\Minimap\\UI-Minimap-Background")
+    iconBorder:SetTexture(WORLD_QUEST_BORDER)
+
+    if self.NoIcon then
+      icon:SetTexture(PENDING_ICON)
+      icon:SetDesaturated(true)
+      icon:SetVertexColor(style.r, style.g, style.b, style.a)
+    else
+
+      icon:SetTexture(self.itemTexture)
+      icon:SetDesaturated(false)
+      icon:SetVertexColor(1, 1, 1)
+    end
+  else
+    iconBorder:SetTexture(PENDING_BORDER)
+    icon:SetTexture(PENDING_ICON)
+    icon:SetDesaturated(true)
+    icon:SetVertexColor(style.r, style.g, style.b, style.a)
   end
 
-  local border = self:GetTypeInfo(self.rewardType)
-  iconBorder:SetVertexColor(border.r, border.g, border.b, border.a)
+  local borderStyle = style
+  if self.rarity and WORLD_QUEST_QUALITY_COLORS[self.rarity] then
+    borderStyle = WORLD_QUEST_QUALITY_COLORS[self.rarity]
+  end
+
+  iconBorder:SetVertexColor(borderStyle.r, borderStyle.g, borderStyle.b, 1)
   iconBorder:SetDesaturated(true)
-
+  iconBorder:SetAlpha(subStyle.alpha or 1)
 
   if questID == GetSuperTrackedQuestID() then
     trackingBorder:SetVertexColor(0,0,0,1)
@@ -547,12 +612,10 @@
     trackingBorder:SetVertexColor(0,0,0,0.5)
   end
 
+  self.tagIcon:SetShown(self.tagSize and true or false)
   self.tagIcon:SetAtlas(self.tagAtlas)
-  self.tagIcon:SetTexCoord(0,1,0,1)
+  self.tagIcon:SetAlpha(subStyle.alpha or 1)
   self.EliteBorder:SetShown(self.isElite and not self.filtered)
-  --qprint('|cFF88FF00updated', questID, self.title, self.rewardType, (style.showNumber and self.itemNumber) or '')
-  --print('  - subStyle:', (self.filtered == true), self.subStyle)
-
   self.Overlay:SetShown(self:IsShown())
   self.Overlay:SetParent(self:GetParent())
   self.Overlay:SetFrameLevel(self:GetFrameLevel()+200)
@@ -560,9 +623,7 @@
 
 
   self:UpdateSize()
-
   self.isStale = nil
-
 end
 
 local cvar_check = {
@@ -574,44 +635,44 @@
 }
 
 
+function QuestPOI:IsFiltered ()
+  for filterKey, value in pairs(db.UsedFilters) do
+    print('|cFFFF4400', filterKey, self[filterKey])
+    if self[filterKey] ~= value then
+      return true
+    end
+  end
+  if self.rewardType  and cvar_check[self.rewardType] then
+    if self.rewardType == REWARD_CASH then
+      print('##', cvar_check[self.rewardType], GetCVarBool(cvar_check[self.rewardType]))
+    end
+    if not GetCVarBool(cvar_check[self.rewardType]) then
+      return true
+    end
+  end
+  print('  '..self.questID..':|cFFFFFF00IsFiltered()|r')
+end
+
 function QuestPOI:IsShowable ()
   local print = qprint
-  local db = WorldPlan.db
   local qType = self.worldQuestType
-  local rType = self.rewardType
-  self.filtered = nil
-  self.used = true
 
-  for filterKey, value in pairs(WorldPlan.UsedFilters) do
-    print('|cFFFF4400', filterKey, self[filterKey])
-    if self[filterKey] ~= value then
-      self.filtered = true
+  if not self.worldQuest then
+    print('ignoring showable check')
+    return self.used, self.filtered
+  end
+  self.used = TQ_IsActive(self.questID)
+
+  if qType == LE_QUEST_TAG_TYPE_PROFESSION then
+    qprint('hide flags:', (not self.isKnownProfession), (db.Config.ShowAllProfessionQuests == false))
+    if (not self.isKnownProfession) and  (db.Config.ShowAllProfessionQuests == false) then
+      qprint(self.used)
+      self.used = nil
+      qprint(self.used)
     end
   end
-
-  self.questId  = self:GetID()
-  if self.rewardType then
-    if cvar_check[self.rewardType] then
-      if self.rewardType == REWARD_CASH then
-        print('##', cvar_check[self.rewardType], GetCVarBool(cvar_check[self.rewardType]))
-      end
-      if not GetCVarBool(cvar_check[self.rewardType]) then
-        self.filtered = true
-
-      end
-
-    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
-  print('  |cFF'.. (((self.rewardType == REWARD_CASH) and 'FFFF00') or '0088FF') ..'IsShowable()|r ', cvar_check[self.rewardType], 'used:', self.used, 'filtered:', self.filtered, self.title)
-  return self.used, self.filtered
+  print('  '..self.questID..':|cFFFFFF00IsShowable()|r ', self.used, self.title)
+  return self.used
 end
 
 function QuestPOI:UpdateTimer (timeLeft, timeType)
@@ -621,12 +682,10 @@
 --- Fixes icons upon size update
 function QuestPOI:UpdateSize ()
 
-  local style,subStyle =  self:GetTypeInfo(self.rewardType)
-  if self.filtered then
-    subStyle = style.minimized
-  end
-  --qprint('|cFF00FF88'..self:GetName()..'|r:UpdateSize()', style, subStyle)
+  qprint('|cFF00FF88'..self:GetName()..'|r:UpdateSize()', self.style, self.subStyle)
 
+  local style = self.style
+  local subStyle = self.subStyle
   local icon = self.icon
   local iconBorder = self.RewardBorder
   local trackingBorder = self.HighlightBorder
@@ -638,6 +697,7 @@
 
   local iconTexture = self.itemTexture
 
+  self.tagIcon:SetSize(self.tagSize, self.tagSize)
   self:SetSize(iconWidth, iconWidth)
   icon:SetSize(iconWidth, iconWidth)
   iconBorder:SetSize(borderWidth, borderWidth)
@@ -658,50 +718,5 @@
 
   self.count:SetShown((subStyle.showNumber and self.itemNumber) and style.hasNumeric)
 
-  --[[
-  if self.tagSize then
-    tag:Show()
-    tag:ClearAllPoints()
-    tag:SetPoint('BOTTOMRIGHT', self, 'BOTTOMRIGHT', borderWidth, -borderWidth)
-  else
-    tag:Hide()
-  end
-
-  if self.NoIcon then
-    self.icon:Hide()
-  else
-    self.icon:Show()
-    if style.rewardMask then
-      icon:SetMask(rewardMask)
-    else
-      icon:SetMask(iconTexture)
-    end
-    if style.pinMask then
-      iconBorder:Show()
-      trackingBorder:Show()
-      iconBorder:SetMask(pinMask)
-      trackingBorder:SetMask(pinMask)
-    else
-      iconBorder:Hide()
-      trackingBorder:Hide()
-    end
-  end
-
-
-  icon:SetTexture(iconTexture)
-  iconBorder:SetTexture(iconBorder:GetTexture())
-  trackingBorder:SetTexture(trackingBorder:GetTexture())
-  --]]
-  self.currentWidth = subStyle.iconWidth
-  self.borderWidth = subStyle.borderWidth
-  self.highlightWidth = subStyle.highlightWidth
-  self.tagSize = subStyle.TagSize
-  self.maxAlertLevel = subStyle.maxAlertLevel
-  self.NoIcon = subStyle.NoIcon
-  self.style = style
-  self.subStyle = subStyle
-  if self.rewardType == REWARD_CASH then
-  qprint('using mask:', pinMask or iconTexture, rewardMask or iconTexture, self.used, self.filtered)
-  end
 
 end
\ No newline at end of file
--- a/WorldPlan.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/WorldPlan.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -1,31 +1,38 @@
 -- 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.0"
+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
 
-WorldPlanCore = {
-  defaults = {},
-  modules = {},
-  FilterOptions = {},
-  UsedFilters = {},
-  QuestsByZone = {},
-  QuestsByID = {},
-  TaskQueue = {},
-}
+-- Define tables here so the pointers match up
+WorldPlanCore = { defaults = {}, modules = {}, TaskQueue = {}, }
 WorldPlanQuestsMixin = {
-  QuestsByZone = {},
-  QuestsByID = {},
-  freePins = {},
+  UsedPositions = {},
 }
 WorldPlanPOIMixin = {}
-local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
-local WP_VERSION = "1.0"
-local tinsert, pairs, floor = table.insert, pairs, floor
-local ITEM_QUALITY_COLORS = ITEM_QUALITY_COLORS
-local BROKEN_ISLES_ID = 1007
-local GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID = GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID
+WorldPlanSummaryMixin = {}
+db.filtersDirty = true
+db.questsDirty = true
+db.OrderedModules = {}
+db.LoadedModules = {}
+db.UsedFilters = {}
+db.QuestsByZone = {}
+db.QuestsByID = {}
+db.TasksByID = {}
+db.FreePins = {}
+db.UsedPins = {}
+db.ReportChunks = {}
 
 -- default color templates
-local DEFAULT_TYPE = {
+db.DefaultType = {
   a = 1,
   r = 1, g = 1, b = 1,
   x = 0, y = 0,
@@ -34,10 +41,10 @@
   rewardMask = "Interface\\Minimap\\UI-Minimap-Background",
   texture = "Interface\\BUTTONS\\YELLOWORANGE64",
   continent = {
-    PinSize = 14,
-    Border = 2,
+    iconWidth = 14,
+    borderWidth = 2,
     highlightWidth = 1,
-    TagSize = 6,
+    TagSize = 8,
     maxAlertLevel = 0,
     showNumber = true,
     numberFontObject = 'WorldPlanFont'
@@ -52,36 +59,32 @@
     numberFontObject = 'WorldPlanNumberFontThin'
   },
   minimized = {
-    iconWidth = 6,
-    borderWidth = 0,
+    r = 0, g = 0, b = 0, a = 0.1,
+    iconWidth = 8,
+    borderWidth = 2,
+    alpha = 0.5,
     highlightWidth = 0,
-    maxAlertLevel = 1,
+    maxAlertLevel = 0,
     NoIcon = true,
+
+    TagSize = 8,
     TimeleftStage = 1,
     showNumber = false,
+    alpha = 0.1,
   }
 }
 
-
-
-
-local defaults = {
+db.DefaultConfig = {
   ShowAllProfessionQuests = false,
   DisplayContinentSummary = true,
   DisplayContinentPins = true,
   NotifyWhenNewQuests = true,
   EnablePins = true,
   FadeWhileGrouped = false,
+  FlightMapAlphaLimits = {1, 1, 1},
+  FlightMapScalingLimits = {1, 3, 1.5},
 }
 
--- operating flags
-local superTrackedID
-local currentMapName
-local hasNewQuestPins
-local isContinentMap
-local hasPendingQuestData
-local notifyPlayed
-local scanner, wmtt, WorldMapPOIFrame
 
 
 -- tracking menu toggler
@@ -97,21 +100,32 @@
   _G.WorldPlan:Refresh()
 end
 
-function WorldPlanCore:print(...)
-  local msg
+function db.print(...)
   for i = 1, select('#', ...) do
-    msg = (msg and (msg .. ' ') or '') .. tostring(select(i, ...))
+    tinsert(db.ReportChunks, tostring(select(i, ...)))
   end
-  DEFAULT_CHAT_FRAME:AddMessage("|cFF0088FFWorldPlan|r: " .. msg)
 end
 
-local current_type_owner
-function WorldPlanCore: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)
+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.GetTypeInfo = function(frame, typeID)
+      return self:GetTypeInfo(frame, typeID)
+    end
+
+    frame.owningFrame = self
+  else
+
+    print('|cFFFF4400'..self:GetName()..':AddHandler()', frame:GetName())
   end
 end
 
@@ -124,10 +138,10 @@
         v = setmetatable(v, {
           __newindex = function(t2,k2,v2)
           if type(v2) == 'table' then
-            print('adding type', k2)
+            --print('adding type', k2)
             v2 = setmetatable(v2, {__index = function(t3,k3)
               --print('##deferring to default key', k3)
-              return  DEFAULT_TYPE[k3]
+              return db.DefaultType[k3]
             end})
           end
           rawset(t2,k2,v2)
@@ -144,47 +158,54 @@
   end
 
 
-  WorldPlanCore:print('v'..WP_VERSION)
+  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:SetParent(WorldMapFrame)
+  self:RegisterEvent("PLAYER_LOGIN")
+  --self:SetParent(WorldMapFrame)
 end
 
 function WorldPlanCore:OnShow()
-  print(self:GetName()..':OnShow()')
-  if self.isStale then
-    self:Refresh()
-  end
-
-  hooksecurefunc(self, 'SetScript', function(...) self:print('|cFFFFFF00'..self:GetName()..':SetScript()|r', ...) end)
+  --print(self:GetName()..':OnShow()')
+  --hooksecurefunc(self, 'SetScript', function(...) self:print('|cFFFFFF00'..self:GetName()..':SetScript()|r', ...) end)
 end
 
+function WorldPlanCore:GetMapInfo()
+
+  db.currentMapID = GetCurrentMapAreaID()
+  db.isContinentMap = (db.currentMapID == BROKEN_ISLES_ID)
+  db.useContinentType = (WorldMapDetailFrame:GetScale() < 1)
+
+end
+
+
 function WorldPlanCore:OnEvent (event, ...)
-  print()
-  print(event, 'init:', self.initialized)
+
+  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' then
-      self.currentMapID = GetCurrentMapAreaID()
-      self.isContinentMap = (self.currentMapID == BROKEN_ISLES_ID)
-      --self:print('|cFFFF4400currentMapID =', self.currentMapID)
-      self.isStale = true
+    if (event == 'WORLD_MAP_UPDATE') or (event == 'PLAYER_ENTERING_WORLD') then
+      print('|cFFFF4400currentMapID =', db.currentMapID, ...)
+      self:GetMapInfo()
     end
 
-    for i, module in ipairs(self.modules) do
+    for i, module in ipairs(db.OrderedModules) do
       if module.OnEvent then
-        print('  |cFF0088FF'..module:GetName() .. ':OnEvent()|r')
+        print('  |cFF00FFFF'..module:GetName() .. ':OnEvent()|r')
         module:OnEvent(event, ...)
       end
     end
@@ -206,48 +227,56 @@
   end
 
   if self.isStale then
-    print('|cFF00FF00pushing global update')
-    self.isStale = nil
-    self:Refresh()
-  else
-    for i, module in ipairs(self.modules) do
-      if module.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
+
 end
 
 function WorldPlanCore:Setup ()
+  print('|cFFFFFF00'..self:GetName()..':Setup()|r')
+
   if not WorldPlanData then
-    WorldPlanData = {key = 0 }
+    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
+  db.Config = WorldPlanData
+  for k,v in pairs(db.DefaultConfig) do
     --[===[@non-debug@
-    if not db[k] then
-      db[k] = v
+    if not db.Config[k] then
+      db.Config[k] = v
     end
 
     --@end-non-debug@]===]
     --@debug@
-    db[k] = v
+    db.Config[k] = v
     --@end-debug@
   end
 
-  self.currentMapID = GetCurrentMapAreaID()
 
-  for i, module in ipairs(self.modules) do
-    module.db = self.db
+  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
   end
+
+
   self.initialized = true
 
   hooksecurefunc("UIDropDownMenu_Initialize", self.OnDropDownInitialize)
@@ -261,14 +290,71 @@
       self:Refresh(true)
     end
   end)
+
+
+  hooksecurefunc("WorldMapFrame_Update", function()
+    print('|cFFFF4400WorldMapFrame_Update|r')
+    self:GetMapInfo()
+  end)
+
+
+  SLASH_WORLDPLAN1 = "/worldplan"
+  SLASH_WORLDPLAN2 = "/wp"
+
+
+
+  SlashCmdList.WORLDPLAN = function(args)
+    local arg1, arg2, extraArgs = args:match("(%S+)%s*(%S*)%s*(.*)")
+
+    if arg1 == 'wq' then
+      if arg2 and WorldPlanQuests[arg2] then
+        self:print('WorldPlanQuests:'..arg2..'()')
+        WorldPlanQuests[arg2](WorldPlanQuests)
+      elseif arg2 == 'flightscale' and extraArgs then
+        local val1, val2, val3 = extraArgs:match("(%S+)%s*(%S*)%s*(%S*)")
+        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 == 'flightalpha' and extraArgs then
+        local val1, val2, val3 = extraArgs:match("(%S+)%s*(%S*)%s*(%S*)")
+        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
+      else
+
+        self:print('WorldPlanQuests:Refresh(true)')
+        WorldPlanQuests:Refresh(true)
+      end
+    elseif arg1 == 'filter' then
+      if arg2 and WorldPlanSummary[arg2] then
+        self:print('WorldPlanSummary:'..arg2..'()')
+        WorldPlanSummary[arg2](WorldPlanSummary)
+      else
+        self:print('WorldPlanSummary:Refresh(true)')
+        WorldPlanSummary:Refresh(true)
+      end
+    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
@@ -280,21 +366,26 @@
   owner = owner or self
   if (not typeID) or (not self.Types[owner][typeID]) then
     --print('## sending list default')
-    info = DEFAULT_TYPE
+    info = db.DefaultType
   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
+  local subType = 'continent'
+  if (
+    FlightMapFrame
+    and FlightMapFrame:IsVisible()
+    and FlightMapFrame:IsZoomedIn()
+  ) or (
+    not db.isContinentMap
+  ) or (
+    db.useContinentType == false
+  ) then
+    subType = 'zone'
+  end
 
-    --print('### zone subtype', extraInfo)
-  end
-  return info, extraInfo
+  return info, info[subType] or db.DefaultType[subType]
 end
 
 do
@@ -314,7 +405,6 @@
   }
   -- Generates a timeleft string
   function WorldPlanCore:GetTimeInfo(timeLeft, limit)
-    limit = limit or #timeStates
     for index = 1, limit do
       local state = timeStates[index]
       if timeLeft <= state.maxSeconds then
@@ -335,12 +425,14 @@
     return
   end
 
-  for i, module in ipairs(self.modules) do
+  for i, module in ipairs(db.OrderedModules) do
     if module.Refresh then
       print('|cFF00FF00external '..module:GetName()..':Refresh()|r')
       module:Refresh(forced)
     end
   end
+
+  self.isStale = nil
 end
 
 -- insert visual options into the tracking button menu
@@ -348,8 +440,7 @@
   if self ~= WorldMapFrameDropDown then
     return
   end
-  local db = WorldPlan.db
-
+  local config = WorldPlanData
   local info = UIDropDownMenu_CreateInfo()
   info.text = ""
   info.isTitle = true
@@ -365,7 +456,7 @@
   info.text = "Enable"
   info.isNotRadio = true
   info.value = "EnablePins"
-  info.checked = db.EnablePins
+  info.checked = config.EnablePins
   info.tooltipTitle = "Enable World Quest Overlays"
   info.tooltipText = "Toggle the detail layers here."
   info.func = DropDown_OnClick
@@ -374,7 +465,7 @@
   info.text = "Display All Profession Quests"
   info.isNotRadio = true
   info.value = "ShowAllProfessionQuests"
-  info.checked = db.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
@@ -383,7 +474,7 @@
   info.text = "Show Continent Pins"
   info.isNotRadio = true
   info.value = "DisplayContinentPins"
-  info.checked = db.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
@@ -394,16 +485,26 @@
   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.checked = config.DisplayContinentSummary
   info.func = DropDown_OnClick
   UIDropDownMenu_AddButton(info)
 
-  info.text = "Fade In Groups"
+
+  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 = db.FadeWhileGrouped
+  info.checked = config.FadeWhileGrouped
   info.func = DropDown_OnClick
   UIDropDownMenu_AddButton(info)
 end
@@ -417,15 +518,4 @@
 
 
 
-SLASH_WORLDPLAN1 = "/worldplan"
-SLASH_WORLDPLAN2 = "/wp"
-SlashCmdList.WORLDPLAN = function()
-  print('command pop')
-  WorldPlanCore:GetPinsForMap()
-  WorldPlanCore: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%
\ No newline at end of file
--- a/WorldPlan.xml	Fri Nov 04 02:54:32 2016 -0400
+++ b/WorldPlan.xml	Sun Dec 25 13:04:57 2016 -0500
@@ -148,15 +148,6 @@
       </Frame>
     </Frames>
   </Button>
-  <Button name="WorldPlanFilterPin" virtual="true" inherits="WorldPlanQuestPin" mixin="WorldPlanFilterPinMixin">
-    <Scripts>
-      <OnMouseDown method="OnMouseDown" />
-      <OnClick method="OnClick" />
-      <OnLoad method="OnLoad" />
-      <OnEnter method="OnEnter" />
-      <OnLeave method="OnLeave" />
-    </Scripts>
-  </Button>
 
   <Frame name="WorldPlan" mixin="WorldPlanCore" parent="UIParent">
     <KeyValues>
--- a/WorldQuests.lua	Fri Nov 04 02:54:32 2016 -0400
+++ b/WorldQuests.lua	Sun Dec 25 13:04:57 2016 -0500
@@ -2,21 +2,21 @@
 -- WorldQuests.lua
 -- Created: 11/2/2016 3:40 PM
 -- %file-revision%
-
+local _, db = ...
 local WorldQuests = WorldPlanQuestsMixin
 
 local MC_GetNumZones, MC_GetZoneInfo = C_MapCanvas.GetNumZones, C_MapCanvas.GetZoneInfo
 local TQ_GetQuestsForPlayerByMapID = C_TaskQuest.GetQuestsForPlayerByMapID -- This function is not yet documented
 local TQ_GetQuestZoneID = C_TaskQuest.GetQuestZoneID
 local GetMapInfo = GetMapInfo
-local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
+local print = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or function() end
+local rprint = DEVIAN_WORKSPACE and function(...) _G.print('WQRefresh', ...) end or function() end
 local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or function() end
-local wqprint = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or function() end
 local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
 local mprint = DEVIAN_WORKSPACE and function(...) _G.print('Canvas', ...) end or function() end
 
 
-local PinBaseIndex = 1000
+local PinBaseIndex = 1200
 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
 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', }
@@ -29,9 +29,8 @@
 
 
 local numPins = 0
-local ZoneInfo = {}
 local NumPinFrames = 1
-
+WorldQuests.TasksByID = {}
 
 --%debug%
 local SetTimedCallbackForAllPins = function(seconds, callback)
@@ -42,21 +41,35 @@
   end)
 end
 
+function WorldQuests:OnShow()
+  self:Refresh(true)
+end
+
+function WorldQuests:OnUpdate(sinceLast)
+  if self.filtersDirty or self.isStale then
+    self:Refresh()
+  end
+end
+
 function WorldQuests:Setup()
-
+  print('|cFFFF4400'..self:GetName()..':Setup()')
 
   for mapID, mapName in pairs(WORLD_QUEST_MAPS) do
-    self.QuestsByZone[mapID] = {}
+    db.QuestsByZone[mapID] = {}
   end
 
 
   -- refresh positions any time blizzard does so (i.e. mousewheel zoom)
   hooksecurefunc("WorldMapScrollFrame_ReanchorQuestPOIs", function()
+    print('|cFFFF4400WorldMapScrollFrame_ReanchorQuestPOIs')
     self:Refresh(true)
   end)
 
+
   -- hide the original world quest POIs
+
   hooksecurefunc("WorldMap_UpdateQuestBonusObjectives", function()
+    print('|cFFFF4400WorldMap_UpdateQuestBonusObjectives')
     for i = 1, NUM_WORLDMAP_TASK_POIS do
       local button = _G['WorldMapFrameTaskPOI'..i]
       if button and button.worldQuest then
@@ -65,22 +78,24 @@
     end
   end)
 end
-local WorldMapPOIFrame
 local defaults = {}
+local REWARD_UNKNOWN = 768
 function WorldQuests:OnLoad()
-  print('|cFF00FF88'..self:GetName()..':OnLoad')
+  print('|cFFFF4400'..self:GetName()..':OnLoad()')
 
+  self:SetParent(WorldMapFrame)
   WorldPlan:AddHandler(self, defaults)
 
-  local rgbWhite = {1, 1, 1}
+  local rgbWhite = {1, 1, 1 }
+  WorldPlan:AddTypeInfo(self, REWARD_UNKNOWN, { r = 1, g = 1, b = 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 = .7, g = .6, b = .32, pinMask = false, rewardMask = false })
+  WorldPlan:AddTypeInfo(self, REWARD_CASH, { r = 1, g = 1, b = .32, pinMask = false, rewardMask = false })
 
   for areaID, fileName in pairs(WORLD_QUEST_MAPS) do
-    self.QuestsByZone[areaID] = {}
+    db.QuestsByZone[areaID] = {}
   end
 
   self:RegisterEvent('WORLD_QUEST_COMPLETED_BY_SPELL')
@@ -91,40 +106,53 @@
 end
 
 function WorldQuests:OnEvent (event, ...)
-  local print = wqprint
-  print('|cFFFFFF00'..self:GetName()..':OnEvent()'..event..'|r', GetTime(), ...)
-  if event == 'QUEST_LOG_UPDATE' then
+
+  print('|cFFFFFF00'..self:GetName()..':OnEvent() '..event..'|r', GetTime(), ...)
+  if event == 'QUEST_LOG_UPDATE' or event == 'PLAYER_LOGIN' then
     local questID, added = ...
     if questID and added then
       local questPOI = self:AcquirePin(questID)
-      self.isStale, self.isPending = questPOI:RefreshData()
+      questPOI:GetQuestInfo()
+      questPOI.isStale = true
+      self.isStale = true
     else
-      self:RefreshData()
+      self:Refresh(true)
     end
     print('WorldMapFrame', WorldMapFrame:IsVisible(), 'hasUpdates:', self.isStale)
-  elseif event == 'WORLD_MAP_UPDATE' or event == 'PLAYER_ENTERING_WORLD' then
-    self.isStale = true
+  elseif event == 'WORLD_MAP_UPDATE' then
+    self:Refresh(true)
   elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then
     local questID = ...
-    if questID and self.QuestsByID[questID] then
-      self:ReleasePin(self.QuestsByID[questID])
+    if questID and db.QuestsByID[questID] then
+      self:ReleasePin(db.QuestsByID[questID])
+      rprint('|cFFFF4400release|r', questID)
     end
   elseif event == 'SKILL_LINES_CHANGED' then
-    self.isStale = true
+    self:SetFilteredPins()
   end
 end
 
+local totalPins = 0
 local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation
-function WorldQuests:AcquirePin (questID, mapID)
-  local pin = self.QuestsByID[questID]
-  local isNew = false
+function WorldQuests:AcquirePin (info)
+  local questID = info.questId
+  if not questID then
+    return nil
+  end
+
+  if not QuestUtils_IsQuestWorldQuest(questID) then
+    return nil
+  end
+
+
+  local pin = db.QuestsByID[questID]
   if not pin then
-    isNew = true
-    local numFree = #self.freePins
+    local numFree = #db.FreePins
     if numFree >= 1 then
-      pin = tremove(self.freePins, numFree)
+      pin = tremove(db.FreePins, numFree)
       --print('|cFF00FF00Re-using', pin:GetName())
     else
+      totalPins = totalPins + 1
       local name = 'WorldPlanQuestMarker' .. NumPinFrames
       --print('|cFF00FF00Creating', name)
       pin = CreateFrame('Frame', name, WorldMapPOIFrame, 'WorldPlanQuestPin')
@@ -133,181 +161,355 @@
       pin.GetTypeInfo = function(frame, typeID)
         return self:GetTypeInfo(typeID)
       end
+      pin:SetID(totalPins)
       NumPinFrames = NumPinFrames + 1
       --pin.iconBorder:SetVertexColor(0,0,0,1)
     end
-    pin:SetID(questID)
+    pin.questID = questID
+    pin.worldQuest = true
+    pin.throttle = 1
     pin.isNew = true
     pin.currentWidth = nil
+    db.QuestsByID[questID] = pin
+    tinsert(db.UsedPins, pin)
+  end
 
-    -- used by TaskPOI_x scripts
-    pin.questID = questID
-    pin.worldQuest = true
+  if pin and info then
+    pin.inProgress = info.inProgress
+    pin.floor = info.floor
+    pin.numObjectives = info.numObjectives or 0
+    if info.x and info.y then
+      pin.x = info.x or pin.x
+      pin.y = info.y or pin.y
+      rprint('|cFFFF4400coords|r', info.x, info.y)
+    end
+  end
 
-    self.QuestsByID[questID] = pin
-  else
-    --print('|cFF00FF00Using', pin:GetName())
-  end
-  mapID = mapID or TQ_GetQuestZoneID(questID)
-  self.QuestsByZone[mapID][questID] = pin
-
-  return pin, isNew
+  pin:GetData()
+  C_TaskQuest.RequestPreloadRewardData(info.questId)
+  return pin
 end
 
 -- remove from index and add it to the recycling heap
 function WorldQuests:ReleasePin (pin)
 
-  local id = pin.questId
+  local id = pin.questID
   if id then
-    self.QuestsByID[id] = nil
-    for i, zone in pairs(self.QuestsByZone) do
+    db.QuestsByID[id] = nil
+
+    for i, zone in pairs(db.QuestsByZone) do
       print('-', i, zone[i])
       zone[id] = nil
     end
+    db.TasksByID[id] = nil
   end
-  pin:Hide()
+  pin:SetShown(false)
   pin:ClearAllPoints()
-  tinsert(self.freePins, pin)
-  print('|cFFFF4400Clearing out', pin:GetName(),id)
+  tinsert(db.FreePins, pin)
+
+  print('|cFF00FF00-'.. (pin.mapID and GetMapNameByID(pin.mapID) or '???') ..'|r', id, pin.title)
 end
 
+function WorldQuests:GetBonusObjectives()
+
+
+  local tasksTable = GetTasksTable()
+  if tasksTable ~= nil then
+    print('|cFF00FF88'..self:GetName()..':BonusObjectives()|r ')
+    self.numTasks = #tasksTable
+    for i, taskID in ipairs(tasksTable) do
+      if not QuestUtils_IsQuestWorldQuest(taskID) then
+      local info = db.TasksByID[taskID]
+        if not info then
+          local isInArea, isOnMap, numObjectives, taskName, displayAsObjective = GetTaskInfo(taskID)
+          if isOnMap then
+            print(' * '..i, taskID, GetTaskInfo(taskID))
+            info = {
+              questID = taskID,
+              numObjectives = numObjectives,
+              title = taskName,
+              isInArea = isInArea,
+              isOnMap = isOnMap,
+              displayAsObjective = displayAsObjective,
+              worldQuest = false,
+              isPending = false,
+              isNew = true,
+            }
+
+
+            db.TasksByID[taskID] = info
+
+            local pin = self:AcquirePin(taskID)
+            for k,v in pairs(info) do
+              pin[k] = v
+            end
+            pin:GetBonusObjectiveInfo(info)
+          end
+        end
+      end
+
+
+    end
+  end
+end
+
+
+
+
+-- use tooltip object to extract item details
+function WorldQuests:GetRewardHeader(questID)
+  local name, icon, quantity, quality, _, itemID = GetQuestLogRewardInfo(1, questID)
+  local scanner = _G.WorldPlanTooltip
+  local print = qprint
+  if not itemID then
+    return
+  end
+  --print('GetRewardHeader', questID)
+
+  scanner:SetOwner(WorldPlan, "ANCHOR_NONE")
+  scanner:SetItemByID(itemID)
+  scanner:Show()
+  local ttl1 = _G['WorldPlanTooltipTextLeft1']
+  local ttl2 = _G['WorldPlanTooltipTextLeft2']
+  local ttl3 = _G['WorldPlanTooltipTextLeft3']
+  local ttl4 = _G['WorldPlanTooltipTextLeft4']
+  --print(ttl2, ttl3, ttl4)
+  if ttl2 then
+    local text = ttl2:GetText()
+    -- Artifact Power
+    --print(text)
+    if text then
+      if 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
+        return REWARD_ARTIFACT_POWER, "Interface\\ICONS\\inv_7xp_inscription_talenttome01", power, name, itemID, quality
+      elseif text:match("Item Level") then
+        --print('equipment!', text)
+        quantity = text:match("Item Level ([%d\+]+)")
+        return REWARD_GEAR, icon, quantity, name, itemID, quality
+      elseif  text:match("Crafting Reagent") then
+        --print('|cFFFF4400it is a reagent', text)
+        return REWARD_REAGENT, icon, quantity, name, itemID, quality
+      end
+    end
+  end
+
+  if ttl3 then
+    local text = ttl3:GetText()
+    if text and text:match("Crafting Reagent") then
+      --print('|cFFFF4400it is a reagent', text)
+      return REWARD_REAGENT, icon, quantity, name, itemID, quality
+    end
+  end
+  return 128, icon, quantity, name, itemID, quality
+end
+
+local GetCurrentMapAreaID, GetMapNameByID= GetCurrentMapAreaID, GetMapNameByID
+local wipe, pairs = wipe, pairs
 -- create of update quest pins for a map and its underlying zones
-function WorldQuests:RefreshData (mapID)
-  local print = wqprint
-  mapID = mapID or WorldPlan.currentMapID
+function WorldQuests:UpdateWorldQuests (mapID)
+
+  mapID = mapID or db.currentMapID
   if not mapID then
     -- info not available yet
     return
   end
 
 
-    print('|cFF00FF88'..self:GetName()..':RefreshData()|r', 'map:', mapID, 'realMap:', GetCurrentMapAreaID())
+    print('|cFF00FF88'..self:GetName()..':UpdateWorldQuests()|r', 'map:', mapID, 'realMap:', db.currentMapID)
 
-  if mapID == BROKEN_ISLES_ID then
-    self.isStale = 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, WorldPlan.currentMapID)
-    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)
-        --  WorldPlan:print('|cFF0088FF'..questPOI.title..'|r', hasUpdate)
-        self.isStale = (self.isStale or hasUpdate)
-        self.isPending = (self.isPending or isPending)
-        numQuests = numQuests + 1
+
+  self.isStale = nil
+  print('|cFF00FFFFContinent:|r', BROKEN_ISLES_ID, GetMapNameByID(BROKEN_ISLES_ID))
+  self.isRecursed = true
+  for i = 1, MC_GetNumZones(BROKEN_ISLES_ID) do
+    local submapID, name, depth = MC_GetZoneInfo(BROKEN_ISLES_ID, i)
+    local taskInfo = TQ_GetQuestsForPlayerByMapID(submapID, BROKEN_ISLES_ID)
+    if taskInfo then
+      local zoneName = GetMapNameByID(submapID)
+      print('|cFF00FFFF  Zone:|r', submapID, zoneName, #taskInfo)
+      db.QuestsByZone[submapID] = db.QuestsByZone[submapID] or {}
+      for i, info in ipairs(taskInfo) do
+        if HaveQuestData(info.questId) then
+          rprint('|cFF44FF44update|r', info.questId, zoneName)
+          local questID = info.questId
+          local pin = self:AcquirePin(questID)
+          local pin = db.QuestsByID[questID]
+          if pin then
+            pin.isStale = true
+            if pin.isPending then
+              self.isPending = true
+            end
+          end
+        else
+          rprint('|cFFFF4400no data|r', info.questId, zoneName)
+        end
       end
-      qprint('|cFFFF4400END of', GetMapNameByID(mapID))
     end
   end
 
-  if not self.fullSearch then
-    print('  hasUpdate:', self.isStale, 'isPending:', self.isPending, 'timer:', (self.OnNext and 'waiting' or ''))
-    --WorldPlan.isStale = (self.isStale or WorldPlan.isStale)
+  self:GetBonusObjectives()
+
+  print('  hasUpdate:', self.isStale, 'isPending:', self.isPending, 'timer:', (self.OnNext and 'waiting' or ''))
+  --WorldPlan.isStale = (self.isStale or WorldPlan.isStale)
+  if self.isStale and self:IsVisible() then
+    self:Refresh()
+  end
+end
+
+function WorldQuests:Report()
+  for i, pin in ipairs(db.UsedPins) do
+    db:print(i, pin.questID, pin.title)
   end
 
+  for id, pin in pairs(db.QuestsByID) do
+    db:print(id, pin.worldQuestType, pin.rewardType, pin.title)
+  end
 end
 
-function WorldQuests:Refresh(forced)
-  local print = wqprint
-  print('|cFF00FF88'..self:GetName()..':Refresh()|r')
+function WorldQuests:Refresh(fromUser)
+  self.currentMapID = GetCurrentMapAreaID()
+  print('|cFF00FF88'..self:GetName()..':Refresh()|r', fromUser or '|cFFFFFF00internal')
   if not self:IsVisible() then
+    print('  not visible, flag for later')
     self.isStale = true
-    print('frame closed, do it later')
+    return
+  end
+  wprint('  |cFF00FF88'..self:GetName()..':Refresh()|r', fromUser or '|cFFFFFF00internal')
+
+  for index, pin in pairs(db.QuestsByID) do
+    pin.used = nil
+    pin:SetShown(false)
+  end
+
+  self:SetFilteredPins(db.QuestsByID)
+  self:UpdateAnchors(nil, fromUser)
+  self:Cleanup (fromUser)
+  self.isStale = nil
+end
+
+-- update visibility states of all pins
+function WorldQuests:SetFilteredPins(pins)
+  print('  |cFFFFFF00'..self:GetName()..':SetFilteredPins()|r', pins)
+  pins = pins or db.QuestsByID
+  for questID, pin in pairs(pins) do
+    pin.filtered = pin:IsFiltered()
+    pin.isStale =  true
+    rprint('|cFF00FF00filter', pin.questID, pin.filtered, 'used:', pin.used)
+  end
+end
+
+local abs = math.abs
+function WorldQuests:UpdateQuestButton(info, mapID)
+  local questID, x, y = info.questId, info.x, info.y
+  local pin = self:AcquirePin(info)
+  if not pin then
     return
   end
 
-  self:Reset()
-  self:UpdateAnchors()
-  self:Cleanup ()
-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
+  print('~ ', pin.mapID, pin.questID, pin.title)
+  rprint('|cFF00FF00update|r', x, y, pin.title)
+  pin:IsShowable()
+
+  if x and y then
+
+    pin.x = x
+    pin.y = y
+    pin:SetFrameLevel(PinBaseIndex+numPins)
+    pin:SetPoint('CENTER', self.hostFrame, 'TOPLEFT', self.hostWidth * pin.x, -self.hostHeight * pin.y)
+    pin.throttle = 1
+    pin:SetShown(pin.used)
+    tinsert(self.UsedPositions, pin)
+  end
+  pin.owningFrame = self.hostFrame
+  pin:SetParent(self.hostFrame)
+
+  if mapID then
+    if not db.QuestsByZone[mapID] then
+      db.QuestsByZone[mapID] = {}
+    end
+    db.QuestsByZone[mapID][questID] = pin
   end
 end
 
--- update visibility states of all pins
-function WorldQuests:UpdateAnchors (submapID)
+function WorldQuests:UpdateMap(taskInfo, mapID)
+  print('Map', GetMapNameByID(mapID), GetMapNameByID(self.currentMapID))
+  for index, info in pairs(taskInfo) do
+    self:UpdateQuestButton(info, mapID)
+  end
+end
 
-  local print = wqprint
-  local db = WorldPlan.db
+function WorldQuests:UpdateAnchors (fromUser)
+
+
+  wipe(self.UsedPositions)
+  print('  |cFF00FF00'..self:GetName()..':UpdateAnchors()', fromUser)
+  self.hostFrame = WorldMapPOIFrame
+  self.hostWidth, self.hostHeight = self.hostFrame:GetSize()
+  self.nudgeThrescholdX = 16/self.hostWidth
+  self.nudgeThrescholdY = 16/self.hostHeight
+  local print = rprint
+  print('|cFF00FF00'..self:GetName()..':UpdateAnchors()', fromUser)
   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
+  numPins = 0
+  local taskInfo = TQ_GetQuestsForPlayerByMapID(self.currentMapID)
+  if taskInfo then
+    self:UpdateMap(taskInfo, self.currentMapID)
   end
-  print('|cFF88FF00'..self:GetName()..':UpdateAnchors|r', submapID, GetMapNameByID(submapID), 'pin count:', numPins)
-  local numZones = MC_GetNumZones(submapID)
+  local numZones = MC_GetNumZones(self.currentMapID)
   if numZones then
     for i = 1, numZones do
-      local subMapID = MC_GetZoneInfo(submapID, i)
-      self:UpdateAnchors(subMapID)
+      local mapAreaID = MC_GetZoneInfo(self.currentMapID, i)
+      local taskInfo = TQ_GetQuestsForPlayerByMapID(mapAreaID, self.currentMapID)
+      if taskInfo then
+        self:UpdateMap(taskInfo, mapAreaID)
+      end
     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.isStale = true
-        pin:SetFrameLevel(PinBaseIndex+ numPins)
-        print('level', PinBaseIndex+ numPins)
-        pin:SetAnchor(_G.WorldMapPOIFrame, currentMap, mapWidth, mapHeight)
-        numPins = numPins + 1
-      end
-    end
+  if self.filtersDirty then
+    self:SetFilteredPins(db.QuestsByID)
   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 = WorldPlan.db.EnablePins
+function WorldQuests:Cleanup (fromUser)
+
   print('|cFFFFFF00'..self:GetName()..':Cleanup()|r')
-  -- continent or zone sizing
+  local print = rprint
+  print('|cFFFFFF00'..self:GetName()..':Cleanup()|r')
+  --local showQuestPOI = db.Config.EnablePins
+  for questID, pin in pairs(db.QuestsByID) do
+    local oV = pin:IsShown()
+    if pin.used then
+      pin:SetShown(true)
+      pin.throttle = 1
+      if oV == false then
+        print('|cFF00FF00cleanup +|r', questID, pin.title)
+      end
+    else
+      if oV == true then
+        print('|cFFFF4400 -|r', questID, pin.title)
+      end
+    end
 
-
-  numPins = 0
-  for questID, pin in pairs(self.QuestsByID) do
-    pin:SetShown((showQuestPOI and pin.used))
+    if pin.worldQuest and (not C_TaskQuest.IsActive(pin.questID)) then
+      self:ReleasePin(pin)
+    end
+    pin.isStale = true
   end
-  self.isStale = nil
 end
 
-function WorldQuests:FilterCheckByID(questID)
-  local pin = WorldQuests:GetPinByQuestID(questID)
-  return pin:IsShowable()
-end
-
-