view ObjectiveTracker/ObjectiveCore.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 4b3da1b221de
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 AddQuestWatch, SetSuperTrackedQuestID, GetNumQuestWatches, AUTO_QUEST_WATCH, MAX_WATCHABLE_QUESTS = AddQuestWatch, SetSuperTrackedQuestID, GetNumQuestWatches, AUTO_QUEST_WATCH, MAX_WATCHABLE_QUESTS
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

--- Performance values
--[[
  self:RegisterEvent("QUEST_LOG_UPDATE");
	self:RegisterEvent("TRACKED_ACHIEVEMENT_LIST_CHANGED");
	self:RegisterEvent("QUEST_WATCH_LIST_CHANGED");
	self:RegisterEvent("QUEST_AUTOCOMPLETE");
	self:RegisterEvent("QUEST_ACCEPTED");
	self:RegisterEvent("SUPER_TRACKED_QUEST_CHANGED");
	self:RegisterEvent("SCENARIO_UPDATE");
	self:RegisterEvent("SCENARIO_CRITERIA_UPDATE");
	self:RegisterEvent("TRACKED_ACHIEVEMENT_UPDATE");
	self:RegisterEvent("ZONE_CHANGED_NEW_AREA");
	self:RegisterEvent("ZONE_CHANGED");
	self:RegisterEvent("QUEST_POI_UPDATE");
	self:RegisterEvent("VARIABLES_LOADED");
	self:RegisterEvent("QUEST_TURNED_IN");
	self:RegisterEvent("PLAYER_MONEY");
 ]]


--- 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,
  default = {
    titlebg = {'HORIZONTAL', 1, 0, .7, .25,    1, 0, .7, .125},
    textbg = {'HORIZONTAL', 0, 0, 0, 0.4,      0, 0, 0, 0 },
    selectionbg = {'HORIZONTAL', 1, 1, 1, 0, 1, 1, 1, 0.225},
  },
  daily = {
    titlebg = {'HORIZONTAL', 0, .7, 1, .25,  0, 1, .7, .125},
    textbg = {'HORIZONTAL', 0, .7, 1, .1,    0, 1, .7, .075 },
  },
  weekly = {
    titlebg = {'HORIZONTAL', 0, .35, .7, .25,   0, .35, .7, .125},
    textbg = {'HORIZONTAL', 0, .35, .7, .1,     0, .35, .7, .075 },
  },
  account = {
    titlebg = {'HORIZONTAL', .1, .1, .1, .25, .1, .1, .1, .125},
    textbg = {'HORIZONTAL', .1, .1, .1, 0.4, .1, .1, .1, .085 },
  },
  -- alliance
  faction_1 = {
    titlebg = {'HORIZONTAL', .2, .4, 1, 0.4, .2, .4, 1, .085 },
    textbg = {'HORIZONTAL', .2, .4, 1, 0.4, .2, .4, 1, .085 },
  },
  -- horde
  faction_2 = {
    titlebg = {'HORIZONTAL', .6, 0, 0.4, 0.4,  .6, 0, 0.4, .085 },
    textbg = {'HORIZONTAL', .6, 0, 0.4, 0.4,   .6, 0, 0.4, .085 },
  }
}

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,
  TitleSpacing = 3,
}



--- 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

  freeBlocks = {},  --- block heap
  usedBlocks = {},

  Info = {},        -- find data by ID
  BlockInfo = {},   -- find data by block ID
  Watched = {},     -- find watchIndex by data ID
  WatchInfo = {},   -- find data by watch index
  WatchBlock = {},  -- find block by watch index
}

T.AutoQuest = {
  name = "AutoQuest",
  displayName = "Notice",
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST +
      OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED,
}
T.Quest = {
  name = "Quest",
  displayName = "Quests",
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST +
      OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED,
  itemButtons = {},
  freeButtons = {},
}
T.Cheevs = {
  name = "Cheevs",
  displayName = "Achievements",
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_ACHIEVEMENT,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT +
      OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED,
}
T.Bonus = {
  name = "Bonus",
  displayName = "Bonus Objectives",
  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE,
  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST + OBJECTIVE_TRACKER_UPDATE_TASK_ADDED
}

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)
  handler.frame = frame
  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')

  T.orderedHandlers[index] = handler
  return true
