annotate Turok/Modules/Timer/Cooldown.lua @ 11:0b1a2f3dbfc4 tip

aura duration override triggering activation twice when aura data still exists briefly after duration end
author Nenue
date Mon, 22 Feb 2016 03:11:54 -0500
parents 9400a0ff8540
children
rev   line source
Nenue@6 1
Nenue@6 2 --- ${PACKAGE_NAME}
Nenue@6 3 -- @file-author@
Nenue@6 4 -- @project-revision@ @project-hash@
Nenue@6 5 -- @file-revision@ @file-hash@
Nenue@6 6 -- Created: 12/25/2015 5:33 AM
Nenue@6 7 --- Spell cooldown tracking resides here.
Nenue@6 8 --
Nenue@6 9 -- Workflow of cooldown tracking:
Nenue@6 10 -- A tracked spell cast is detected. (UNIT_SPELLCAST_*)
Nenue@6 11 -- That spell ID enters the timer table.
Nenue@6 12 -- The table is read by a handler that fires on the next frame, when cooldown information is available. (COOLDOWN_*)
Nenue@6 13 -- Set() is called on the corresponding timer frame, and frame script takes over.
Nenue@6 14 -- Timer table spells are polled on each COOLDOWN_* event, re-applying Set() when certain conditions are met.
Nenue@6 15 -- The framescript or certain handlers will remove the timer table entry when there are no more positive conditions.
Nenue@6 16 --
Nenue@6 17 local tostring, tonumber, tinsert = tostring, tonumber, tinsert
Nenue@6 18 local GetTime, GetSpellInfo, GetInventoryItemCooldown, GetSpellCooldown, PlaySoundFile = GetTime, GetSpellInfo, GetInventoryItemCooldown, GetSpellCooldown, PlaySoundFile
Nenue@6 19 local GetSpellCharges, GetSpellCount, GetInventoryItemCount, UnitAura = GetSpellCharges, GetSpellCount, GetInventoryItemCount, UnitAura
Nenue@6 20 local IsUsableItem, IsUsableSpell, GetItemSpell = IsUsableItem, IsUsableSpell, GetItemSpell
Nenue@6 21 local xpcall = xpcall
Nenue@6 22
Nenue@6 23 local CD_SLOT, CD_ITEM, CD_SPELL = 1, 2, 3
Nenue@6 24 local HIDDEN, PASSIVE, ACTIVE = 0, 1, 2
Nenue@6 25 local format, ceil = string.format, math.ceil
Nenue@6 26 local strrep, gsub, pairs = string.rep, string.gsub, pairs
Nenue@6 27 local mod = Turok.modules.TimerControl
Nenue@6 28 local T = Turok
Nenue@6 29 local db
Nenue@6 30 local FADE_TIME = 0.2
Nenue@6 31 --@debug@
Nenue@6 32 local cType, cText, cNum, cWord, cKey, cPink, cBool = cType, cText, cNum, cWord, cKey, cPink, cBool
Nenue@9 33
Nenue@9 34 local print = function(...)
Nenue@9 35 if _G.Devian and _G.DevianDB.workspace ~= 1 then
Nenue@9 36 _G.print('Cooldown', ...)
Nenue@9 37 end
Nenue@9 38 end
Nenue@6 39 local function GetPrint (trace)
Nenue@6 40 return trace and print or function() end
Nenue@6 41 end
Nenue@6 42
Nenue@6 43
Nenue@6 44 local item_spells = {
Nenue@6 45 ['PvP Trinket'] = 42292,
Nenue@6 46 ['Burning Mirror'] = 184270,
Nenue@6 47 }
Nenue@6 48
Nenue@6 49 T.defaults.spirit.cooldown = {
Nenue@6 50
Nenue@6 51 alpha = 1,
Nenue@6 52 alpha_ooc = 0.2,
Nenue@6 53 inverse = false,
Nenue@6 54 persist = false,
Nenue@6 55 desaturated = false,
Nenue@6 56 fill_inverse = true,
Nenue@6 57 size = 24,
Nenue@9 58 counterText = "%i",
Nenue@6 59 subCounterText = "%.p",
Nenue@6 60 chargesText = "%s",
Nenue@6 61 justifyH = 'CENTER',
Nenue@6 62 justifyV = 'TOP',
Nenue@6 63
Nenue@6 64 iconText = "%p",
Nenue@6 65 leftText = "%p",
Nenue@6 66 rightText = "%n / %d",
Nenue@6 67 passive = {
Nenue@6 68 icon = {
Nenue@6 69 desaturated = false,
Nenue@6 70 color = {1, 1, 1, 1},
Nenue@6 71 }
Nenue@6 72 },
Nenue@6 73 active = {
Nenue@6 74 icon = {
Nenue@9 75 desaturated = true,
Nenue@6 76 color = {1, 1, 1, .6},
Nenue@6 77 }
Nenue@6 78 },
Nenue@6 79
Nenue@6 80 --- control displays of aura information in cooldown displays
Nenue@9 81 overrideAura = false,
Nenue@9 82 overrideDuration = false,
Nenue@9 83 override = {
Nenue@6 84 icon = {
Nenue@6 85 desaturated = true,
Nenue@6 86 color = {0,1,0,1},
Nenue@6 87 }
Nenue@6 88 }
Nenue@6 89 }
Nenue@6 90
Nenue@6 91 local p = mod.prototype.trigger.cooldown
Nenue@6 92 --@end-debug@
Nenue@6 93 p.class = 'trigger'
Nenue@6 94 p.type = 'cooldown'
Nenue@6 95 p.cvars = {
Nenue@6 96 }
Nenue@6 97 --- Sets initial values before dry Event is fired to check for presence
Nenue@6 98 p.Init = function(self, spellID, caster, tristate, minValue, maxValue)
Nenue@6 99 local print = GetPrint(self.trace)
Nenue@6 100
Nenue@6 101 self.spellID = spellID and spellID or self.spellID
Nenue@6 102 self.unit = caster and caster or self.unit
Nenue@6 103 self.persist = tristate and tristate or self.persist
Nenue@6 104 self.minValue = minValue and minValue or tonumber(self.minValue)
Nenue@6 105 self.maxValue = maxValue and maxValue or tonumber(self.maxValue)
Nenue@6 106
Nenue@9 107 self.start = 0
Nenue@9 108 self.duration = 0
Nenue@9 109 self.expires = 0
Nenue@9 110 self.charges = nil
Nenue@9 111 self.charges_max = 0
Nenue@9 112 self.charge_start = 0
Nenue@9 113 self.charge_duration = 0
Nenue@9 114 self.charge_expires = 0
Nenue@9 115 self.override = false
Nenue@9 116 self.override_start = 0
Nenue@9 117 self.override_duration = 0
Nenue@9 118 self.override_expires = 0
Nenue@9 119
Nenue@6 120 --- current and last state values need to be flipped for inverted conditional
Nenue@6 121 --- last state is defined in case it needs to be overridden to ensure proper frame update
Nenue@6 122 print(cWord('Load:'),cNum(self.spellID or self.inventoryID or self.itemID), cText(self.spellName or GetItemSpell('player', self.inventoryID or self.itemID)) )
Nenue@6 123 print(cWord(' inverse=')..cBool(self.cvars.inverse))
Nenue@6 124 if self.cvars.inverse then
Nenue@6 125 self.flags = {
Nenue@6 126 active = HIDDEN,
Nenue@6 127 active_prev = ACTIVE,
Nenue@6 128 passive = PASSIVE,
Nenue@6 129 passive_prev = PASSIVE,
Nenue@6 130 hidden = PASSIVE,
Nenue@6 131 hidden_prev = HIDDEN,
Nenue@6 132 }
Nenue@6 133 else
Nenue@6 134 self.flags = {
Nenue@6 135 active = ACTIVE,
Nenue@6 136 active_prev = HIDDEN,
Nenue@6 137 passive = PASSIVE,
Nenue@6 138 passive_prev = HIDDEN,
Nenue@6 139 hidden = HIDDEN,
Nenue@6 140 hidden_prev = PASSIVE
Nenue@6 141 }
Nenue@6 142 end
Nenue@6 143 if not (self.spellID or self.spellName) then
Nenue@6 144 self.debug_info('No valid spell ID or Name')
Nenue@6 145 end
Nenue@6 146 end
Nenue@6 147
Nenue@6 148 local GetItemCooldown, GetItemInfo = GetItemCooldown, GetItemInfo
Nenue@6 149 p.Query = function(self)
Nenue@6 150 local print = GetPrint(self.trace)
Nenue@6 151 local id = self.inventoryID or self.itemID or self.spellID
Nenue@9 152 local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration = nil, false, 0, 0, nil, nil, nil, 0, 0
Nenue@9 153 local override, overrideStart, overrideDuration = false, 0, 0
Nenue@6 154
Nenue@6 155 --- checked in order of precedence
Nenue@6 156 if self.inventoryID then
Nenue@6 157 print(cText(' type'), cWord('inventory slot'), cNum(id))
Nenue@6 158 self.cooldownType = CD_SLOT
Nenue@6 159 start, duration, enabled = GetInventoryItemCooldown('player', id)
Nenue@6 160 charges, maxCharges, chargeStart, chargeDuration = nil, nil, nil, nil
Nenue@6 161 usable = name and true or false
Nenue@6 162 elseif self.itemID then
Nenue@6 163 self.cooldownType = CD_ITEM
Nenue@6 164
Nenue@6 165 start, duration, enabled = GetItemCooldown(self.itemID)
Nenue@6 166 print(GetItemCooldown(self.itemID))
Nenue@6 167 print(GetItemInfo(id))
Nenue@6 168
Nenue@6 169 elseif self.spellID then
Nenue@6 170 self.cooldownType = CD_SPELL
Nenue@6 171 name = GetSpellInfo(self.spellID)
Nenue@6 172 start, duration, enabled = GetSpellCooldown(self.spellID)
Nenue@6 173 charges, maxCharges, chargeStart, chargeDuration = GetSpellCharges(self.spellID)
Nenue@6 174 usable = true -- they still exist even when dead
Nenue@6 175 else
Nenue@6 176 self.unit = 'notaunit'
Nenue@6 177 T:Print('Timer \''..tostring(self.timerName)..'\' doesn\'t have a valid status ID.')
Nenue@6 178 end
Nenue@6 179
Nenue@6 180 -- may not have been stored for some reason
Nenue@6 181 if charges and not self.maxCharges then
Nenue@6 182 self.maxCharges = maxCharges
Nenue@6 183 end
Nenue@6 184
Nenue@9 185 if self.spellName and self.cvars.overrideAura then
Nenue@9 186 local name, _, _, _, _, d, e = UnitAura(self.unit , self.spellName, nil, 'HELPFUL')
Nenue@9 187 if name then
Nenue@9 188 override = true
Nenue@9 189 overrideDuration = d
Nenue@9 190 overrideStart = e - d
Nenue@9 191 end
Nenue@9 192 end
Nenue@9 193
Nenue@9 194 if self.overrideDuration and start > 0 then
Nenue@9 195 override = true
Nenue@9 196 overrideDuration = self.overrideDuration
Nenue@9 197 overrideStart = start
Nenue@9 198 end
Nenue@9 199
Nenue@9 200 print('cooldown.Query(',id,')', 'name:'..cText(name), 'usable:'..cBool(usable), 'start:'..cNum(start), 'duration:'..cNum(duration), 'enabled:'..cWord(enabled),
Nenue@9 201 'charges:'.. cNum(charges), 'charge_start:'.. cNum(chargeStart), 'charge_duration:'.. cNum(chargeDuration),
Nenue@9 202 'override:' .. cBool(override), 'override_start'..cNum(overrideStart), 'override_duration:'.. cNum(overrideDuration))
Nenue@9 203 return name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, override, overrideStart, overrideDuration
Nenue@6 204 end
Nenue@6 205
Nenue@6 206 p.Set = function(self, ...)
Nenue@6 207 local print = GetPrint(self.trace)
Nenue@6 208
Nenue@6 209 --name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count
Nenue@6 210 local name
Nenue@9 211 name, self.usable, self.start, self.duration, self.enabled, self.charges, self.charges_max, self.charge_start, self.charge_duration, self.override, self.override_start, self.override_duration = ...
Nenue@6 212 if name then
Nenue@6 213 self.spellName = name
Nenue@6 214 end
Nenue@6 215
Nenue@6 216 if self.duration and self.start then
Nenue@6 217 self.expires = self.start + self.duration
Nenue@6 218 else
Nenue@6 219 self.expires = 0
Nenue@6 220 end
Nenue@9 221
Nenue@9 222 if self.charges then
Nenue@9 223 self.charge_expires = self.charge_start + self.charge_duration
Nenue@9 224 end
Nenue@9 225 if self.override then
Nenue@9 226 self.override_expires = self.override_start + self.override_duration
Nenue@9 227 end
Nenue@9 228
Nenue@6 229 end
Nenue@6 230
Nenue@6 231 p.Value = function(self)
Nenue@6 232 return (self.charges and self.charges < self.maxCharges) and ((GetTime() - self.chargeStart) / self.chargeDuration) or ((GetTime() - self.start) / self.duration)
Nenue@6 233 end
Nenue@6 234
Nenue@6 235 p.SetText = mod.SetText
Nenue@6 236
Nenue@6 237 --- Assign where meaning won't be amibiguous
Nenue@6 238 local Cooldown_OnCast = function(self)
Nenue@6 239 self.triggerState = true
Nenue@6 240 end
Nenue@6 241
Nenue@6 242 local Cooldown_OnUpdate = function(self, event)
Nenue@6 243 local print = GetPrint(self.trace)
Nenue@6 244
Nenue@6 245 if self.triggerState then
Nenue@6 246 print(cWord('Event'), cText(self.timerName))
Nenue@6 247 if not event then
Nenue@6 248 print(' *', cWord('Poke'))
Nenue@6 249 end
Nenue@6 250 local diff = 'start='..cText(self.start)..' duration='..cText(self.duration)..' charges='..
Nenue@6 251 cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration)
Nenue@9 252 local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, override, overrideStart, overrideDuration = self:Query()
Nenue@6 253
Nenue@6 254 -- print(name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count)
Nenue@6 255 if duration ~= self.duration or
Nenue@6 256 start ~= self.start or
Nenue@9 257 chargeStart ~= self.charge_start or
Nenue@9 258 charges ~= self.charges or
Nenue@9 259 override ~= self.override
Nenue@9 260 then
Nenue@6 261 print('a variable has changed')
Nenue@6 262 local state
Nenue@6 263
Nenue@6 264
Nenue@6 265 if duration == 0 and charges == self.maxCharges then
Nenue@6 266 print(cText(' cooldown has reset, drop it from the queue'))
Nenue@6 267 state = self.cvars.persist and self.flags.passive or self.flags.hidden
Nenue@6 268 self.triggerState = nil
Nenue@6 269 print(' ', cText('dropping'), cWord(self.timerName), cText('from spell tracking'))
Nenue@6 270
Nenue@6 271
Nenue@6 272 self:Stats(state)
Nenue@6 273 else
Nenue@6 274 if duration ~= 0 then
Nenue@6 275 print(cText(' cooldown has a hard duration'))
Nenue@6 276 if duration > T.GCD and self.displayState ~= self.flags.active then
Nenue@6 277 print(cText(' and its > GCD, update it'))
Nenue@6 278 state = self.flags.active
Nenue@6 279 end
Nenue@6 280 end
Nenue@6 281
Nenue@6 282 if charges then
Nenue@6 283 print(cText(' cooldown has charges'))
Nenue@6 284 if charges ~= self.charges or chargeStart ~= self.chargeStart then
Nenue@6 285 print(cText(' charges count or starting time has changed'))
Nenue@6 286 state = self.flags.active
Nenue@6 287 end
Nenue@6 288 end
Nenue@6 289
Nenue@6 290 self:Stats(state)
Nenue@6 291 end
Nenue@6 292
Nenue@6 293 if state then
Nenue@9 294 self:Set(name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, override, overrideStart, overrideDuration)
Nenue@6 295 self.expires = charges and (self.chargeStart + self.chargeDuration) or (self.start + self.duration)
Nenue@6 296 self:SetState(state)
Nenue@6 297 print(' ',diff)
Nenue@6 298 print(' start='..cText(self.start)..' duration='..cText(self.duration)..' charges='..
Nenue@6 299 cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration))
Nenue@6 300 end
Nenue@9 301 else
Nenue@9 302 if self.cooldownType == CD_SPELL then
Nenue@9 303 if duration == 0 and charges == self.maxCharges and self.displayState == HIDDEN then
Nenue@9 304 print(cKey(self.timerName), cText('post-framescript clean-up'))
Nenue@9 305 self.triggerState = nil
Nenue@9 306 end
Nenue@6 307 end
Nenue@6 308 end
Nenue@6 309 end
Nenue@6 310 end
Nenue@6 311
Nenue@6 312 p.Stats = function(self, state)
Nenue@6 313 print(self.unit, self.spellName)
Nenue@9 314 if self.spellName then
Nenue@9 315 local auraName, _, _, auraCharges, _, auraDuration, auraExpires = UnitAura(self.unit, self.spellName, nil, 'HELPFUL')
Nenue@9 316 print(' # Aura', auraName and 'yes' or 'no', auraName and ('d='..cNum(auraDuration)) or '', auraName and ('e='..auraExpires))
Nenue@9 317 end
Nenue@9 318
Nenue@6 319 local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count = self:Query()
Nenue@9 320 print(' # GCD =', T.GCD)
Nenue@9 321 print(' # SpellCooldown', 's =', start, 'd =', duration, 's =', charges and (charges..'/'..maxCharges) or 'NA')
Nenue@9 322 print(' # Frame', 'state1 =', self.displayState, 'state2 =', self.previousState, state and ('change to '..cWord(state)) or '')
Nenue@6 323
Nenue@6 324
Nenue@6 325 end
Nenue@6 326
Nenue@6 327 --- Event pointers
Nenue@6 328 p.events = {
Nenue@6 329 ['UNIT_SPELLCAST_SUCCEEDED'] = Cooldown_OnCast,
Nenue@6 330 ['UNIT_SPELLCAST_CHANNEL_START'] = Cooldown_OnCast,
Nenue@6 331 ['BAG_UPDATE_COOLDOWN'] = Cooldown_OnUpdate,
Nenue@6 332 ['SPELL_UPDATE_COOLDOWN'] = Cooldown_OnUpdate,
Nenue@6 333 ['SPELL_UPDATE_USABLE'] = Cooldown_OnUpdate,
Nenue@6 334 ['SPELL_UPDATE_CHARGES'] = Cooldown_OnUpdate,
Nenue@6 335 }
Nenue@6 336
Nenue@6 337 local unpack, select, debugstack = unpack, select, debugstack
Nenue@6 338 p.Event = function(self, e, ...)
Nenue@6 339 local print = GetPrint(self.trace)
Nenue@6 340
Nenue@6 341 self.event = e
Nenue@6 342 --- dry event case
Nenue@6 343 if not e then
Nenue@6 344 print('onEvent', self.timerName, e)
Nenue@6 345 self.triggerState = true
Nenue@6 346 Cooldown_OnUpdate(self, e, ...)
Nenue@6 347 else
Nenue@6 348 local unit = select(1,...)
Nenue@6 349 if self.unit and unit ~= self.unit and e ~= 'SPELL_UPDATE_COOLDOWN' then
Nenue@6 350 return
Nenue@6 351 end
Nenue@6 352 local args = {...}
Nenue@6 353 local success, d = xpcall(function() self.events[e](self, e, unpack(args)) end, function(m) print(self.name .."\n".. m .. "\n".. debugstack(3)) end)
Nenue@6 354
Nenue@6 355 end
Nenue@6 356 end
Nenue@6 357
Nenue@6 358 p.triggerList = {
Nenue@6 359 ['on_cooldown'] = function(self)
Nenue@6 360 local start, duration, enabled = GetSpellCooldown(self.spellID)
Nenue@6 361 local charges, _, cStart, cDuration = GetSpellCharges(self.spellID)
Nenue@6 362 return (enabled and (duration ~= 0 or start ~= 0 or charges ~= self.maxCharges)), start, duration, enabled, charges, cStart, cDuration
Nenue@6 363 end,
Nenue@6 364 ['not_on_cooldown'] = function(self)
Nenue@6 365 local start, duration, enabled = GetSpellCooldown(self.spellID)
Nenue@6 366 local charges, _, cStart, cDuration = GetSpellCharges(self.spellID)
Nenue@6 367 return (duration == 0 and start == 0 and charges == self.maxCharges), start, duration, enabled, charges, cStart, cDuration
Nenue@6 368 end,
Nenue@6 369 ['buff_active'] = function(self)
Nenue@6 370 local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura(self.unit, self.spellName, self.filters)
Nenue@6 371 return (name and true or false), expires - duration, duration, true, count, 0, 0
Nenue@6 372 end
Nenue@6 373 }