view ObjectiveTracker/Quests.lua @ 37:e84d645c8ab8

- revised the tracker update function to build its complete data list up front and use the values as points of comparison for determining possible out of place blocks, which will be iterated over afterward to remove what wasn't re-used - also entailed revising the exact role of global event handlers and function hooks, limiting their directions of communication so one doesn't end up calling the other multiple or inifinity times - schema handling polish
author Nenue
date Mon, 18 Apr 2016 07:56:23 -0400
parents 69d03f8e293e
children 1f8f9cc3d956
line wrap: on
line source
local B = select(2,...).frame
local T = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
local _G, ipairs, max, min, unpack, floor, pairs, tostring, type, band = _G, ipairs, max, min, unpack, floor, pairs, tostring, type, bit.band
local GetQuestWatchInfo, GetQuestLogCompletionText = GetQuestWatchInfo, GetQuestLogCompletionText
local GetQuestLogLeaderBoard, GetNumQuestLogEntries, GetQuestLogTitle = GetQuestLogLeaderBoard, GetNumQuestLogEntries, GetQuestLogTitle
local GetQuestLogSpecialItemInfo, GetQuestLogSpecialItemCooldown = GetQuestLogSpecialItemInfo, GetQuestLogSpecialItemCooldown
local GetSuperTrackedQuestID, GetMoney, C_Scenario, GetCVarBool, GetNumQuestWatches = GetSuperTrackedQuestID, GetMoney, C_Scenario, GetCVarBool, GetNumQuestWatches
local GetQuestTagInfo, GetMoneyString, GetDistanceSqToQuest, GetQuestFactionGroup = GetQuestTagInfo, GetMoneyString, GetDistanceSqToQuest, GetQuestFactionGroup
local QUEST_TAG_ACCOUNT, LE_QUEST_FACTION_HORDE, LE_QUEST_FREQUENCY_DAILY, LE_QUEST_FREQUENCY_WEEKLY = QUEST_TAG_ACCOUNT, LE_QUEST_FACTION_HORDE, LE_QUEST_FREQUENCY_DAILY, LE_QUEST_FREQUENCY_WEEKLY
local QUEST_TAG_TCOORDS, IsQuestSequenced = QUEST_TAG_TCOORDS, IsQuestSequenced
local Default, Quest = T.DefaultHandler, T.Quest
local format, wipe, select = format, table.wipe, select
local wipeall = B.wipeall
local lprint, iprint, tprint = B.print('Line'), B.print('Info'), B.print('Tracker')
local print = tprint

local superTrackQuestID, playerMoney, inScenario, showPOIs
Quest.Update = function(self, reason, ...)
  local print = tprint
  print('QuestTracker:Update() received')
  T.UpdateActionButtons()
  Default.Update(self, reason, ...)
end

T.Quest.numButtons = 0
local usedButtons = T.Quest.itemButtons
local freeButtons = T.Quest.freeButtons

Quest.UpdateObjectives = function(handler, block)
  local print = lprint
  print('|cFF00FFFFUpdateObjectives()')
  local info = block.info

  print((info.isAccount and 'isAccount' or ''), (info.isFaction and 'isFaction' or ''), (info.isDaily and 'isDaily' or ''), (info.isWeekly and 'isWeekly' or ''), info.tagID, info.tagName)

  local block_schema = 'default'
  if info.isAccount then
    if info.isFaction then
      print('     faction', info.tagID)
      block_schema = 'faction_'..info.tagID
    else
      print('     account', info.isAccount, info.isFaction)
      block_schema = 'account'
    end
  elseif info.isDaily then
    print('     daily', info.frequency)
    block_schema = 'daily'
  elseif info.isWeekly then
    print('     weekly', info.frequency)
    block_schema = 'weekly'
  end
  local completionText
  if info.isComplete then
    if info.isAutoComplete then
      local questID, popupType = GetAutoQuestPopUp(info.logIndex)
      if popupType == 'COMPLETE' then
        print('    :: auto-complete quest :: set the message')
        info.completionText = T.strings.CLICK_TO_COMPLETE
      end
    else
      if not completionText or info.completionText then
        info.completionText = GetQuestLogCompletionText(info.logIndex)
      end
    end
    print('    :: complete quest :: show instruction: "'.. tostring(info.completionText) .. '"')
  end

  Default.UpdateObjectives(handler, block, block_schema)
  return block_schema
