view Turok/Modules/Timer/Cooldown.lua @ 6:a9b8b0866ece

clear out log jam
author Nenue
date Sun, 21 Feb 2016 08:32:53 -0500
parents
children 9400a0ff8540
line wrap: on
line source

--- ${PACKAGE_NAME}
-- @file-author@
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Created: 12/25/2015 5:33 AM
--- Spell cooldown tracking resides here.
--
-- Workflow of cooldown tracking:
--  A tracked spell cast is detected. (UNIT_SPELLCAST_*)
--  That spell ID enters the timer table.
--  The table is read by a handler that fires on the next frame, when cooldown information is available.  (COOLDOWN_*)
--  Set() is called on the corresponding timer frame, and frame script takes over.
--  Timer table spells are polled on each COOLDOWN_* event, re-applying Set() when certain conditions are met.
--  The framescript or certain handlers will remove the timer table entry when there are no more positive conditions.
--
local tostring, tonumber, tinsert = tostring, tonumber, tinsert
local GetTime, GetSpellInfo, GetInventoryItemCooldown, GetSpellCooldown, PlaySoundFile = GetTime, GetSpellInfo, GetInventoryItemCooldown, GetSpellCooldown, PlaySoundFile
local GetSpellCharges, GetSpellCount, GetInventoryItemCount, UnitAura = GetSpellCharges, GetSpellCount, GetInventoryItemCount, UnitAura
local IsUsableItem, IsUsableSpell, GetItemSpell = IsUsableItem, IsUsableSpell, GetItemSpell
local xpcall = xpcall

local CD_SLOT, CD_ITEM, CD_SPELL = 1, 2, 3
local HIDDEN, PASSIVE, ACTIVE  = 0, 1, 2
local format, ceil = string.format, math.ceil
local strrep, gsub, pairs = string.rep, string.gsub, pairs
local mod = Turok.modules.TimerControl
local T = Turok
local db
local FADE_TIME = 0.2
--@debug@
local cType, cText, cNum, cWord, cKey, cPink, cBool = cType, cText, cNum, cWord, cKey, cPink, cBool
local print = function(...) print('Cooldown', ...) end
local function GetPrint (trace)
  return trace and print or function() end
end


local item_spells = {
  ['PvP Trinket'] = 42292,
  ['Burning Mirror'] = 184270,
}

T.defaults.spirit.cooldown = {

  alpha = 1,
  alpha_ooc = 0.2,
  inverse = false,
  persist = false,
  desaturated = false,
  fill_inverse = true,
  size = 24,
  counterText = "%p",
  subCounterText = "%.p",
  chargesText = "%s",
  justifyH = 'CENTER',
  justifyV = 'TOP',

  iconText = "%p",
  leftText = "%p",
  rightText = "%n / %d",
  passive = {
    icon = {
      desaturated = false,
      color = {1, 1, 1, 1},
      blend = 'BLEND'
    }
  },
  active = {
    icon = {
      desaturated = false,
      color = {1, 1, 1, .6},
      blend = 'ADD'
    }
  },

  --- control displays of aura information in cooldown displays
  showAura = false,
  cooldownAura = {
    icon = {
      desaturated = true,
      color = {0,1,0,1},
    }
  }
}

local p = mod.prototype.trigger.cooldown
--@end-debug@
p.class  = 'trigger'
p.type = 'cooldown'
p.cvars = {
}
--- Sets initial values before dry Event is fired to check for presence
p.Init = function(self, spellID, caster, tristate, minValue, maxValue)
  local print = GetPrint(self.trace)

  self.spellID = spellID and spellID or self.spellID
  self.unit = caster and caster or self.unit
  self.persist = tristate and tristate or self.persist
  self.minValue = minValue and minValue or tonumber(self.minValue)
  self.maxValue = maxValue and maxValue or tonumber(self.maxValue)

  --- current and last state values need to be flipped for inverted conditional
  --- last state is defined in case it needs to be overridden to ensure proper frame update
  print(cWord('Load:'),cNum(self.spellID or self.inventoryID or self.itemID), cText(self.spellName or GetItemSpell('player', self.inventoryID or self.itemID)) )
  print(cWord('  inverse=')..cBool(self.cvars.inverse))
  if self.cvars.inverse then
    self.flags = {
      active = HIDDEN,
      active_prev = ACTIVE,
      passive = PASSIVE,
      passive_prev = PASSIVE,
      hidden = PASSIVE,
      hidden_prev = HIDDEN,
    }
  else
    self.flags = {
      active = ACTIVE,
      active_prev = HIDDEN,
      passive = PASSIVE,
      passive_prev = HIDDEN,
      hidden = HIDDEN,
      hidden_prev = PASSIVE
    }
  end
  if not (self.spellID or self.spellName) then
    self.debug_info('No valid spell ID or Name')
  end
