Nenue@77: -- Veneer Nenue@77: -- ObjectiveTracker.lua Nenue@77: -- Created: 8/16/2016 8:19 AM Nenue@77: -- %file-revision% Nenue@77: -- This is more or less a copy of the blizzard code with some customization stuff added Nenue@77: Nenue@77: local plugin = VeneerObjectives Nenue@77: local vn, print = LibStub("LibKraken").register(Veneer, VeneerObjectives) Nenue@77: local ot Nenue@77: local otvn Nenue@77: Nenue@77: local band, floor, min = bit.band, math.floor, math.min Nenue@77: Nenue@77: Nenue@77: local X_QUEST = 0x00001; Nenue@77: local X_QUEST_ADDED = 0x00002; Nenue@77: local X_TASK_ADDED = 0x00004; Nenue@77: local X_WORLD_QUEST_ADDED = 0x00008; Nenue@77: local X_SCENARIO = 0x00010; Nenue@77: local X_SCENARIO_NEW_STAGE = 0x00020; Nenue@77: local X_ACHIEVEMENT = 0x00040; Nenue@77: local X_ACHIEVEMENT_ADDED = 0x00080; Nenue@77: local X_SCENARIO_BONUS_DELAYED = 0x00100; Nenue@77: local X_SUPER_TRACK_CHANGED = 0x00200; Nenue@77: -- these are for the specific module ONLY! Nenue@77: local X_MODULE_QUEST = 0x00400; Nenue@77: local X_MODULE_AUTO_QUEST_POPUP = 0x00800; Nenue@77: local X_MODULE_BONUS_OBJECTIVE = 0x01000; Nenue@77: local X_MODULE_WORLD_QUEST = 0x02000; Nenue@77: local X_MODULE_SCENARIO = 0x04000; Nenue@77: local X_MODULE_ACHIEVEMENT = 0x08000; Nenue@77: local X_SCENARIO_SPELLS = 0x10000; Nenue@77: -- special updates Nenue@77: local X_STATIC = 0x0000; Nenue@77: local X_ALL = 0xFFFF; Nenue@77: Nenue@77: -- state information Nenue@77: local X_REASON = X_ALL; -- default Nenue@77: local X_MONEY = 0 Nenue@77: local X_ANIMATION = 0 Nenue@77: local X_ID = 0; Nenue@77: Nenue@77: local INSET_V = -20 Nenue@77: Nenue@77: Nenue@77: Nenue@77: local trackerWidth = 240 Nenue@77: local blockIndent = 12 Nenue@77: local blockSpacing = 3 Nenue@77: Nenue@77: local tracker = { Nenue@77: freeBlocks = {}, Nenue@77: usedBlocks = {}, Nenue@77: freeProgressBars = {}, Nenue@77: usedProgressBars = {}, Nenue@77: freeTimers = {}, Nenue@77: usedTimers = {}, Nenue@77: freeLines = {}, Nenue@77: Nenue@77: headerText = 'Default Header', Nenue@77: contentHeight = 0, Nenue@77: animationHeight = 0, Nenue@77: updateReasonModule = 0, Nenue@77: updateReasonEvents = 0, Nenue@77: } Nenue@77: local GetTrackerHandler = function(handler) Nenue@77: handler = handler or {} Nenue@77: setmetatable(handler, {__index=tracker}) Nenue@77: return handler Nenue@77: end Nenue@77: Nenue@77: local Header_OnAnimationStart = function (self, forced) Nenue@77: local header = self:GetParent() Nenue@77: header.animating = true Nenue@77: plugin:UpdateAnimation(header) Nenue@77: end Nenue@77: Nenue@77: local Header_OnAnimationFinished = function (self, forced) Nenue@77: local header = self:GetParent() Nenue@77: header.animating = nil Nenue@77: plugin:Update(X_ANIMATION) Nenue@77: end Nenue@77: Nenue@77: -- reset generic flags for layout compilation Nenue@77: function tracker:InitLayout (isStatic) Nenue@77: print('|cFF00FFFFlayout|r', self.headerText) Nenue@77: self.firstBlock = nil Nenue@77: self.lastBlock = nil Nenue@77: self.currentBlock = nil Nenue@77: self.oldContentHeight = self.contentHeight Nenue@77: self.oldAnimationHeight = self.animationHeight Nenue@77: self.numBlocks = 0 Nenue@77: for id, block in pairs(self.usedBlocks) do Nenue@77: block.used = nil Nenue@77: end Nenue@77: if not isStatic then Nenue@77: self.hasSkippedBlocks = false Nenue@77: end Nenue@77: Nenue@77: if not self.header then Nenue@77: self:SetHeader(self.headerText, X_REASON) Nenue@77: self.header:SetPoint('TOPLEFT', plugin, 'TOPLEFT', 0, -plugin.contentHeight) Nenue@77: plugin.contentHeight = plugin.contentHeight + self.header:GetHeight() Nenue@77: end Nenue@77: Nenue@77: Nenue@77: self.contentHeight = 0 Nenue@77: Nenue@77: end Nenue@77: Nenue@77: -- clear out generic flags and set aside block frames for re-use Nenue@77: tracker.EndLayout = function(self) Nenue@77: self.lastBlock = self.currentBlock Nenue@77: for id, block in pairs(self.usedBlocks) do Nenue@77: if not block.used then Nenue@77: self:FreeBlock(block) Nenue@77: end Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: function tracker:SetHeader (text, animateReason) Nenue@77: if self.header then Nenue@77: return self.header Nenue@77: end Nenue@77: Nenue@77: local block = CreateFrame('Button', nil, plugin, 'VeneerObjectiveHeader') Nenue@77: block.handler = self Nenue@77: block.text:SetText(self.headerText) Nenue@77: Nenue@77: self.currentBlock = block Nenue@77: self.header = block Nenue@77: return block Nenue@77: end Nenue@77: Nenue@77: function tracker:FreeBlock(block) Nenue@77: self.usedBlocks[block.id] = nil Nenue@77: print('|cFF0088FFfree|r', block:GetName()) Nenue@77: tinsert(self.freeBlocks, block) Nenue@77: Nenue@77: block.numLines = 0 Nenue@77: block:SetHeight(0) Nenue@77: block:Hide() Nenue@77: end Nenue@77: Nenue@77: -- should only be used in an N-lastN loop Nenue@77: function tracker:FreeLine(line) Nenue@77: if line.block then Nenue@77: line.block.lines[line.index] = nil Nenue@77: end Nenue@77: print('|cFF0088FFfree|r', line:GetName()) Nenue@77: local freeLines = line.type or self.freeLines Nenue@77: tinsert(freeLines, line) Nenue@77: end Nenue@77: Nenue@77: local blocksn = 0 Nenue@77: tracker.GetBlock = function(self, id) Nenue@77: local block = self.usedBlocks[id] Nenue@77: if not block then Nenue@77: local numFree = #self.freeBlocks Nenue@77: if numFree >= 1 then Nenue@77: block = tremove(self.freeBlocks, numFree) Nenue@77: else Nenue@77: blocksn = blocksn + 1 Nenue@77: block = CreateFrame('Button', 'OTVNBlock'..blocksn, plugin, 'VeneerObjectiveBlock') Nenue@77: block.lines = {} Nenue@77: end Nenue@77: self.usedBlocks[id] = block Nenue@77: block.id = id Nenue@77: block.handler = self Nenue@77: end Nenue@77: Nenue@77: block.used = true Nenue@77: block.currentLine = nil Nenue@77: block.numLines = 0 Nenue@77: Nenue@77: if block.lines then Nenue@77: for i, line in ipairs(block.lines) do Nenue@77: line.used = nil Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: block.contentHeight = blockSpacing Nenue@77: block:SetWidth(trackerWidth) Nenue@77: block:SetHeight(0) Nenue@77: Nenue@77: return block Nenue@77: end Nenue@77: Nenue@77: -- obtain line Nenue@77: local linesn = 0 Nenue@77: function tracker:GetLine(block, index, lineType) Nenue@77: local line = block.lines[index] Nenue@77: if line and line.lineType ~= lineType then Nenue@77: Nenue@77: tinsert(self.freeLines, line) Nenue@77: line = nil Nenue@77: end Nenue@77: Nenue@77: if not line then Nenue@77: local freeLines = (lineType and lineType.freeLines) or self.freeLines Nenue@77: local numFreeLines = #freeLines Nenue@77: if numFreeLines >= 1 then Nenue@77: line = tremove(freeLines, numFreeLines) Nenue@77: else Nenue@77: linesn = linesn + 1 Nenue@77: line = CreateFrame('Frame', 'OTVNLine'.. linesn, block, (lineType and lineType.template) or 'VeneerObjectiveLine') Nenue@77: end Nenue@77: line:SetParent(block) Nenue@77: end Nenue@77: Nenue@77: line.type = lineType Nenue@77: line.index = index Nenue@77: line.used = true Nenue@77: line.block = block Nenue@77: line.text:SetPoint('TOPLEFT', line, 'TOPLEFT', blockIndent, 0) Nenue@77: line:SetHeight(0) -- in order for GetStringHeight to be useful Nenue@77: Nenue@77: block.lines[index] = line Nenue@77: Nenue@77: return line Nenue@77: end Nenue@77: Nenue@77: function tracker:AddProgressBar(block, line, questID) Nenue@77: end Nenue@77: Nenue@77: function tracker:FreeProgressBar(block, line) Nenue@77: end Nenue@77: Nenue@77: function tracker:AddTimerBar(block, line, duration, startTime) Nenue@77: end Nenue@77: Nenue@77: function tracker:FreeTimerBar(block, line) Nenue@77: end Nenue@77: Nenue@77: -- Checks for space and anchors or frees the block accordingly Nenue@77: function tracker:AddBlock (block, force) Nenue@77: local anchor = self.currentBlock or self.header Nenue@77: self.numBlocks = self.numBlocks + 1 Nenue@77: Nenue@77: if block.header then Nenue@77: print('header!', floor(block.contentHeight), '+', floor(block.header:GetStringHeight())) Nenue@77: block.contentHeight = block.contentHeight + block.header:GetStringHeight() Nenue@77: self.used = true Nenue@77: end Nenue@77: Nenue@77: Nenue@77: if self.currentBlock then Nenue@77: self.currentBlock.nextBlock = block Nenue@77: block.prevBlock = self.currentBlock Nenue@77: else Nenue@77: self.firstBlock = block Nenue@77: end Nenue@77: Nenue@77: self.contentHeight = self.contentHeight + block.contentHeight Nenue@77: self.currentBlock = block Nenue@77: Nenue@77: if not plugin.currentBlock then Nenue@77: plugin.firstBlock = block Nenue@77: end Nenue@77: plugin.currentBlock = block Nenue@77: Nenue@77: Nenue@77: Nenue@77: block:SetHeight(block.contentHeight) Nenue@77: print('block|cFF88FF00', self.numBlocks, block, '|rto', anchor, 'size', block.contentHeight) Nenue@77: block:ClearAllPoints() Nenue@77: block:SetPoint('TOPLEFT', anchor, 'BOTTOMLEFT', 0, -blockSpacing) Nenue@77: block:Show() Nenue@77: Nenue@77: -- free unused lines Nenue@77: local numLines = #block.lines Nenue@77: for i = 1, numLines do Nenue@77: local line = block.lines[i] Nenue@77: if not line.used or block.collapsed then Nenue@77: print('|cFFFF4400!|r') Nenue@77: self:FreeLine(line) Nenue@77: else Nenue@77: if not line:IsShown() then Nenue@77: line:Show() Nenue@77: end Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: return true Nenue@77: end Nenue@77: Nenue@77: function tracker:SetLine(block, index, lineType, textOrFunc) Nenue@77: local anchor = block.currentLine or block.header Nenue@77: local line = self:GetLine(block, index, lineType) Nenue@77: Nenue@77: if line.ticker then Nenue@77: line.ticker:Cancel() Nenue@77: line.ticker = nil Nenue@77: end Nenue@77: Nenue@77: local text = textOrFunc Nenue@77: if type(textOrFunc) == 'function' then Nenue@77: text = textOrFunc() Nenue@77: Nenue@77: line.ticker = C_Timer.NewTicker(10, function() Nenue@77: line.height = tracker:SetLineText(line.text, textOrTextFunc()) Nenue@77: line:SetHeight(line.height) Nenue@77: end) Nenue@77: end Nenue@77: line.height = tracker:SetLineText(line.text, text) Nenue@77: line:SetHeight(line.height) Nenue@77: Nenue@77: print('line|cFFFFFF00', line:GetName(), '|rto', anchor:GetName(), 'size', line.height) Nenue@77: line:SetPoint('TOPLEFT', anchor, 'BOTTOMLEFT', 0, 0) Nenue@77: Nenue@77: block.contentHeight = block.contentHeight + line.height Nenue@77: block.numLines = block.numLines + 1 Nenue@77: Nenue@77: if block.currentLine then Nenue@77: line.prevLine = block.currentLine Nenue@77: block.currentLine.nextLine = line Nenue@77: else Nenue@77: block.firstLine = line Nenue@77: end Nenue@77: Nenue@77: block.currentLine = line Nenue@77: plugin.currentLine = line Nenue@77: Nenue@77: end Nenue@77: Nenue@77: function tracker:SetLineText(fontString, text) Nenue@77: fontString:SetText(text) Nenue@77: return fontString:GetStringHeight() Nenue@77: end Nenue@77: Nenue@77: -- Update lite Nenue@77: function tracker:ResetAnchors () Nenue@77: self:InitLayout() Nenue@77: Nenue@77: self:EndLayout() Nenue@77: end Nenue@77: Nenue@77: Nenue@77: local UpdateQuestTracker = function(watchIndex, logIndex) Nenue@77: return true Nenue@77: end Nenue@77: Nenue@77: -- top-down updaters Nenue@77: Nenue@77: local questTracker = GetTrackerHandler({ Nenue@77: headerText = 'Quests', Nenue@77: updateReasonModule = X_MODULE_QUEST, Nenue@77: updateReasonEvents = (X_QUEST + X_QUEST_ADDED), Nenue@77: }) Nenue@77: function questTracker:Update () Nenue@77: self:InitLayout() Nenue@77: for watchIndex = 1, GetNumQuestWatches() do Nenue@77: local questID, title, questLogIndex, numObjectives, requiredMoney, isComplete, startEvent, isAutoComplete, failureTime, timeElapsed, questType, isTask, isBounty, isStory, isOnMap, hasLocalPOI = GetQuestWatchInfo(watchIndex) Nenue@77: if not questID then Nenue@77: -- stop parsing; end of data or variables haven't completely loaded Nenue@77: break Nenue@77: end Nenue@77: local showQuest = true Nenue@77: if isTask or (isBounty and not IsQuestComplete(questID)) then Nenue@77: -- do nothing Nenue@77: else Nenue@77: -- obtain a block Nenue@77: local block = self:GetBlock(questID) Nenue@77: Nenue@77: block.header:SetText(title) Nenue@77: Nenue@77: for i = 1, numObjectives do Nenue@77: local text, objectiveType, finished = GetQuestLogLeaderBoard(i, questLogIndex) Nenue@77: if text then Nenue@77: self:SetLine(block, i, nil, text) Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: block:SetScript('OnClick', self.OnClick) Nenue@77: block:RegisterForClicks('AnyDown') Nenue@77: Nenue@77: self:AddBlock(block) Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: self:EndLayout() Nenue@77: end Nenue@77: questTracker.OnClick = function(block, button) Nenue@77: if button == 'RightButton' then Nenue@77: ObjectiveTracker_ToggleDropDown(block, QuestObjectiveTracker_OnOpenDropDown) Nenue@77: else Nenue@77: CloseDropDownMenus() Nenue@77: Nenue@77: local questLogIndex = GetQuestLogIndexByID(block.id) Nenue@77: if ( IsModifiedClick("QUESTWATCHTOGGLE") ) then Nenue@77: QuestObjectiveTracker_UntrackQuest(nil, block.id); Nenue@77: else Nenue@77: if ( IsQuestComplete(block.id) and GetQuestLogIsAutoComplete(questLogIndex) ) then Nenue@77: AutoQuestPopupTracker_RemovePopUp(block.id); Nenue@77: ShowQuestComplete(questLogIndex); Nenue@77: else Nenue@77: QuestLogPopupDetailFrame_Show(questLogIndex); Nenue@77: end Nenue@77: end Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: local UpdateAchievementTracker = function() end Nenue@77: local UpdateTaskTracker = function() end Nenue@77: local UpdateScenarioTracker = function() end Nenue@77: Nenue@77: function plugin:Update(reason, id) Nenue@77: Nenue@77: print('update', reason, #plugin.trackers) Nenue@77: Nenue@77: if plugin.minimized then Nenue@77: for i, tracker in ipairs(plugin.trackers) do Nenue@77: tracker:Hide() Nenue@77: end Nenue@77: return Nenue@77: end Nenue@77: Nenue@77: Nenue@77: local X_REASON = reason or X_ALL Nenue@77: local X_ID = id Nenue@77: Nenue@77: plugin.maxHeight = plugin:GetHeight() Nenue@77: plugin.oldContentsHeight = plugin.contentHeight Nenue@77: plugin.contentHeight = 0 Nenue@77: plugin.currentBlock = nil Nenue@77: plugin.firstBlock = nil Nenue@77: plugin:SetWidth(trackerWidth) Nenue@77: Nenue@77: -- pad for header Nenue@77: plugin.contentHeight = plugin.header:GetHeight() Nenue@77: Nenue@77: for i, header in ipairs(plugin.trackers) do Nenue@77: header.used = nil Nenue@77: end Nenue@77: Nenue@77: Nenue@77: local addedSpace = false Nenue@77: for i, tracker in ipairs(plugin.trackers) do Nenue@77: if ( band(X_REASON, tracker.updateReasonModule + tracker.updateReasonEvents ) > 0 ) then Nenue@77: print('->', tracker.headerText) Nenue@77: tracker:Update() Nenue@77: if tracker.oldContentHeight - tracker.contentHeight >= 1 then Nenue@77: addedSpace = true Nenue@77: end Nenue@77: else Nenue@77: -- if we can fit another header or an animation handler popped, contents need to be generated Nenue@77: if addedSpace or (tracker.header and tracker.header.animating) then Nenue@77: print('->', tracker.headerText, 'added space') Nenue@77: tracker:Update() Nenue@77: else Nenue@77: print('->', tracker.headerText, 'lite update') Nenue@77: -- otherwise, measure contents and hide anything that won't fit Nenue@77: tracker:ResetAnchors() Nenue@77: end Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: for i, tracker in ipairs(plugin.trackers) do Nenue@77: if tracker.currentBlock then Nenue@77: if not tracker.header:IsShown() then Nenue@77: tracker.header:Show() Nenue@77: end Nenue@77: else Nenue@77: if tracker.header:IsShown() then Nenue@77: tracker.header:Hide() Nenue@77: end Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: if VeneerBuffFrame and VeneerBuffFrame.lastBuff then Nenue@77: plugin:ClearAllPoints() Nenue@77: plugin:SetPoint('TOP', VeneerBuffFrame.lastBuff, 'BOTTOM', 0, -4) Nenue@77: plugin:SetPoint('RIGHT', UIParent, 'RIGHT', -6, 0) Nenue@77: else Nenue@77: plugin:ClearAllPoints() Nenue@77: plugin:SetPoint('TOPRIGHT', UIParent, 'TOPRIGHT', -6, -120) Nenue@77: end Nenue@77: Nenue@77: plugin:Show() Nenue@77: end Nenue@77: Nenue@77: plugin.init = function() Nenue@77: Nenue@77: plugin:RegisterEvent("QUEST_LOG_UPDATE") Nenue@77: plugin:RegisterEvent("TRACKED_ACHIEVEMENT_LIST_CHANGED") Nenue@77: plugin:RegisterEvent("QUEST_WATCH_LIST_CHANGED") Nenue@77: plugin:RegisterEvent("QUEST_AUTOCOMPLETE") Nenue@77: plugin:RegisterEvent("QUEST_ACCEPTED") Nenue@77: plugin:RegisterEvent("SUPER_TRACKED_QUEST_CHANGED") Nenue@77: plugin:RegisterEvent("SCENARIO_UPDATE") Nenue@77: plugin:RegisterEvent("SCENARIO_CRITERIA_UPDATE") Nenue@77: plugin:RegisterEvent("SCENARIO_SPELL_UPDATE") Nenue@77: plugin:RegisterEvent("TRACKED_ACHIEVEMENT_UPDATE") Nenue@77: plugin:RegisterEvent("ZONE_CHANGED_NEW_AREA"); Nenue@77: plugin:RegisterEvent("ZONE_CHANGED"); Nenue@77: plugin:RegisterEvent("QUEST_POI_UPDATE"); Nenue@77: plugin:RegisterEvent("VARIABLES_LOADED"); Nenue@77: plugin:RegisterEvent("QUEST_TURNED_IN"); Nenue@77: plugin:RegisterEvent("PLAYER_MONEY"); Nenue@77: Nenue@77: plugin.trackers = { Nenue@77: questTracker, Nenue@77: } Nenue@77: print('bub') Nenue@77: end Nenue@77: function plugin:event(event, ...) Nenue@77: print('|cFFFF0088' .. event, ...) Nenue@77: if event == 'QUEST_WATCH_LIST_CHANGED' then Nenue@77: local questID, added = ... Nenue@77: if added then Nenue@77: if not IsQuestBounty(questID) or IsQuestComplete(questId) then Nenue@77: print(questID, added) Nenue@77: plugin:Update(X_QUEST_ADDED, questID) Nenue@77: end Nenue@77: else Nenue@77: print() Nenue@77: plugin:Update(X_QUEST) Nenue@77: end Nenue@77: elseif event == 'QUEST_LOG_UPDATE' then Nenue@77: plugin:Update(X_MODULE_QUEST) Nenue@77: elseif event == 'QUEST_POI_UPDATE' then Nenue@77: Nenue@77: if GetCVar("trackQuestSorting") == "proximity" then Nenue@77: -- todo: sort blocks Nenue@77: end Nenue@77: plugin:Update(X_MODULE_QUEST) Nenue@77: elseif event == 'PLAYER_MONEY' then Nenue@77: plugin:Update(X_MONEY) Nenue@77: end Nenue@77: Nenue@77: end Nenue@77: Nenue@77: -- adjusts money events bitcode when a tracker is displaying money data Nenue@77: function plugin:UpdateMoneyFlag(watchMoney, reason) Nenue@77: if watchMoney then Nenue@77: if (band(X_MONEY, reason) == 0) then Nenue@77: X_MONEY = X_MONEY - reason Nenue@77: end Nenue@77: else Nenue@77: if (band(X_MONEY, reason) > 0) then Nenue@77: X_MONEY = X_MONEY + reason Nenue@77: end Nenue@77: end Nenue@77: end Nenue@77: Nenue@77: -- used by framescripts to poke the updater when an animation has ended Nenue@77: function plugin:UpdateAnimation(block) Nenue@77: Nenue@77: local reason = block.handler.updateReasonEvents Nenue@77: if block.animating then Nenue@77: if (band(X_ANIMATION, reason) == 0) then Nenue@77: X_ANIMATION = X_ANIMATION - reason Nenue@77: end Nenue@77: else Nenue@77: if (band(X_ANIMATION, reason) > 0) then Nenue@77: X_ANIMATION = X_ANIMATION + reason Nenue@77: end Nenue@77: end Nenue@77: end