end

Quest.UpdateLine = function(handler, block, line, data)
  local print = lprint
  local objectiveType = data.type
  return data.text, nil, objectiveType
end

-----------------------------
--- QUEST
local tremove, tinsert = tremove, tinsert
local GetQuestLogIndexByID, IsQuestWatched = GetQuestLogIndexByID, IsQuestWatched
Quest.QuestBlock = {}
Quest.LogBlock = {}
Quest.LogInfo = {}
function Quest:FreeBlock (block)
  local used = Quest.usedBlocks
  local free = Quest.freeBlocks
  local reason = ''
  local doRelease = false
  local info = block.info
  local questID = info.questID
  local logIndex = info.logIndex

  if info.posIndex then
    if used[info.posIndex] == block then
      doRelease = true
      reason = 'posIndex mismatch'
    end
  elseif logIndex then
    if not IsQuestWatched(logIndex) then
      reason = 'not being watched'
    elseif not self.LogBlock[logIndex] then
      doRelease = true
      reason = 'missing logBlock entry'
    elseif (self.LogBlock[logIndex] ~= block) then
      doRelease = true
      reason = 'different block using index'
    end
  elseif info.questID then
    if not GetQuestLogIndexByID(info.questID) then
      doRelease = true
      reason = 'no identifiable quest log entry'
    end
  end


  if doRelease then
    print('  |cFF00FF00FreeBlock (' .. block:GetName() .. '):', reason)
    block:Hide()
    tremove(used, info.posIndex)
    tinsert(free, block)
  end


end
local watchesChecked = {}
local infosChecked = {}
local blocksChecked = {}
local GetQuestWatchIndex = GetQuestWatchIndex
--- Get a total of things to show, and straighten out the index while we're at it
--- Return the number shown, total in log, and the info table to parse
Quest.GetNumWatched = function (self, id, added)
  superTrackQuestID = GetSuperTrackedQuestID()
  playerMoney = GetMoney();
  inScenario = C_Scenario.IsInScenario();
  showPOIs = GetCVarBool("questPOI");
  local numAll = GetNumQuestLogEntries()
  local numWatched = GetNumQuestWatches()
  local bottomIndex = 1
  print('    |cFF00FF88GetNumWatched:|r',self.name, numWatched, 'of', numAll)
  local start, limit = 1, numAll

  --- start a list of blocks affected by this function
  wipe(blocksChecked)
  if id and not added then
    if self.InfoBlock[id] then
      tinsert(blocksChecked, self.InfoBlock[id])
    end
  end

  for logIndex = start, limit do
    local reason1, reason2 = '', ''
    local title, level, suggestedGroup, isHeader, isCollapsed, isComplete, frequency, questID, startEvent, displayQuestID, isOnMap, hasLocalPOI, isTask, isStory = GetQuestLogTitle(logIndex)
    local watchIndex = GetQuestWatchIndex(logIndex)

    if watchIndex and watchIndex >= bottomIndex then
      local watchInfo = self.WatchInfo[watchIndex]
      local watchBlock = self.WatchBlock[watchIndex]
      if watchInfo and watchInfo.questID ~= questID then
        print('    |cFFBBFF00GetNumWatched: trimming WatchInfo ['..watchIndex..'] =/=', questID)
        self.WatchInfo[watchIndex] = nil
      end
      if watchBlock and watchBlock.info.questID ~= questID then
        print('    |cFFBBFF00GetNumWatched: trimming WatchBlock ['..watchIndex..'] =/=', watchBlock:GetName())
        self.WatchBlock[watchIndex] = nil
        tinsert(blocksChecked, watchBlock)
      end
    end

    local logBlock = self.LogBlock[logIndex]
    if logBlock and logBlock.info.questID ~= questID then
      --print('    |cFFBBFF00GetNumWatched: trimming LogBlock ['..logIndex..'] =/=', logBlock:GetName())
      self.LogBlock[logIndex] = nil
      tinsert(blocksChecked, logBlock)
    end

    if questID ~= 0 then
      self.Info[questID] = self:GetInfo(logIndex, watchIndex)
      --print('    |cFF44BBFFGetNumWatched:|r map', questID, 'to', logIndex, (watchIndex and ('('..watchIndex..')') or ''))
    end
  end

  --- remove any orphaned blocks from view and, if possible, free it for re-use
  for i, block in ipairs(blocksChecked) do
    if not GetQuestLogIndexByID(block.info.questID, 'player') then
      print('    |cFFBBFF00GetNumWatched:|r literating a block without an index |cFFBBFF00'.. block:GetName()..'|r')
      block:Hide()
      self:FreeBlock(block)
    end
    if not IsQuestWatched(block.info.logIndex) then
      print('    |cFFBBFF00GetNumWatched:|r hiding untracked quest |cFFBBFF00'.. block:GetName()..'|r')
      block:Hide()
    end
  end

  self.numWatched = numWatched
  self.numAll = numAll

  return numWatched, numAll, self.WatchList
