view Veneer_Objectives/ObjectiveTracker.lua @ 80:bb6b532c5d2f

- added "world state" frame for zone-specific currencies and context-based artifact/experience progress
author Nenue
date Tue, 11 Oct 2016 08:03:41 -0400
parents 0784b87f9722
children
line wrap: on
line source
-- Veneer
-- Veneer_WorldQuests.lua
-- Created: 8/16/2016 8:19 AM
-- %file-revision%
-- This is more or less a copy of the blizzard code with some customization stuff added

local plugin = VeneerObjectives
local vn, print = LibStub("LibKraken").register(Veneer, VeneerObjectives)
local ot
local otvn

local band, floor, min = bit.band, math.floor, math.min


local X_QUEST						= 0x00001;
local X_QUEST_ADDED				= 0x00002;
local X_TASK_ADDED					= 0x00004;
local X_WORLD_QUEST_ADDED			= 0x00008;
local X_SCENARIO					= 0x00010;
local X_SCENARIO_NEW_STAGE			= 0x00020;
local X_ACHIEVEMENT				= 0x00040;
local X_ACHIEVEMENT_ADDED			= 0x00080;
local X_SCENARIO_BONUS_DELAYED		= 0x00100;
local X_SUPER_TRACK_CHANGED		= 0x00200;
-- these are for the specific module ONLY!
local X_MODULE_QUEST				= 0x00400;
local X_MODULE_AUTO_QUEST_POPUP	= 0x00800;
local X_MODULE_BONUS_OBJECTIVE		= 0x01000;
local X_MODULE_WORLD_QUEST			= 0x02000;
local X_MODULE_SCENARIO			= 0x04000;
local X_MODULE_ACHIEVEMENT			= 0x08000;
local X_SCENARIO_SPELLS			= 0x10000;
-- special updates
local X_STATIC						= 0x0000;
local X_ALL						= 0xFFFF;

-- state information
local X_REASON = X_ALL;		-- default
local X_MONEY = 0
local X_ANIMATION = 0
local X_ID = 0;

local INSET_V = -20



local trackerWidth = 240
local blockIndent = 12
local blockSpacing = 3

local tracker = {
  freeBlocks = {},
  usedBlocks = {},
  freeProgressBars = {},
  usedProgressBars = {},
  freeTimers = {},
  usedTimers = {},
  freeLines = {},

  headerText = 'Default Header',
  contentHeight = 0,
  animationHeight = 0,
  updateReasonModule = 0,
  updateReasonEvents = 0,
}
local GetTrackerHandler = function(handler)
  handler = handler or {}
  setmetatable(handler, {__index=tracker})
  return handler
end

local Header_OnAnimationStart = function (self, forced)
  local header = self:GetParent()
  header.animating = true
  plugin:UpdateAnimation(header)
end

local Header_OnAnimationFinished = function (self, forced)
  local header = self:GetParent()
  header.animating = nil
  plugin:Update(X_ANIMATION)
end

-- reset generic flags for layout compilation
function tracker:InitLayout (isStatic)
  print('|cFF00FFFFlayout|r', self.headerText)
  self.firstBlock = nil
  self.lastBlock = nil
  self.currentBlock = nil
  self.oldContentHeight = self.contentHeight
  self.oldAnimationHeight = self.animationHeight
  self.numBlocks = 0
  for id, block in pairs(self.usedBlocks) do
    block.used = nil
  end
  if not isStatic then
    self.hasSkippedBlocks = false
  end

  if not self.header then
    self:SetHeader(self.headerText, X_REASON)
    self.header:SetPoint('TOPLEFT', plugin, 'TOPLEFT', 0, -plugin.contentHeight)
    plugin.contentHeight = plugin.contentHeight + self.header:GetHeight()
  end


  self.contentHeight = 0

end

-- clear out generic flags and set aside block frames for re-use
tracker.EndLayout = function(self)
  self.lastBlock = self.currentBlock
  for id, block in pairs(self.usedBlocks) do
    if not block.used then
      self:FreeBlock(block)
    end
  end
end

function tracker:SetHeader (text, animateReason)
  if self.header then
    return self.header
  end

  local block = CreateFrame('Button', nil, plugin, 'VeneerObjectiveHeader')
  block.handler = self
  block.text:SetText(self.headerText)

  self.currentBlock = block
  self.header = block
  return block
