view ObjectiveTracker/ObjectiveTracker.lua @ 56:159855c2e9ae

Added tag v1.0-release for changeset d7655c4e6e06
author Nenue
date Fri, 10 Jun 2016 20:53:22 -0400
parents ed74c5cabe98
children
line wrap: on
line source
--- ${PACKAGE_NAME}
-- @file-author@
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Created: 3/26/2016 1:51 AM
local B, _G = select(2,...).frame, _G
local pairs, setmetatable, type, tostring, band, format = _G.pairs, _G.setmetatable, _G.type, _G.tostring, bit.band, string.format
local ipairs, tinsert, hooksecurefunc = _G.ipairs, _G.tinsert, _G.hooksecurefunc
local PlaySoundFile, IsQuestTask, SortQuestWatches, GetCurrentMapAreaID, GetZoneText, GetMinimapZoneText = PlaySoundFile, IsQuestTask, SortQuestWatches, GetCurrentMapAreaID, GetZoneText, GetMinimapZoneText
local QuestPOIUpdateIcons, GetCVar, IsPlayerInMicroDungeon, WorldMapFrame, GetCVarBool, SetMapToCurrentZone = QuestPOIUpdateIcons, GetCVar, IsPlayerInMicroDungeon, WorldMapFrame, GetCVarBool, SetMapToCurrentZone
local AddAutoQuestPopUp = AddAutoQuestPopUp
local T = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
local print = B.print('Objectives')
local ObjectiveTrackerFrame, VeneerObjectiveScroll, CreateFrame = _G.ObjectiveTrackerFrame, _G.VeneerObjectiveScroll, _G.CreateFrame
local Wrapper = _G.VeneerObjectiveWrapper
local Scroller = VeneerObjectiveWrapper.scrollArea
local Scroll = _G.VeneerObjectiveScroll
local unitLevel = UnitLevel('player')

--- Performance values

--- Done once per ui load
local BlizzHooks = {
  'AcceptQuest',
  'AddQuestWatch',
  'CompleteQuest',
  'RemoveQuestWatch',
  'AbandonQuest',
  'AcknowledgeAutoAcceptQuest',
  'AddAutoQuestPopUp',
  'RemoveAutoQuestPopUp',
  'AddTrackedAchievement',
  'RemoveTrackedAchievement',
  'SetSuperTrackedQuestID',
  'SelectQuestLogEntry',
}
local enabledOnly = {
  ['AddQuestWatch'] = true,
}

--- These are the bitfields used by Blizzard_ObjectiveTracker to determine which segments get parsed.
--- They are replicated here so that plugins can make use of any securehook args involving this info.
local OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST = OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST                       -- 0x0100
local OBJECTIVE_TRACKER_UPDATE_MODULE_AUTO_QUEST_POPUP = OBJECTIVE_TRACKER_UPDATE_MODULE_AUTO_QUEST_POPUP	-- 0x0200
local OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE = OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE		-- 0x0400
local OBJECTIVE_TRACKER_UPDATE_MODULE_SCENARIO = OBJECTIVE_TRACKER_UPDATE_MODULE_SCENARIO	    		        -- 0x0800
local OBJECTIVE_TRACKER_UPDATE_MODULE_ACHIEVEMENT	= OBJECTIVE_TRACKER_UPDATE_MODULE_ACHIEVEMENT           -- 0x1000


local OBJECTIVE_TRACKER_UPDATE_STATIC = OBJECTIVE_TRACKER_UPDATE_STATIC					                         	-- 0x0000
local OBJECTIVE_TRACKER_UPDATE_ALL = OBJECTIVE_TRACKER_UPDATE_ALL			                    	          		-- 0xFFFF
local OBJECTIVE_TRACKER_UPDATE_ID = OBJECTIVE_TRACKER_UPDATE_ID                                           -- 0

local OBJECTIVE_TRACKER_UPDATE_QUEST = OBJECTIVE_TRACKER_UPDATE_QUEST				        	                  	-- 0x0001
local OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED = OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED		               		    -- 0x0002
local OBJECTIVE_TRACKER_UPDATE_TASK_ADDED = OBJECTIVE_TRACKER_UPDATE_TASK_ADDED		    	              		-- 0x0004
local OBJECTIVE_TRACKER_UPDATE_SCENARIO = OBJECTIVE_TRACKER_UPDATE_SCENARIO			      	                 	-- 0x0008
local OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE = OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE		       	-- 0x0010
local OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT = OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT			                   	-- 0x0020
local OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED = OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED		  	      -- 0x0040
local OBJECTIVE_TRACKER_UPDATE_SCENARIO_BONUS_DELAYED = OBJECTIVE_TRACKER_UPDATE_SCENARIO_BONUS_DELAYED		-- 0x0080

