changeset 6:589de8ea05b9

- validate tracked objects' existence by use of those handler.Info tables we made - apply collision checking to action buttons when their corresponding entry has scrolled out
author Nenue
date Fri, 01 Apr 2016 01:30:42 -0400
parents e9b61fd5f607
children 5301c68f28d8
files ObjectiveCore.lua ObjectiveEvents.lua ObjectiveFrame.lua ObjectiveInfo.lua ObjectiveTracker.lua ObjectiveTracker.xml ObjectiveUI.lua
diffstat 7 files changed, 449 insertions(+), 337 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ObjectiveCore.lua	Fri Apr 01 01:30:42 2016 -0400
@@ -0,0 +1,209 @@
+--- ${PACKAGE_NAME}
+-- @file-author@
+-- @project-revision@ @project-hash@
+-- @file-revision@ @file-hash@
+-- Created: 3/26/2016 1:51 AM
+local B = select(2,...).frame
+local wipe, pairs, ipairs, min, max, unpack = table.wipe, pairs, ipairs, min, max, unpack
+local setmetatable, type = setmetatable, type
+local GetNumQuestLeaderBoards, GetAchievementNumCriteria, GetQuestLogLeaderBoard, GetAchievementCriteriaInfo = GetNumQuestLeaderBoards, GetAchievementNumCriteria, GetQuestLogLeaderBoard, GetAchievementCriteriaInfo
+local GetQuestLogIndexByID, GetSuperTrackedQuestID, SetSuperTrackedQuestID, GetQuestWatchInfo = GetQuestLogIndexByID, GetSuperTrackedQuestID, SetSuperTrackedQuestID, GetQuestWatchInfo
+local mod = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
+local print = B.print('Objectives')
+local ObjectiveTrackerFrame = ObjectiveTrackerFrame
+
+--[[
+  Full quest info is available if:
+    - It's in the player quest log, or is available from the Gossip interface
+    - It's being shared from another player and is acceptible
+    - It's an auto-quest that is available in the current location
+  Partial quest info is availabe if:
+    - It's already completed (i.e. it appears in CompletedQuestInfo()).
+    - It's an scheduled interval quest (daily, weekly, etc.)
+    - It's contained in a quest link received from chat
+  Under any other circumstances, only minimal info can be pulled:
+    - Its availability to the player
+    - Its relation with the currently engaged NPC
+    - Its binary completion status
+
+]]
+--- Global Frames
+local Wrapper = _G.VeneerObjectiveWrapper
+local Scroller = Wrapper.scrollArea
+local Scroll = _G.VeneerObjectiveScroll
+
+--- list used to make things happen
+mod.orderedNames = {[1] = 'AutoQuest',  [2] = 'Quest', [3] = 'Cheevs'}
+
+--- ipairs() list of handlers for wrapper update
+mod.orderedHandlers = {}
+mod.orderedTrackers = {}
+mod.indexedTrackers = {}
+--- pairs() list of handler frames for tracker updates
+mod.namedTrackers = {}
+
+--- Handler stubs
+mod.AutoQuest = {
+  name = "AutoQuest"
+}
+mod.Quest = {
+  name = "Quest"
+}
+mod.Cheevs = {
+  name = "Cheevs"
+}
+
+
+--- Temp values set during updates
+local wrapperWidth, wrapperHeight
+local scrollWidth, scrollHeight
+local previousBlock
+local currentBlock
+
+local frame_guide_init = function(self)
+  self.testU = self.testU or self:CreateTexture('TestU', 'OVERLAY', 'VnTestLine')
+  self.testB = self.testB or self:CreateTexture('TestB', 'OVERLAY', 'VnTestLine')
+  self.testL = self.testL or self:CreateTexture('TestL', 'OVERLAY', 'VnTestLine')
+  self.testR = self.testR or self:CreateTexture('TestR', 'OVERLAY', 'VnTestLine')
+end
+local frame_guide = function(self, target)
+  if not target then return end
+  if target:IsDragging() then return end
+  local thickness = 1
+  local midX, midY = target:GetCenter()
+  local width, height = target:GetWidth() * 1.5, target:GetHeight() * 1.5
+  --print('frame', target:GetLeft(), target:GetTop(), target:GetRight(), target:GetBottom())
+  self.testB:ClearAllPoints()
+  self.testB:SetPoint('TOP', UIParent, 'BOTTOMLEFT', midX, target:GetBottom())
+  self.testB:SetSize(width,thickness)
+
+  self.testU:ClearAllPoints()
+  self.testU:SetPoint('BOTTOM', UIParent, 'BOTTOMLEFT', midX, target:GetTop())
+  self.testU:SetSize(width,thickness)
+
+  self.testL:ClearAllPoints()
+  self.testL:SetPoint('RIGHT', UIParent, 'BOTTOMLEFT', target:GetLeft(), midY)
+  self.testL:SetSize(thickness,height)
+
+  self.testR:ClearAllPoints()
+  self.testR:SetPoint('LEFT', UIParent, 'BOTTOMLEFT', target:GetRight(), midY)
+  self.testR:SetSize(thickness,height)
+end
+
+--- Handler template
+local CreateHandler = function (self, name, index)
+  print(self, name)
+  local handler = setmetatable({}, {
+    __tostring = function() return name end,
+    __call = function (self) mod.UpdateTracker(self) end
+  })
+  if type(mod.orderedHandlers[index]) == 'table' then
+    return mod.orderedHandlers[index]
+  end
+
+  print('take up locals first')
+  local preset = {}
+  for k,v in pairs(mod[name]) do
+    preset[k] = true
+    if type(v) == 'table' then
+      handler[k] = {}
+    else
+      handler[k] = v
+    end
+  end
+
+  print('resulting handler contents')
+  for k, v in pairs(self) do
+    if not handler[k] then
+      if type(v) == 'table' then
+        -- assume all tables to be local data; don't inherit or ref
+        handler[k] = {}
+      else
+        handler[k] = mod.Tracker[k]
+      end
+    else
+      print(name, 'has its own', k)
+    end
+  end
+  print('|cFFFF4400'..tostring(name)..'|r:')
+  for k, v in pairs(handler) do
+    print(string.format("%24s %8s %s", (preset[k] and '|cFFFFFFFF' or '|cFFFFFF00') .. k .. '|r', type(v), tostring(v)))
+  end
+  mod[name] = handler
+  mod.orderedHandlers[index] = handler
+  return true
+end
+
+mod.Tracker = setmetatable({}, {
+  __call = CreateHandler,
+  __tostring = function() return 'DEFAULT_TRACKING_HANDLER' end
+})
+local Tracker = mod.Tracker
+Tracker.numWatched = 0   --- number of entries being handled
+Tracker.numBlocks = 0    --- number of blocks created
+Tracker.actualBlocks = 0 --- number of blocks in use
+
+Tracker.freeBlocks = {}  --- block heap
+Tracker.usedBlocks = {}
+
+Tracker.Watched = {}     -- find by watchIndex
+Tracker.Info = {}        -- find by data ID
+Tracker.BlockInfo = {}   -- find by block ID
+Tracker.LogInfo = {}     -- find by log ID (quest log mainly)
+Tracker.WatchBlock = {}
+Tracker.WatchInfo = {}
+Tracker.LogBlock = {}
+
+
+
+Tracker.GetBlock = function(handler, blockIndex)
+  local block = handler.usedBlocks[blockIndex]
+  if not handler.usedBlocks[blockIndex] then
+    if #handler.freeBlocks >= 1 then
+      block = handler.freeBlocks[#handler.freeBlocks]
+      handler.freeBlocks[#handler.freeBlocks] = nil
+    else
+      block = CreateFrame('Frame', 'Veneer'..tostring(handler)..'Block'..blockIndex, Scroll, 'VeneerTrackerBlock')
+      block.SetStyle = mod.SetBlockStyle
+      block:ClearAllPoints() -- making sure the anchors are clear in case they get added for some other template usage
+    end
+
+    handler.usedBlocks[blockIndex] = block
+  end
+  return handler.usedBlocks[blockIndex]
+end
+
+function mod:OnInitialize()
+  self.InitializeTrackers()
+  self.InitializeXPTracker()
+  mod.SetEvents()
+  ObjectiveTrackerFrame:UnregisterAllEvents()
+  ObjectiveTrackerFrame:Hide()
+end
+
+--[[
+QUESTLINE_UPDATE	This event is not yet documented
+QUESTTASK_UPDATE	This event is not yet documented
+QUEST_ACCEPTED	Fires when a new quest is added to the player's quest log (which is what happens after a player accepts a quest).
+QUEST_ACCEPT_CONFIRM	Fires when certain kinds of quests (e.g. NPC escort quests) are started by another member of the player's group
+QUEST_AUTOCOMPLETE	Fires when a quest is automatically completed (remote handin available)
+QUEST_BOSS_EMOTE	This event is not yet documented
+QUEST_CHOICE_CLOSE	This event is not yet documented
+QUEST_CHOICE_UPDATE	This event is not yet documented
+QUEST_COMPLETE	Fires when the player is looking at the "Complete" page for a quest, at a questgiver.
+QUEST_DETAIL	Fires when details of an available quest are presented by a questgiver
+QUEST_FINISHED	Fires when the player ends interaction with a questgiver or ends a stage of the questgiver dialog
+QUEST_GREETING	Fires when a questgiver presents a greeting along with a list of active or available quests
+QUEST_ITEM_UPDATE	Fires when information about items in a questgiver dialog is updated
+QUEST_LOG_UPDATE	Fires when the game client receives updates relating to the player's quest log (this event is not just related to the quests inside it)
+QUEST_POI_UPDATE	This event is not yet documented
+QUEST_PROGRESS	Fires when interacting with a questgiver about an active quest
+QUEST_REMOVED	This event is not yet documented
+QUEST_TURNED_IN	Fired when a quest is turned in
+QUEST_WATCH_LIST_CHANGED	This event is not yet documented
+QUEST_WATCH_OBJECTIVES_CHANGED	This event is not yet documented
+QUEST_WATCH_UPDATE	Fires when the player's status regarding a quest's objectives changes, for instance picking up a required object or killing a mob for that quest. All forms of (quest objective) progress changes will trigger this event.]
+
+TRACKED_ACHIEVEMENT_LIST_CHANGED	This event is not yet documented
+TRACKED_ACHIEVEMENT_UPDATE	Fires when the player's progress changes on an achievement marked for watching in the objectives tracker
+ ]]
\ No newline at end of file
--- a/ObjectiveEvents.lua	Thu Mar 31 15:58:08 2016 -0400
+++ b/ObjectiveEvents.lua	Fri Apr 01 01:30:42 2016 -0400
@@ -7,8 +7,78 @@
 local mod = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
 local print = B.print('ObjectiveEvent')
 
+local isHooked
+local SmallEvents = {
+  QUEST_ACCEPTED = 'OnQuestAccepted',
+  QUEST_REMOVED = 'OnQuestRemoved'
+}
+
+local HandlerEvents = {
+  QUEST_ACCEPTED = mod.Quest,
+  QUEST_REMOVED = mod.Quest,
+  QUEST_WATCH_LIST_CHANGED = mod.Quest,
+  SUPER_TRACKED_QUEST_CHANGED = mod.Quest,
+  QUEST_LOG_UPDATE = mod.Quest,
+  TRACKED_ACHIEVEMENT_LIST_CHANGED = mod.Cheevs,
+  TRACKED_ACHIEVEMENT_UPDATE = mod.Cheevs
+}
+
+local BlizzHooks = {
+  ['AddQuestWatch'] = 'AddQuestWatch',
+  ['RemoveQuestWatch'] = 'RemoveQuestWatch',
+  ['AbandonQuest'] = 'AbandonQuest',
+  ['AcknowledgeAutoAcceptQuest'] = 'AcknowledgeAutoAcceptQuest',
+  ['AddAutoQuestPopUp'] = 'AddAutoQuestPopUp'
+}
+
+mod.SetEvents = function()
+
+  for event, _ in pairs(SmallEvents) do
+    mod:RegisterEvent(event)
+  end
+
+  for event, _ in pairs(HandlerEvents) do
+    mod:RegisterEvent(event)
+  end
+  mod:SetScript('OnEvent', mod.OnEvent)
+
+
+  if not isHooked then
+    VeneerData.CallLog = {}
+    isHooked = true
+    for blizzFunc, veneerFunc in pairs(BlizzHooks) do
+      if mod[veneerFunc] then
+        hooksecurefunc(blizzFunc, mod[veneerFunc])
+      else
+        hooksecurefunc(blizzFunc, function(...)
+          print('catching', blizzFunc, ...)
+          tinsert(VeneerData.CallLog, {blizzFunc, ...})
+        end)
+      end
+    end
+
+  end
+end
+
+function mod:OnEvent (event, ...)
+  local isHandled
+  if SmallEvents[event] then
+    print('|cFF00FF00'..SmallEvents[event]..'(' ..event..'|r', ...)
+    mod[SmallEvents[event]](event, ...)
+    isHandled = true
+  end
+  if HandlerEvents[event] then
+    print('|cFF0088FF'..event..'|r wrapper update')
+    mod.UpdateWrapper()
+    isHandled = true
+  end
+  if not isHandled then
+    print('|cFFFF4400'..event..'|r', ...)
+  end
+end
+
 --------------------------------------------------------------------
---- Specialized event handlers
+--- Events that are handled by Blizzard_ObjectiveTracker
 --------------------------------------------------------------------
 print(mod:GetName())
 mod.OnQuestAccepted = function(_, questLogIndex, questID)
@@ -25,11 +95,31 @@
   RemoveQuestWatch(questLogIndex)
 end
 
+
 mod.OnQuestRemoved = function(_, questLogIndex, questID)
+
+  mod.UpdateWrapper()
+
 end
 
 mod.OnQuestFromLocation = function(event) end
 
-mod.OnAddQuestWatch = function(questID)
-  mod.UpdateActionButtons()
+-------------------------------------------------------------------
+--- Function hooks for BlizzUI compatibility
+-------------------------------------------------------------------
+mod.AddQuestWatch = function(questID)
+  mod.UpdateWrapper()
 end
+
+mod.RemoveQuestWatch = function(questIndex)
+  mod.UpdateWrapper()
+end
+
+mod.AcceptQuest = function()
+end
+
+mod.AbandonQuest = function()
+end
+
+mod.TurnInQuest = function()
+end
\ No newline at end of file
--- a/ObjectiveFrame.lua	Thu Mar 31 15:58:08 2016 -0400
+++ b/ObjectiveFrame.lua	Fri Apr 01 01:30:42 2016 -0400
@@ -27,6 +27,7 @@
 local previousBlock
 local currentBlock
 --- todo: map these into config table when its sorted out
+local itemButtonSize, itemButtonSpacing =  36, 1
 local titleFont, textFont = [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Bold.ttf]], [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Regular.ttf]]
 local titleSize, textSize = 15, 15
 local titleOutline, textOutline = "OUTLINE", "OUTLINE"
