diff Turok/Modules/Combat/CombatLog.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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Turok/Modules/Combat/CombatLog.lua	Sun Feb 21 08:32:53 2016 -0500
@@ -0,0 +1,758 @@
+--- Combat
+-- @file-author@
+-- @project-revision@ @project-hash@
+-- @file-revision@ @file-hash@
+-- Created: 1/11/2016 4:27 PM
+--- Data collector for API Combat Log Event
+local T, _G = Turok, _G
+local mod = T:NewModule("Combat")
+local pairs, ipairs, select, concat, format, tinsert, wipe = pairs, ipairs, select, table.concat, string.format, tinsert, table.wipe
+local GetSpellTexture, CreateFrame, GetTime, unpack, floor = GetSpellTexture, CreateFrame, GetTime, unpack, floor
+local db
+local cText, cNum, cKey, cWord, cPink, cType, cBool = cText, cNum, cKey, cWord, cPink, cType, cBool
+local print = function(...)
+  if _G.Devian and _G.DevianDB.workspace ~= 1 then
+    _G.print('CombatText', ...)
+  end
+end
+
+
+--- performance constants
+local SCT_NAME = 'TurokSCT%s'
+local SCT_TEMPLATE = 'TurokSCTAnchorTemplate'
+local SCT_PREGAME = 5
+local SCT_MESSAGE_TEMPLATE = 'TurokCombatMessageTemplate'
+local SCT_MESSAGE_NAME = 'CombatString%d'
+local LOG_TEMPLATE =  'TurokCombatLogAnchorTemplate'
+
+--- UX structures
+T.defaults.CombatText = {
+  width = 400, height = 800,
+   parent = 'UIParent',
+  Outgoing = {
+    anchor = 'RIGHT', anchorTo = 'RIGHT',
+    x = -400, y = -200,
+  },
+  Incoming = {
+    anchor = 'LEFT', anchorTo = 'LEFT',parent = 'UIParent',
+    x = 400, y = -200,
+  },
+  DoTTracker = {
+    anchor = 'BOTTOMLEFT', anchorTo = 'BOTTOM', parent = 'UIParent',
+    x = 80, y = 400,
+  },
+  defaultFont = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Regular.ttf", 18, 'OUTLINE'},
+  defaultAnimation = 'slide',
+
+  textFonts = {
+    font1 = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Regular.ttf" , 30, 'OUTLINE'},
+    font2 = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Regular.ttf" , 24, 'OUTLINE'},
+    font3 = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Bold.ttf" , 20, 'OUTLINE'},
+  },
+
+  --- [(string) combatEvent] = {[1] = (string) format [, [2] = fontKey]}
+  --- Substitution values
+  -- %d [1] amount [2] overkill [3] absorbed [4] blocked
+  -- %s [5] spell [6] caster [7] school
+  textFormat = {
+    ['SWING_DAMAGE'] = {'%d'},
+    ['SPELL_DAMAGE'] = {'%d'},
+    ['RANGE_DAMAGE'] = {'%d'},
+    Incoming = {
+      ['SWING_DAMAGE'] = {'-%d'},
+      ['SPELL_DAMAGE'] = {'-%d (%s)'},
+      ['RANGE_DAMAGE'] = {'-%d'},
+    },
+  },
+  textModifiers ={
+    critical = {'%s!', 'pop'},
+    overKill = {'%s |cFF0088FFKilling Blow!|r', 'slide'},
+    multistrike = {'<%s>', 'lateralSlide', 'font3'},
+    absorbed = {'%s (%d)', 'slide'},
+    blocked = {'%s {%d}', 'slide'},
+    pet = {'(%s)', 'lateralSlide'},
+    grouped = {'%s (%d hit)', 'slide'},
+  },
+  --- [AnimationGroup key] = {[XML attrib] = [value], d[x/y] = (number) 0-1 }
+   -- d[x/y] indicates the proportional relevance of each FontString dimension when frames are displaced by a new event
+   -- x/y    indicates the ranges of movement made by an animation, and are also considered when calculating displacement
+  animation = {
+    slide = {
+      x = 0,   dx = 0,
+      y = 300, dy = 1,
+      duration = 2
+    },
+    lateralSlide = {
+      x = 300, dx = 1,
+      y = 0,   dy = 0,
+      duration = 2
+    },
+    pop = {
+      toScale = 1.4,
+      fromScale = 0.1,
+      duration = 0.14,
+      dx = 0, dy = 1,
+    },
+    fadeOut = {change = -1, duration = 0.5},
+  },
+}
+--- [1] text wrapper [2] animation type
+local dotEvents = {
+  SPELL_AURA_APPLIED = true,
+  SPELL_PERIODIC_DAMAGE = true,
+  SPELL_AURA_REMOVED = true,
+  SPELL_AURA_REFRESHED = true,
+}
+local petGUID = {}
+
+------------- data structures
+local defaultAnimation, defaultFont
+local criticalModifier, absorbedModifier, blockedModifier, overKillModifier, multistrikeModifier, groupedModifier
+local criticalAnimation, absorbedAnimation, blockedAnimation, overKillAnimation, multistrikeAnimation, groupedAnimation
+local criticalFont, absorbedFont, blockedFont, overKillFont,multistrikeFont, groupedFont
+local textFonts = {}
+local textFormat = {}
+local animation = {}
+local spellCache = {}
+local DoTFrames = {}
+
+local dotTrackingEvents = {
+  ['SPELL_AURA_APPLIED'] = true,
+  ['SPELL_AURA_REMOVED'] = true,
+  ['SPELL_AURA_REFRESHED'] = true,
+}
+
+--- multi-hit events data
+-- [(string) localized name] = {
+--   [1] = (bool)   controlled channel
+--   [2] = (number) minimum time to wait for next damage tick
+--   [3] = (number) maximum time to log grouped spell hits
+--   [4] = (string) combatEvent full string
+--   [5] = (string) combatEvent short prefix (as opposed to SPELL_PERIODIC)
+--   [6] = (string) combatEvent short suffix (as opposed to AURA_APPLIED)
+-- }
+-- tailing fields are set to reduce the number of arguments passed around
+local groupedSpells = {
+  global = {false, 1, 'SPELL_PERIODIC_DAMAGE', 'SPELL', 'DAMAGE'},
+  ['Crimson Tempest'] = {false,   0.3,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- shockwave dot
+  ['Mind Sear'] =       {false,   1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- scanning aoe
+  ['Searing Insanity'] =       {false,   1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- scanning aoe
+  ['Ice Nova'] =        {false, 0.4,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- shockwave aoe
+  ['Blizzard'] =        {false,   1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- scanning aoe
+  ['Frozen Orb'] =      {false,   1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'},
+  ['Ice Bomb'] =        {false, 0.4,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- shockwave aoe
+  ['Comet Storm'] =     {false, 2.5,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- staggered multi-hit
+  ['Barrage'] =         {false, 1.1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- projectile vommit
+  ['Glaive Toss'] =     {false,   3,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- path projectile,
+  ['Doom Nova'] =       {false, 0.4,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}
+}
+
+local dotSpellIndex = {
+  ['Shadow Word: Pain'] = {'HARMFUL|PLAYER'},
+  ['Vampiric Touch'] = {'HARMFUL|PLAYER'}
+}
+
+local offhandSpellIndex = {
+  ['Execute'] = {'Execute Off-Hand'},
+  ['Mutilate'] = {'Mutilate Off-Hand'},
+  ['Stormstrike'] = {'Stormstrike Off-Hand'},
+}
+
+--- stored as a list of field representations to be concatenated together within a loadstring def
+
+
+
+--- Tracking tables
+local groupedQueue = {}
+local groupedEvents = {}
+local offhandQueue = {}
+local sct_format = {}
+
+
+local function GetGUIDInfo(guid)
+  local unitType, flags1, flags2 = guid:match('(%a+)\-(%x+)\-(%x+)')
+  return concat({unitType, flags1, flags2},'|r::|cFF0088FF')..'|r'
+end
+
+local SpellSchoolColors = {
+  [1] = {255, 255,0},     -- physical
+  [2] = {255, 230, 128},  -- holy
+  [3] = {255, 255, 128},  -- holy+phys
+  [4] = {255, 128, 0},    -- fire
+  [8] = {77, 255, 77},    -- nature
+  [16] = {128, 255, 255}, -- frost
+  [20] = {255, 192, 128},   -- frostfire
+  [32] = {128, 128, 255}, -- shadow
+  [48] = {128, 192, 255}, -- shadow+frost
+  [64] = {255, 128, 255}, -- arcane,
+  [72] = {255, 128, 128}, -- spellstorm,
+}
+
+local h = '|cFF%02X%02X%02X'
+local SpellSchoolColor = function (flags, spellName)
+  local i = 64
+  local rA, gA, bA
+  if SpellSchoolColors[flags] then
+    print(flags, 'match')
+    print(format('%02X%02X%02X', unpack(SpellSchoolColors[flags])))
+    return format(h, unpack(SpellSchoolColors[flags]))
+  end
+
+  repeat
+    local rB, gB, bB = unpack(SpellSchoolColors[i])
+    if i <= flags then
+      if not rA then
+        rA = rB
+        gA = gB
+        bA = bB
+      else
+        rA = (rA+rB)/2
+        gA = (gA+gB)/2
+        bA = (bA+bB)/2
+      end
+      print('test:', cWord(i), '<=', cKey(flags), '=', (i <= flags), cPink(rA), cNum(gA), cText(bA))
+      flags = flags - i
+    else
+      print(i, 'skip')
+    end
+    i = i/2
+  until (i == 1)
+  SpellSchoolColors[flags] = {rA, gA, bA }
+  --print(string.format('%02X%02X%02X', unpack(SpellSchoolColors[flags])))
+  return format(h, unpack(SpellSchoolColors[flags]))
+end
+local myGUID
+
+mod.NewCombatMessage = function(frame, index)
+  local ct = CreateFrame('Frame', SCT_MESSAGE_NAME:format(frame.lineID), frame, SCT_MESSAGE_TEMPLATE)
+  index = index and 'active' or 'expired'
+  frame.lineID = frame.lineID + 1
+  frame.active[#frame.active+1] = ct
+  ct.index = #frame.active
+  ct.x = 0
+  ct.y = 0
+  ct.point = 'BOTTOMLEFT'
+  return ct
+end
+
+--- frame interaction logic
+mod.AddCombatMessage = function(frame, text, icon, animationType, fontKey)
+  local line
+  print(fontKey)
+
+  local expired = frame.expired
+  local active = frame.active
+  local a = 1
+  local shiftY, shiftX
+  local lastFrame
+  --- If animation has overlap delta values, find the last active frame of that animation type and check for overlaps.
+  --- Frames are considered overlapping when the last member's ((height - distance traveled) * delta) is over 0.
+  --- This assumes the same string height for the upcoming frame since wordwrap isn't enabled.
+  print(animationType)
+  if animation[animationType].dx or animation[animationType].dy then
+    print('animation has displacement')
+    if frame.last then
+      lastFrame = frame.last
+      local dp = lastFrame[animationType]:GetProgress()
+      if animation[animationType].dx then
+        local dx = dp * animation[animationType].x
+        shiftX = (lastFrame.string:GetStringWidth() - dx) * animation[animationType].dx
+      end
+      if animation[animationType].dy then
+        local dy = dp * animation[animationType].y
+        shiftY = (lastFrame.string:GetStringHeight() - dy) * animation[animationType].dy
+        print('  ', 'h=', floor(lastFrame.string:GetStringHeight()), 'dY=', dy, 'offsetY=', shiftY)
+      end
+      print(cWord('lastFrame hit:'), lastFrame and lastFrame:GetName(), cNum(shiftX), cNum(shiftY))
+    end
+  end
+
+  -- find a usable frame
+  local currentFrame
+  for i, ct in ipairs(frame.active) do
+    if not currentFrame then
+      if ct.discard then
+        ct.discard=  nil
+        currentFrame = ct
+      end
+    end
+
+    if lastFrame and ct.animationType == animationType and not ct.discard then
+      --print('lastFrame defined, check for overlap')
+      if shiftY > 0 then
+        print(cWord('  * vertical shift'), cNum('+'..floor(shiftY)))
+        ct.y = ct.y + shiftY
+      end
+      if shiftX > 0 then
+        print(cWord('  * horizontal shift'), cNum('+'..floor(shiftX)))
+        ct.x = ct.x + shiftX
+      end
+      ct:SetPoint(ct.point, frame, ct.point, ct.x, ct.y)
+    end
+  end
+  -- if no expired frames became available, make a new one (should max at 20 or so if groupings are right)
+  if  not currentFrame then
+    currentFrame = mod.NewCombatMessage(frame)
+    print(cNum('     creating new string object for the heap'))
+  end
+
+  print(cText(' * Starting'), cPink(animationType), 'on', cKey(currentFrame:GetName()), ' ['..cNum(currentFrame.index)..']')
+
+  if icon then
+    currentFrame.icon:Show()
+    currentFrame.icon:SetTexture(icon)
+  else
+    currentFrame.icon:Hide()
+  end
+  if fontKey  then
+    local newFont = fontKey and textFonts[fontKey] or defaultFont
+
+    local path, size, flags = currentFrame.string:GetFont()
+    path = newFont[1] or path
+    size = newFont[2] or size
+    flags = newFont[3] or flags
+    print(cText('font ('..cWord(fontKey)..'):'), path, size, flags)
+    local result = currentFrame.string:SetFont(path, size, flags)
+    print(cNum('     result:'), cNum(result), currentFrame.string:GetFont())
+    --currentFrame.fontKey = fontKey
+  end
+
+  currentFrame.animationType = animationType
+  currentFrame.string:SetText(text)
+  local cHeight = currentFrame.string:GetStringHeight()
+  currentFrame:SetSize(currentFrame.string:GetStringWidth(), cHeight)
+  currentFrame.icon:SetSize(cHeight, cHeight)
+  currentFrame.y = 0
+  currentFrame.x = 0
+  currentFrame:SetPoint(currentFrame.point, frame, currentFrame.point, currentFrame.x, currentFrame.y)
+  currentFrame[animationType]:Play()
+  frame.last = currentFrame
+end
+
+local GetTextFormatFunc = function(amount, overKill, absorbed, blocked, multistrike, spellName, sourceName, destName)
+  local sub_text = {
+    ['%d'] = amount,
+    ['%o'] = overKill,
+    ['%a'] = absorbed,
+    ['%b'] = blocked,
+    ['%m'] = multistrike,
+    ['%s'] = spellName,
+    ['%n'] = sourceName,
+    ['%t'] = destName,
+  }
+  local func = function(token)
+    print(cPink('gsub run:'), token)
+    return sub_text[token]
+  end
+  return func
+end
+
+--- builds CT messages from combat log event data
+--  separated from frame interaction for auxiliary events such as combat/loot/etc
+local CreateDamageText = function(frame, timestamp, combatEvent, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike, ticks)
+  if combatEvent:match('^SWING') then
+    spellID = 1
+    spellName = 'Attack'
+  elseif combatEvent:match('^RANGE') then
+    spellID = 2
+    spellName = 'Auto Shot'
+  end
+  if not combatEvent:match('DAMAGE') then
+    return
+  end
+
+
+  local fontKey = 'font1'
+  local icon = GetSpellTexture(spellID)
+  local animationType = defaultAnimation
+  local text = amount
+  if textFormat[combatEvent] then
+    local tString, fontKey = unpack(textFormat[combatEvent])
+    if amount > 1000000 then
+      amount = (floor(amount/100000)/10) ..'M'
+    elseif amount > 1000 then
+      amount = (floor(amount/100)/10) ..'k'
+    end
+
+    text = tString:gsub('%%[doabsnt]', GetTextFormatFunc(amount, overKill, absorbed, blocked, multistrike, spellName, sourceName, destName))
+    print("** font override:", '"'..cText(tString)..'",', cPink(font), cNum(size), cWord(outline))
+  end
+
+  print('** getting color data', cText(spellName))
+  if type(effectSchool) == 'number' then
+    text = SpellSchoolColor(effectSchool) ..text..'|r'
+  end
+
+  if overKill > 0 then
+    text = overKillModifier:format(text, overKill)
+    animationType = overKillAnimation
+    fontKey = overKillFont or fontKey
+  end
+  if critical then
+    text = criticalModifier:format(text)
+    animationType = criticalAnimation
+    fontKey = criticalFont or fontKey
+  end
+  if absorbed then
+    text = absorbedModifier:format(text, absorbed)
+    animationType = absorbedAnimation
+    fontKey = absorbedFont or fontKey
+  end
+  if blocked then
+    text = blockedModifier:format(text, blocked)
+    animationType = blockedAnimation
+    fontKey = blockedFont or fontKey
+  end
+  if multistrike then
+    text = multistrikeModifier:format(text, multistrike)
+    animationType = multistrikeAnimation
+    fontKey = multistrikeFont or fontKey
+  end
+
+  print(ticks)
+  if ticks then
+    text = groupedModifier:format(text, ticks)
+    animationType = groupedAnimation
+    fontKey = groupedFont or fontKey
+  end
+
+
+  print('** sending format to SCT:', text, icon, animationType, fontKey)
+
+
+  mod.AddCombatMessage(frame, text .. '|r', icon, animationType, fontKey)
+end
+
+local CT_ShowConsolidated = function()
+  for spellName, queuedSpell in pairs(groupedQueue) do
+    local isChannel, minDelay, combatEvent, sourceName, destName, spellID, school, SCT, timestamp = unpack(queuedSpell)
+    for i, v  in ipairs(queuedSpell) do
+      print('   ', i, '=', v)
+    end
+
+    print(spellName, 'vars:', spellID, spellName, school, SCT, timestamp)
+    if groupedEvents[spellName] and #groupedEvents[spellName] ~= 0 then
+      local amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, hit = 0, -1, 0, 0, 0, 0, 0, 0, 0, 0
+      for i, line in ipairs(groupedEvents[spellName]) do
+        -- extra strike?
+        --{amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike}
+        local amount_n, overKill_n, resisted_n, blocked_n, absorbed_n, critical_n, glancing_n, crushing_n, multi_n, sourceGUID, destGUID, sourceName, destName = unpack (line)
+
+        amount = amount + amount_n
+        if overKill_n > 0   then overKill = overKill + overKill_n end
+        if blocked_n        then blocked = blocked + line[4] end
+        if absorbed_n       then absorbed = absorbed + line[5] end
+        if critical_n       then critical = critical + 1 end
+        if multi_n          then multistrike = multistrike + 1  end
+
+        hit = hit + 1
+      end
+      if overKill == -1 then
+        overKill = overKill + 1
+      end
+      wipe(groupedEvents[spellName])
+      groupedQueue[spellName] = nil
+      print('   expelling', spellName, cText('A:'), cNum(amount), cText('O:'), cNum(overKill), cText('Ticks:'), cNum(hit), cText('Crits:'), cNum(critical), cText(school))
+      print(SCT, timestamp, combatEvent, sourceName, destName, spellID, spellName, school, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, hit)
+      CreateDamageText(SCT, timestamp, combatEvent, sourceName, destName, spellID, spellName, school, amount, -1, nil, nil, nil, false, glancing, crushing, false, multistrike, hit)
+    end
+  end
+end
+
+local CT_ConsolidateText = function (self, timestamp, sourceGUID, destGUID, sourceName, destName, spellID, spellName, school, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike)
+  local archive = groupedSpells[spellName] and groupedSpells[spellName] or groupedSpells.global
+  local isChannel, minDelay, combatEvent = unpack(archive)
+  if not groupedEvents[spellName] then
+    groupedEvents[spellName] = {}
+  end
+
+  if #groupedEvents[spellName] == 0 then
+    groupedQueue[spellName] = {isChannel, minDelay, combatEvent, sourceName, destName, spellID, school, self, timestamp}
+    T:ScheduleTimer(CT_ShowConsolidated, minDelay)
+    print('  starting archive for', select(7, unpack(archive)))
+  else
+    print('  recording into archive', cText(spellName))
+  end
+  tinsert(groupedEvents[spellName], {amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, sourceGUID, destGUID, sourceName, destName, sourceName, destName,})
+end
+
+local CT_OnCast = function(self, event, unit, spellName, lineID, spellID)
+  if unit ~= 'player' and unit ~= 'pet' then
+    return
+  end
+
+  if groupedSpells[spellName] then
+    print(cText('** Spell casting info received'))
+    local isChannel, delay = unpack(groupedSpells[spellName])
+    if isChannel and event == 'CHANNEL_STOP' and groupedEvents[spellName] then
+      T:ScheduleTimer(CT_ShowConsolidated, delay)
+    else
+      if not groupedEvents[spellName] then
+        groupedEvents[spellName] = {}
+      end
+    end
+  end
+end
+
+local CT_Unlock = function(str)
+  for i, CT in pairs(mod.CombatTextFrames) do
+      local frame = CT.frame
+      frame.configMode = (frame.configMode == nil) and true or nil
+      frame:RegisterForDrag(frame.configMode and 'LeftButton' or nil)
+      frame:EnableMouse(frame.configMode and true or false)
+      print(i, frame.configMode and 'ON' or 'OFF')
+      for _, reg in ipairs(frame.configRegions) do
+        if frame.configMode then
+          reg:Show()
+        else
+          reg:Hide()
+        end
+      end
+
+      if frame.configMode then
+        CT.configTimer = T:ScheduleRepeatingTimer(function()
+          print(i, 'config tick')
+          for i, s in ipairs(CT.sample) do
+            CT.OnEvent(frame, unpack(s))
+          end
+        end, 2)
+      else
+        print(CT.configTimer)
+        T:CancelTimer(CT.configTimer)
+      end
+
+  end
+end
+
+--- check for sectors
+local queue = {}
+mod.OnDamage = function(self, event, ...)
+  local isVisible
+
+  local timestamp, combatEvent = ...
+  print(cText('* CT'), cKey(combatEvent))
+  if combatEvent == 'UNIT_DIED' then
+    return
+  end
+  local sourceGUID, sourceName, sourceFlags, _, destGUID, destName, destFlags, _, spellID, spellName, spellSchool = select(4, ...)
+  print('  from', cPink(sourceGUID), 'spell', cNum(spellID), cText(spellName), '->', cKey(destGUID))
+
+  -- SWING starts at arg 10, SPELL/RANGE start at arg 13, ENVIRONMENTAL starts at arg 8
+  local offset = 15
+  print(combatEvent:sub(0,3))
+  if combatEvent:sub(0,3) == 'SWI' then
+    offset = 10
+  elseif combatEvent:sub(0,3) == 'ENV' then
+    offset = 8
+  end
+  local amount, overKill, effectSchool, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike = select(offset, ...)
+
+  print('  dmg', amount, overKill)
+
+  local sc = SpellSchoolColors[effectSchool]
+  if groupedSpells[spellName] then
+    print('* ', cText(spellName), 'to consolidator')
+    CT_ConsolidateText(self, timestamp, sourceGUID, destGUID, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike)
+    return
+  end
+
+  print('displaying on', cWord(self:GetName()))
+  CreateDamageText(self, timestamp, combatEvent, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike)
+end
+
+mod.maxDotFrame = 1
+mod.OnDotEvent = function (self, event, timestamp, combatEvent, ...)
+  print(cWord('DOT'), combatEvent)
+  local sourceGUID, sourceName, sourceFlags, _, destGUID, destName, destFlags, destExtraFlags, spellID, spellName, castSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike = select(2, ...)
+  print(cText('dot from'), cWord(sourceGUID), 'to', cKey(destGUID))
+  if sourceGUID == T.GUID then
+    local p = mod.PeriodicTable
+    p[destGUID] = p[destGUID] or {}
+    local unit = p[destGUID]
+    if not p[spellID] then
+      if not p.frames[#p.frames] then
+        unit[spellID] = CreateFrame('Frame', 'DotBar'..mod.maxDotFrame, p, 'TkDotBarTemplate')
+        mod.maxDotFrame = mod.maxDotFrame + 1
+        unit[spellID].dotID = mod.maxDotFrame
+
+        print('  create new frame ['..cKey(unit[spellID]:GetID(), '] for'), cWord(spellName), cKey(destGUID))
+      else
+        unit[spellID] = p.frames[#p.frames]
+        p.frames[#p.frames] = nil -- remove that entry
+        print('  recycling frame ['..cKey(unit[spellID].dotID, '] for'), cWord(spellName), cKey(destGUID))
+      end
+
+      unit[spellID]:Show()
+    end
+    local dot = unit[spellID]
+    local time = GetTime()
+  end
+end
+
+
+
+
+function mod:OnInitialize()
+  print('This is a thing.')
+  self.UNIT_SPELLCAST_SUCCEEDED = CT_OnCast
+  self.UNIT_SPELLCAST_CHANNEL_START = CT_OnCast
+  self.UNIT_SPELLCAST_CHANNEL_STOP = CT_OnCast
+end
+
+function mod:OnInitialize()
+  mod.db = TurokData.CombatText
+  db = TurokData.CombatText
+  myGUID = T.GUID
+  mod.PeriodicTable = mod.PeriodicTable or CreateFrame('Frame', 'TurokPeriodicFrame', UIParent)
+  mod.PeriodicTable.frames = {}
+
+  local m = mod.db.textModifiers
+  --- These values are going to be looked up a lot, so cache as close as possible
+  criticalModifier = m.critical[1] or '%s CRIT'
+  criticalAnimation = m.critical[2] or 'slide'
+  criticalFont = m.critical[3]
+
+  absorbedModifier = m.absorbed[1] or '%s ABS'
+  absorbedAnimation = m.absorbed[2] or 'slide'
+  absorbedFont = m.absorbed[3]
+
+  blockedModifier = m.blocked[1] or '%s BLK'
+  blockedAnimation = m.blocked[2] or 'slide'
+  blockedFont = m.blocked[3]
+
+  overKillModifier = m.overKill[1] or '%s KILL'
+  overKillAnimation = m.overKill[2] or 'slide'
+  overKillFont = m.overKill[3]
+
+  multistrikeModifier = m.multistrike[1] or '%s MS'
+  multistrikeAnimation = m.multistrike[2] or 'slide'
+  multistrikeFont = m.multistrike[3]
+
+  groupedModifier = m.grouped[1] or '%s'
+  groupedAnimation = m.grouped[2] or 'slide'
+  groupedFont = m.grouped[3]
+
+  --- Same as above, but for specific table values, key is determined by the combat event
+  defaultAnimation = mod.db.defaultAnimation
+  defaultFont = mod.db.defaultFont
+  for k,v in pairs(mod.db.textFormat) do
+    textFormat[k] = {v[1], v[2]}
+    print('imported textFormat.'..k, cText(textFormat[k][1]), cNum(textFormat[k][2]))
+  end
+  for k,v in pairs(mod.db.textFonts) do
+    textFonts[k] = {v[1] or defaultFont[1], v[2] or defaultFont[2], v[3] or defaultFont[3]}
+    print('imported font.'..k, cText(textFonts[k][1]), cNum(textFonts[k][2]), cWord(textFonts[k][3]))
+  end
+
+  for k,v in pairs(mod.db.animation) do
+    animation[k] = {}
+    animation[k].x = v.x
+    animation[k].y = v.y
+    animation[k].dx = v.dx
+    animation[k].dy = v.dy
+    animation[k].fromScale = v.fromScale
+    animation[k].toScale = v.toScale
+    animation[k].deviation = v.deviation
+    animation[k].change = v.change
+    animation[k].fromAlpha = v.fromAlpha
+    animation[k].toAlpha = v.toAlpha
+    animation[k].duration = v.duration
+  end
+end
+
+function mod:OnEnable()
+  T:RegisterChatCommand('tkc', CT_Unlock)
+
+  --- Populate CT frames
+  for name, CT in pairs(mod.CombatTextFrames) do
+    print('create CT', name)
+    -- make frame
+    CT.frame = CT.frame or CreateFrame('Frame', SCT_NAME:format(name), UIParent, SCT_TEMPLATE)
+
+    -- local vars
+    local db = db[name] or db
+    local frame = CT.frame
+
+    -- script defs
+    frame.IsFrameEvent = CT.trigger
+    frame.name = name
+    frame.lineID = 0
+    frame.expired = {}
+    frame.active = {}
+
+    -- frame defs
+    frame:SetPoint(db.anchor, db.parent, db.anchorTo, db.x, db.y)
+    frame:RegisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
+    print('bound ', name, 'with', CT.OnEvent)
+    db.sample = {}
+    local logged = {}
+    frame:SetScript('OnEvent', function(self, e,...)
+      if CT.trigger(e, ...) then
+        print('event trigger fired', name)
+          if #db.sample < 5 and not logged[select(2,...)] then
+            logged[select(2,...)] = true
+            tinsert(db.sample, {e, ...})
+          end
+
+        CT.OnEvent(frame, e, ...)
+      end
+    end)
+
+    -- configurators
+    frame.configHeader:SetText(name)
+    frame:EnableMouse(false)
+
+    -- pre-pop some text frames
+    for i = (CT.lineID or 1), SCT_PREGAME do
+      print(frame:GetName(), 'pre-pop #'..i)
+      mod.NewCombatMessage(frame)
+    end
+  end
+end
+
+
+local damageEvents = {
+  ['SPELL_DAMAGE'] = true,
+  ['SPELL_PERIODIC_DAMAGE'] = true,
+  ['SWING_DAMAGE'] = true,
+  ['RANGE_DAMAGE'] = true,
+  ['SPELL_HEAL'] = true,
+}
+mod.CombatTextFrames = {
+  Incoming = {
+    trigger = function(e, _, c, _, _, _, _, _, d) return (d == T.GUID and damageEvents[c]) end,
+    OnEvent = mod.OnDamage,
+    sample = {
+      {"COMBAT_LOG_EVENT_UNFILTERED", 1455887795.271, "SPELL_HEAL", false, "Player-3684-07235A4E", "Klakyn", 1297, 0, "Player-3684-07235A4E", "Klakyn", 1297, 0, 143924, "Leech", 1, 93, 93, 0, false, false, },
+    }
+  },
+  Outgoing = {
+    trigger = function(e, _,c,_, s) return (s == T.GUID and damageEvents[c]) end,
+    OnEvent = mod.OnDamage,
+    sample = {
+    {
+        "COMBAT_LOG_EVENT_UNFILTERED", 1455887795.006, "SPELL_DAMAGE", false, "Player-3684-07235A4E", "Klakyn", 1297, 0, "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0, 589, "Shadow Word: Pain", 32, 3944, -1, 32, nil, nil, nil, false, false, false, false, false, }, {
+        "COMBAT_LOG_EVENT_UNFILTERED", 1455887795.271, "SPELL_HEAL", false, "Player-3684-07235A4E", "Klakyn", 1297, 0, "Player-3684-07235A4E", "Klakyn", 1297, 0, 143924, "Leech", 1, 93, 93, 0, false, false, }, {
+        "COMBAT_LOG_EVENT_UNFILTERED", 1455887797.377, "SPELL_PERIODIC_DAMAGE", false, "Player-3684-07235A4E", "Klakyn", 1297, 0, "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0, 589, "Shadow Word: Pain", 32, 3944, -1, 32, nil, nil, nil, false, false, false, false, false, }, {
+        "COMBAT_LOG_EVENT_UNFILTERED", 1455887812.702, "SWING_DAMAGE", false, "Player-3684-07235A4E", "Klakyn", 1297, 0, "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0, 279, -1, 1, nil, 120, nil, false, false, false, false, false, }, -- [4]
+    }
+  },
+  DoTTracker = {
+    trigger = function(e, _, c, _, s) return (s == T.GUID and dotEvents[c]) end,
+    OnEvent = mod.OnDotEvent,
+    sample = {
+      {"COMBAT_LOG_EVENT_UNFILTERED", 1455887795.006, "SPELL_AURA_APPLIED",    false,
+        "Player-3684-07235A4E", "Klakyn", 1297, 0,
+        "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0,
+        589, "Shadow Word: Pain", 32, "DEBUFF", },
+      {"COMBAT_LOG_EVENT_UNFILTERED", 1455887797.377, "SPELL_PERIODIC_DAMAGE", false,
+        "Player-3684-07235A4E", "Klakyn", 1297, 0,
+        "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0,
+        589, "Shadow Word: Pain", 32, 3944, -1, 32, nil, nil, nil, false, false, false, false, false, },
+      {"COMBAT_LOG_EVENT_UNFILTERED", 1455887807.199, "SPELL_AURA_REMOVED",    false,
+        "Player-3684-07235A4E", "Klakyn", 1297, 0,
+        "Player-3684-07235A4E", "Klakyn", 1297, 0,
+        15473, "Shadowform", 32, "BUFF", },
+    }
+  }
+}
\ No newline at end of file