local OBJECTIVE_TRACKER_UPDATE_REASON = OBJECTIVE_TRACKER_UPDATE_ALL -- default
--- Used to determine which trackers are listening for money events

T.strings = {}
T.strings.CLICK_TO_ACCCEPT = 'Click to Accept'
T.strings.CLICK_TO_COMPLETE = 'Click to complete'
T.colors ={
  enable = true,
}

T.watchMoneyReasons = 0

--- Baseline defaults table; values defined in the files that they pertain to
T.defaults = {}


--- Tracker display order
T.orderedNames = {'Bonus', 'AutoQuest', 'Quest', 'Cheevs'}

--- ipairs() argument tables
T.orderedHandlers = setmetatable({}, {__mode = "k"})
T.orderedTrackers = setmetatable({}, {__mode = "k"})
T.indexedTrackers = setmetatable({}, {__mode = "k"})

--- pairs() argument tables
T.namedTrackers = setmetatable({}, {__mode = "k"})

local WRAPPER_ANCHOR_POINT = 'TOPRIGHT'
local WRAPPER_OFFSET_X = 0
local WRAPPER_OFFSET_Y = -200
local WRAPPER_MAX_HEIGHT = 670
local WRAPPER_WIDTH = 280
local WRAPPER_HEADER_HEIGHT = 24

T.defaults.Wrapper = {
  AnchorPoint = WRAPPER_ANCHOR_POINT,
  OffsetX = WRAPPER_OFFSET_X,
  OffsetY = WRAPPER_OFFSET_Y,
  Height = WRAPPER_MAX_HEIGHT,
  Width = WRAPPER_WIDTH,
  HeaderHeight = WRAPPER_HEADER_HEIGHT,
  TextSpacing = 3,
  TextIndent = 4,
  TitleSpacing = 3,
  TitleIndent = 4,
}



--- Tracker module definitions begin here; innards dealing with data retreival and output are defined further in
T.DefaultHandler = {
  previousHeight = 0,

  name = "temp",
  displayName = "temp",
  updateReasonModule = 0xFF00,
  updateReasonEvent  = 0x00FF,

  numWatched = 0,   --- number of entries being handled
  numBlocks = 0,    --- number of blocks created
  actualBlocks = 0, --- number of blocks in use
  Info = {},        --- stored watch list information, keyed by whatever unique ID is involved for that tracker
  Watched = {},     --- stores whether the given unique ID is tracked

  freeBlocks = {},  --- blocks hidden due to list shrinkage
  usedBlocks = {},  --- block in use
  BlockInfo = {},   --- by block creation offset, used by framescript
  WatchList = {},   --- ordered manifest of watched items

  --- Indexes
  InfoBlock = {},   --- by unique ID
  LogBlock = {},    --- by API log offset, used by GetBlock if possible
  WatchBlock = {},  --- block by internal offset, used in GetBlock scope
  WatchInfo = {},   --- info by internal offset, used in Update scope
}

T.AutoQuest = {
  name = "AutoQuest",
  displayName = "Notice",
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST + OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED,
  internalColor = '00FFFF',
  LogInfo = {},
  QuestBlock = {},
}
T.Quest = {
  name = "Quest",
  displayName = "Quests",
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST + OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED,
  internalColor = '0088FF',
  itemButtons = {},
  freeButtons = {},
  LogInfo = {},
  QuestBlock = {},
}
T.Cheevs = {
  name = "Cheevs",
  displayName = "Achievements",
  schema = 'achievement',
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_ACHIEVEMENT,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT +
      OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED,
  internalColor = '00FF88',
}
T.Bonus = {
  name = "Bonus",
  displayName = "Bonus Objectives",
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST + OBJECTIVE_TRACKER_UPDATE_TASK_ADDED,
  internalColor = '00FF00',
  QuestBlock = {},
}

