view Turok/Modules/Combat/Castbar.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
--- Turok
-- Castbar.lua
-- Created: 12/4/2015 11:17 PM
-- @file-author@
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Deals with casting events and works the castingbar interface
local print, tostring, tonumber, rep, pairs, min, format, unpack, select, strpad = print, tostring, tonumber, string.rep, pairs, math.min, string.format, unpack, select, string.rep
local GetTime, UnitCastingInfo, UnitChannelInfo, GetSpellInfo, GetSpellTabInfo = GetTime, UnitCastingInfo, UnitChannelInfo, GetSpellInfo, GetSpellTabInfo
local floor = math.floor
local T, _G = Turok, _G
local db
local PING_CEILING = 190 -- transpacific
local PING_MIDLINE = 100
local PING_FLOOR = 10 -- transcontinental
--@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('Tek', ...)
  end
end
local uprint = function(...)
  if _G.Devian and _G.DevianDB.workspace ~= 1 then
    _G.print('Update', ...)
  end
end
local mprint = function(...)
  if _G.Devian and _G.DevianDB.workspace ~= 1 then
    _G.print('Main', ...)
  end
end

local EVENT_COLOR = '|cFF44FF44'
local EVENT_COLOR2 = '|cFF88FF88'
local DATA_COLOR = '|cFFDD77DD'
--@end-debug@

local mod = T:NewModule("Tek")
function mod:OnInitialize()
  self.db = T.db.tek
  self.castbar = {}
  self.UNIT_SPELLCAST_SENT          = self.SpellCastRequest
  self.UNIT_SPELLCAST_START           = self.SpellCastEvent
  self.UNIT_SPELLCAST_DELAYED         = self.SpellCastEvent
  self.UNIT_SPELLCAST_SUCCEEDED       = self.SpellCastEvent
  self.UNIT_SPELLCAST_STOP            = self.SpellCastEvent
  self.UNIT_SPELLCAST_FAILED          = self.SpellCastEvent
  self.UNIT_SPELLCAST_INTERRUPTED     = self.SpellCastEvent
  self.UNIT_SPELLCAST_CHANNEL_START   = self.SpellCastEvent
  self.UNIT_SPELLCAST_CHANNEL_STOP    = self.SpellCastEvent
  self.UNIT_SPELLCAST_CHANNEL_UPDATE  = self.SpellCastEvent
  self.UNIT_SPELLCAST_INTERRUPTIBLE   = self.SpellCastEvent
  self.UNIT_SPELLCAST_UNINTERRUPTIBLE = self.SpellCastEvent
end

--- events & units
local TRACKED_UNITS = {'player', 'target', 'focus', 'pet'}
local FADE_OUT_TIME, FADE_IN_TIME = 1, .2

local TEXTURE_SUFFIX = {
  ['CHANNEL_START'] = '_channeling',
  ['CHANNEL_UPDATE'] = '_channeling',
  ['CHANNEL_STOPPED'] = '_channeling',
  ['START'] = '_casting',
  ['INTERRUPTED'] = '_interrupted',
  ['SUCCEEDED'] = '_finished',
}

function mod:OnEnable()
  db = self.db
  -- setup castingbar frames
  local c = self.castbar
  for _, unit in pairs(TRACKED_UNITS) do
    local cdb = db[unit]
    --@debug@
    print(DATA_COLOR .. unit .. '|r castbar creation')--@end-debug@

    -- Set frames
    local fn = 'Tek' .. unit .. 'CastBar'
    c[unit] = CreateFrame('Frame', fn, _G[cdb.parent], 'TurokCastingBar')

    local pc = c[unit]
    T.SetFrameLayout(pc, cdb)
    T.SetTextureLayout(pc.icon, cdb.icon or db.icon)
    T.SetStatusTextures(pc, cdb.statusbar or db.statusbar)

    if unit == 'player' then
      T.SetFontLayout(pc.ping, db.ping)
      T.SetFontLayout(pc.downtime, db.downtime)
      pc.pingbar:ClearAllPoints()
      pc.pingbar:SetPoint('TOPRIGHT', pc.background, 'TOPRIGHT', 0, 0)
      pc.pingbar:SetHeight(pc.background:GetHeight())
    else
      if pc.interrupt then
        pc.interrupt:SetSize(pc.icon.size*3, pc.icon.size* 3)
      end

    end

    T.SetFontLayout(pc.casttime, cdb.casttime or db.casttime)
    T.SetFontLayout(pc.spelltext, cdb.spelltext or db.spelltext)

    pc:SetAlpha(0)
    pc.last = nil
    pc.sent = {}
    pc.unit = unit
    pc.SetSpell = self.SetSpell
    pc.SetState = self.SetState
    pc.Update = self.Update
  end


  -- kill default casting bar
  -- T.cbscripts = {CastingBarFrame:GetScript('OnUpdate'), CastingBarFrame:GetScript('OnEvent')}
  CastingBarFrame:SetScript('OnUpdate', nil)
  CastingBarFrame:SetScript('OnEvent', nil)
  CastingBarFrame:Hide()
  PetCastingBarFrame:SetScript('OnUpdate', nil)
  PetCastingBarFrame:SetScript('OnEvent', nil)
  PetCastingBarFrame:Hide()