end

local Event = {}
Event.QUEST_LOG_UPDATE =  function()
  return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST + OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE
end
Event.QUEST_ACCEPTED = function(questLogIndex, questID)
  if ( IsQuestTask(questID) ) then
    return OBJECTIVE_TRACKER_UPDATE_TASK_ADDED, questID
  else
    if ( AUTO_QUEST_WATCH == "1" and GetNumQuestWatches() < MAX_WATCHABLE_QUESTS ) then
      AddQuestWatch(questLogIndex);
      SetSuperTrackedQuestID(questID);
    end
    return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST
  end
end

Event.QUEST_WATCH_LIST_CHANGED = function(questID, added)
  if ( added ) then
    if ( not IsQuestTask(questID) ) then
      return OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED, questID, added
    end
  else
    return OBJECTIVE_TRACKER_UPDATE_QUEST, questID, added
  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()
  return OBJECTIVE_TRACKER_UPDATE_QUEST
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
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)
  if ( added ) then
    return OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED, achievementID
  else
    return OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT
  end
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

--- Done once per ui load
local BlizzHooks = {
  ['AddQuestWatch'] = 'AddQuestWatch',
  ['RemoveQuestWatch'] = 'RemoveQuestWatch',
  ['AbandonQuest'] = 'AbandonQuest',
  ['AcknowledgeAutoAcceptQuest'] = 'AcknowledgeAutoAcceptQuest',
  ['AddAutoQuestPopUp'] = 'AddAutoQuestPopUp',
  ['RemoveAutoQuestPopUp'] = 'RemoveAutoQuestPopUp',
  ['AddTrackedAchievement'] = 'AddTrackedAchievement',
  ['RemoveTrackedAchievement'] = 'RemoveTrackedAchievement',
  ['SetSuperTrackedQuestID'] = 'SetSuperTrackedQuestID'
}
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
T.animateReasons = 0
T.SetAnimate = function(reason)
  print('comparing', T.animateReasons, reason)
  if animate then
    if band(T.animateReasons, reason) == 0 then
      T.animateReasons = T.animateReasons + reason
    end
  else
    if band(T.animateReasons, reason) > 0 then
      T.animateReasons = T.animateReasons - reason
    end
  end
end

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

function T:OnEvent (event, ...)
  local isHandled
  print('OnEvent(|cFF00FF00'.. event ..'|r):', ...)
  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](...)
    elseif type(Event[event]) == 'table' then
      Play([[Interface\Addons\SharedMedia_MyMedia\sound\Link.ogg]])
      for i, action in ipairs(Event[event]) do
        if type(action) == 'function' then
          reason, arg1, arg2, arg3 = action(event, ...)
        else
          reason = action
        end

        if reason then
          T:Update(reason, arg1, arg2, arg3)
        end
      end
    else
      Play([[Interface\Addons\SharedMedia_MyMedia\sound\Heart.ogg]])
      reason = Event[event]
    end
  else
    Play([[Interface\Addons\SharedMedia_MyMedia\sound\Quack.ogg]])
  end
  if reason then
    T:Update(reason, arg1, arg2, arg3)
  else
    print('no reason value returned')
    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 blizzFunc, veneerFunc in pairs(BlizzHooks) do
      if T[veneerFunc] then
        hooksecurefunc(blizzFunc, T[veneerFunc])
      else
        hooksecurefunc(blizzFunc, function(...)
          print('|cFFFF0088securehook('..tostring(blizzFunc)..')|r args:', ...)
          tinsert(VeneerData.CallLog, {blizzFunc, ...})
        end)
      end
    end
  end

  T.Conf.TasksLog = T.Conf.TasksLog or {}

  ObjectiveTrackerFrame:UnregisterAllEvents()
  ObjectiveTrackerFrame: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)
  if not B.Conf.VeneerObjectiveWrapper.enabled then
    return
  end

  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

  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:GetName(), to, x,y)

  T:Update()

  -- run once to prime the data structure
  T.UpdateActionButtons()
end

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