end

function tracker:FreeBlock(block)
  self.usedBlocks[block.id] = nil
  print('|cFF0088FFfree|r', block:GetName())
  tinsert(self.freeBlocks, block)

  block.numLines = 0
  block:SetHeight(0)
  block:Hide()
end

-- should only be used in an N-lastN loop
function tracker:FreeLine(line)
  if line.block then
    line.block.lines[line.index] = nil
  end
  print('|cFF0088FFfree|r', line:GetName())
  local freeLines = line.type or self.freeLines
  tinsert(freeLines, line)
end

local blocksn = 0
tracker.GetBlock = function(self, id)
  local block = self.usedBlocks[id]
  if not block then
    local numFree = #self.freeBlocks
    if numFree >= 1 then
      block = tremove(self.freeBlocks, numFree)
    else
      blocksn = blocksn + 1
      block = CreateFrame('Button', 'OTVNBlock'..blocksn, plugin, 'VeneerObjectiveBlock')
      block.lines = {}
    end
    self.usedBlocks[id] = block
    block.id = id
    block.handler = self
  end

  block.used = true
  block.currentLine = nil
  block.numLines = 0

  if block.lines then
    for i, line in ipairs(block.lines) do
      line.used = nil
    end
  end

  block.contentHeight = blockSpacing
  block:SetWidth(trackerWidth)
  block:SetHeight(0)

  return block
end

-- obtain line
local linesn = 0
function tracker:GetLine(block, index, lineType)
  local line = block.lines[index]
  if line and line.lineType ~= lineType then

    tinsert(self.freeLines, line)
    line = nil
  end

  if not line then
    local freeLines = (lineType and lineType.freeLines) or self.freeLines
    local numFreeLines = #freeLines
    if numFreeLines >= 1 then
      line = tremove(freeLines, numFreeLines)
    else
      linesn = linesn + 1
      line = CreateFrame('Frame', 'OTVNLine'.. linesn, block, (lineType and lineType.template) or 'VeneerObjectiveLine')
    end
    line:SetParent(block)
  end

  line.type = lineType
  line.index = index
  line.used = true
  line.block = block
  line.text:SetPoint('TOPLEFT', line, 'TOPLEFT', blockIndent, 0)
  line:SetHeight(0) -- in order for GetStringHeight to be useful

  block.lines[index] = line

  return line
end

function tracker:AddProgressBar(block, line, questID)
end

function tracker:FreeProgressBar(block, line)
end

function tracker:AddTimerBar(block, line, duration, startTime)
end

function tracker:FreeTimerBar(block, line)
end

-- Checks for space and anchors or frees the block accordingly
function tracker:AddBlock (block, force)
  local anchor = self.currentBlock or self.header
  self.numBlocks = self.numBlocks + 1

  if block.header then
    print('header!', floor(block.contentHeight), '+', floor(block.header:GetStringHeight()))
    block.contentHeight = block.contentHeight + block.header:GetStringHeight()
    self.used = true
  end


  if self.currentBlock then
    self.currentBlock.nextBlock = block
    block.prevBlock = self.currentBlock
  else
    self.firstBlock = block
  end

  self.contentHeight = self.contentHeight + block.contentHeight
  self.currentBlock = block

  if not plugin.currentBlock then
    plugin.firstBlock = block
  end
  plugin.currentBlock = block



  block:SetHeight(block.contentHeight)
  print('block|cFF88FF00', self.numBlocks, block, '|rto', anchor, 'size', block.contentHeight)
  block:ClearAllPoints()
  block:SetPoint('TOPLEFT', anchor, 'BOTTOMLEFT', 0, -blockSpacing)
  block:Show()

  -- free unused lines
  local numLines = #block.lines
  for i = 1, numLines do
    local line = block.lines[i]
    if not line.used or block.collapsed then
      print('|cFFFF4400!|r')
      self:FreeLine(line)
    else
      if not line:IsShown() then
        line:Show()
      end
    end
  end

  return true
end