end


--- Returns an iterable table from which tracker blocks can be filled out. Data includes:
-- All entry-layer GetXInfo return values
-- Manifest of line data to be displayed in relation to the tracked object
Quest.GetInfo = function (self, logIndex, watchIndex)
  local print = iprint
  local title, level, suggestedGroup, isHeader, isCollapsed, isComplete, frequency, questID, startEvent, displayQuestID, isOnMap, hasLocalPOI, isTask, isStory = GetQuestLogTitle(logIndex)
  if ( not questID ) then
    tprint('      |cFFFF0088GetInfo:|r', logIndex, watchIndex, '|cFFFF2299no data|r')
    return
  end

  Quest.Info[questID] = Quest.Info[questID] or {}
  local q = Quest.Info[questID]
  q.questID = questID
  q.id = questID
  q.logIndex = logIndex
  q.watchIndex = watchIndex

  local numObjectives, requiredMoney, isAutoComplete, failureTime, timeElapsed, questType
   = 0, 0, nil, false, false, 0
  if watchIndex then
    local _
    _,_,_, numObjectives, requiredMoney, _, _, isAutoComplete,
    failureTime, timeElapsed, questType = GetQuestWatchInfo(watchIndex)
    self.WatchList[watchIndex] = q
    --tprint('      |cFF88FF00GetInfo:|r set watch entry', watchIndex)
    tprint('      |cFFFFBB00GetInfo:|r', logIndex, watchIndex, '|cFFFF2299'..title..'|r')
  end
  self.LogInfo[logIndex] = q

  q.numObjectives = numObjectives
  q.requiredMoney = requiredMoney
  q.failureTime = failureTime
  q.timeElapsed = timeElapsed



  q.type = 'Quest'
  q.title = title
  q.level = level
  q.displayQuestID = displayQuestID
  q.suggestedGroup = suggestedGroup

  -- re-use Blizzard logic for consistency
  local showQuest = true
  if isTask then showQuest = false end
  local watchMoney = false;
  local tagID, typeTag, frequencyTag, completionTag, completionText
  local isAccount, isFaction, isWeekly, isDaily = false, false, false, false
  local isBreadcrumb = false
  local questFailed = false
  local watchMoney = false
  local timerInfo, moneyInfo = false, false
  local objectives = q.objectives or {}


  -- Case 1: completed quest or "go to thing" breadcrumb
  -- * 1 line containing the completion text
  if ( isComplete and isComplete < 0 ) then
    isComplete = false
    questFailed = true
  elseif ( numObjectives == 0 and playerMoney >= requiredMoney and not startEvent ) then
    isComplete = true;
    questFailed = false
    if ( requiredMoney == 0 ) then
      isBreadcrumb = true;
    end
  end
  print('|cFF0088FFflags:|r', (isComplete and 'isComplete' or ''), (questFailed and 'questFailed' or ''), (isBreadcrumb and 'isBreadcrumb' or ''))

  -- completion message?
  local isSequenced = IsQuestSequenced(questID)
  local temp_status = ''
  if ( isComplete ) then
    temp_status = 'COMPLETED_OBJECTIVES'
    objectives = Quest.GetObjectives(questLogIndex, numObjectives, true, isSequenced, isStory)
    if ( isAutoComplete ) then
      temp_status = 'AUTOCOMPLETE_OBJECTIVES'
      completionText = _G.QUEST_WATCH_CLICK_TO_COMPLETE
    else
      if ( isBreadcrumb ) then
        temp_status = 'COMPLETE_BREADCRUMB'
        completionText = GetQuestLogCompletionText(questLogIndex)
      else
        temp_status = 'COMPLETE_READY_FOR_TURN_IN'
        completionText = _G.QUEST_WATCH_QUEST_READY
      end
    end
  elseif ( questFailed ) then
    temp_status = 'FAILED'
    -- Case 2: failed quest
    -- * 1 status line; hide other info
    completionText = _G.FAILED
  else

    temp_status = 'PROGRESS_OBJECTIVES'
    -- Case 3: quest in progress
    -- * Multiple objective lines
    -- * Possible extra lines for money and timer data respectively
    objectives = Quest.GetObjectives(logIndex, numObjectives, false, isSequenced, isStory)
    q.objectives = objectives

    --- anything past here gets appended to existing objectives

    -- money
    if ( requiredMoney > playerMoney ) then

      temp_status = temp_status .. '_MONEY'
      local text = GetMoneyString(playerMoney).." / "..GetMoneyString(requiredMoney);
      moneyInfo = {
        type = 'money',
        text = text,
        finished = false,
        requiredMoney = requiredMoney,
        playerMoney = playerMoney,
      }
      tinsert(objectives, moneyInfo)
    end

    -- time limit
    if ( failureTime ) then
      temp_status = temp_status .. '_TIMED'
      if ( timeElapsed and timeElapsed <= failureTime ) then
        timerInfo = {
          type = 'timer',
          finished = false,
          timeElapsed = timeElapsed,
          failureTime = failureTime,
        }
        tinsert(objectives, timerInfo)
      end
    end
  end
  q.objectives = objectives
  q.moneyInfo = moneyInfo
  q.timerInfo = timerInfo
  q.completionText = completionText

  -- POI data
  local POI = false
  if ( showPOIs ) then
    POI = {
      questID = questID,
      logIndex = logIndex,
      watchIndex = watchIndex
    }
    local poiButton;
    if ( hasLocalPOI ) then

      if ( isComplete ) then
        POI.type = 'normal'
      else
        POI.type = 'numeric'
      end
    elseif ( isComplete ) then
      POI.type = 'remote'
    end

    local distance, onContinent = GetDistanceSqToQuest(logIndex)
    if distance ~= nil and distance > 0 then
      POI.distance = distance
      POI.onContinent = onContinent
    end
  end
  q.POI = POI

  --- Block Tags
  -- completionTag  - in progres, complete, failed, autocomplete
  -- typeTag        - account, faction, pvp, dungeon, group
  -- frequencyTag   - daily/weekly
  local schema = 'default'
  local questTagID, tagName = GetQuestTagInfo(questID)
  local tagInfo = {}
  local tagCoords = {}
  local factionGroup = GetQuestFactionGroup(questID);
  if( questTagID and questTagID == QUEST_TAG_ACCOUNT ) then
    if( factionGroup ) then
      tagID = "ALLIANCE"
      schema = 'alliance'
      if ( factionGroup == LE_QUEST_FACTION_HORDE ) then
        tagID = "HORDE"
        schema = 'horde'
      end
      isFaction = true
    else
      tagID = QUEST_TAG_ACCOUNT
      isAccount = true
    end
    tagInfo['typeTag'] = tagID
    tagCoords['typeTag'] = QUEST_TAG_TCOORDS[tagID]
  elseif ( factionGroup) then
    tagID = "ALLIANCE"
    if ( factionGroup == LE_QUEST_FACTION_HORDE ) then
      tagID = "HORDE"
    end
    isFaction = true
    tagInfo['typeTag'] = tagID
    tagCoords['typeTag'] = QUEST_TAG_TCOORDS[tagID]
  end

  if( frequency == LE_QUEST_FREQUENCY_DAILY and (not isComplete or isComplete == 0) ) then
    tagID = 'DAILY'
    tagInfo['frequencyTag'] = tagID
    tagCoords['frequencyTag'] = QUEST_TAG_TCOORDS[tagID]
    isDaily = true
    schema = 'daily'
  elseif( frequency == LE_QUEST_FREQUENCY_WEEKLY and (not isComplete or isComplete == 0) )then
    tagID = 'WEEKLY'
    tagInfo['frequencyTag'] = tagID
    tagCoords['frequencyTag'] = QUEST_TAG_TCOORDS[tagID]
    isWeekly = true
    schema = 'weekly'
  elseif( questTagID ) then
    tagID = questTagID
  end

  if( isComplete ) then
    tagInfo['completionTag'] = 'COMPLETED'
  elseif ( questFailed ) then
    tagInfo['completionTag'] = 'FAILED'
  end
  tagCoords['completionTag'] = QUEST_TAG_TCOORDS[tagInfo['completionTag']]

  q.tagInfo = tagInfo
  q.tagCoords = tagCoords
  -- establishes the primary block tag for view compacting
  q.tagID = tagID
  q.tagName = tagName

  -- action button information
  local link, icon, charges = GetQuestLogSpecialItemInfo(logIndex)
  local start, duration, enable = GetQuestLogSpecialItemCooldown(logIndex)
  if link or icon or charges then
    q.specialItem = {
      questID = questID,
      logIndex = questLogIndex,
      link = link,
      charges = charges,
      icon = icon,
      start = start,
      duration = duration,
      enable = enable,
    }
  end

  if moneyInfo or timerInfo then
    numObjectives = #objectives
  end

  -- raw data
  q.isComplete = isComplete
  q.startEvent = startEvent
  q.isAutoComplete = isAutoComplete
  q.questType = questType
  q.isTask = isTask
  q.isStory = isStory
  q.isOnMap = isOnMap
  q.hasLocalPOI = hasLocalPOI
  q.frequency = frequency
  q.isComplete = isComplete
  q.isStory = isStory
  q.isTask = isTask
  q.statusKey = temp_status
  q.selected =  (questID == superTrackQuestID)

  T.SetRewards(q, questID)

  q.questID = questID
  q.logIndex = logIndex
  q.watchIndex = watchIndex
  q.id = questID
  q.schema = schema

  if Devian and Devian.InWorkspace() then
    print('|cFF00DDFFstatus:|r', temp_status, '|cFF00FF00questLogIndex|r:', logIndex, title)
    local temp  ={}
    local data_txt = '|cFFFF4400values:|r'
    for k,v in pairs(q) do
      if type(v) =='number' then
        data_txt = data_txt .. ' |cFFFFFF00'..k..'|r: ' .. tostring(v)
      elseif type(v) == 'table' then
        tinsert(temp, k)
      end
    end
    print(data_txt)
    sort(temp, function(a,b) return a < b end)
    for i, k in ipairs(temp) do
      print('|cFF00FF00'..k..'|r')
      for kk,v in pairs(q[k]) do
        print('  ', kk, '=', v)
      end
    end
  end

  return q