T.Scenario = {
  name = 'Scenario',
  displayName = 'Scenario Objectives',
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_SCENARIO,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE + OBJECTIVE_TRACKER_UPDATE_SCENARIO_BONUS_DELAYED,
  internalColor = '88FF00',
}
local lastLabel
local Tracker_debug = function(handler, channel)
  local func = B.print(channel)
  local color = handler.internalColor
  return function(label, ...)
    if lastLabel == label then
      label = label:gsub('%a', ' ').. ' '
    else
      lastLabel = label
      label = '|cFF'..color..label..':|r'
    end
    func(label, ...)
  end
end
local Tracker_string = function (self)
  return self.name
end
local Tracker_call = function (self, reason)
  self:Update(reason)
end
local Handler_Initialize = function (self, name, index)
  local c = T.Conf.Wrapper
  print('Initializing |cFF00FFFF'..name..'|r module...')

  local handler = setmetatable(T[name] or {}, {
    __tostring = Tracker_string,
    __call = Tracker_call
  })
  if type(T.orderedHandlers[index]) == 'table' then
    return T.orderedHandlers[index]
  end

  print('|cFFFFFF00Acquiring locals')
  local preset = {}
  for k, _ in pairs(handler) do
    preset[k] = true
  end


  print('|cFFFF8800Inheriting')
  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] = self[k]
      end
      print('copying', k)
    end
  end
  print('|cFFFF4400'..tostring(name)..'|r:')
  for k, v in pairs(handler) do
    print(format("%32s %8s %s", (preset[k] and '|cFFFFFFFF' or '|cFFFFFF00') .. k .. '|r', type(v), tostring(v)))
  end

  T[name] = handler

  local trackerName = 'Veneer'..name..'Tracker'
  local handler = T[name]
  local frame = CreateFrame('Frame', trackerName, _G.VeneerObjectiveScroll, 'VeneerTrackerTemplate')
  frame.title:SetText(handler.displayName)
  frame:SetWidth(c.Width)
  frame.previousOffset = 0
  frame:SetScript('OnHide', T.DefaultHandler.OnHide)
  frame.handler = handler

  handler.frame = frame
  handler.numBlocks = 0
  handler.actualBlocks = 0
  handler.trackerName = trackerName
  handler.lines = {}
  T.orderedTrackers[index] = frame
  T.namedTrackers[name] = frame
  T.indexedTrackers[handler] = frame
  print('|cFFFF0088' .. trackerName .. '|r created for |cFF00FFFF' .. handler.displayName .. '|r module')

  handler.print = Tracker_debug(handler, 'Tracker')
  handler.lprint = Tracker_debug(handler, 'Line')
  handler.bprint = Tracker_debug(handler, 'Block')

  T.orderedHandlers[index] = handler
  return true
end

local Event = {}

Event.QUEST_LOG_UPDATE =  function()
  return OBJECTIVE_TRACKER_UPDATE_QUEST
end
local GetNumQuestWatches, AddQuestWatch, SetSuperTrackedQuestID = GetNumQuestWatches, AddQuestWatch, SetSuperTrackedQuestID
Event.QUEST_ACCEPTED = function(questLogIndex, questID, added)

  --todo: stall non-confirmed autoquests
  AddQuestWatch(questLogIndex)
  if ( IsQuestTask(questID) ) then
    return OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED + OBJECTIVE_TRACKER_UPDATE_TASK_ADDED, questID, added
  else
    return OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED, questID, added
  end
end

Event.QUEST_WATCH_LIST_CHANGED = function(questID, added)
  if ( added == true ) then
    if ( not IsQuestTask(questID) ) then
      return OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED, questID, added
    end
  elseif questID then
    return OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED, questID, added
  else
    return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST
  end
end

Event.QUEST_POI_UPDATE = function()
  QuestPOIUpdateIcons();
  if ( GetCVar("trackQuestSorting") == "proximity" ) then
    SortQuestWatches();
  end
  return OBJECTIVE_TRACKER_UPDATE_ALL
end
Event.SUPER_TRACKED_QUEST_CHANGED = function(questID)
  --return OBJECTIVE_TRACKER_UPDATE_QUEST, questID
end
Event.ZONE_CHANGED = function()
  local inMicroDungeon = IsPlayerInMicroDungeon();
  if ( inMicroDungeon ~= T.inMicroDungeon ) then
    if ( not WorldMapFrame:IsShown() and GetCVarBool("questPOI") ) then
      SetMapToCurrentZone();			-- update the zone to get the right POI numbers for the tracker
    end
    --SortQuestWatches();
    T.inMicroDungeon = inMicroDungeon;
  end
  return OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE + OBJECTIVE_TRACKER_UPDATE_MODULE_SCENARIO
