| 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@6 | 33 local print = function(...) print('Cooldown', ...) end | 
| Nenue@6 | 34 local function GetPrint (trace) | 
| Nenue@6 | 35   return trace and print or function() end | 
| Nenue@6 | 36 end | 
| Nenue@6 | 37 | 
| Nenue@6 | 38 | 
| Nenue@6 | 39 local item_spells = { | 
| Nenue@6 | 40   ['PvP Trinket'] = 42292, | 
| Nenue@6 | 41   ['Burning Mirror'] = 184270, | 
| Nenue@6 | 42 } | 
| Nenue@6 | 43 | 
| Nenue@6 | 44 T.defaults.spirit.cooldown = { | 
| Nenue@6 | 45 | 
| Nenue@6 | 46   alpha = 1, | 
| Nenue@6 | 47   alpha_ooc = 0.2, | 
| Nenue@6 | 48   inverse = false, | 
| Nenue@6 | 49   persist = false, | 
| Nenue@6 | 50   desaturated = false, | 
| Nenue@6 | 51   fill_inverse = true, | 
| Nenue@6 | 52   size = 24, | 
| Nenue@6 | 53   counterText = "%p", | 
| Nenue@6 | 54   subCounterText = "%.p", | 
| Nenue@6 | 55   chargesText = "%s", | 
| Nenue@6 | 56   justifyH = 'CENTER', | 
| Nenue@6 | 57   justifyV = 'TOP', | 
| Nenue@6 | 58 | 
| Nenue@6 | 59   iconText = "%p", | 
| Nenue@6 | 60   leftText = "%p", | 
| Nenue@6 | 61   rightText = "%n / %d", | 
| Nenue@6 | 62   passive = { | 
| Nenue@6 | 63     icon = { | 
| Nenue@6 | 64       desaturated = false, | 
| Nenue@6 | 65       color = {1, 1, 1, 1}, | 
| Nenue@6 | 66       blend = 'BLEND' | 
| Nenue@6 | 67     } | 
| Nenue@6 | 68   }, | 
| Nenue@6 | 69   active = { | 
| Nenue@6 | 70     icon = { | 
| Nenue@6 | 71       desaturated = false, | 
| Nenue@6 | 72       color = {1, 1, 1, .6}, | 
| Nenue@6 | 73       blend = 'ADD' | 
| Nenue@6 | 74     } | 
| Nenue@6 | 75   }, | 
| Nenue@6 | 76 | 
| Nenue@6 | 77   --- control displays of aura information in cooldown displays | 
| Nenue@6 | 78   showAura = false, | 
| Nenue@6 | 79   cooldownAura = { | 
| Nenue@6 | 80     icon = { | 
| Nenue@6 | 81       desaturated = true, | 
| Nenue@6 | 82       color = {0,1,0,1}, | 
| Nenue@6 | 83     } | 
| Nenue@6 | 84   } | 
| Nenue@6 | 85 } | 
| Nenue@6 | 86 | 
| Nenue@6 | 87 local p = mod.prototype.trigger.cooldown | 
| Nenue@6 | 88 --@end-debug@ | 
| Nenue@6 | 89 p.class  = 'trigger' | 
| Nenue@6 | 90 p.type = 'cooldown' | 
| Nenue@6 | 91 p.cvars = { | 
| Nenue@6 | 92 } | 
| Nenue@6 | 93 --- Sets initial values before dry Event is fired to check for presence | 
| Nenue@6 | 94 p.Init = function(self, spellID, caster, tristate, minValue, maxValue) | 
| Nenue@6 | 95   local print = GetPrint(self.trace) | 
| Nenue@6 | 96 | 
| Nenue@6 | 97   self.spellID = spellID and spellID or self.spellID | 
| Nenue@6 | 98   self.unit = caster and caster or self.unit | 
| Nenue@6 | 99   self.persist = tristate and tristate or self.persist | 
| Nenue@6 | 100   self.minValue = minValue and minValue or tonumber(self.minValue) | 
| Nenue@6 | 101   self.maxValue = maxValue and maxValue or tonumber(self.maxValue) | 
| Nenue@6 | 102 | 
| Nenue@6 | 103   --- current and last state values need to be flipped for inverted conditional | 
| Nenue@6 | 104   --- last state is defined in case it needs to be overridden to ensure proper frame update | 
| Nenue@6 | 105   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 | 106   print(cWord('  inverse=')..cBool(self.cvars.inverse)) | 
| Nenue@6 | 107   if self.cvars.inverse then | 
| Nenue@6 | 108     self.flags = { | 
| Nenue@6 | 109       active = HIDDEN, | 
| Nenue@6 | 110       active_prev = ACTIVE, | 
| Nenue@6 | 111       passive = PASSIVE, | 
| Nenue@6 | 112       passive_prev = PASSIVE, | 
| Nenue@6 | 113       hidden = PASSIVE, | 
| Nenue@6 | 114       hidden_prev = HIDDEN, | 
| Nenue@6 | 115     } | 
| Nenue@6 | 116   else | 
| Nenue@6 | 117     self.flags = { | 
| Nenue@6 | 118       active = ACTIVE, | 
| Nenue@6 | 119       active_prev = HIDDEN, | 
| Nenue@6 | 120       passive = PASSIVE, | 
| Nenue@6 | 121       passive_prev = HIDDEN, | 
| Nenue@6 | 122       hidden = HIDDEN, | 
| Nenue@6 | 123       hidden_prev = PASSIVE | 
| Nenue@6 | 124     } | 
| Nenue@6 | 125   end | 
| Nenue@6 | 126   if not (self.spellID or self.spellName) then | 
| Nenue@6 | 127     self.debug_info('No valid spell ID or Name') | 
| Nenue@6 | 128   end | 
| Nenue@6 | 129 end | 
| Nenue@6 | 130 | 
| Nenue@6 | 131 local GetItemCooldown, GetItemInfo = GetItemCooldown, GetItemInfo | 
| Nenue@6 | 132 p.Query = function(self) | 
| Nenue@6 | 133   local print = GetPrint(self.trace) | 
| Nenue@6 | 134   local id  = self.inventoryID or self.itemID or self.spellID | 
| Nenue@6 | 135   local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count | 
| Nenue@6 | 136 | 
| Nenue@6 | 137   --- checked in order of precedence | 
| Nenue@6 | 138   if self.inventoryID then | 
| Nenue@6 | 139     print(cText('   type'), cWord('inventory slot'), cNum(id)) | 
| Nenue@6 | 140     self.cooldownType = CD_SLOT | 
| Nenue@6 | 141     start, duration, enabled = GetInventoryItemCooldown('player', id) | 
| Nenue@6 | 142     charges, maxCharges, chargeStart, chargeDuration = nil, nil, nil, nil | 
| Nenue@6 | 143     count = GetInventoryItemCount('player', id) | 
| Nenue@6 | 144     usable = name and true or false | 
| Nenue@6 | 145   elseif self.itemID then | 
| Nenue@6 | 146     self.cooldownType = CD_ITEM | 
| Nenue@6 | 147 | 
| Nenue@6 | 148     start, duration, enabled = GetItemCooldown(self.itemID) | 
| Nenue@6 | 149     print(GetItemCooldown(self.itemID)) | 
| Nenue@6 | 150     print(GetItemInfo(id)) | 
| Nenue@6 | 151 | 
| Nenue@6 | 152   elseif self.spellID then | 
| Nenue@6 | 153     self.cooldownType = CD_SPELL | 
| Nenue@6 | 154     name = GetSpellInfo(self.spellID) | 
| Nenue@6 | 155     start, duration, enabled = GetSpellCooldown(self.spellID) | 
| Nenue@6 | 156     charges, maxCharges, chargeStart, chargeDuration = GetSpellCharges(self.spellID) | 
| Nenue@6 | 157     count = GetSpellCount(self.spellID) | 
| Nenue@6 | 158     usable = true -- they still exist even when dead | 
| Nenue@6 | 159   else | 
| Nenue@6 | 160     self.unit = 'notaunit' | 
| Nenue@6 | 161     T:Print('Timer \''..tostring(self.timerName)..'\' doesn\'t have a valid status ID.') | 
| Nenue@6 | 162   end | 
| Nenue@6 | 163 | 
| Nenue@6 | 164   -- may not have been stored for some reason | 
| Nenue@6 | 165   if charges and not self.maxCharges then | 
| Nenue@6 | 166     self.maxCharges = maxCharges | 
| Nenue@6 | 167   end | 
| Nenue@6 | 168 | 
| Nenue@6 | 169   print('cooldown.Query(',id,')', name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration) | 
| Nenue@6 | 170   return name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count | 
| Nenue@6 | 171 end | 
| Nenue@6 | 172 | 
| Nenue@6 | 173 p.Set = function(self, ...) | 
| Nenue@6 | 174   local print = GetPrint(self.trace) | 
| Nenue@6 | 175 | 
| Nenue@6 | 176   --name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count | 
| Nenue@6 | 177   local name | 
| Nenue@6 | 178   name, self.usable, self.start, self.duration, self.enabled, self.charges, self.chargeStart, self.chargeDuration, self.count = ... | 
| Nenue@6 | 179   if name then | 
| Nenue@6 | 180     self.spellName = name | 
| Nenue@6 | 181   end | 
| Nenue@6 | 182 | 
| Nenue@6 | 183   if self.duration and self.start then | 
| Nenue@6 | 184     self.expires = self.start + self.duration | 
| Nenue@6 | 185   else | 
| Nenue@6 | 186     self.expires = 0 | 
| Nenue@6 | 187   end | 
| Nenue@6 | 188 end | 
| Nenue@6 | 189 | 
| Nenue@6 | 190 p.Value = function(self) | 
| Nenue@6 | 191   return (self.charges and self.charges < self.maxCharges) and ((GetTime()  - self.chargeStart) / self.chargeDuration) or ((GetTime()  - self.start) / self.duration) | 
| Nenue@6 | 192 end | 
| Nenue@6 | 193 | 
| Nenue@6 | 194 p.SetText = mod.SetText | 
| Nenue@6 | 195 | 
| Nenue@6 | 196 --- Assign where meaning won't be amibiguous | 
| Nenue@6 | 197 local Cooldown_OnCast = function(self) | 
| Nenue@6 | 198   self.triggerState = true | 
| Nenue@6 | 199 end | 
| Nenue@6 | 200 | 
| Nenue@6 | 201 local Cooldown_OnUpdate = function(self, event) | 
| Nenue@6 | 202   local print = GetPrint(self.trace) | 
| Nenue@6 | 203 | 
| Nenue@6 | 204   if self.triggerState then | 
| Nenue@6 | 205     print(cWord('Event'), cText(self.timerName)) | 
| Nenue@6 | 206     if not event then | 
| Nenue@6 | 207       print(' *', cWord('Poke')) | 
| Nenue@6 | 208     end | 
| Nenue@6 | 209     local diff = 'start='..cText(self.start)..' duration='..cText(self.duration)..' charges='.. | 
| Nenue@6 | 210         cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration) | 
| Nenue@6 | 211     local name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count = self:Query() | 
| Nenue@6 | 212 | 
| Nenue@6 | 213     -- If we want and can, pull aura data and use that in place of cooldown information | 
| Nenue@6 | 214     local expires, hasAura, _ | 
| Nenue@6 | 215     if self.cvars.showAura then | 
| Nenue@6 | 216       print(cText('UnitAura'), self.unit, self.spellName, nil, 'HELPFUL') | 
| Nenue@6 | 217       local name, _, _, count, _, auraDuration, auraExpires = UnitAura(self.unit , self.spellName, nil, 'HELPFUL') | 
| Nenue@6 | 218       if name and (auraDuration ~= self.auraDuration or auraExpires ~= self.auraExpires) then | 
| Nenue@6 | 219 | 
| Nenue@6 | 220         print(cText('aura check ='), cBool(name), 's='..cNum(count), 'd='..cNum(auraDuration), 'e='..cNum(auraExpires)) | 
| Nenue@6 | 221         start = auraExpires - auraDuration | 
| Nenue@6 | 222         duration = auraDuration | 
| Nenue@6 | 223         expires = auraExpires | 
| Nenue@6 | 224       end | 
| Nenue@6 | 225     end | 
| Nenue@6 | 226 | 
| Nenue@6 | 227     --  print(name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count) | 
| Nenue@6 | 228     if duration ~= self.duration or | 
| Nenue@6 | 229           start ~= self.start or | 
| Nenue@6 | 230           chargeStart ~= self.chargeStart | 
| Nenue@6 | 231           or charges ~= self.charges then | 
| Nenue@6 | 232       print('a variable has changed') | 
| Nenue@6 | 233       local state | 
| Nenue@6 | 234 | 
| Nenue@6 | 235 | 
| Nenue@6 | 236       if duration == 0 and charges == self.maxCharges then | 
| Nenue@6 | 237         print(cText('  cooldown has reset, drop it from the queue')) | 
| Nenue@6 | 238         state = self.cvars.persist and self.flags.passive or self.flags.hidden | 
| Nenue@6 | 239         self.triggerState = nil | 
| Nenue@6 | 240         print('  ', cText('dropping'), cWord(self.timerName), cText('from spell tracking')) | 
| Nenue@6 | 241 | 
| Nenue@6 | 242 | 
| Nenue@6 | 243         self:Stats(state) | 
| Nenue@6 | 244       else | 
| Nenue@6 | 245         if duration ~= 0 then | 
| Nenue@6 | 246           print(cText('  cooldown has a hard duration')) | 
| Nenue@6 | 247           if duration > T.GCD and self.displayState ~= self.flags.active then | 
| Nenue@6 | 248             print(cText('    and its > GCD, update it')) | 
| Nenue@6 | 249             state = self.flags.active | 
| Nenue@6 | 250           end | 
| Nenue@6 | 251         end | 
| Nenue@6 | 252 | 
| Nenue@6 | 253         if charges then | 
| Nenue@6 | 254           print(cText('  cooldown has charges')) | 
| Nenue@6 | 255           if charges ~= self.charges or chargeStart ~= self.chargeStart then | 
| Nenue@6 | 256             print(cText('  charges count or starting time has changed')) | 
| Nenue@6 | 257             state = self.flags.active | 
| Nenue@6 | 258           end | 
| Nenue@6 | 259         end | 
| Nenue@6 | 260 | 
| Nenue@6 | 261         self:Stats(state) | 
| Nenue@6 | 262       end | 
| Nenue@6 | 263 | 
| Nenue@6 | 264 | 
| Nenue@6 | 265       -- form ID, id type, displayState, prevState | 
| Nenue@6 | 266       --T:Dispatch('TK_COOLDOWN_UPDATE', self.spellID, self.cooldownType, state, self.displayState) | 
| Nenue@6 | 267       if state then | 
| Nenue@6 | 268         self:Set(name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count) | 
| Nenue@6 | 269         self.expires = charges and (self.chargeStart + self.chargeDuration) or (self.start + self.duration) | 
| Nenue@6 | 270         self:SetState(state) | 
| Nenue@6 | 271         --print('  ', cText('SetState'), cNum(self.displayState), 'from', cNum(self.prevState), cWord(self.timerName)) | 
| Nenue@6 | 272         print('   ',diff) | 
| Nenue@6 | 273         print('    start='..cText(self.start)..' duration='..cText(self.duration)..' charges='.. | 
| Nenue@6 | 274             cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration)) | 
| Nenue@6 | 275       end | 
| Nenue@6 | 276     elseif self.cooldownType == CD_SPELL then | 
| Nenue@6 | 277       if duration == 0 and charges == self.maxCharges and self.displayState == HIDDEN then | 
| Nenue@6 | 278         print(cKey(self.timerName), cText('post-framescript clean-up')) | 
| Nenue@6 | 279         self.triggerState = nil | 
| Nenue@6 | 280       end | 
| Nenue@6 | 281     end | 
| Nenue@6 | 282   end | 
| Nenue@6 | 283   --self:DumpMessages() | 
| Nenue@6 | 284 | 
| Nenue@6 | 285 end | 
| Nenue@6 | 286 | 
| Nenue@6 | 287 p.Stats = function(self, state) | 
| Nenue@6 | 288   print(self.unit, self.spellName) | 
| Nenue@6 | 289   local auraName, _, _, auraCharges, _, auraDuration, auraExpires = UnitAura(self.unit, self.spellName, nil, 'HELPFUL') | 
| Nenue@6 | 290   local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count = self:Query() | 
| Nenue@6 | 291   print('# GCD =', T.GCD) | 
| Nenue@6 | 292   print('# SpellCooldown', 's =', start, 'd =', duration, 's =', charges and (charges..'/'..maxCharges) or 'NA') | 
| Nenue@6 | 293   print('# Aura', auraName and 'yes' or 'no', auraName and ('d='..cNum(auraDuration)) or '', auraName and ('e='..auraExpires)) | 
| Nenue@6 | 294   print('# Frame', 'state1 =', self.displayState, 'state2 =', self.previousState, state and ('change to '..cWord(state)) or '') | 
| Nenue@6 | 295 | 
| Nenue@6 | 296 | 
| Nenue@6 | 297 end | 
| Nenue@6 | 298 | 
| Nenue@6 | 299 --- Event pointers | 
| Nenue@6 | 300 p.events = { | 
| Nenue@6 | 301   ['UNIT_SPELLCAST_SUCCEEDED'] = Cooldown_OnCast, | 
| Nenue@6 | 302   ['UNIT_SPELLCAST_CHANNEL_START'] = Cooldown_OnCast, | 
| Nenue@6 | 303   ['BAG_UPDATE_COOLDOWN'] = Cooldown_OnUpdate, | 
| Nenue@6 | 304   ['SPELL_UPDATE_COOLDOWN'] = Cooldown_OnUpdate, | 
| Nenue@6 | 305   ['SPELL_UPDATE_USABLE'] = Cooldown_OnUpdate, | 
| Nenue@6 | 306   ['SPELL_UPDATE_CHARGES'] = Cooldown_OnUpdate, | 
| Nenue@6 | 307 } | 
| Nenue@6 | 308 | 
| Nenue@6 | 309 local unpack, select, debugstack = unpack, select, debugstack | 
| Nenue@6 | 310 p.Event = function(self, e, ...) | 
| Nenue@6 | 311   local print = GetPrint(self.trace) | 
| Nenue@6 | 312 | 
| Nenue@6 | 313   self.event = e | 
| Nenue@6 | 314   --- dry event case | 
| Nenue@6 | 315   if not e then | 
| Nenue@6 | 316     print('onEvent', self.timerName, e) | 
| Nenue@6 | 317     self.triggerState = true | 
| Nenue@6 | 318     Cooldown_OnUpdate(self, e, ...) | 
| Nenue@6 | 319   else | 
| Nenue@6 | 320     local unit = select(1,...) | 
| Nenue@6 | 321     if self.unit and unit ~= self.unit and  e ~= 'SPELL_UPDATE_COOLDOWN' then | 
| Nenue@6 | 322       return | 
| Nenue@6 | 323     end | 
| Nenue@6 | 324     local args = {...} | 
| Nenue@6 | 325     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 | 326 | 
| Nenue@6 | 327   end | 
| Nenue@6 | 328 end | 
| Nenue@6 | 329 | 
| Nenue@6 | 330 p.triggerList = { | 
| Nenue@6 | 331   ['on_cooldown'] = function(self) | 
| Nenue@6 | 332     local start, duration, enabled = GetSpellCooldown(self.spellID) | 
| Nenue@6 | 333     local charges, _, cStart, cDuration = GetSpellCharges(self.spellID) | 
| Nenue@6 | 334     return (enabled and (duration ~= 0 or start ~= 0 or charges ~= self.maxCharges)), start, duration, enabled, charges, cStart, cDuration | 
| Nenue@6 | 335   end, | 
| Nenue@6 | 336   ['not_on_cooldown'] = function(self) | 
| Nenue@6 | 337     local start, duration, enabled = GetSpellCooldown(self.spellID) | 
| Nenue@6 | 338     local charges, _, cStart, cDuration = GetSpellCharges(self.spellID) | 
| Nenue@6 | 339     return (duration == 0 and start == 0 and charges == self.maxCharges), start, duration, enabled, charges, cStart, cDuration | 
| Nenue@6 | 340   end, | 
| Nenue@6 | 341   ['buff_active'] = function(self) | 
| Nenue@6 | 342     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 | 343     return (name and true or false), expires - duration, duration, true, count, 0, 0 | 
| Nenue@6 | 344   end | 
| Nenue@6 | 345 } |