end

Quest.GetObjectives = function(logIndex, numObjectives, isComplete, isSequenced, isStory)
  local objectives = {}
  for i = 1, numObjectives do
    local text, type, finished = GetQuestLogLeaderBoard(i, logIndex)
    print(format('      |cFFFF4400GetObjectives:|r #%d %s %s %s', i, tostring(type), tostring(text), tostring(finished)))
    objectives[i] = {
      index = i,
      type = type,
      text = text,
      finished = finished
    }
  end
  return objectives
end

local huge, sqrt = math.huge, math.sqrt
Quest.GetClosest = function()
  local minID, minTitle
  local minDist = huge
  local numQuests = GetNumQuestLogEntries()
  for questIndex =  1, numQuests do
    local distance, onContinent = GetDistanceSqToQuest(questIndex)
    local title, level, _, _, _, _, _, _, questID = GetQuestLogTitle(questIndex)
    if onContinent and distance < minDist then
      minDist = distance
      minTitle = title
      minID = questID
    end
  end

  print('nearest quest is', minTitle, 'by', sqrt(minDist))
  return minID, minTitle, minDist
end

Quest.OnTurnIn = function(self, questID, xp, money)
end

Quest.Select = function (handler, block)
  if block.info.isAutoComplete and block.info.isComplete then
    ShowQuestComplete(block.info.logIndex)
  else
    SetSuperTrackedQuestID(block.info.questID)
  end
end

Quest.Link = function(handler, block)
  local questLink = GetQuestLink(block.info.logIndex);
  if ( questLink ) then
    ChatEdit_InsertLink(questLink);
  end
end

Quest.Open = function(handler, block)
  QuestMapFrame_OpenToQuestDetails(block.info.questID)
end

Quest.Remove = function(handler, block)
  print('removing', block.info.logIndex, 'from watcher')
  RemoveQuestWatch(block.info.logIndex)
end