view ObjectiveUI.lua @ 8:7923243ae972

ObjectiveUI & ObjectiveEvents - securehook to API calls for compatibility with addons that work with the objective tracking interface - let the API hooks invoke ObjectiveUI functions when possible - ObjectiveUI framescript handlers should use the corresponding API call if possible, so that addon space can be fully aware of our actions - Sanity check cached data when possible during 'Remove' hooks ObjectiveInfo - Add cheevID to criteria info ObjectiveCore - Index quest tracker blocks by their watch offset, and use that to verify whether the given block frame should be released into pool ObjectiveFrame - Differentiate between visible and non-visible unused buttons, and only release when their quest has been dropped
author Nenue
date Fri, 01 Apr 2016 14:40:14 -0400
parents 5301c68f28d8
children 2698173edd40
line wrap: on
line source
--- ${PACKAGE_NAME}
-- @file-author@
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Created: 3/29/2016 7:07 PM
local B = select(2,...).frame
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
local tremove, tremovebyval = table.remove, table.removebyval

--------------------------------------------------------------------
--- Tracker-specific widgets and their handlers
--------------------------------------------------------------------

Tracker.Select = function(self) end
Tracker.Open = function(self) end
Tracker.Remove = function(self) end
Tracker.Report = function(self)
  print('Stats:', self.numWatched,'items tracked,', self.numBlocks,'blocks assigned.')
end

Tracker.OnMouseUp = function(self, button)
  if self.initialButton == 'LeftButton' then
    if self.modChatLink and ChatEdit_GetActiveWindow() then
      self:Link()
    elseif self.modQuestWatch then
      self:Remove()
    else
      self:Select()
    end
  elseif button == 'RightButton' then
    self:Open()
  end
  self.initialButton = nil
  self.modChatLink = nil
  self.modQuestWatch = nil
  print(IsModifiedClick("CHATLINK"), IsModifiedClick("QUESTWATCHTOGGLE"))
  print('|cFFFF8800'..tostring(self:GetName())..':MouseUp()|r')
end

Tracker.OnMouseDown = function(self, button)
  self.initialButton = button
  self.modChatLink = IsModifiedClick("CHATLINK")
  self.modQuestWatch = IsModifiedClick("QUESTWATCHTOGGLE")
  self:SetStyle('Active')
  print(IsModifiedClick("CHATLINK"), IsModifiedClick("QUESTWATCHTOGGLE"))
  print(self.info.title)
end

-----------------------------
--- AUTO_QUEST
AutoQuest.name = "Remote Quests"
AutoQuest.GetNumWatched = GetNumAutoQuestPopUps

-----------------------------
--- QUEST
Quest.name = "Quests"
Quest.Select = function(self)
  SetSuperTrackedQuestID(self.info.questID)
  mod.UpdateWrapper()
end
Quest.Link = function(self)
  local questLink = GetQuestLink(block.questLogIndex);
  if ( questLink ) then
    ChatEdit_InsertLink(questLink);
  end
end
Quest.Open = function(self)
  QuestMapFrame_OpenToQuestDetails(self.info.questID)
end

Quest.Remove = function(self)
  print('removing', self.info.questLogIndex, 'from watcher')
  RemoveQuestWatch(self.info.questLogIndex)
end


-----------------------------
--- CHEEVS
Cheevs.Select = function(self)
end
Cheevs.Remove = function(self)
  RemoveTrackedAchievement(self.info.cheevID)
end
Cheevs.OnMouseUp = function(self)
  Tracker.OnMouseUp(self)
  self:SetStyle('CheevNormal')
end
Cheevs.Link = function(self)
  local achievementLink = GetAchievementLink(self.info.cheevID);
  if ( achievementLink ) then
    ChatEdit_InsertLink(achievementLink);
  end
end

Cheevs.Open = function(self)
  if ( not AchievementFrame ) then
    AchievementFrame_LoadUI();
  end
  if ( not AchievementFrame:IsShown() ) then
    AchievementFrame_ToggleAchievementFrame();
  end
  AchievementFrame_SelectAchievement(self.info.cheevID);
end