function tracker:SetLine(block, index, lineType, textOrFunc)
  local anchor = block.currentLine or block.header
  local line = self:GetLine(block, index, lineType)

  if line.ticker then
    line.ticker:Cancel()
    line.ticker = nil
  end

  local text = textOrFunc
  if type(textOrFunc) == 'function' then
    text = textOrFunc()

    line.ticker = C_Timer.NewTicker(10, function()
      line.height = tracker:SetLineText(line.text, textOrTextFunc())
      line:SetHeight(line.height)
    end)
  end
  line.height = tracker:SetLineText(line.text, text)
  line:SetHeight(line.height)

  print('line|cFFFFFF00', line:GetName(), '|rto', anchor:GetName(), 'size', line.height)
  line:SetPoint('TOPLEFT', anchor, 'BOTTOMLEFT', 0, 0)

  block.contentHeight = block.contentHeight + line.height
  block.numLines = block.numLines + 1

  if block.currentLine then
    line.prevLine = block.currentLine
    block.currentLine.nextLine = line
  else
    block.firstLine = line
  end

  block.currentLine = line
  plugin.currentLine = line

end

function tracker:SetLineText(fontString, text)
  fontString:SetText(text)
  return fontString:GetStringHeight()
end

-- Update lite
function tracker:ResetAnchors ()
  self:InitLayout()

  self:EndLayout()
end


local UpdateQuestTracker = function(watchIndex, logIndex)
  return true
end

-- top-down updaters

local questTracker = GetTrackerHandler({
  headerText = 'Quests',
  updateReasonModule = X_MODULE_QUEST,
  updateReasonEvents = (X_QUEST + X_QUEST_ADDED),
})
function questTracker:Update ()
  self:InitLayout()
  for watchIndex = 1, GetNumQuestWatches() do
    local questID, title, questLogIndex, numObjectives, requiredMoney, isComplete, startEvent, isAutoComplete, failureTime, timeElapsed, questType, isTask, isBounty, isStory, isOnMap, hasLocalPOI = GetQuestWatchInfo(watchIndex)
    if not questID then
      -- stop parsing; end of data or variables haven't completely loaded
      break
    end
    local showQuest = true
    if isTask or (isBounty and not IsQuestComplete(questID)) then
      -- do nothing
    else
      -- obtain a block
      local block = self:GetBlock(questID)

      block.header:SetText(title)

      for i = 1, numObjectives do
        local text, objectiveType, finished = GetQuestLogLeaderBoard(i, questLogIndex)
        if text then
          self:SetLine(block, i, nil, text)
        end
      end

      block:SetScript('OnClick', self.OnClick)
      block:RegisterForClicks('AnyDown')

      self:AddBlock(block)
    end
  end

  self:EndLayout()
end
questTracker.OnClick = function(block, button)
  if button == 'RightButton' then
    ObjectiveTracker_ToggleDropDown(block, QuestObjectiveTracker_OnOpenDropDown)
  else
    CloseDropDownMenus()

    local questLogIndex = GetQuestLogIndexByID(block.id)
    if ( IsModifiedClick("QUESTWATCHTOGGLE") ) then
      QuestObjectiveTracker_UntrackQuest(nil, block.id);
    else
      if ( IsQuestComplete(block.id) and GetQuestLogIsAutoComplete(questLogIndex) ) then
        AutoQuestPopupTracker_RemovePopUp(block.id);
        ShowQuestComplete(questLogIndex);
      else
        QuestLogPopupDetailFrame_Show(questLogIndex);
      end
    end
  end
end

local UpdateAchievementTracker = function() end
local UpdateTaskTracker = function() end
local UpdateScenarioTracker = function() end