end
Event.QUEST_AUTOCOMPLETE = function(questId)
  AddAutoQuestPopUp(questId, "COMPLETE");
  return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST + OBJECTIVE_TRACKER_UPDATE_MODULE_AUTO_QUEST_POPUP
end
Event.SCENARIO_CRITERIA_UPDATE =  function()
  return OBJECTIVE_TRACKER_UPDATE_SCENARIO
end
Event.SCENARIO_UPDATE = function(newStage)
  if ( newStage ) then
    return OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE
  else
    return OBJECTIVE_TRACKER_UPDATE_SCENARIO
  end
end
Event.TRACKED_ACHIEVEMENT_UPDATE = function()
  return OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT
end
Event.TRACKED_ACHIEVEMENT_LIST_CHANGED = function(achievementID, added)
  return OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED, achievementID, added
end
Event.ZONE_CHANGED_NEW_AREA = function ()
  if ( not WorldMapFrame:IsShown() and GetCVarBool("questPOI") ) then
    SetMapToCurrentZone();			-- update the zone to get the right POI numbers for the tracker
  end
  SortQuestWatches();
  T.currentZoneArea = GetCurrentMapAreaID()
  print('Updating zone ID to', T.currentZoneArea, '=', GetZoneText(), GetMinimapZoneText())


  return OBJECTIVE_TRACKER_UPDATE_TASK_ADDED
end


Event.PLAYER_MONEY = function()
  if T.watchMoneyReasons > 0 then
    return T.watchMoneyReasons
  end
end
Event.CRITERIA_COMPLETE = function()
  return OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE
end
Event.QUEST_TURN_IN = function(questID, xp, money)
  if ( IsQuestTask(questID) ) then
    T.Bonus:OnTurnIn(questID, xp, money)
    print('updating bonus modules (code', OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE, ',', questID, xp, money)
    return OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE, questID, xp, money
  else
    return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST, questID, xp, money
  end
end
T.Event = Event

local VeneerData

T.SetWatchMoney = function(watchMoney, reason)
  if watchMoney then
    if band(T.watchMoneyReasons, reason) == 0 then
      T.watchMoneyReasons = T.watchMoneyReasons + reason;
    end
  else
    if band(T.watchMoneyReasons, reason) > 0 then
      T.watchMoneyReasons = T.watchMoneyReasons - reason;
    end
  end
end

local iprint = B.print('Info')
T.SetRewards = function(t, questID)
  local previousSelection = GetQuestLogSelection()

  SelectQuestLogEntry(GetQuestLogIndexByID(questID))
  local numQuestChoices = GetNumQuestLogChoices();
  local skillName, skillIcon, skillPoints = GetQuestLogRewardSkillPoints();
  local xp = GetQuestLogRewardXP();
  local playerTitle = GetQuestLogRewardTitle();
  ProcessQuestLogRewardFactions();

  local rewards = {}
  local texture, name, isTradeskillSpell, isSpellLearned, hideSpellLearnText, isBoostSpell, garrFollowerID = GetQuestLogRewardSpell(questID)
  if name  then
    tinsert(rewards,{
      type = 'spell',
      name = name,
      texture = texture,
    })
  end
  if previousSelection then
    SelectQuestLogEntry(previousSelection)
  end

  t.numCurrencies = GetNumQuestLogRewardCurrencies(questID)
  for i = 1, t.numCurrencies do
    local name, texture, count = GetQuestLogRewardCurrencyInfo(i, questID)
    tinsert(rewards,{
      type = 'currency',
      index = i,
      name = name,
      texture = texture,
      count = count
    });
  end
  -- items
  t.numItems = GetNumQuestLogRewards(questID)
  for i = 1, t.numItems do
    local name, texture, count, quality, isUsable = GetQuestLogRewardInfo(i, questID)
    tinsert(rewards, {
      type = 'item',
      index = i ,
      name = name,
      texture = texture,
      count = count,
      quality = quality,
      isUsable = isUsable
    });
  end
  -- money

  local money = GetQuestLogRewardMoney(questID)
  if ( money > 0 ) then
    tinsert(rewards, {
      type = 'money',
      name = GetMoneyString(money),
      texture = "Interface\\Icons\\inv_misc_coin_01",
      count = 0,
    });
  end

  if #rewards >= 1 then
    t.rewardInfo = rewards
  end