end

function mod:UpdateLocked()
    for k, v in pairs(self.castbar) do
      if T.unlocked then
        local name, texture, offset, numSpells = GetSpellTabInfo(T.specPage)
        v.value = offset
        v.duration = numSpells
        v.spelltext:SetText(k)
        v.icon:SetTexture(texture)
        v:SetAlpha(1)
        v:Show()
        v:EnableMouse(true)
        v:SetMovable(true)
        v:RegisterForDrag("LeftButton")
        v:SetScript('OnDragStart', function(self)  self:StartMoving() end)
        v:SetScript('OnDragStop', function(self) self:StopMovingOrSizing() end)
      else

        v:SetScript('OnDragStart', nil)
        v:SetScript('OnDragStop', nil)
        v:EnableMouse(false)
        v:SetMovable(false)

        print('Saving bar coordinates post unlock')

        db[k].x = v:GetLeft()
        db[k].y = v:GetTop()
        db[k].anchor = 'BOTTOMLEFT'
        db[k].anchorTo = 'BOTTOMLEFT'
        db[k].parent = 'UIParent'
      end
    end
end

--- Store spell requests for use in tracking lag time
function mod:SpellCastRequest(e, unit, spellName, rank, target, castID)
  print('|cFFFF4400Request sent:|r ', spellName, castID, target)
  self.castbar.player.sent[castID] = {
    spellName = spellName,
    castID = castID,
    sendTime = GetTime()*1000,
  }
end

