view ObjectiveTracker/Quests.lua @ 39:92534dc793f2

- restore the previous QuestLogSelection after pulling for selection-restricted quest data; fixes icon mixups while quest map is open - moved progressbar builders into the schema environment, with all the other Frame.lua functions; prep for configuration access - relegate the various removal events to a framescript in their corresponding blocks; this takes care of resolving dead frames
author Nenue
date Thu, 21 Apr 2016 16:43:37 -0400
parents 1f8f9cc3d956
children 03ed70f846de
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 = self.print
  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(self, block)
  local print = lprint
  print('|cFF'..self.internalColor..'UpdateObjectives()')
  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 displayObjectives = true
  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')
        self:AddLine(block, T.strings.CLICK_TO_COMPLETE, nil, 'complete')
      end
    else
      if not completionText or info.completionText then
        info.completionText = GetQuestLogCompletionText(info.logIndex)
      end
    end
    self:AddLine(block, info.completionText, nil, 'complete')
    displayObjectives = false
    print('|cFF'..self.internalColor..'    :: complete quest :: show instruction: "'.. tostring(info.completionText) .. '"')
  end

  Default.UpdateObjectives(self, block, block_schema, displayObjectives)
  return block_schema
end

Quest.UpdateLine = function(handler, block, data)
  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

Quest.OnRemoved = function(block)

end

Quest.GetBlock = function(self, index)
  local block = Default.GetBlock(self, index)
  block:SetScript('OnEvent', Quest.OnRemoved)
  block:RegisterEvent('QUEST_REMOVED')
  return block
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)
  local print = self.print
  superTrackQuestID = GetSuperTrackedQuestID()
  playerMoney = GetMoney();
  inScenario = C_Scenario.IsInScenario();
  showPOIs = GetCVarBool("questPOI");
  local numAll = GetNumQuestLogEntries()
  local numWatched = GetNumQuestWatches()
  local bottomIndex = 1
  print('GetNumWatched', 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('GetNumWatched', 'trimming WatchInfo ['..watchIndex..'] =/=', questID)
        self.WatchInfo[watchIndex] = nil
      end
      if watchBlock and watchBlock.info.questID ~= questID then
        print('GetNumWatched', '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('GetNumWatched', 'iterating a block without an index |cFFBBFF00'.. block:GetName()..'|r')
      block:Hide()
      self:FreeBlock(block)
    end
    if not IsQuestWatched(block.info.logIndex) then
      print('GetNumWatched', '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('GetNumWatched', 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)
    print('GetInfo:', 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('QuestFlags', (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
    self.print('    QuestInfo', title, questType, isAutoComplete)
    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('QuestStatus', temp_status, '|cFF00FF00questLogIndex|r:', logIndex, title)
    local temp  ={}
    local data_txt = '|cFF'..self.internalColor..'values:|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('DataStatus',data_txt)
    sort(temp, function(a,b) return a < b end)
    for i, k in ipairs(temp) do
      iprint('GetInfo', questID, ''..k..'|r')
      for kk,v in pairs(q[k]) do
        iprint('GetInfo', questID, kk, '=', v)
      end
    end
  end

  return q
end

Quest.GetObjectives = function(logIndex, numObjectives, isComplete, isSequenced, isStory)
  local print = Quest.print
  local objectives = {}
  if not logIndex then
    return
  end

  for i = 1, numObjectives do
    local text, type, finished = GetQuestLogLeaderBoard(i, logIndex)

    local progress = 0
    if finished then
      progress = 1
    elseif text then
      local quantity, maxQuantity = text:match('^(%d+)/(%d+)')
      if quantity and maxQuantity then
        progress = quantity / maxQuantity
        --print('GetObjectives', 'calculated objective progress:', quantity, '/', maxQuantity, '=', progress)
      end
    end

    print('GetObjectives', format('|cFF88FF88#%d %s %s %s', i, tostring(type), tostring(text), tostring(finished)), '('.. tostring(progress)..')')


    objectives[i] = {
      index = i,
      type = type,
      text = text,
      finished = finished,
      progress = progress
    }
  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