end

local GetItemCooldown, GetItemInfo = GetItemCooldown, GetItemInfo
p.Query = function(self)
  local print = GetPrint(self.trace)
  local id  = self.inventoryID or self.itemID or self.spellID
  local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count

  --- checked in order of precedence
  if self.inventoryID then
    print(cText('   type'), cWord('inventory slot'), cNum(id))
    self.cooldownType = CD_SLOT
    start, duration, enabled = GetInventoryItemCooldown('player', id)
    charges, maxCharges, chargeStart, chargeDuration = nil, nil, nil, nil
    count = GetInventoryItemCount('player', id)
    usable = name and true or false
  elseif self.itemID then
    self.cooldownType = CD_ITEM

    start, duration, enabled = GetItemCooldown(self.itemID)
    print(GetItemCooldown(self.itemID))
    print(GetItemInfo(id))

  elseif self.spellID then
    self.cooldownType = CD_SPELL
    name = GetSpellInfo(self.spellID)
    start, duration, enabled = GetSpellCooldown(self.spellID)
    charges, maxCharges, chargeStart, chargeDuration = GetSpellCharges(self.spellID)
    count = GetSpellCount(self.spellID)
    usable = true -- they still exist even when dead
  else
    self.unit = 'notaunit'
    T:Print('Timer \''..tostring(self.timerName)..'\' doesn\'t have a valid status ID.')
  end

  -- may not have been stored for some reason
  if charges and not self.maxCharges then
    self.maxCharges = maxCharges
  end

  print('cooldown.Query(',id,')', name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration)
  return name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count
end

p.Set = function(self, ...)
  local print = GetPrint(self.trace)

  --name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count
  local name
  name, self.usable, self.start, self.duration, self.enabled, self.charges, self.chargeStart, self.chargeDuration, self.count = ...
  if name then
    self.spellName = name
  end

  if self.duration and self.start then
    self.expires = self.start + self.duration
  else
    self.expires = 0
  end
end

p.Value = function(self)
  return (self.charges and self.charges < self.maxCharges) and ((GetTime()  - self.chargeStart) / self.chargeDuration) or ((GetTime()  - self.start) / self.duration)
end

p.SetText = mod.SetText

--- Assign where meaning won't be amibiguous
local Cooldown_OnCast = function(self)
  self.triggerState = true
end

local Cooldown_OnUpdate = function(self, event)
  local print = GetPrint(self.trace)

  if self.triggerState then
    print(cWord('Event'), cText(self.timerName))
    if not event then
      print(' *', cWord('Poke'))
    end
    local diff = 'start='..cText(self.start)..' duration='..cText(self.duration)..' charges='..
        cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration)
    local name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count = self:Query()

    -- If we want and can, pull aura data and use that in place of cooldown information
    local expires, hasAura, _
    if self.cvars.showAura then
      print(cText('UnitAura'), self.unit, self.spellName, nil, 'HELPFUL')
      local name, _, _, count, _, auraDuration, auraExpires = UnitAura(self.unit , self.spellName, nil, 'HELPFUL')
      if name and (auraDuration ~= self.auraDuration or auraExpires ~= self.auraExpires) then

        print(cText('aura check ='), cBool(name), 's='..cNum(count), 'd='..cNum(auraDuration), 'e='..cNum(auraExpires))
        start = auraExpires - auraDuration
        duration = auraDuration
        expires = auraExpires
      end
    end

    --  print(name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count)
    if duration ~= self.duration or
          start ~= self.start or
          chargeStart ~= self.chargeStart
          or charges ~= self.charges then
      print('a variable has changed')
      local state


      if duration == 0 and charges == self.maxCharges then
        print(cText('  cooldown has reset, drop it from the queue'))
        state = self.cvars.persist and self.flags.passive or self.flags.hidden
        self.triggerState = nil
        print('  ', cText('dropping'), cWord(self.timerName), cText('from spell tracking'))


        self:Stats(state)
      else
        if duration ~= 0 then
          print(cText('  cooldown has a hard duration'))
          if duration > T.GCD and self.displayState ~= self.flags.active then
            print(cText('    and its > GCD, update it'))
            state = self.flags.active
          end
        end

        if charges then
          print(cText('  cooldown has charges'))
          if charges ~= self.charges or chargeStart ~= self.chargeStart then
            print(cText('  charges count or starting time has changed'))
            state = self.flags.active
          end
        end

        self:Stats(state)
      end


      -- form ID, id type, displayState, prevState
      --T:Dispatch('TK_COOLDOWN_UPDATE', self.spellID, self.cooldownType, state, self.displayState)
      if state then
        self:Set(name, usable, start, duration, enabled, charges, chargeStart, chargeDuration, count)
        self.expires = charges and (self.chargeStart + self.chargeDuration) or (self.start + self.duration)
        self:SetState(state)
        --print('  ', cText('SetState'), cNum(self.displayState), 'from', cNum(self.prevState), cWord(self.timerName))
        print('   ',diff)
        print('    start='..cText(self.start)..' duration='..cText(self.duration)..' charges='..
            cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration))
      end
    elseif self.cooldownType == CD_SPELL then
      if duration == 0 and charges == self.maxCharges and self.displayState == HIDDEN then
        print(cKey(self.timerName), cText('post-framescript clean-up'))
        self.triggerState = nil
      end
    end
  end
  --self:DumpMessages()

