Nenue@6: Nenue@6: --- ${PACKAGE_NAME} Nenue@6: -- @file-author@ Nenue@6: -- @project-revision@ @project-hash@ Nenue@6: -- @file-revision@ @file-hash@ Nenue@6: -- Created: 12/25/2015 5:33 AM Nenue@6: --- Spell cooldown tracking resides here. Nenue@6: -- Nenue@6: -- Workflow of cooldown tracking: Nenue@6: -- A tracked spell cast is detected. (UNIT_SPELLCAST_*) Nenue@6: -- That spell ID enters the timer table. Nenue@6: -- The table is read by a handler that fires on the next frame, when cooldown information is available. (COOLDOWN_*) Nenue@6: -- Set() is called on the corresponding timer frame, and frame script takes over. Nenue@6: -- Timer table spells are polled on each COOLDOWN_* event, re-applying Set() when certain conditions are met. Nenue@6: -- The framescript or certain handlers will remove the timer table entry when there are no more positive conditions. Nenue@6: -- Nenue@6: local tostring, tonumber, tinsert = tostring, tonumber, tinsert Nenue@6: local GetTime, GetSpellInfo, GetInventoryItemCooldown, GetSpellCooldown, PlaySoundFile = GetTime, GetSpellInfo, GetInventoryItemCooldown, GetSpellCooldown, PlaySoundFile Nenue@6: local GetSpellCharges, GetSpellCount, GetInventoryItemCount, UnitAura = GetSpellCharges, GetSpellCount, GetInventoryItemCount, UnitAura Nenue@6: local IsUsableItem, IsUsableSpell, GetItemSpell = IsUsableItem, IsUsableSpell, GetItemSpell Nenue@6: local xpcall = xpcall Nenue@6: Nenue@6: local CD_SLOT, CD_ITEM, CD_SPELL = 1, 2, 3 Nenue@6: local HIDDEN, PASSIVE, ACTIVE = 0, 1, 2 Nenue@6: local format, ceil = string.format, math.ceil Nenue@6: local strrep, gsub, pairs = string.rep, string.gsub, pairs Nenue@6: local mod = Turok.modules.TimerControl Nenue@6: local T = Turok Nenue@6: local db Nenue@6: local FADE_TIME = 0.2 Nenue@6: --@debug@ Nenue@6: local cType, cText, cNum, cWord, cKey, cPink, cBool = cType, cText, cNum, cWord, cKey, cPink, cBool Nenue@6: local print = function(...) print('Cooldown', ...) end Nenue@6: local function GetPrint (trace) Nenue@6: return trace and print or function() end Nenue@6: end Nenue@6: Nenue@6: Nenue@6: local item_spells = { Nenue@6: ['PvP Trinket'] = 42292, Nenue@6: ['Burning Mirror'] = 184270, Nenue@6: } Nenue@6: Nenue@6: T.defaults.spirit.cooldown = { Nenue@6: Nenue@6: alpha = 1, Nenue@6: alpha_ooc = 0.2, Nenue@6: inverse = false, Nenue@6: persist = false, Nenue@6: desaturated = false, Nenue@6: fill_inverse = true, Nenue@6: size = 24, Nenue@6: counterText = "%p", Nenue@6: subCounterText = "%.p", Nenue@6: chargesText = "%s", Nenue@6: justifyH = 'CENTER', Nenue@6: justifyV = 'TOP', Nenue@6: Nenue@6: iconText = "%p", Nenue@6: leftText = "%p", Nenue@6: rightText = "%n / %d", Nenue@6: passive = { Nenue@6: icon = { Nenue@6: desaturated = false, Nenue@6: color = {1, 1, 1, 1}, Nenue@6: blend = 'BLEND' Nenue@6: } Nenue@6: }, Nenue@6: active = { Nenue@6: icon = { Nenue@6: desaturated = false, Nenue@6: color = {1, 1, 1, .6}, Nenue@6: blend = 'ADD' Nenue@6: } Nenue@6: }, Nenue@6: Nenue@6: --- control displays of aura information in cooldown displays Nenue@6: showAura = false, Nenue@6: cooldownAura = { Nenue@6: icon = { Nenue@6: desaturated = true, Nenue@6: color = {0,1,0,1}, Nenue@6: } Nenue@6: } Nenue@6: } Nenue@6: Nenue@6: local p = mod.prototype.trigger.cooldown Nenue@6: --@end-debug@ Nenue@6: p.class = 'trigger' Nenue@6: p.type = 'cooldown' Nenue@6: p.cvars = { Nenue@6: } Nenue@6: --- Sets initial values before dry Event is fired to check for presence Nenue@6: p.Init = function(self, spellID, caster, tristate, minValue, maxValue) Nenue@6: local print = GetPrint(self.trace) Nenue@6: Nenue@6: self.spellID = spellID and spellID or self.spellID Nenue@6: self.unit = caster and caster or self.unit Nenue@6: self.persist = tristate and tristate or self.persist Nenue@6: self.minValue = minValue and minValue or tonumber(self.minValue) Nenue@6: self.maxValue = maxValue and maxValue or tonumber(self.maxValue) Nenue@6: Nenue@6: --- current and last state values need to be flipped for inverted conditional Nenue@6: --- last state is defined in case it needs to be overridden to ensure proper frame update Nenue@6: 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: print(cWord(' inverse=')..cBool(self.cvars.inverse)) Nenue@6: if self.cvars.inverse then Nenue@6: self.flags = { Nenue@6: active = HIDDEN, Nenue@6: active_prev = ACTIVE, Nenue@6: passive = PASSIVE, Nenue@6: passive_prev = PASSIVE, Nenue@6: hidden = PASSIVE, Nenue@6: hidden_prev = HIDDEN, Nenue@6: } Nenue@6: else Nenue@6: self.flags = { Nenue@6: active = ACTIVE, Nenue@6: active_prev = HIDDEN, Nenue@6: passive = PASSIVE, Nenue@6: passive_prev = HIDDEN, Nenue@6: hidden = HIDDEN, Nenue@6: hidden_prev = PASSIVE Nenue@6: } Nenue@6: end Nenue@6: if not (self.spellID or self.spellName) then Nenue@6: self.debug_info('No valid spell ID or Name') Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: local GetItemCooldown, GetItemInfo = GetItemCooldown, GetItemInfo Nenue@6: p.Query = function(self) Nenue@6: local print = GetPrint(self.trace) Nenue@6: local id = self.inventoryID or self.itemID or self.spellID Nenue@6: local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count Nenue@6: Nenue@6: --- checked in order of precedence Nenue@6: if self.inventoryID then Nenue@6: print(cText(' type'), cWord('inventory slot'), cNum(id)) Nenue@6: self.cooldownType = CD_SLOT Nenue@6: start, duration, enabled = GetInventoryItemCooldown('player', id) Nenue@6: charges, maxCharges, chargeStart, chargeDuration = nil, nil, nil, nil Nenue@6: count = GetInventoryItemCount('player', id) Nenue@6: usable = name and true or false Nenue@6: elseif self.itemID then Nenue@6: self.cooldownType = CD_ITEM Nenue@6: Nenue@6: start, duration, enabled = GetItemCooldown(self.itemID) Nenue@6: print(GetItemCooldown(self.itemID)) Nenue@6: print(GetItemInfo(id)) Nenue@6: Nenue@6: elseif self.spellID then Nenue@6: self.cooldownType = CD_SPELL Nenue@6: name = GetSpellInfo(self.spellID) Nenue@6: start, duration, enabled = GetSpellCooldown(self.spellID) Nenue@6: charges, maxCharges, chargeStart, chargeDuration = GetSpellCharges(self.spellID) Nenue@6: count = GetSpellCount(self.spellID) Nenue@6: usable = true -- they still exist even when dead Nenue@6: else Nenue@6: self.unit = 'notaunit' Nenue@6: T:Print('Timer \''..tostring(self.timerName)..'\' doesn\'t have a valid status ID.') Nenue@6: end Nenue@6: Nenue@6: -- may not have been stored for some reason Nenue@6: if charges and not self.maxCharges then Nenue@6: self.maxCharges = maxCharges Nenue@6: end Nenue@6: Nenue@6: print('cooldown.Query(',id,')', name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration) Nenue@6: return name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count Nenue@6: end Nenue@6: Nenue@6: p.Set = function(self, ...) Nenue@6: local print = GetPrint(self.trace) Nenue@6: Nenue@6: --name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count Nenue@6: local name Nenue@6: name, self.usable, self.start, self.duration, self.enabled, self.charges, self.chargeStart, self.chargeDuration, self.count = ... Nenue@6: if name then Nenue@6: self.spellName = name Nenue@6: end Nenue@6: Nenue@6: if self.duration and self.start then Nenue@6: self.expires = self.start + self.duration Nenue@6: else Nenue@6: self.expires = 0 Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: p.Value = function(self) Nenue@6: return (self.charges and self.charges < self.maxCharges) and ((GetTime() - self.chargeStart) / self.chargeDuration) or ((GetTime() - self.start) / self.duration) Nenue@6: end Nenue@6: Nenue@6: p.SetText = mod.SetText Nenue@6: Nenue@6: --- Assign where meaning won't be amibiguous Nenue@6: local Cooldown_OnCast = function(self) Nenue@6: self.triggerState = true Nenue@6: end Nenue@6: Nenue@6: local Cooldown_OnUpdate = function(self, event) Nenue@6: local print = GetPrint(self.trace) Nenue@6: Nenue@6: if self.triggerState then Nenue@6: print(cWord('Event'), cText(self.timerName)) Nenue@6: if not event then Nenue@6: print(' *', cWord('Poke')) Nenue@6: end Nenue@6: local diff = 'start='..cText(self.start)..' duration='..cText(self.duration)..' charges='.. Nenue@6: cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration) Nenue@6: local name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count = self:Query() Nenue@6: Nenue@6: -- If we want and can, pull aura data and use that in place of cooldown information Nenue@6: local expires, hasAura, _ Nenue@6: if self.cvars.showAura then Nenue@6: print(cText('UnitAura'), self.unit, self.spellName, nil, 'HELPFUL') Nenue@6: local name, _, _, count, _, auraDuration, auraExpires = UnitAura(self.unit , self.spellName, nil, 'HELPFUL') Nenue@6: if name and (auraDuration ~= self.auraDuration or auraExpires ~= self.auraExpires) then Nenue@6: Nenue@6: print(cText('aura check ='), cBool(name), 's='..cNum(count), 'd='..cNum(auraDuration), 'e='..cNum(auraExpires)) Nenue@6: start = auraExpires - auraDuration Nenue@6: duration = auraDuration Nenue@6: expires = auraExpires Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: -- print(name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count) Nenue@6: if duration ~= self.duration or Nenue@6: start ~= self.start or Nenue@6: chargeStart ~= self.chargeStart Nenue@6: or charges ~= self.charges then Nenue@6: print('a variable has changed') Nenue@6: local state Nenue@6: Nenue@6: Nenue@6: if duration == 0 and charges == self.maxCharges then Nenue@6: print(cText(' cooldown has reset, drop it from the queue')) Nenue@6: state = self.cvars.persist and self.flags.passive or self.flags.hidden Nenue@6: self.triggerState = nil Nenue@6: print(' ', cText('dropping'), cWord(self.timerName), cText('from spell tracking')) Nenue@6: Nenue@6: Nenue@6: self:Stats(state) Nenue@6: else Nenue@6: if duration ~= 0 then Nenue@6: print(cText(' cooldown has a hard duration')) Nenue@6: if duration > T.GCD and self.displayState ~= self.flags.active then Nenue@6: print(cText(' and its > GCD, update it')) Nenue@6: state = self.flags.active Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: if charges then Nenue@6: print(cText(' cooldown has charges')) Nenue@6: if charges ~= self.charges or chargeStart ~= self.chargeStart then Nenue@6: print(cText(' charges count or starting time has changed')) Nenue@6: state = self.flags.active Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: self:Stats(state) Nenue@6: end Nenue@6: Nenue@6: Nenue@6: -- form ID, id type, displayState, prevState Nenue@6: --T:Dispatch('TK_COOLDOWN_UPDATE', self.spellID, self.cooldownType, state, self.displayState) Nenue@6: if state then Nenue@6: self:Set(name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count) Nenue@6: self.expires = charges and (self.chargeStart + self.chargeDuration) or (self.start + self.duration) Nenue@6: self:SetState(state) Nenue@6: --print(' ', cText('SetState'), cNum(self.displayState), 'from', cNum(self.prevState), cWord(self.timerName)) Nenue@6: print(' ',diff) Nenue@6: print(' start='..cText(self.start)..' duration='..cText(self.duration)..' charges='.. Nenue@6: cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration)) Nenue@6: end Nenue@6: elseif self.cooldownType == CD_SPELL then Nenue@6: if duration == 0 and charges == self.maxCharges and self.displayState == HIDDEN then Nenue@6: print(cKey(self.timerName), cText('post-framescript clean-up')) Nenue@6: self.triggerState = nil Nenue@6: end Nenue@6: end Nenue@6: end Nenue@6: --self:DumpMessages() Nenue@6: Nenue@6: end Nenue@6: Nenue@6: p.Stats = function(self, state) Nenue@6: print(self.unit, self.spellName) Nenue@6: local auraName, _, _, auraCharges, _, auraDuration, auraExpires = UnitAura(self.unit, self.spellName, nil, 'HELPFUL') Nenue@6: local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count = self:Query() Nenue@6: print('# GCD =', T.GCD) Nenue@6: print('# SpellCooldown', 's =', start, 'd =', duration, 's =', charges and (charges..'/'..maxCharges) or 'NA') Nenue@6: print('# Aura', auraName and 'yes' or 'no', auraName and ('d='..cNum(auraDuration)) or '', auraName and ('e='..auraExpires)) Nenue@6: print('# Frame', 'state1 =', self.displayState, 'state2 =', self.previousState, state and ('change to '..cWord(state)) or '') Nenue@6: Nenue@6: Nenue@6: end Nenue@6: Nenue@6: --- Event pointers Nenue@6: p.events = { Nenue@6: ['UNIT_SPELLCAST_SUCCEEDED'] = Cooldown_OnCast, Nenue@6: ['UNIT_SPELLCAST_CHANNEL_START'] = Cooldown_OnCast, Nenue@6: ['BAG_UPDATE_COOLDOWN'] = Cooldown_OnUpdate, Nenue@6: ['SPELL_UPDATE_COOLDOWN'] = Cooldown_OnUpdate, Nenue@6: ['SPELL_UPDATE_USABLE'] = Cooldown_OnUpdate, Nenue@6: ['SPELL_UPDATE_CHARGES'] = Cooldown_OnUpdate, Nenue@6: } Nenue@6: Nenue@6: local unpack, select, debugstack = unpack, select, debugstack Nenue@6: p.Event = function(self, e, ...) Nenue@6: local print = GetPrint(self.trace) Nenue@6: Nenue@6: self.event = e Nenue@6: --- dry event case Nenue@6: if not e then Nenue@6: print('onEvent', self.timerName, e) Nenue@6: self.triggerState = true Nenue@6: Cooldown_OnUpdate(self, e, ...) Nenue@6: else Nenue@6: local unit = select(1,...) Nenue@6: if self.unit and unit ~= self.unit and e ~= 'SPELL_UPDATE_COOLDOWN' then Nenue@6: return Nenue@6: end Nenue@6: local args = {...} Nenue@6: 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: Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: p.triggerList = { Nenue@6: ['on_cooldown'] = function(self) Nenue@6: local start, duration, enabled = GetSpellCooldown(self.spellID) Nenue@6: local charges, _, cStart, cDuration = GetSpellCharges(self.spellID) Nenue@6: return (enabled and (duration ~= 0 or start ~= 0 or charges ~= self.maxCharges)), start, duration, enabled, charges, cStart, cDuration Nenue@6: end, Nenue@6: ['not_on_cooldown'] = function(self) Nenue@6: local start, duration, enabled = GetSpellCooldown(self.spellID) Nenue@6: local charges, _, cStart, cDuration = GetSpellCharges(self.spellID) Nenue@6: return (duration == 0 and start == 0 and charges == self.maxCharges), start, duration, enabled, charges, cStart, cDuration Nenue@6: end, Nenue@6: ['buff_active'] = function(self) Nenue@6: 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: return (name and true or false), expires - duration, duration, true, count, 0, 0 Nenue@6: end Nenue@6: }