@@ -46,7 +47,7 @@
   ObjectiveWrapperParent = '',
   WrapperStyle = {
     Header = {
-      Background = {Left = [[Objective-Header]], Right = [[Objective-Header]], Tile = [[Objective-Header]]},
+      Background = {Left = '', Right = '', Tile = ''},
       BackgroundCrop = {Left = {0, 0.4, 0,1}, Right={0.6,1,0,1}, Tile = {0.4,.6,0,1,}},
       BackgroundScale = {Left = 100, Right = 100},
       Font = {wrapperHeadFont, wrapperHeadSize, wrapperHeadOutline}
@@ -54,18 +55,18 @@
   },
   ObjectiveHeaderStyle = {
     Normal = {
-      Gradient = {MinColor = {0,0,0,0.5}, MaxColor = {0,0,0,.25}},
+      Gradient = {MinColor = {.05,.15,0.5,0.7}, MaxColor = {.05,.15,0.5,.35}},
       Font = {headerFont, headerSize, headerOutline}, Spacing = headerSpacing,
     }
   },
   ObjectiveTrackerStyle = {
     Normal = {
       Title = {
-        Gradient = { MinColor = {0.2, .4, 1, 0.45}, MaxColor = {.7, 0, 0.9, 0}},
+        Gradient = { MinColor = {0.2, .4, 1, 0.45}, MaxColor = {.7, 0, 0.9, .19}},
         Font = {titleFont, titleSize, titleOutline}, Spacing = titleSpacing,
       },
       Text = {
-        Gradient = { MinColor = {0.2, .4, 1, 0.25}, MaxColor = {.7, 0, 0.9, 0}},
+        Gradient = { MinColor = {0.2, .4, 1, 0.25}, MaxColor = {.7, 0, 0.9, .12}},
         Font = {textFont, textSize, textOutline}, Spacing = textSpacing,
       },
     },
@@ -146,7 +147,7 @@
   self:SetVerticalScroll(s)
   print(s, r, self:GetVerticalScroll())
 
-  mod.UpdateActionButtons()
+  mod.UpdateActionButtons('SCROLLING')
 end
 
 local WrapperCloseButton_OnClick = function(self)
@@ -205,52 +206,76 @@
   end
 
   --- xp bar
-  XPBar:Show()
-  XPBar.rested:SetTexture(2,.6,1,1)
-  XPBar.fg:SetTexture(.3,.1,.95,1)
-  XPBar.bg:SetTexture(0,0,0,.25)
-  XPBar:RegisterEvent('PLAYER_XP_UPDATE')
-  XPBar:RegisterEvent('PLAYER_LEVEL_UP')
-  XPBar:RegisterEvent('PLAYER_UPDATE_RESTING')
+  XPBar:SetWidth(wrapperWidth - Wrapper.close:GetWidth())
+  XPBar.bg:SetAllPoints(XPBar)
+  XPBar:RegisterEvent('DISABLE_XP_GAIN')
+  XPBar:RegisterEvent('ENABLE_XP_GAIN')
   XPBar:SetScript('OnEvent', mod.UpdateXP)
-  mod.UpdateXP(Wrapper.xpBar)
+
+  if not IsXPUserDisabled() then
+    mod.EnableXP(XPBar)
+  else
+    mod.DisableXP(XPBar)
+  end
+
+  mod.UpdateXP(XPBar)
 end
 
-mod.UpdateXP = function()
-  local XPBar = Wrapper.XPBar
-  local xp = UnitXP('player')
-  local xpmax = UnitXPMax('player')
-  local rest = GetXPExhaustion()
+mod.EnableXP = function(self)
+  self:RegisterEvent('PLAYER_XP_UPDATE')
+  self:RegisterEvent('PLAYER_LEVEL_UP')
+  self:RegisterEvent('PLAYER_UPDATE_RESTING')
+  self.bg:SetTexture(0,0,0,.25)
+  self:Show()
+end
 
-  XPBar.bg:SetAllPoints(XPBar)
-  XPBar.fg:SetWidth((xp/xpmax) * XPBar:GetWidth())
+mod.DisableXP = function(self)
+  self:UnregisterEvent('PLAYER_XP_UPDATE')
+  self:UnregisterEvent('PLAYER_LEVEL_UP')
+  self:UnregisterEvent('PLAYER_UPDATE_RESTING')
+  self.bg:SetTexture(0.5,0.5,0.5,0.5)
+  self:Hide()
+end
 
-  if IsResting() then
-    XPBar.bg:SetTexture(.2,.8,.2,.5)
-  else
-    XPBar.bg:SetTexture(0,0,0,.25)
+mod.UpdateXP = function(self, event)
+  if event == 'DISABLE_XP_GAIN' then
+    mod.DisableXP(self)
+  elseif event == 'ENABLE_XP_GAIN' then
+    mod.EnableXP(self)
   end
 
-  if rest then
-    XPBar.rested:ClearAllPoints()
-    if xp == 0 then
-      XPBar.rested:SetPoint('TOPLEFT', XPBar, 'TOPLEFT', 0, 0)
+  if not IsXPUserDisabled() then
+
+    local xp = UnitXP('player')
+    local xpmax = UnitXPMax('player')
+    local rest = GetXPExhaustion()
+    self.fg:SetWidth((xp/xpmax) * self:GetWidth())
+    if rest then
+      self.rested:ClearAllPoints()
+      if xp == 0 then
+        self.rested:SetPoint('TOPLEFT', self, 'TOPLEFT', 0, 0)
+      else
+        self.rested:SetPoint('TOPLEFT', self.fg, 'TOPRIGHT', 0, 0)
+      end
+
+      if (xp + rest) > xpmax then
+        self.rested:SetPoint('BOTTOMRIGHT', self, 'BOTTOMRIGHT', 0, 0)
+      else
+        self.rested:SetWidth((rest/xpmax) * self:GetWidth())
+      end
+      self.rested:SetPoint('BOTTOM', self, 'BOTTOM')
+      self.rested:Show()
     else
-      XPBar.rested:SetPoint('TOPLEFT', XPBar.fg, 'TOPRIGHT', 0, 0)
+      self.rested:Hide()
     end
 
-    if (xp + rest) > xpmax then
-      XPBar.rested:SetPoint('BOTTOMRIGHT', XPBar, 'BOTTOMRIGHT', 0, 0)
+    if IsResting() then
+      self.bg:SetTexture(.2,.8,.2,.5)
     else
-      XPBar.rested:SetWidth((rest/xpmax) * XPBar:GetWidth())
+      self.bg:SetTexture(0,0,0,.25)
     end
-    XPBar.rested:SetPoint('BOTTOM', XPBar, 'BOTTOM')
-    XPBar.rested:Show()
-  else
-    XPBar.rested:Hide()
+    self.xpText:SetText(xp .. '/'.. xpmax .. (rest and (' ('..tostring(rest)..')') or ''))
   end
-
-  XPBar.xpText:SetText(xp .. '/'.. xpmax .. (rest and (' ('..tostring(rest)..')') or ''))
 end
 
 mod.UpdateReputation = function(self)
@@ -415,6 +440,11 @@
     --info.itemButton = nil
   end
 
+  if Devian and Devian.InWorkspace() then
+    t.debugText:Show()
+    t.debugText:SetText(tostring(blockIndex) .. '\n' .. tostring(info.itemButton and info.itemButton:GetName()))
+  end
+
   --- metrics are calculated in SetStyle
   t:SetStyle(style)
   t:Show()
@@ -598,69 +628,109 @@
   Wrapper:SetSize(wrapperWidth, wrapperHeight + headerHeight)
 
   -- update action buttons
+  print('|cFF00FF00'..Scroll:GetName()..'|r:', Scroll:GetWidth(), Scroll:GetHeight(),
+    '|cFF00FF00'..Scroller:GetName()..'|r:', Scroller:GetWidth(), Scroller:GetHeight(),
+    '|cFF00FF00'..Wrapper:GetName()..'|r:', Wrapper:GetWidth(), Wrapper:GetHeight(),
+    '|cFF0088FFvScrollRange|r:', floor(Scroller:GetVerticalScrollRange()+.5)
+  )
+  mod.UpdateActionButtons('FULL_UPDATE')
 
-
-  print('scroll size:', Scroll:GetSize())
-  print('scroller size:',Scroller:GetSize())
-  print('wrapper size:', Wrapper:GetSize())
-  print('scroll range :', floor(Scroller:GetVerticalScrollRange()+.5))
-
-
-  mod.UpdateActionButtons()
+  QuestPOIUpdateIcons()
 end
 
 --- Queue any active item buttons for update for that frame
-mod.UpdateActionButtons = function()
+mod.UpdateActionButtons = function(updateReason)
+  Scroller.snap_upper = 0
+  Scroller.snap_lower = 0
+  local print = B.print('ItemButton')
+  if updateReason then
+    print = B.print('IB_'..updateReason)
+  end
+
   local previousItem
   for questID, itemButton in pairs(usedButtons) do
-    local questIndex = mod.Quest.Info[questID].questLogIndex
+    local info= mod.Quest.Info[questID]
+
     print('|cFF00FFFF'.. questID .. '|r', itemButton:GetName())
     local block = mod.Quest.QuestBlock[questID]
     if block then
         -- Dispatch the probe
-      if IsQuestWatched(questIndex) then
-
+      if IsQuestWatched(info.questLogIndex) then
+        itemButton.previousItem = previousItem
         print('  |cFFFFFF00probing', block:GetName())
         block:SetScript('OnUpdate', function()
           if block:GetBottom() and not InCombatLockdown() then
             print('  '..block:GetName()..' |cFF00FF00probe hit!')
-            mod.UpdateBlockAction(block, itemButton, previousItem)
+            mod.UpdateBlockAction(block, itemButton, itemButton.previousItem) -- needs to be previousItem from this scope
             block:SetScript('OnUpdate', nil)
           end
         end)
+        previousItem = itemButton
       else
         print('hidden block or unwatched quest')
+        itemButton.previousItem = nil
         itemButton:Hide()
       end
     else
       print('  |cFFFF0088missing block data', itemButton:GetName())
+      itemButton.previousItem = nil
+      itemButton:Hide()
     end
   end
 end
 
-mod.UpdateBlockAction = function (block, itemButton, previousItem)
+mod.UpdateBlockAction = function (block, itemButton)
   print('**|cFF0088FF'..itemButton:GetName(), '|r:Update()')
   if itemButton.questID ~= block.info.questID then
     print('** |cFFFF0088mismatched block assignment', itemButton.questID,'<~>', block.info.questID)
-
+    -- something happened between this and last frame, go back and set new probes
     return mod.UpdateActionButtons()
   end
 
-  if block:GetBottom() < Scroller:GetBottom() then
-    print('** ',block:GetName() ,'|cFFFFFF00bottom not fully visible')
+  local previousItem = itemButton.previousItem
+  local upper_bound = Scroller:GetTop() + Scroller.snap_upper
+  local lower_bound = Scroller:GetBottom() + Scroller.snap_lower + itemButtonSize
+  local point, anchor, relative
+
+  if block:GetBottom() < lower_bound then
+    print('** ',block:GetName() ,'|cFFFFFF00bottom =', floor(block:GetBottom()+.5), 'threschold =', floor(lower_bound+.5))
     if previousItem then
+      print('adjusting', previousItem:GetName())
       previousItem:ClearAllPoints()
-      previousItem:SetPoint('BOTTOM', itemButton, 'TOP', 0, 4)
+      previousItem:SetPoint('BOTTOM', itemButton, 'TOP', 0, itemButtonSpacing)
     end
     itemButton:ClearAllPoints()
-    itemButton:SetPoint('BOTTOMRIGHT', UIParent, 'BOTTOMLEFT', Wrapper:GetLeft(), Wrapper:GetBottom())
+    itemButton.x = Wrapper:GetLeft() -4
+    itemButton.y = Wrapper:GetBottom()
+    point, anchor, relative = 'BOTTOMRIGHT', UIParent, 'BOTTOMLEFT'
+    Scroller.snap_lower = Scroller.snap_lower + itemButtonSize + itemButtonSpacing
+
+  elseif block:GetTop() > upper_bound then
+    print('** ',block:GetName() ,'|cFFFFFF00top =', floor(block:GetTop()+.5), 'threschold =', floor(upper_bound+.5))
+    itemButton:ClearAllPoints()
+    if previousItem then
+      print('latch onto another piece')
+      point, anchor, relative ='TOP', previousItem, 'BOTTOM'
+      itemButton.x = 0
+      itemButton.y = -itemButtonSpacing
+    else
+      print('latch at corner', Scroller:GetLeft() -itemButtonSpacing, Scroller:GetTop())
+      point, anchor, relative = 'TOPRIGHT', UIParent, 'BOTTOMLEFT'
+      itemButton.x = Scroller:GetLeft() -4
+      itemButton.y = Scroller:GetTop()
+    end
     itemButton:Show()
+    Scroller.snap_upper = Scroller.snap_upper - (itemButtonSize + itemButtonSpacing)
   else
-    print('** ',block:GetName() ,'|cFF00FF00visible positions')
+    print('** ',block:GetName() ,'|cFF00FF00span =', floor(block:GetBottom()+.5), floor(block:GetTop()+.5), 'threschold =', floor(lower_bound+.5))
     itemButton:ClearAllPoints()
-    itemButton:SetPoint('TOPRIGHT', UIParent, 'BOTTOMLEFT', block:GetLeft(), block:GetTop())
-    itemButton:Show()
+    itemButton.x = block:GetLeft() - itemButtonSpacing
+    itemButton.y = block:GetTop()
+    point, anchor, relative = 'TOPRIGHT', UIParent, 'BOTTOMLEFT'
   end
+
+  itemButton:SetPoint(point, anchor, relative, itemButton.x, itemButton.y)
+  itemButton:Show()
 end
 
 mod.UpdateItemButtonCooldown = function(button)
--- a/ObjectiveInfo.lua	Thu Mar 31 15:58:08 2016 -0400
+++ b/ObjectiveInfo.lua	Fri Apr 01 01:30:42 2016 -0400
@@ -121,6 +121,11 @@
   end
 end
 
+AutoQuest.GetInfo = function(watchIndex)
+  return Quest.GetInfo(watchIndex)
+end
+
+
 Cheevs.GetNumWatched = function(self)
   Cheevs.trackedCheevs = {GetTrackedAchievements()}
   return GetNumTrackedAchievements()
--- a/ObjectiveTracker.lua	Thu Mar 31 15:58:08 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,253 +0,0 @@
---- ${PACKAGE_NAME}
--- @file-author@
--- @project-revision@ @project-hash@
--- @file-revision@ @file-hash@
--- Created: 3/26/2016 1:51 AM
-local B = select(2,...).frame
-local wipe, pairs, ipairs, min, max, unpack = table.wipe, pairs, ipairs, min, max, unpack
-local setmetatable, type = setmetatable, type
-local GetNumQuestLeaderBoards, GetAchievementNumCriteria, GetQuestLogLeaderBoard, GetAchievementCriteriaInfo = GetNumQuestLeaderBoards, GetAchievementNumCriteria, GetQuestLogLeaderBoard, GetAchievementCriteriaInfo
-local GetQuestLogIndexByID, GetSuperTrackedQuestID, SetSuperTrackedQuestID, GetQuestWatchInfo = GetQuestLogIndexByID, GetSuperTrackedQuestID, SetSuperTrackedQuestID, GetQuestWatchInfo
-local mod = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
-local print = B.print('Objectives')
-local ObjectiveTrackerFrame = ObjectiveTrackerFrame
-
---[[
-  Full quest info is available if:
-    - It's in the player quest log, or is available from the Gossip interface
-    - It's being shared from another player and is acceptible
-    - It's an auto-quest that is available in the current location
-  Partial quest info is availabe if:
-    - It's already completed (i.e. it appears in CompletedQuestInfo()).
-    - It's an scheduled interval quest (daily, weekly, etc.)
-    - It's contained in a quest link received from chat
-  Under any other circumstances, only minimal info can be pulled:
-    - Its availability to the player
-    - Its relation with the currently engaged NPC
-    - Its binary completion status
-
-]]
---- Global Frames
-local Wrapper = _G.VeneerObjectiveWrapper
-local Scroller = Wrapper.scrollArea
-local Scroll = _G.VeneerObjectiveScroll
-
---- list used to make things happen
-mod.orderedNames = {[1] = 'AutoQuest',  [2] = 'Quest', [3] = 'Cheevs'}
-
---- ipairs() list of handlers for wrapper update
-mod.orderedHandlers = {}
-mod.orderedTrackers = {}
-mod.indexedTrackers = {}
---- pairs() list of handler frames for tracker updates
-mod.namedTrackers = {}
-
---- Handler stubs
-mod.AutoQuest = {
-  name = "AutoQuest"
-}
-mod.Quest = {
-  name = "Quest"
-}
-mod.Cheevs = {
-  name = "Cheevs"
-}
-
-
---- Temp values set during updates
-local wrapperWidth, wrapperHeight
-local scrollWidth, scrollHeight
-local previousBlock
-local currentBlock
-
-local frame_guide_init = function(self)
-  self.testU = self.testU or self:CreateTexture('TestU', 'OVERLAY', 'VnTestLine')
-  self.testB = self.testB or self:CreateTexture('TestB', 'OVERLAY', 'VnTestLine')
-  self.testL = self.testL or self:CreateTexture('TestL', 'OVERLAY', 'VnTestLine')
-  self.testR = self.testR or self:CreateTexture('TestR', 'OVERLAY', 'VnTestLine')
-end
-local frame_guide = function(self, target)
-  if not target then return end
-  if target:IsDragging() then return end
-  local thickness = 1
-  local midX, midY = target:GetCenter()
-  local width, height = target:GetWidth() * 1.5, target:GetHeight() * 1.5
-  --print('frame', target:GetLeft(), target:GetTop(), target:GetRight(), target:GetBottom())
-  self.testB:ClearAllPoints()
-  self.testB:SetPoint('TOP', UIParent, 'BOTTOMLEFT', midX, target:GetBottom())
-  self.testB:SetSize(width,thickness)
-
-  self.testU:ClearAllPoints()
-  self.testU:SetPoint('BOTTOM', UIParent, 'BOTTOMLEFT', midX, target:GetTop())
-  self.testU:SetSize(width,thickness)
-
-  self.testL:ClearAllPoints()
-  self.testL:SetPoint('RIGHT', UIParent, 'BOTTOMLEFT', target:GetLeft(), midY)
-  self.testL:SetSize(thickness,height)
-
-  self.testR:ClearAllPoints()
-  self.testR:SetPoint('LEFT', UIParent, 'BOTTOMLEFT', target:GetRight(), midY)
-  self.testR:SetSize(thickness,height)
-end
-
---- Handler template
-local CreateHandler = function (self, name, index)
-  print(self, name)
-  local handler = setmetatable({}, {
-    __tostring = function() return name end,
-    __call = function (self) mod.UpdateTracker(self) end
-  })
-  if type(mod.orderedHandlers[index]) == 'table' then
-    return mod.orderedHandlers[index]
-  end
-
-  print('take up locals first')
-  local preset = {}
-  for k,v in pairs(mod[name]) do
-    preset[k] = true
-    if type(v) == 'table' then
-      handler[k] = {}
-    else
-      handler[k] = v
-    end
-  end
-
-  print('resulting handler contents')
-  for k, v in pairs(self) do
-    if not handler[k] then
-      if type(v) == 'table' then
-        -- assume all tables to be local data; don't inherit or ref
-        handler[k] = {}
-      else
-        handler[k] = mod.Tracker[k]
-      end
-    else
-      print(name, 'has its own', k)
-    end
-  end
-  print('|cFFFF4400'..tostring(name)..'|r:')
-  for k, v in pairs(handler) do
-    print(string.format("%24s %8s %s", (preset[k] and '|cFFFFFFFF' or '|cFFFFFF00') .. k .. '|r', type(v), tostring(v)))
-  end
-  mod[name] = handler
-  mod.orderedHandlers[index] = handler
-  return true
-end
-
-mod.Tracker = setmetatable({}, {
-  __call = CreateHandler,
-  __tostring = function() return 'DEFAULT_TRACKING_HANDLER' end
-})
-local Tracker = mod.Tracker
-Tracker.numWatched = 0   --- number of entries being handled
-Tracker.numBlocks = 0    --- number of blocks created
-Tracker.actualBlocks = 0 --- number of blocks in use
-
-Tracker.freeBlocks = {}  --- block heap
-Tracker.usedBlocks = {}
-
-Tracker.Watched = {}     -- find by watchIndex
-Tracker.Info = {}        -- find by data ID
-Tracker.BlockInfo = {}   -- find by block ID
-Tracker.LogInfo = {}     -- find by log ID (quest log mainly)
-Tracker.WatchBlock = {}
-Tracker.WatchInfo = {}
-Tracker.LogBlock = {}
-
-
-
-Tracker.GetBlock = function(handler, blockIndex)
-  local block = handler.usedBlocks[blockIndex]
-  if not handler.usedBlocks[blockIndex] then
-    if #handler.freeBlocks >= 1 then
-      block = handler.freeBlocks[#handler.freeBlocks]
-      handler.freeBlocks[#handler.freeBlocks] = nil
-    else
-      block = CreateFrame('Frame', 'Veneer'..tostring(handler)..'Block'..blockIndex, Scroll, 'VeneerTrackerBlock')
-      block.SetStyle = mod.SetBlockStyle
-      block:ClearAllPoints() -- making sure the anchors are clear in case they get added for some other template usage
-    end
-
-    handler.usedBlocks[blockIndex] = block
-  end
-  return handler.usedBlocks[blockIndex]
-end
-local SmallEvents = {
-  QUEST_ACCEPTED = 'OnQuestAccepted',
-  QUEST_REMOVED = 'OnQuestRemoved'
-}
-
-local HandlerEvents = {
-  QUEST_ACCEPTED = mod.Quest,
-  QUEST_REMOVED = mod.Quest,
-  QUEST_WATCH_LIST_CHANGED = mod.Quest,
-  SUPER_TRACKED_QUEST_CHANGED = mod.Quest,
-  QUEST_LOG_UPDATE = mod.Quest,
-  TRACKED_ACHIEVEMENT_LIST_CHANGED = mod.Cheevs,
-  TRACKED_ACHIEVEMENT_UPDATE = mod.Cheevs
-}
-
-function mod:OnEvent (event, ...)
-  local isHandled
-  if SmallEvents[event] then
-    print('|cFF00FF00'..SmallEvents[event]..'(' ..event..'|r', ...)
-    mod[SmallEvents[event]](event, ...)
-    isHandled = true
-  end
-  if HandlerEvents[event] then
-    print('|cFF0088FF'..event..'|r wrapper update')
-    mod.UpdateWrapper()
-    isHandled = true
-  end
-  if not isHandled then
-    print('|cFFFF4400'..event..'|r', ...)
-  end
-end
-
-mod.SetEvents = function()
-
-  for event, _ in pairs(SmallEvents) do
-    mod:RegisterEvent(event)
-  end
-
-  for event, _ in pairs(HandlerEvents) do
-    mod:RegisterEvent(event)
-  end
-  mod:SetScript('OnEvent', mod.OnEvent)
-end
-
-function mod:OnInitialize()
-  self.InitializeTrackers()
-  self.InitializeXPTracker()
-  mod.SetEvents()
-  ObjectiveTrackerFrame:UnregisterAllEvents()
-  ObjectiveTrackerFrame:Hide()
-
-end
-
---[[
-QUESTLINE_UPDATE	This event is not yet documented
-QUESTTASK_UPDATE	This event is not yet documented
-QUEST_ACCEPTED	Fires when a new quest is added to the player's quest log (which is what happens after a player accepts a quest).
-QUEST_ACCEPT_CONFIRM	Fires when certain kinds of quests (e.g. NPC escort quests) are started by another member of the player's group
-QUEST_AUTOCOMPLETE	Fires when a quest is automatically completed (remote handin available)
-QUEST_BOSS_EMOTE	This event is not yet documented
-QUEST_CHOICE_CLOSE	This event is not yet documented
-QUEST_CHOICE_UPDATE	This event is not yet documented
-QUEST_COMPLETE	Fires when the player is looking at the "Complete" page for a quest, at a questgiver.
-QUEST_DETAIL	Fires when details of an available quest are presented by a questgiver
-QUEST_FINISHED	Fires when the player ends interaction with a questgiver or ends a stage of the questgiver dialog
-QUEST_GREETING	Fires when a questgiver presents a greeting along with a list of active or available quests
-QUEST_ITEM_UPDATE	Fires when information about items in a questgiver dialog is updated
-QUEST_LOG_UPDATE	Fires when the game client receives updates relating to the player's quest log (this event is not just related to the quests inside it)
-QUEST_POI_UPDATE	This event is not yet documented
-QUEST_PROGRESS	Fires when interacting with a questgiver about an active quest
-QUEST_REMOVED	This event is not yet documented
-QUEST_TURNED_IN	Fired when a quest is turned in
-QUEST_WATCH_LIST_CHANGED	This event is not yet documented
-QUEST_WATCH_OBJECTIVES_CHANGED	This event is not yet documented
-QUEST_WATCH_UPDATE	Fires when the player's status regarding a quest's objectives changes, for instance picking up a required object or killing a mob for that quest. All forms of (quest objective) progress changes will trigger this event.]
-
-TRACKED_ACHIEVEMENT_LIST_CHANGED	This event is not yet documented
-TRACKED_ACHIEVEMENT_UPDATE	Fires when the player's progress changes on an achievement marked for watching in the objectives tracker
- ]]
\ No newline at end of file
--- a/ObjectiveTracker.xml	Thu Mar 31 15:58:08 2016 -0400
+++ b/ObjectiveTracker.xml	Fri Apr 01 01:30:42 2016 -0400
@@ -44,28 +44,9 @@
     <Layers>
 
       <Layer level="ARTWORK">
-        <Texture parentKey="BackgroundLeft" parentArray="header" hidden="false" alpha="1" atlas="Objective-Header" useAtlasSize="false">
-          <Size x="100" y="84" />
-          <TexCoords up="0" down="1" left="0" right="0.3" />
-          <Anchors>
-            <Anchor point="TOPLEFT" x="-30" y="12"/>
-          </Anchors>
-        </Texture>
-        <Texture parentKey="BackgroundRight" parentArray="header"  hidden="false" alpha="1" atlas="Objective-Header" useAtlasSize="false">
-          <Size x="70" y="84" />
-          <TexCoords up="0" down="1" left="0.6" right="1" />
-          <Anchors>
-            <Anchor point="TOPRIGHT"  x="30" y="12" />
-          </Anchors>
-        </Texture>
-        <Texture parentKey="BackgroundTile" parentArray="header" hidden="false" alpha="1" atlas="Objective-Header" useAtlasSize="false" tile="true">
-          <Size x="100" y="84" />
-          <TexCoords up="0" down="1" left=".3" right=".6" />
-          <Anchors>
-            <Anchor point="TOPLEFT" relativePoint="TOPRIGHT" relativeKey="$parent.BackgroundLeft" />
-            <Anchor point="TOPRIGHT" relativePoint="TOPLEFT" relativeKey="$parent.BackgroundRight" />
-          </Anchors>
-        </Texture>
+        <Texture parentKey="BackgroundLeft" parentArray="header" />
+        <Texture parentKey="BackgroundRight" parentArray="header" />
+        <Texture parentKey="BackgroundTile" parentArray="header" />
         <Texture parentKey="HeaderBounds" parentArray="config" alphaMode="ADD" hidden="true">
           <Color r="0" g="0" b="1" a="0.5" />
           <Anchors>
@@ -292,6 +273,11 @@
             <Anchor point="RIGHT" relativeKey="$parent" />
           </Anchors>
         </FontString>
+        <FontString name="$parentDebugText" parentKey="debugText" inherits="VeneerCriteriaFontNormal">
+          <Anchors>
+            <Anchor point="TOPRIGHT" relativePoint="TOPRIGHT" />
+          </Anchors>
+        </FontString>
       </Layer>
     </Layers>
   </Frame>
@@ -299,7 +285,7 @@
   <Include file="ObjectiveSecure.xml" />
   <Include file="ObjectiveWidgets.xml" />
 
-  <Script file="ObjectiveTracker.lua" />
+  <Script file="ObjectiveCore.lua" />
   <Script file="ObjectiveInfo.lua" />
   <Script file="ObjectiveUI.lua" />
   <Script file="ObjectiveFrame.lua" />
--- a/ObjectiveUI.lua	Thu Mar 31 15:58:08 2016 -0400
+++ b/ObjectiveUI.lua	Fri Apr 01 01:30:42 2016 -0400
@@ -7,6 +7,7 @@
 local mod = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
 local print = B.print('Objectives')
 local Tracker, AutoQuest, Quest, Cheevs = mod.Tracker, mod.AutoQuest, mod.Quest, mod.Cheevs
+local itemButtonSize, itemButtonSpacing =  36, 1
 
 --------------------------------------------------------------------
 --- Tracker-specific widgets and their handlers
@@ -113,8 +114,8 @@
       local buttonIndex = mod.Quest.numButtons + #freeButtons + 1
       itemButton = CreateFrame('Button', 'VeneerQuestItemButton' .. buttonIndex, UIParent, 'VeneerItemButtonTemplate')
       itemButton.buttonIndex = buttonIndex
-      itemButton:SetSize(48, 48)
-      itemButton:GetNormalTexture():SetSize(80,80)
+      itemButton:SetSize(itemButtonSize, itemButtonSize)
+      itemButton:GetNormalTexture():SetSize(itemButtonSize * (5/3), itemButtonSize * (5/3))
       print('    |cFFFF4400starting new button', itemButton:GetName())
     end
     mod.Quest.numButtons = mod.Quest.numButtons + 1