end

local Play = function(file) if Devian and Devian.InWorkspace() then PlaySoundFile(file) end end

local tprint = B.print('Tracker')
local ignoredNames = {
  ['SelectQuestLogEntry'] = true
}
T.OnHookedFunc = function(name, ...)
  --tprint('|cFFFF8800securehook:|r', name, '|cFF00FFFFargs:|r', ...)
  local updateReason, arg1, arg2, arg3 = T[name](...)
  if updateReason then
    tprint('OnHookedFunc(|cFF00FFFF'..name..'|r):', ...)
    T:Update(updateReason, arg1, arg2, arg3)
  elseif not ignoredNames[name] then
    tprint('OnHookedFunc(|cFFFF4400'..name..'|r):', ...)
  end
end

function T:OnEvent (event, ...)
  local isHandled
  local reason, arg1, arg2, arg3
  if Event[event] then
    if type(Event[event]) == 'function' then
      Play([[Interface\Addons\SharedMedia_MyMedia\sound\Info.ogg]])
      reason, arg1, arg2, arg3 = Event[event](...)
    else
      Play([[Interface\Addons\SharedMedia_MyMedia\sound\Heart.ogg]])
      reason = Event[event]
    end
  else
    print('no event handler set for', event)
    tprint('no event handler set for', event)
    Play([[Interface\Addons\SharedMedia_MyMedia\sound\IM.ogg]])
  end
  if reason then
    local args = (reason or '0')
    if arg1 then args = args .. ', ' .. tostring(arg1) end
    if arg2 then args = args .. ', ' .. tostring(arg2) end
    if arg3 then args = args .. ', ' .. tostring(arg3) end
    print('OnEvent(|cFF00FF00'.. event ..'|r):', ..., '|cFFFFFF00=> Update (|r', args,'|cFFFFFF00)|r')
    tprint('OnEvent(|cFF00FF00'.. event ..'|r):', ..., '|cFFFFFF00=> Update (|r', args,'|cFFFFFF00)|r')
    T:Update(reason, arg1, arg2, arg3)
  else
    tprint('OnEvent(|cFFFF4400'.. event ..'|r):', ...)
    print('no detected reason')
    --Play([[Interface\Addons\SharedMedia_MyMedia\sound\Quack.ogg]])
  end

end


function T:OnInitialize()
  local c = T.Conf.Wrapper
  VeneerData = _G.VeneerData
  --VeneerData.CallLog = VeneerData.CallLog or {}
  if not T.isHooked then
    T.isHooked = true
    for _, func in ipairs(BlizzHooks) do
      if T[func] then
        hooksecurefunc(func, function(...) return T.OnHookedFunc(func, ...) end)
      else
        hooksecurefunc(func, function(...)
          print('|cFFFF0088securehook('..tostring(func)..')|r args:', ...)
          tinsert(VeneerData.CallLog, {func, ...})
        end)
      end
    end
  end



  ObjectiveTrackerFrame:UnregisterAllEvents()
  ObjectiveTrackerFrame:Hide()
  _G.MinimapCluster:Hide()

  for id, name in ipairs(T.orderedNames) do
    if not T.orderedHandlers[id] then
      Handler_Initialize(T.DefaultHandler, name, id)
    end
  end
  self:SetSize(c.Width, 40)
  T.InitializeWidgets()
end

--- Done any time the the minimize button is toggled up
function T:OnEnable()

  print(B.Conf.VeneerObjectiveWrapper.enabled)


  for event, action in pairs(Event) do
    print('|cFFFF0088listen to', event, 'for action|r', tostring(action))
    Wrapper:RegisterEvent(event)
  end

  local c = T.Conf.Wrapper
  Wrapper.previousHeight = 0
  Scroller:SetScrollChild(Scroll)
  Scroller:SetWidth(c.Width)
  Scroll:SetWidth(c.Width)
  Scroll:ClearAllPoints()
  Scroll:SetPoint('TOP', Scroller, 'TOP')
  self:SetScript('OnEvent', T.OnEvent)

  Scroller:Show()

  local from, target, to, x, y = Wrapper:GetPoint(1)
  print(from, target, to, x,y)


  T.UpdateSchema('tracker', 'default')
  -- run once to prime the data structure
  T.UpdateActionButtons()
end

function T:OnDisable()
  self:UnregisterAllEvents()
  Scroller:Hide()
end