view Turok/Modules/Timer/Cooldown.lua @ 9:9400a0ff8540

Ugh Timer: - container update directionality - talent update iterates over a non-volatile table to carry out updates - index management steps organized - talentRow status implemented, returns the spell associated with the talent chosen from that row CombatLog: - sort out font controls and unbork arguments
author Nenue
date Sun, 21 Feb 2016 13:08:30 -0500
parents a9b8b0866ece
children
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(...)
  if _G.Devian and _G.DevianDB.workspace ~= 1 then
    _G.print('Cooldown', ...)
  end
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 = "%i",
  subCounterText = "%.p",
  chargesText = "%s",
  justifyH = 'CENTER',
  justifyV = 'TOP',

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

  --- control displays of aura information in cooldown displays
  overrideAura = false,
  overrideDuration = false,
  override = {
    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)

  self.start = 0
  self.duration = 0
  self.expires = 0
  self.charges = nil
  self.charges_max = 0
  self.charge_start = 0
  self.charge_duration = 0
  self.charge_expires = 0
  self.override = false
  self.override_start = 0
  self.override_duration = 0
  self.override_expires = 0

  --- 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 = nil, false, 0, 0, nil, nil, nil, 0, 0
  local override, overrideStart, overrideDuration = false, 0, 0

  --- 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
    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)
    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

  if self.spellName and self.cvars.overrideAura then
    local name, _, _, _, _, d, e = UnitAura(self.unit , self.spellName, nil, 'HELPFUL')
    if name then
      override = true
      overrideDuration = d
      overrideStart = e - d
    end
  end

  if self.overrideDuration and start > 0 then
    override = true
    overrideDuration = self.overrideDuration
    overrideStart = start
  end

  print('cooldown.Query(',id,')', 'name:'..cText(name), 'usable:'..cBool(usable), 'start:'..cNum(start), 'duration:'..cNum(duration), 'enabled:'..cWord(enabled),
    'charges:'.. cNum(charges), 'charge_start:'.. cNum(chargeStart), 'charge_duration:'.. cNum(chargeDuration),
    'override:' .. cBool(override), 'override_start'..cNum(overrideStart), 'override_duration:'.. cNum(overrideDuration))
  return name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, override, overrideStart, overrideDuration
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.charges_max, self.charge_start, self.charge_duration, self.override, self.override_start, self.override_duration = ...
  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

  if self.charges then
    self.charge_expires = self.charge_start + self.charge_duration
  end
  if self.override then
    self.override_expires = self.override_start + self.override_duration
  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, maxCharges, chargeStart, chargeDuration, override, overrideStart, overrideDuration = self:Query()

    --  print(name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, count)
    if duration ~= self.duration or
          start ~= self.start or
    chargeStart ~= self.charge_start or
        charges ~= self.charges or
       override ~= self.override
    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

      if state then
        self:Set(name, usable, start, duration, enabled, charges, maxCharges, chargeStart, chargeDuration, override, overrideStart, overrideDuration)
        self.expires = charges and (self.chargeStart + self.chargeDuration) or (self.start + self.duration)
        self:SetState(state)
        print('   ',diff)
        print('    start='..cText(self.start)..' duration='..cText(self.duration)..' charges='..
            cText(self.charges).. ' chargeStart='..cText(self.chargeStart).. ' chargeDuration='..cText(self.chargeDuration))
      end
    else
      if 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
  end
end

p.Stats = function(self, state)
  print(self.unit, self.spellName)
  if self.spellName then
    local auraName, _, _, auraCharges, _, auraDuration, auraExpires = UnitAura(self.unit, self.spellName, nil, 'HELPFUL')
    print('  # Aura', auraName and 'yes' or 'no', auraName and ('d='..cNum(auraDuration)) or '', auraName and ('e='..auraExpires))
  end

  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('  # 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
}