end

p.Stats = function(self, state)
  print(self.unit, self.spellName)
  local auraName, _, _, auraCharges, _, auraDuration, auraExpires = UnitAura(self.unit, self.spellName, nil, 'HELPFUL')
  local name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count = self:Query()
  print('# GCD =', T.GCD)
  print('# SpellCooldown', 's =', start, 'd =', duration, 's =', charges and (charges..'/'..maxCharges) or 'NA')
  print('# Aura', auraName and 'yes' or 'no', auraName and ('d='..cNum(auraDuration)) or '', auraName and ('e='..auraExpires))
  print('# Frame', 'state1 =', self.displayState, 'state2 =', self.previousState, state and ('change to '..cWord(state)) or '')


end

--- Event pointers
p.events = {
  ['UNIT_SPELLCAST_SUCCEEDED'] = Cooldown_OnCast,
  ['UNIT_SPELLCAST_CHANNEL_START'] = Cooldown_OnCast,
  ['BAG_UPDATE_COOLDOWN'] = Cooldown_OnUpdate,
  ['SPELL_UPDATE_COOLDOWN'] = Cooldown_OnUpdate,
  ['SPELL_UPDATE_USABLE'] = Cooldown_OnUpdate,
  ['SPELL_UPDATE_CHARGES'] = Cooldown_OnUpdate,
}

local unpack, select, debugstack = unpack, select, debugstack
p.Event = function(self, e, ...)
  local print = GetPrint(self.trace)

  self.event = e
  --- dry event case
  if not e then
    print('onEvent', self.timerName, e)
    self.triggerState = true
    Cooldown_OnUpdate(self, e, ...)
  else
    local unit = select(1,...)
    if self.unit and unit ~= self.unit and  e ~= 'SPELL_UPDATE_COOLDOWN' then
      return
    end
    local args = {...}
    local success, d = xpcall(function() self.events[e](self, e, unpack(args)) end, function(m) print(self.name .."\n".. m .. "\n".. debugstack(3)) end)

  end
end

p.triggerList = {
  ['on_cooldown'] = function(self)
    local start, duration, enabled = GetSpellCooldown(self.spellID)
    local charges, _, cStart, cDuration = GetSpellCharges(self.spellID)
    return (enabled and (duration ~= 0 or start ~= 0 or charges ~= self.maxCharges)), start, duration, enabled, charges, cStart, cDuration
  end,
  ['not_on_cooldown'] = function(self)
    local start, duration, enabled = GetSpellCooldown(self.spellID)
    local charges, _, cStart, cDuration = GetSpellCharges(self.spellID)
    return (duration == 0 and start == 0 and charges == self.maxCharges), start, duration, enabled, charges, cStart, cDuration
  end,
  ['buff_active'] = function(self)
    local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura(self.unit, self.spellName, self.filters)
    return (name and true or false), expires - duration, duration, true, count, 0, 0
  end
}