----------------------------------------------------------------------------------------
--- frame template and scripts lifted from "QuestKing 2" by Barjack
--- url: http://mods.curse.com/addons/wow/questking
----------------------------------------------------------------------------------------
local usedButtons = mod.Quest.itemButtons
local freeButtons = mod.Quest.freeButtons
mod.SetItemButton = function(block, info)
  local itemInfo = info.specialItem
  if not itemInfo then
    return
  end
  --- .specialItem :: {link = link, charges = charges, icon = icon, start = start, duration = duration, enable = enable}


  local itemButton
  if not info.itemButton then
    if #freeButtons >= 1 then
      print('    |cFF00FFFFfound a free button')
      itemButton = freeButtons[#freeButtons]
      freeButtons[#freeButtons] = nil
      if itemButton.block then
        itemButton.block.itemButton = nil
        itemButton.block = nil
      end
    else
      local buttonIndex = mod.Quest.numButtons + #freeButtons + 1
      itemButton = CreateFrame('Button', 'VeneerQuestItemButton' .. buttonIndex, UIParent, 'VeneerItemButtonTemplate')
      itemButton.buttonIndex = buttonIndex
      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
  else
    itemButton = info.itemButton
    print('    |cFF00FF00found assigned button', itemButton:GetName())

  end
  -- set values

  info.itemButton = itemButton
  usedButtons[info.questID] = itemButton
  print('      |cFF8800FFassigning|r', itemButton:GetName(), 'to quest|cFF00FF00', info.questID, '|rat|cFFFFFF00', block:GetName(),'|r')

  for k,v in pairs(usedButtons) do
    print('|cFFFF44DD'..k..'|r', v:GetName())
    end

  itemButton:SetAttribute("type", "item")
  itemButton:SetAttribute("item", itemInfo.link)

  itemButton.questID = info.questID
  itemButton.questLogIndex = info.questLogIndex
  itemButton.charges = itemInfo.charges
  itemButton.rangeTimer = -1
  itemButton.block = block

  SetItemButtonTexture(itemButton, itemInfo.icon)
  SetItemButtonCount(itemButton, itemInfo.charges)
  Veneer_QuestObjectiveItem_UpdateCooldown(itemButton);

  return itemButton
end
--- Clear an itemButton from the given block
mod.FreeItemButtons = function(block)

  if block.itemButton then
    local itemButton = block.itemButton
    if itemButton.questID ~= block.info.questID then
      block.itemButton = nil
      itemButton.block = mod.Quest.InfoBlock[itemButton.questID]
    else
      itemButton.block = nil
      itemButton:Hide()

      usedButtons[itemButton.questID] = nil
      freeButtons[#freeButtons + 1] = itemButton
      mod.Quest.numButtons = mod.Quest.numButtons - 1
      print('|cFFFF0088released', itemButton:GetName(),'and', block:GetName())
    end
  end
end

function Veneer_QuestObjectiveItem_OnUpdate (self, elapsed)
  -- Handle range indicator
  local rangeTimer = self.rangeTimer
  if (rangeTimer) then
    rangeTimer = rangeTimer - elapsed
    if (rangeTimer <= 0) then
      local link, item, charges, showItemWhenComplete = GetQuestLogSpecialItemInfo(self.questLogIndex)
      if ((not charges) or (charges ~= self.charges)) then
        mod.UpdateWrapper()
        return
      end

      local count = self.HotKey
      local valid = IsQuestLogSpecialItemInRange(self.questLogIndex)
      if (valid == 0) then
        count:Show()
        count:SetVertexColor(1.0, 0.1, 0.1)
      elseif (valid == 1) then
        count:Show()
        count:SetVertexColor(0.6, 0.6, 0.6)
      else
        count:Hide()
      end
      rangeTimer = TOOLTIP_UPDATE_TIME
    end

    self.rangeTimer = rangeTimer
  end
end

function Veneer_QuestObjectiveItem_UpdateCooldown (itemButton)
  local start, duration, enable = GetQuestLogSpecialItemCooldown(itemButton.questLogIndex)
  if (start) then
    CooldownFrame_SetTimer(itemButton.Cooldown, start, duration, enable)
    if (duration > 0 and enable == 0) then
      SetItemButtonTextureVertexColor(itemButton, 0.4, 0.4, 0.4)
    else
      SetItemButtonTextureVertexColor(itemButton, 1, 1, 1)
    end
  end
end

-----------------------------------------
-- Criteria frames

--[[
      text = description,
      type = type,
      finished = completed,
      quantity = quantity,
      requiredQuantity = requiredQuantity,
      characterName = characterName,
      flags = flags,
      assetID = assetID,
      quantityString = quantityString,
      criteriaID = criteriaID,
]]
local newWidgetID = 0
mod.WidgetRegistry = {}
local wr = mod.WidgetRegistry

--- Get a usable widget for the given achievement criteria set.
 -- Returns a frame object with dimensioning parameters needed to size the receiving tracker block
mod.SetWidget = function(obj, info)
  local print = B.print('ObjectiveWidgets')
  local widgetType = obj.type
  local widget
  if wr[widgetType] and wr[widgetType].used[obj.criteriaID] then
    widget = wr[widgetType].used[obj.criteriaID]
    print('|cFF00FF00Updating ('..obj.criteriaID..')', widget)
  elseif not wr[widgetType] or #wr[widgetType].free == 0 then
    widget = CreateFrame('Frame', 'VeneerObjective' .. widgetType .. (wr[widgetType] and (wr[widgetType].lastn+1) or (1)), VeneerObjectiveScroll, 'VeneerObjectiveCriteria' .. widgetType)

    print('|cFFFF0088Creating `'..widget:GetName()..'` id', wr[widgetType].lastn)
  else
    widget = tremove(wr[widgetType].free)
    print('|cFFFFFF00Acquiring released widget', widget:GetName())
  end

  wr[widgetType].used[obj.criteriaID] = widget
  widget.info = obj
  widget.parentInfo = info
  mod.InitializeWidget(widget)
  return widget
end

--- WidgetTemplate 'OnLoad'
mod.RegisterWidget = function(frame)
  local print = B.print('ObjectiveWidgets')
  local widgetType = frame.widgetType
  if not wr[frame.widgetType] then
    print('|cFFFF4400[[WidgetTemplate]]|r', widgetType)
    wr[widgetType] = { lastn = 1, free = {}, used = {}, usedIndex = {}, freeIndex = {} }
  else
    print('|cFF0088FF+ [[WidgetTemplate]]r', widgetType, wr[widgetType].lastn)
    wr[widgetType].lastn = wr[widgetType].lastn + 1
  end
end

--- WidgetTemplate 'OnShow'
mod.InitializeWidget = setmetatable({}, {
  __call = function(t, frame)
    -- todo: config pull
    local maxWidth = 250

    frame:SetWidth(maxWidth)
    mod.UpdateWidget[frame.widgetType](frame)
    frame:SetScript('OnEvent', mod.UpdateWidget[frame.widgetType])
    if frame.info.isCurrency then
      frame:RegisterEvent('CHAT_MSG_CURRENCY')
      frame:RegisterEvent('CURRENCY_LIST_UPDATE')
    end
    frame:RegisterEvent('TRACKED_ACHIEVEMENT_UPDATE')
    frame:RegisterEvent('TRACKED_ACHIEVEMENT_LIST_CHANGED')
    frame:RegisterEvent('CRITERIA_UPDATE')
    frame:RegisterEvent('CRITERIA_COMPLETE')
    frame:RegisterEvent('CRITERIA_EARNED')

    return t[frame.widgetType](frame)
  end,
})

--- WidgetTemplate 'OnEvent'
mod.UpdateWidget = setmetatable({}, {
  __call = function(t, frame)
    if not frame.widgetType then
      error('Invalid widget template, needs .widgetType')
      return
    end

    return t[frame.widgetType](frame)
  end
})

--- WidgetTemplate 'OnHide'
mod.ReleaseWidget = function(frame)
  local print = B.print('ObjectiveWidgets')
  local reg = wr[frame.widgetType]
  if reg and reg.used[frame.info.criteriaID] then
    reg.used[frame.info.criteriaID] = nil
    frame.info = nil
    frame.parentInfo = nil
    frame:UnregisterAllEvents()
    tinsert(reg.free, frame)
    print('|cFFBBBBBBreleased from service', frame:GetName())
  end
end

--- RemoveTrackedAchievement post-hook
mod.CleanWidgets = function()
  local print = B.print('ObjectiveWidgets')
  local tracked = {GetTrackedAchievements() }
  for type, reg in pairs(mod.WidgetRegistry) do
    print('collecting', type)
    for criteriaID, frame in pairs(reg.used) do
      local id = frame.info.cheevID

      if id and not tContains(tracked, id) then

        print('  untracked achievement', id, 'associated with', criteriaID, frame:GetName())
        frame:Hide()
      end
    end
  end
end

mod.WidgetParams = {
  ['ProgressBar'] = {
    height = 20,
    caption = {},
    quantityString = {SetFontObject = _G.VeneerFontNormal}
  }
}

mod.InitializeWidget.ProgressBar = function(self)
  local print = B.print('ObjectiveWidgets')
  local params = mod.WidgetParams[self.widgetType]
  self.height = params.height
  self:SetHeight(20)
  self.bg:SetHeight(20)
  self.fg:SetHeight(16)
  self.fg:SetPoint('BOTTOMLEFT', self.bg, 'BOTTOMLEFT', 1, 1)
  self.quantityString:SetFontObject(params.quantityString.SetFontObject)
  self.quantityString:SetText(self.info.quantityString)
end

mod.UpdateWidget.ProgressBar = function (self)
  local print = B.print('ObjectiveWidgets')
  local quantity, requiredQuantity = self.info.quantity, self.info.requiredQuantity

  if self.info.finished then
    self.fg:SetWidth(self.bg:GetWidth() - 2)
  elseif quantity == 0 then
    self.fg:Hide()
  else
    self.fg:Show()
    self.fg:SetWidth((self:GetWidth()-2) * (quantity / requiredQuantity))
  end
end