view ObjectiveTracker/ObjectiveCore.lua @ 36:a487841050be

- attempts to solve the mystery of disappearing objectives
author Nenue
date Sun, 17 Apr 2016 13:00:31 -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