annotate 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
rev   line source
Nenue@0 1 --- ${PACKAGE_NAME}
Nenue@0 2 -- @file-author@
Nenue@0 3 -- @project-revision@ @project-hash@
Nenue@0 4 -- @file-revision@ @file-hash@
Nenue@0 5 -- Created: 3/29/2016 7:07 PM
Nenue@0 6 local B = select(2,...).frame
Nenue@0 7 local mod = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
Nenue@0 8 local print = B.print('Objectives')
Nenue@0 9 local Tracker, AutoQuest, Quest, Cheevs = mod.Tracker, mod.AutoQuest, mod.Quest, mod.Cheevs
Nenue@6 10 local itemButtonSize, itemButtonSpacing = 36, 1
Nenue@7 11 local tremove, tremovebyval = table.remove, table.removebyval
Nenue@0 12
Nenue@0 13 --------------------------------------------------------------------
Nenue@3 14 --- Tracker-specific widgets and their handlers
Nenue@0 15 --------------------------------------------------------------------
Nenue@0 16
Nenue@0 17 Tracker.Select = function(self) end
Nenue@0 18 Tracker.Open = function(self) end
Nenue@0 19 Tracker.Remove = function(self) end
Nenue@0 20 Tracker.Report = function(self)
Nenue@0 21 print('Stats:', self.numWatched,'items tracked,', self.numBlocks,'blocks assigned.')
Nenue@0 22 end
Nenue@0 23
Nenue@0 24 Tracker.OnMouseUp = function(self, button)
Nenue@1 25 if self.initialButton == 'LeftButton' then
Nenue@7 26 if self.modChatLink and ChatEdit_GetActiveWindow() then
Nenue@7 27 self:Link()
Nenue@7 28 elseif self.modQuestWatch then
Nenue@1 29 self:Remove()
Nenue@7 30 else
Nenue@7 31 self:Select()
Nenue@1 32 end
Nenue@0 33 elseif button == 'RightButton' then
Nenue@0 34 self:Open()
Nenue@0 35 end
Nenue@1 36 self.initialButton = nil
Nenue@7 37 self.modChatLink = nil
Nenue@7 38 self.modQuestWatch = nil
Nenue@8 39 print(IsModifiedClick("CHATLINK"), IsModifiedClick("QUESTWATCHTOGGLE"))
Nenue@8 40 print('|cFFFF8800'..tostring(self:GetName())..':MouseUp()|r')
Nenue@0 41 end
Nenue@0 42
Nenue@0 43 Tracker.OnMouseDown = function(self, button)
Nenue@1 44 self.initialButton = button
Nenue@7 45 self.modChatLink = IsModifiedClick("CHATLINK")
Nenue@7 46 self.modQuestWatch = IsModifiedClick("QUESTWATCHTOGGLE")
Nenue@7 47 self:SetStyle('Active')
Nenue@8 48 print(IsModifiedClick("CHATLINK"), IsModifiedClick("QUESTWATCHTOGGLE"))
Nenue@0 49 print(self.info.title)
Nenue@0 50 end
Nenue@0 51
Nenue@0 52 -----------------------------
Nenue@0 53 --- AUTO_QUEST
Nenue@0 54 AutoQuest.name = "Remote Quests"
Nenue@0 55 AutoQuest.GetNumWatched = GetNumAutoQuestPopUps
Nenue@0 56
Nenue@0 57 -----------------------------
Nenue@0 58 --- QUEST
Nenue@0 59 Quest.name = "Quests"
Nenue@0 60 Quest.Select = function(self)
Nenue@0 61 SetSuperTrackedQuestID(self.info.questID)
Nenue@7 62 mod.UpdateWrapper()
Nenue@7 63 end
Nenue@7 64 Quest.Link = function(self)
Nenue@7 65 local questLink = GetQuestLink(block.questLogIndex);
Nenue@7 66 if ( questLink ) then
Nenue@7 67 ChatEdit_InsertLink(questLink);
Nenue@7 68 end
Nenue@0 69 end
Nenue@0 70 Quest.Open = function(self)
Nenue@1 71 QuestMapFrame_OpenToQuestDetails(self.info.questID)
Nenue@1 72 end
Nenue@1 73
Nenue@1 74 Quest.Remove = function(self)
Nenue@5 75 print('removing', self.info.questLogIndex, 'from watcher')
Nenue@5 76 RemoveQuestWatch(self.info.questLogIndex)
Nenue@0 77 end
Nenue@0 78
Nenue@0 79
Nenue@0 80 -----------------------------
Nenue@0 81 --- CHEEVS
Nenue@0 82 Cheevs.Select = function(self)
Nenue@0 83 end
Nenue@8 84 Cheevs.Remove = function(self)
Nenue@8 85 RemoveTrackedAchievement(self.info.cheevID)
Nenue@8 86 end
Nenue@8 87 Cheevs.OnMouseUp = function(self)
Nenue@8 88 Tracker.OnMouseUp(self)
Nenue@8 89 self:SetStyle('CheevNormal')
Nenue@8 90 end
Nenue@7 91 Cheevs.Link = function(self)
Nenue@8 92 local achievementLink = GetAchievementLink(self.info.cheevID);
Nenue@8 93 if ( achievementLink ) then
Nenue@8 94 ChatEdit_InsertLink(achievementLink);
Nenue@8 95 end
Nenue@7 96 end
Nenue@0 97
Nenue@0 98 Cheevs.Open = function(self)
Nenue@3 99 if ( not AchievementFrame ) then
Nenue@3 100 AchievementFrame_LoadUI();
Nenue@3 101 end
Nenue@3 102 if ( not AchievementFrame:IsShown() ) then
Nenue@3 103 AchievementFrame_ToggleAchievementFrame();
Nenue@3 104 end
Nenue@3 105 AchievementFrame_SelectAchievement(self.info.cheevID);
Nenue@0 106 end
Nenue@0 107
Nenue@1 108 ----------------------------------------------------------------------------------------
Nenue@1 109 --- frame template and scripts lifted from "QuestKing 2" by Barjack
Nenue@1 110 --- url: http://mods.curse.com/addons/wow/questking
Nenue@1 111 ----------------------------------------------------------------------------------------
Nenue@1 112 local usedButtons = mod.Quest.itemButtons
Nenue@1 113 local freeButtons = mod.Quest.freeButtons
Nenue@1 114 mod.SetItemButton = function(block, info)
Nenue@1 115 local itemInfo = info.specialItem
Nenue@1 116 if not itemInfo then
Nenue@1 117 return
Nenue@1 118 end
Nenue@1 119 --- .specialItem :: {link = link, charges = charges, icon = icon, start = start, duration = duration, enable = enable}
Nenue@1 120
Nenue@1 121
Nenue@1 122 local itemButton
Nenue@1 123 if not info.itemButton then
Nenue@1 124 if #freeButtons >= 1 then
Nenue@2 125 print(' |cFF00FFFFfound a free button')
Nenue@1 126 itemButton = freeButtons[#freeButtons]
Nenue@1 127 freeButtons[#freeButtons] = nil
Nenue@2 128 if itemButton.block then
Nenue@2 129 itemButton.block.itemButton = nil
Nenue@2 130 itemButton.block = nil
Nenue@2 131 end
Nenue@1 132 else
Nenue@1 133 local buttonIndex = mod.Quest.numButtons + #freeButtons + 1
Nenue@1 134 itemButton = CreateFrame('Button', 'VeneerQuestItemButton' .. buttonIndex, UIParent, 'VeneerItemButtonTemplate')
Nenue@1 135 itemButton.buttonIndex = buttonIndex
Nenue@6 136 itemButton:SetSize(itemButtonSize, itemButtonSize)
Nenue@6 137 itemButton:GetNormalTexture():SetSize(itemButtonSize * (5/3), itemButtonSize * (5/3))
Nenue@5 138 print(' |cFFFF4400starting new button', itemButton:GetName())
Nenue@1 139 end
Nenue@1 140 mod.Quest.numButtons = mod.Quest.numButtons + 1
Nenue@1 141 else
Nenue@1 142 itemButton = info.itemButton
Nenue@5 143 print(' |cFF00FF00found assigned button', itemButton:GetName())
Nenue@2 144
Nenue@1 145 end
Nenue@1 146 -- set values
Nenue@2 147
Nenue@5 148 info.itemButton = itemButton
Nenue@5 149 usedButtons[info.questID] = itemButton
Nenue@5 150 print(' |cFF8800FFassigning|r', itemButton:GetName(), 'to quest|cFF00FF00', info.questID, '|rat|cFFFFFF00', block:GetName(),'|r')
Nenue@2 151
Nenue@5 152 for k,v in pairs(usedButtons) do
Nenue@5 153 print('|cFFFF44DD'..k..'|r', v:GetName())
Nenue@5 154 end
Nenue@1 155
Nenue@1 156 itemButton:SetAttribute("type", "item")
Nenue@1 157 itemButton:SetAttribute("item", itemInfo.link)
Nenue@1 158
Nenue@2 159 itemButton.questID = info.questID
Nenue@1 160 itemButton.questLogIndex = info.questLogIndex
Nenue@1 161 itemButton.charges = itemInfo.charges
Nenue@1 162 itemButton.rangeTimer = -1
Nenue@1 163 itemButton.block = block
Nenue@1 164
Nenue@1 165 SetItemButtonTexture(itemButton, itemInfo.icon)
Nenue@1 166 SetItemButtonCount(itemButton, itemInfo.charges)
Nenue@1 167 Veneer_QuestObjectiveItem_UpdateCooldown(itemButton);
Nenue@1 168
Nenue@1 169 return itemButton
Nenue@1 170 end
Nenue@1 171 --- Clear an itemButton from the given block
Nenue@2 172 mod.FreeItemButtons = function(block)
Nenue@2 173
Nenue@2 174 if block.itemButton then
Nenue@2 175 local itemButton = block.itemButton
Nenue@5 176 if itemButton.questID ~= block.info.questID then
Nenue@5 177 block.itemButton = nil
Nenue@5 178 itemButton.block = mod.Quest.InfoBlock[itemButton.questID]
Nenue@5 179 else
Nenue@5 180 itemButton.block = nil
Nenue@5 181 itemButton:Hide()
Nenue@2 182
Nenue@5 183 usedButtons[itemButton.questID] = nil
Nenue@5 184 freeButtons[#freeButtons + 1] = itemButton
Nenue@5 185 mod.Quest.numButtons = mod.Quest.numButtons - 1
Nenue@5 186 print('|cFFFF0088released', itemButton:GetName(),'and', block:GetName())
Nenue@5 187 end
Nenue@1 188 end
Nenue@1 189 end
Nenue@1 190
Nenue@1 191 function Veneer_QuestObjectiveItem_OnUpdate (self, elapsed)
Nenue@1 192 -- Handle range indicator
Nenue@1 193 local rangeTimer = self.rangeTimer
Nenue@1 194 if (rangeTimer) then
Nenue@1 195 rangeTimer = rangeTimer - elapsed
Nenue@1 196 if (rangeTimer <= 0) then
Nenue@1 197 local link, item, charges, showItemWhenComplete = GetQuestLogSpecialItemInfo(self.questLogIndex)
Nenue@1 198 if ((not charges) or (charges ~= self.charges)) then
Nenue@2 199 mod.UpdateWrapper()
Nenue@1 200 return
Nenue@1 201 end
Nenue@1 202
Nenue@1 203 local count = self.HotKey
Nenue@1 204 local valid = IsQuestLogSpecialItemInRange(self.questLogIndex)
Nenue@1 205 if (valid == 0) then
Nenue@1 206 count:Show()
Nenue@1 207 count:SetVertexColor(1.0, 0.1, 0.1)
Nenue@1 208 elseif (valid == 1) then
Nenue@1 209 count:Show()
Nenue@1 210 count:SetVertexColor(0.6, 0.6, 0.6)
Nenue@1 211 else
Nenue@1 212 count:Hide()
Nenue@1 213 end
Nenue@1 214 rangeTimer = TOOLTIP_UPDATE_TIME
Nenue@1 215 end
Nenue@1 216
Nenue@1 217 self.rangeTimer = rangeTimer
Nenue@1 218 end
Nenue@1 219 end
Nenue@1 220
Nenue@1 221 function Veneer_QuestObjectiveItem_UpdateCooldown (itemButton)
Nenue@1 222 local start, duration, enable = GetQuestLogSpecialItemCooldown(itemButton.questLogIndex)
Nenue@1 223 if (start) then
Nenue@1 224 CooldownFrame_SetTimer(itemButton.Cooldown, start, duration, enable)
Nenue@1 225 if (duration > 0 and enable == 0) then
Nenue@1 226 SetItemButtonTextureVertexColor(itemButton, 0.4, 0.4, 0.4)
Nenue@1 227 else
Nenue@1 228 SetItemButtonTextureVertexColor(itemButton, 1, 1, 1)
Nenue@1 229 end
Nenue@1 230 end
Nenue@3 231 end
Nenue@3 232
Nenue@3 233 -----------------------------------------
Nenue@7 234 -- Criteria frames
Nenue@7 235
Nenue@7 236 --[[
Nenue@7 237 text = description,
Nenue@7 238 type = type,
Nenue@7 239 finished = completed,
Nenue@7 240 quantity = quantity,
Nenue@7 241 requiredQuantity = requiredQuantity,
Nenue@7 242 characterName = characterName,
Nenue@7 243 flags = flags,
Nenue@7 244 assetID = assetID,
Nenue@7 245 quantityString = quantityString,
Nenue@7 246 criteriaID = criteriaID,
Nenue@7 247 ]]
Nenue@7 248 local newWidgetID = 0
Nenue@7 249 mod.WidgetRegistry = {}
Nenue@7 250 local wr = mod.WidgetRegistry
Nenue@7 251
Nenue@7 252 --- Get a usable widget for the given achievement criteria set.
Nenue@7 253 -- Returns a frame object with dimensioning parameters needed to size the receiving tracker block
Nenue@7 254 mod.SetWidget = function(obj, info)
Nenue@8 255 local print = B.print('ObjectiveWidgets')
Nenue@7 256 local widgetType = obj.type
Nenue@7 257 local widget
Nenue@8 258 if wr[widgetType] and wr[widgetType].used[obj.criteriaID] then
Nenue@8 259 widget = wr[widgetType].used[obj.criteriaID]
Nenue@8 260 print('|cFF00FF00Updating ('..obj.criteriaID..')', widget)
Nenue@8 261 elseif not wr[widgetType] or #wr[widgetType].free == 0 then
Nenue@8 262 widget = CreateFrame('Frame', 'VeneerObjective' .. widgetType .. (wr[widgetType] and (wr[widgetType].lastn+1) or (1)), VeneerObjectiveScroll, 'VeneerObjectiveCriteria' .. widgetType)
Nenue@7 263
Nenue@8 264 print('|cFFFF0088Creating `'..widget:GetName()..'` id', wr[widgetType].lastn)
Nenue@8 265 else
Nenue@8 266 widget = tremove(wr[widgetType].free)
Nenue@8 267 print('|cFFFFFF00Acquiring released widget', widget:GetName())
Nenue@7 268 end
Nenue@7 269
Nenue@8 270 wr[widgetType].used[obj.criteriaID] = widget
Nenue@7 271 widget.info = obj
Nenue@7 272 widget.parentInfo = info
Nenue@7 273 mod.InitializeWidget(widget)
Nenue@7 274 return widget
Nenue@3 275 end
Nenue@3 276
Nenue@8 277 --- WidgetTemplate 'OnLoad'
Nenue@7 278 mod.RegisterWidget = function(frame)
Nenue@8 279 local print = B.print('ObjectiveWidgets')
Nenue@7 280 local widgetType = frame.widgetType
Nenue@7 281 if not wr[frame.widgetType] then
Nenue@7 282 print('|cFFFF4400[[WidgetTemplate]]|r', widgetType)
Nenue@8 283 wr[widgetType] = { lastn = 1, free = {}, used = {}, usedIndex = {}, freeIndex = {} }
Nenue@8 284 else
Nenue@8 285 print('|cFF0088FF+ [[WidgetTemplate]]r', widgetType, wr[widgetType].lastn)
Nenue@8 286 wr[widgetType].lastn = wr[widgetType].lastn + 1
Nenue@3 287 end
Nenue@7 288 end
Nenue@8 289
Nenue@8 290 --- WidgetTemplate 'OnShow'
Nenue@7 291 mod.InitializeWidget = setmetatable({}, {
Nenue@7 292 __call = function(t, frame)
Nenue@7 293 -- todo: config pull
Nenue@7 294 local maxWidth = 250
Nenue@3 295
Nenue@7 296 frame:SetWidth(maxWidth)
Nenue@7 297 mod.UpdateWidget[frame.widgetType](frame)
Nenue@7 298 frame:SetScript('OnEvent', mod.UpdateWidget[frame.widgetType])
Nenue@7 299 if frame.info.isCurrency then
Nenue@7 300 frame:RegisterEvent('CHAT_MSG_CURRENCY')
Nenue@7 301 frame:RegisterEvent('CURRENCY_LIST_UPDATE')
Nenue@7 302 end
Nenue@7 303 frame:RegisterEvent('TRACKED_ACHIEVEMENT_UPDATE')
Nenue@7 304 frame:RegisterEvent('TRACKED_ACHIEVEMENT_LIST_CHANGED')
Nenue@7 305 frame:RegisterEvent('CRITERIA_UPDATE')
Nenue@7 306 frame:RegisterEvent('CRITERIA_COMPLETE')
Nenue@7 307 frame:RegisterEvent('CRITERIA_EARNED')
Nenue@7 308
Nenue@7 309 return t[frame.widgetType](frame)
Nenue@7 310 end,
Nenue@7 311 })
Nenue@8 312
Nenue@8 313 --- WidgetTemplate 'OnEvent'
Nenue@7 314 mod.UpdateWidget = setmetatable({}, {
Nenue@7 315 __call = function(t, frame)
Nenue@7 316 if not frame.widgetType then
Nenue@7 317 error('Invalid widget template, needs .widgetType')
Nenue@7 318 return
Nenue@7 319 end
Nenue@7 320
Nenue@7 321 return t[frame.widgetType](frame)
Nenue@7 322 end
Nenue@7 323 })
Nenue@7 324
Nenue@8 325 --- WidgetTemplate 'OnHide'
Nenue@7 326 mod.ReleaseWidget = function(frame)
Nenue@8 327 local print = B.print('ObjectiveWidgets')
Nenue@7 328 local reg = wr[frame.widgetType]
Nenue@8 329 if reg and reg.used[frame.info.criteriaID] then
Nenue@8 330 reg.used[frame.info.criteriaID] = nil
Nenue@8 331 frame.info = nil
Nenue@8 332 frame.parentInfo = nil
Nenue@7 333 frame:UnregisterAllEvents()
Nenue@7 334 tinsert(reg.free, frame)
Nenue@8 335 print('|cFFBBBBBBreleased from service', frame:GetName())
Nenue@8 336 end
Nenue@8 337 end
Nenue@8 338
Nenue@8 339 --- RemoveTrackedAchievement post-hook
Nenue@8 340 mod.CleanWidgets = function()
Nenue@8 341 local print = B.print('ObjectiveWidgets')
Nenue@8 342 local tracked = {GetTrackedAchievements() }
Nenue@8 343 for type, reg in pairs(mod.WidgetRegistry) do
Nenue@8 344 print('collecting', type)
Nenue@8 345 for criteriaID, frame in pairs(reg.used) do
Nenue@8 346 local id = frame.info.cheevID
Nenue@8 347
Nenue@8 348 if id and not tContains(tracked, id) then
Nenue@8 349
Nenue@8 350 print(' untracked achievement', id, 'associated with', criteriaID, frame:GetName())
Nenue@8 351 frame:Hide()
Nenue@8 352 end
Nenue@8 353 end
Nenue@3 354 end
Nenue@3 355 end
Nenue@7 356
Nenue@7 357 mod.WidgetParams = {
Nenue@7 358 ['ProgressBar'] = {
Nenue@7 359 height = 20,
Nenue@7 360 caption = {},
Nenue@7 361 quantityString = {SetFontObject = _G.VeneerFontNormal}
Nenue@7 362 }
Nenue@7 363 }
Nenue@7 364
Nenue@7 365 mod.InitializeWidget.ProgressBar = function(self)
Nenue@8 366 local print = B.print('ObjectiveWidgets')
Nenue@7 367 local params = mod.WidgetParams[self.widgetType]
Nenue@7 368 self.height = params.height
Nenue@7 369 self:SetHeight(20)
Nenue@7 370 self.bg:SetHeight(20)
Nenue@8 371 self.fg:SetHeight(16)
Nenue@7 372 self.fg:SetPoint('BOTTOMLEFT', self.bg, 'BOTTOMLEFT', 1, 1)
Nenue@7 373 self.quantityString:SetFontObject(params.quantityString.SetFontObject)
Nenue@7 374 self.quantityString:SetText(self.info.quantityString)
Nenue@3 375 end
Nenue@7 376
Nenue@7 377 mod.UpdateWidget.ProgressBar = function (self)
Nenue@8 378 local print = B.print('ObjectiveWidgets')
Nenue@7 379 local quantity, requiredQuantity = self.info.quantity, self.info.requiredQuantity
Nenue@8 380
Nenue@7 381 if self.info.finished then
Nenue@7 382 self.fg:SetWidth(self.bg:GetWidth() - 2)
Nenue@7 383 elseif quantity == 0 then
Nenue@7 384 self.fg:Hide()
Nenue@7 385 else
Nenue@7 386 self.fg:Show()
Nenue@8 387 self.fg:SetWidth((self:GetWidth()-2) * (quantity / requiredQuantity))
Nenue@7 388 end
Nenue@8 389 end