--- Handle events pertaining to a cast bar
function mod:SpellCastEvent(event, unit, ...)
  if not self.castbar[unit] then
    return
  end

  local u = T.unit[unit]
  local c = self.castbar[unit]

  --- doubling as an invocation source test
  local spellName, rank, castID, spellID = ...

  local channelinfo = T.unit[unit].channeling
  local castinginfo = T.unit[unit].casting
  local sendq = c.sent
  local castd = c.last
  local timestamp = GetTime()*1000

  --todo: remove truncation
  local e = event:match("UNIT_SPELLCAST_([%a_]+)")
  print(GetTime(), '|cFFFF8747'..e..(strpad(' ',12-#e))..'|r |cFFDDFF00#'..tostring(castID)..'|r', spellName, spellID)

  c.channeling = channelinfo and channelinfo[1] or nil
  c.casting    = castinginfo and castinginfo[1] or nil
  if c.channeling then
    -- {name, subText, text, texture, startTime, endTime, isTradeSkill, notInterruptible}
    c.startTime = channelinfo[5]
    c.endTime = channelinfo[6]
    c.nonInterruptible = channelinfo[8]
  elseif c.casting then
    --name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible
    c.startTime = castinginfo[5]
    c.endTime = castinginfo[6]
    c.castID = castinginfo[8]
    c.nonInterruptible = castinginfo[9]
  else
    print('  |cFFFF7700internal data was nil|r')
  end

  --- process the "event"
  local setSpell, setState
  if e == 'START' then
    setSpell = true
    setState = true

    -- player data calculations
    if unit == 'player' then
      if sendq[castID] and sendq[castID].spellName == spellName then
        local sent = sendq[castID]
        print('  |cFF00AAFFmatched:|r', sent.spellName, castID)
        c.pingTime = c.startTime - sent.sendTime
        print('    ping:', c.pingTime)
        for cid, cdata in pairs(sendq) do
          if cdata.sendTime <= sent.sendTime then
            --print('    forgetting #'..cdata.sendTime, cid, cdata.spellName)
            sendq[cid] = nil
          end
        end

        if castd ~= nil then
          print('   downtime calc hit')
          c.downTime = timestamp - castd.endTime
          castd = nil
        else
          c.downTime = nil
        end
        c.castID = castID
      end
    end
  elseif e == 'CHANNEL_START' or e == 'CHANNEL_UPDATE' then
    setSpell = true
    setState = true
    c.event = e

  elseif e == 'STOP' then
    setState = true
    c.casting = nil
    castd = {
      spellName = spellName,
      spellID = spellID,
      castID = castID,
      endTime = timestamp,
    }
    print('    |cFFFFFF00found', timestamp, spellName)

  elseif e == 'CHANNEL_STOP' then
    setState = true
    c.channeling = nil
    castd = {
      spellName = spellName,
      spellID = spellID,
      castID = castID,
      endTime = timestamp,
    }
    print('    |cFFFF0088found', timestamp, spellName)

  elseif e == 'SUCCEEDED'  then
    if c.spellName == spellName and not c.channeling then
      setState = true
      print('      |cFFFF4400',c.spellName,'=',spellName,' pushing event', e)
    end
    -- if there are 'send' args when this fires, it means it's an instant cast
    if sendq[castID] and sendq[castID].spellName == spellName then
      castd = {
        spellName = spellName,
        spellID = spellID,
        castID = castID,
        endTime = timestamp,
      }
      print('    |cFFFFAA00found', timestamp, spellName)
    end

  elseif e =='FAILED' then
    if spellName == c.spellName and not c.channeling then
      setState = true
    end

  elseif e == 'INTERRUPTED' then
    setState = true

  end




  if setSpell then
    print('      |cFF44FF00setting spell', spellName)
  end
  if setState then
    print('      |cFFFF4400pushing event', e)
  end

  if setSpell then
    c:SetSpell(e)
  end
  if setState then
    c:SetState(e)
  end
end


--- Sets all the static elements of the casting bar, such as config values, icon texture, and name
function mod.SetSpell(c, event)
  local _
  local time, alpha = GetTime(), c:GetAlpha()
  local u, cdb  = T.unit[c.unit], c.db

  c.stopped = nil
  c.stopping = nil
  print('New spell:', c.spellName)
  --- use only internal info
  local data = (event == 'CHANNEL_START' or event == 'CHANNEL_UPDATE' or event == 'CHANNEL_STOP') and u.channeling or u.casting
  local spellName, rank, displayName, texture, startTime, endTime, isTradeSkill, castID, nonInterruptible = unpack(data)


  if spellName == nil then
    print("  Can't do arithmetic, no data.")
  else
    print(cText("  Arithmetic vars:"), cNum(startTime), cNum(endTime))
  end

         c.spellName = spellName
            c.castID = castID or 0
              c.rank = rank
       c.displayName = displayName
           c.texture = texture
  c.nonInterruptible = nonInterruptible
      c.isTradeSkill = isTradeSkill
           c.endTime = endTime   or 0
         c.startTime = startTime or 0
          c.duration = c.endTime - c.startTime

  if c.unit == 'player' then
    if c.pingTime then
      print('     ping',c.pingTime, endTime, startTime, c.duration)
      local draw_dist = (c.pingTime / c.duration) * (c.fill_width)
      if draw_dist > c.fill_width then
        draw_dist = c.fill_width
      end
      print('SET AND SHOW PING ', c.pingTime,'on', c.pingbar:GetName())
      c.pingbar:SetWidth(draw_dist)
      c.pingbar:Show()

      local rv = c.pingTime - PING_FLOOR
      local rr = PING_MIDLINE - PING_FLOOR
      local gv = c.pingTime   - PING_MIDLINE
      local gr = PING_CEILING - PING_MIDLINE
      local r = c.pingTime > PING_MIDLINE and 1 or (c.pingTime < PING_FLOOR and 0 or (rv / rr))
      local g = c.pingTime < PING_MIDLINE and 1 or (c.pingTime > PING_CEILING and 0 or 1-(gv / gr))
      print(c.pingTime, rv, '/', rr, '=', r)
      print(c.pingTime, gv, '/', gr, '=', g)

      c.ping:SetText(floor(c.pingTime))
      c.ping:SetTextColor(r,g,0)
      c.ping:Show()
    else
      c.pingbar:Hide()
      c.ping:Hide()
    end
    if c.downTime then
      c.downtime:Show()
      if c.downTime > 1500 then
        c.downtime:SetText(nil)
      else
        c.downtime:SetText(c.downTime)
      end
    else
      c.downtime:Hide()
    end
  else
    if c.nonInterruptible then
      c.interrupt:Show()
    else
      c.interrupt:Hide()
    end
  end

  c.icon:SetTexture(texture)
  c.spelltext:Show()
  c.spelltext:SetText(spellName)

  c.fill_inverse = c.channeling
  -- set timers
end

--- Deals with failed/succeeded/interrupted visuals and fading cues
-- Fired by one of those events, or cast/channel info is returning nothing
function mod:SetState(event)
  if T.unlocked then
    return
  end

  local time = GetTime() * 1000
  print(' ',self:GetName(), '|cFF44FF00event trigger:|r', event)

  -- We want these to be updating no matter what is happening
  if TEXTURE_SUFFIX[event] then
    local cdb = self.db
    if cdb.foreground_texture then
      print('   |cFF00AAFFevent|r '..event..', |cFF00AAFFtexture|r ', cdb.foreground_texture)
      self.foreground:SetVertexColor(unpack(cdb['foreground' .. TEXTURE_SUFFIX[event]] and cdb['foreground' .. TEXTURE_SUFFIX[event]] or cdb.foreground_color))
    else
      self.foreground:SetTexture(unpack(cdb['foreground' .. TEXTURE_SUFFIX[event]] and cdb['foreground' .. TEXTURE_SUFFIX[event]] or cdb.foreground_color))
    end

    if cdb.background_texture then
      print('  texture=', cdb.background_texture)
      self.background:SetVertexColor(unpack(cdb['background' .. TEXTURE_SUFFIX[event]] and cdb['background' .. TEXTURE_SUFFIX[event]] or cdb.background_color))
    else
      self.background:SetTexture(unpack(cdb['background' .. TEXTURE_SUFFIX[event]] and cdb['background' .. TEXTURE_SUFFIX[event]] or cdb.background_color))
    end
  end

  -- are we starting or stopping
  if event == 'START' or event == 'CHANNEL_START' then
    print('   |cFF00AAFFStarting display|r ', self.spellName)
    self:Show()
    if self.__fade:IsPlaying() then
      self.__fade:Stop()
    end

    self:Fade(FADE_IN_TIME, self.alpha)
  else
    if event == 'SUCCEEDED' and not self.channeling then
      print('   |cFF00AAFFsuccess deduced values|r', self.value, 'to', self.duration)
      self.percent = 1
      self.value = self.duration
    end

    -- Actual fading out begins here, anything else is statistical sugar
    if event == 'STOP' or event == 'CHANNEL_STOP' then
      print('yeah we done')
      if self.__fade:IsPlaying() then
        self.__fade:Stop()
      end
      self:Fade(FADE_OUT_TIME, 0)
    end
  end
end

--- Animation loop
function mod:Update()
  local time = GetTime() * 1000
  local u = self.unit
  local s = self

  -- update vals
  if s.casting or s.channeling then
    self.value = s.casting and (time - s.startTime) or (s.endTime - time)
    self.percent = (time - s.startTime) / self.duration
    if time > s.endTime then
      self.value = self.fill_inverse and 0 or s.duration
      self.percent = self.fill_inverse and 0 or 1
      self.elapsed = time - s.endTime
    end
    uprint('   ',self.unit, self.value, self.percent)
  else
    self.value = 1000
    self.percent = 1
  end
  self.casttime:SetText(format("%.2f", self.value / 1000))

  self:SetProgress(self.percent)
end

--- Refresh the CastingBar for things like target change, pet despawn, etc.
-- Will attempt to flush out any existing cast action data, then re-invoke SpellCastEvent() with as much data can be acquired.
-- There is no event data such as spellID directly available, so this is essentially the biggest constraint we have on what
-- the castingbar can depend on.
function mod:UpdateUnit(unit)
  local print = function(...) uprint(...) end
  print(cText 'GUID changed:|cFFFFFF99', unit)
  local c = mod.castbar[unit]
  if c.channeling or c.casting then
    print(cText '  was casting a spell, run STOP event and hide completely')
    c:SetState(c.casting and 'STOP' or 'CHANNEL_STOP')
    c.channeling = nil
    c.casting = nil
    c:Hide()
  end

  if T.unit[unit].casting then
    print(cText('  new target is CASTING'))
    mod:SpellCastEvent('UNIT_SPELLCAST_START', unit)
  end
  if T.unit[unit].channeling then
    print(cText('  new target is CHANNELING'))
    mod:SpellCastEvent('UNIT_SPELLCAST_CHANNEL_START', unit)
  end

  T:UNIT_SPELLCAST(unit)-- name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible
  T:UNIT_CHANNEL(unit)  -- name, subText, text, texture, startTime, endTime, isTradeSkill, notInterruptible

end

function mod:UpdateInterrupt(e, unit, ...)
    mod.castbar[unit].nonInterruptible = (e == 'UNIT_SPELLCAST_UNINTERRUPTIBLE') and true or false
  if unit ~= 'player' and unit ~='pet' then

  end
end

mod.UNIT_PET = function(self, e, unit)
  if unit == 'player' then
    self:UpdateUnit('pet')
  end
end
mod.PLAYER_TARGET_CHANGED = function(self, ...)
  mprint(cPink(self),...)
  self:UpdateUnit('target')
end
mod.PLAYER_FOCUS_CHANGED = function(self)
  self:UpdateUnit('focus')
end