function plugin:Update(reason, id)

  print('update', reason, #plugin.trackers)

  if plugin.minimized then
    for i, tracker in ipairs(plugin.trackers) do
      tracker:Hide()
    end
    return
  end


  local X_REASON = reason or X_ALL
  local X_ID = id

  plugin.maxHeight = plugin:GetHeight()
  plugin.oldContentsHeight = plugin.contentHeight
  plugin.contentHeight = 0
  plugin.currentBlock = nil
  plugin.firstBlock = nil
  plugin:SetWidth(trackerWidth)

  -- pad for header
  plugin.contentHeight = plugin.header:GetHeight()

  for i, header in ipairs(plugin.trackers) do
    header.used = nil
  end


  local addedSpace = false
  for i, tracker in ipairs(plugin.trackers) do
    if ( band(X_REASON, tracker.updateReasonModule + tracker.updateReasonEvents ) > 0 ) then
      print('->', tracker.headerText)
      tracker:Update()
      if tracker.oldContentHeight - tracker.contentHeight >= 1 then
        addedSpace = true
      end
    else
      -- if we can fit another header or an animation handler popped, contents need to be generated
      if addedSpace or (tracker.header and tracker.header.animating) then
        print('->', tracker.headerText, 'added space')
        tracker:Update()
      else
        print('->', tracker.headerText, 'lite update')
        -- otherwise, measure contents and hide anything that won't fit
        tracker:ResetAnchors()
      end
    end
  end

  for i, tracker in ipairs(plugin.trackers) do
    if tracker.currentBlock then
      if not tracker.header:IsShown() then
        tracker.header:Show()
      end
    else
      if tracker.header:IsShown() then
        tracker.header:Hide()
      end
    end
  end

  if VeneerBuffFrame and VeneerBuffFrame.lastBuff then
    plugin:ClearAllPoints()
    plugin:SetPoint('TOP', VeneerBuffFrame.lastBuff, 'BOTTOM', 0, -4)
    plugin:SetPoint('RIGHT', UIParent, 'RIGHT', -6, 0)
  else
    plugin:ClearAllPoints()
    plugin:SetPoint('TOPRIGHT', UIParent, 'TOPRIGHT', -6, -120)
  end

  plugin:Show()
end

plugin.init = function()

  plugin:RegisterEvent("QUEST_LOG_UPDATE")
  plugin:RegisterEvent("TRACKED_ACHIEVEMENT_LIST_CHANGED")
  plugin:RegisterEvent("QUEST_WATCH_LIST_CHANGED")
  plugin:RegisterEvent("QUEST_AUTOCOMPLETE")
  plugin:RegisterEvent("QUEST_ACCEPTED")
  plugin:RegisterEvent("SUPER_TRACKED_QUEST_CHANGED")
  plugin:RegisterEvent("SCENARIO_UPDATE")
  plugin:RegisterEvent("SCENARIO_CRITERIA_UPDATE")
  plugin:RegisterEvent("SCENARIO_SPELL_UPDATE")
  plugin:RegisterEvent("TRACKED_ACHIEVEMENT_UPDATE")
  plugin:RegisterEvent("ZONE_CHANGED_NEW_AREA");
  plugin:RegisterEvent("ZONE_CHANGED");
  plugin:RegisterEvent("QUEST_POI_UPDATE");
  plugin:RegisterEvent("VARIABLES_LOADED");
  plugin:RegisterEvent("QUEST_TURNED_IN");
  plugin:RegisterEvent("PLAYER_MONEY");

  plugin.trackers = {
    questTracker,
  }
  print('bub')
end
function plugin:event(event, ...)
  print('|cFFFF0088' .. event, ...)
  if event == 'QUEST_WATCH_LIST_CHANGED' then
    local questID, added = ...
    if added then
      if not IsQuestBounty(questID) or IsQuestComplete(questId) then
        print(questID, added)
        plugin:Update(X_QUEST_ADDED, questID)
      end
    else
      print()
      plugin:Update(X_QUEST)
    end
  elseif event == 'QUEST_LOG_UPDATE' then
    plugin:Update(X_MODULE_QUEST)
  elseif event == 'QUEST_POI_UPDATE' then
    
    if GetCVar("trackQuestSorting") == "proximity" then
      -- todo: sort blocks
    end
    plugin:Update(X_MODULE_QUEST)
    elseif event == 'PLAYER_MONEY' then
    plugin:Update(X_MONEY)
  end

end

-- adjusts money events bitcode when a tracker is displaying money data
function plugin:UpdateMoneyFlag(watchMoney, reason)
  if watchMoney then
    if (band(X_MONEY, reason) == 0) then
      X_MONEY = X_MONEY - reason
    end
  else
    if (band(X_MONEY, reason) > 0) then
      X_MONEY = X_MONEY + reason
    end
  end
end

-- used by framescripts to poke the updater when an animation has ended
function plugin:UpdateAnimation(block)

  local reason = block.handler.updateReasonEvents
  if block.animating then
    if (band(X_ANIMATION, reason) == 0) then
      X_ANIMATION = X_ANIMATION - reason
    end
  else
    if (band(X_ANIMATION, reason) > 0) then
      X_ANIMATION = X_ANIMATION + reason
    end
  end
end