| Nenue@6 | 1 --- Combat | 
| Nenue@6 | 2 -- @file-author@ | 
| Nenue@6 | 3 -- @project-revision@ @project-hash@ | 
| Nenue@6 | 4 -- @file-revision@ @file-hash@ | 
| Nenue@6 | 5 -- Created: 1/11/2016 4:27 PM | 
| Nenue@6 | 6 --- Data collector for API Combat Log Event | 
| Nenue@6 | 7 local T, _G = Turok, _G | 
| Nenue@6 | 8 local mod = T:NewModule("Combat") | 
| Nenue@6 | 9 local pairs, ipairs, select, concat, format, tinsert, wipe = pairs, ipairs, select, table.concat, string.format, tinsert, table.wipe | 
| Nenue@6 | 10 local GetSpellTexture, CreateFrame, GetTime, unpack, floor = GetSpellTexture, CreateFrame, GetTime, unpack, floor | 
| Nenue@6 | 11 local db | 
| Nenue@6 | 12 local cText, cNum, cKey, cWord, cPink, cType, cBool = cText, cNum, cKey, cWord, cPink, cType, cBool | 
| Nenue@6 | 13 local print = function(...) | 
| Nenue@6 | 14   if _G.Devian and _G.DevianDB.workspace ~= 1 then | 
| Nenue@6 | 15     _G.print('CombatText', ...) | 
| Nenue@6 | 16   end | 
| Nenue@6 | 17 end | 
| Nenue@6 | 18 | 
| Nenue@6 | 19 | 
| Nenue@6 | 20 --- performance constants | 
| Nenue@6 | 21 local SCT_NAME = 'TurokSCT%s' | 
| Nenue@6 | 22 local SCT_TEMPLATE = 'TurokSCTAnchorTemplate' | 
| Nenue@6 | 23 local SCT_PREGAME = 5 | 
| Nenue@6 | 24 local SCT_MESSAGE_TEMPLATE = 'TurokCombatMessageTemplate' | 
| Nenue@6 | 25 local SCT_MESSAGE_NAME = 'CombatString%d' | 
| Nenue@6 | 26 local LOG_TEMPLATE =  'TurokCombatLogAnchorTemplate' | 
| Nenue@6 | 27 | 
| Nenue@6 | 28 --- UX structures | 
| Nenue@6 | 29 T.defaults.CombatText = { | 
| Nenue@6 | 30   width = 400, height = 800, | 
| Nenue@6 | 31    parent = 'UIParent', | 
| Nenue@6 | 32   Outgoing = { | 
| Nenue@6 | 33     anchor = 'RIGHT', anchorTo = 'RIGHT', | 
| Nenue@6 | 34     x = -400, y = -200, | 
| Nenue@6 | 35   }, | 
| Nenue@6 | 36   Incoming = { | 
| Nenue@6 | 37     anchor = 'LEFT', anchorTo = 'LEFT',parent = 'UIParent', | 
| Nenue@6 | 38     x = 400, y = -200, | 
| Nenue@6 | 39   }, | 
| Nenue@6 | 40   DoTTracker = { | 
| Nenue@6 | 41     anchor = 'BOTTOMLEFT', anchorTo = 'BOTTOM', parent = 'UIParent', | 
| Nenue@6 | 42     x = 80, y = 400, | 
| Nenue@6 | 43   }, | 
| Nenue@6 | 44   defaultFont = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Regular.ttf", 18, 'OUTLINE'}, | 
| Nenue@6 | 45   defaultAnimation = 'slide', | 
| Nenue@6 | 46 | 
| Nenue@6 | 47   textFonts = { | 
| Nenue@6 | 48     font1 = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Regular.ttf" , 30, 'OUTLINE'}, | 
| Nenue@6 | 49     font2 = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Regular.ttf" , 24, 'OUTLINE'}, | 
| Nenue@6 | 50     font3 = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Bold.ttf" , 20, 'OUTLINE'}, | 
| Nenue@6 | 51   }, | 
| Nenue@6 | 52 | 
| Nenue@6 | 53   --- [(string) combatEvent] = {[1] = (string) format [, [2] = fontKey]} | 
| Nenue@6 | 54   --- Substitution values | 
| Nenue@6 | 55   -- %d [1] amount [2] overkill [3] absorbed [4] blocked | 
| Nenue@6 | 56   -- %s [5] spell [6] caster [7] school | 
| Nenue@6 | 57   textFormat = { | 
| Nenue@6 | 58     ['SWING_DAMAGE'] = {'%d'}, | 
| Nenue@6 | 59     ['SPELL_DAMAGE'] = {'%d'}, | 
| Nenue@6 | 60     ['RANGE_DAMAGE'] = {'%d'}, | 
| Nenue@6 | 61     Incoming = { | 
| Nenue@6 | 62       ['SWING_DAMAGE'] = {'-%d'}, | 
| Nenue@6 | 63       ['SPELL_DAMAGE'] = {'-%d (%s)'}, | 
| Nenue@6 | 64       ['RANGE_DAMAGE'] = {'-%d'}, | 
| Nenue@6 | 65     }, | 
| Nenue@6 | 66   }, | 
| Nenue@6 | 67   textModifiers ={ | 
| Nenue@6 | 68     critical = {'%s!', 'pop'}, | 
| Nenue@6 | 69     overKill = {'%s |cFF0088FFKilling Blow!|r', 'slide'}, | 
| Nenue@6 | 70     multistrike = {'<%s>', 'lateralSlide', 'font3'}, | 
| Nenue@6 | 71     absorbed = {'%s (%d)', 'slide'}, | 
| Nenue@6 | 72     blocked = {'%s {%d}', 'slide'}, | 
| Nenue@6 | 73     pet = {'(%s)', 'lateralSlide'}, | 
| Nenue@6 | 74     grouped = {'%s (%d hit)', 'slide'}, | 
| Nenue@6 | 75   }, | 
| Nenue@6 | 76   --- [AnimationGroup key] = {[XML attrib] = [value], d[x/y] = (number) 0-1 } | 
| Nenue@6 | 77    -- d[x/y] indicates the proportional relevance of each FontString dimension when frames are displaced by a new event | 
| Nenue@6 | 78    -- x/y    indicates the ranges of movement made by an animation, and are also considered when calculating displacement | 
| Nenue@6 | 79   animation = { | 
| Nenue@6 | 80     slide = { | 
| Nenue@6 | 81       x = 0,   dx = 0, | 
| Nenue@6 | 82       y = 300, dy = 1, | 
| Nenue@6 | 83       duration = 2 | 
| Nenue@6 | 84     }, | 
| Nenue@6 | 85     lateralSlide = { | 
| Nenue@6 | 86       x = 300, dx = 1, | 
| Nenue@6 | 87       y = 0,   dy = 0, | 
| Nenue@6 | 88       duration = 2 | 
| Nenue@6 | 89     }, | 
| Nenue@6 | 90     pop = { | 
| Nenue@6 | 91       toScale = 1.4, | 
| Nenue@6 | 92       fromScale = 0.1, | 
| Nenue@6 | 93       duration = 0.14, | 
| Nenue@6 | 94       dx = 0, dy = 1, | 
| Nenue@6 | 95     }, | 
| Nenue@6 | 96     fadeOut = {change = -1, duration = 0.5}, | 
| Nenue@6 | 97   }, | 
| Nenue@6 | 98 } | 
| Nenue@6 | 99 --- [1] text wrapper [2] animation type | 
| Nenue@6 | 100 local dotEvents = { | 
| Nenue@6 | 101   SPELL_AURA_APPLIED = true, | 
| Nenue@6 | 102   SPELL_PERIODIC_DAMAGE = true, | 
| Nenue@6 | 103   SPELL_AURA_REMOVED = true, | 
| Nenue@6 | 104   SPELL_AURA_REFRESHED = true, | 
| Nenue@6 | 105 } | 
| Nenue@6 | 106 local petGUID = {} | 
| Nenue@6 | 107 | 
| Nenue@6 | 108 ------------- data structures | 
| Nenue@6 | 109 local defaultAnimation, defaultFont | 
| Nenue@6 | 110 local criticalModifier, absorbedModifier, blockedModifier, overKillModifier, multistrikeModifier, groupedModifier | 
| Nenue@6 | 111 local criticalAnimation, absorbedAnimation, blockedAnimation, overKillAnimation, multistrikeAnimation, groupedAnimation | 
| Nenue@6 | 112 local criticalFont, absorbedFont, blockedFont, overKillFont,multistrikeFont, groupedFont | 
| Nenue@6 | 113 local textFonts = {} | 
| Nenue@6 | 114 local textFormat = {} | 
| Nenue@6 | 115 local animation = {} | 
| Nenue@6 | 116 local spellCache = {} | 
| Nenue@6 | 117 local DoTFrames = {} | 
| Nenue@6 | 118 | 
| Nenue@6 | 119 local dotTrackingEvents = { | 
| Nenue@6 | 120   ['SPELL_AURA_APPLIED'] = true, | 
| Nenue@6 | 121   ['SPELL_AURA_REMOVED'] = true, | 
| Nenue@6 | 122   ['SPELL_AURA_REFRESHED'] = true, | 
| Nenue@6 | 123 } | 
| Nenue@6 | 124 | 
| Nenue@6 | 125 --- multi-hit events data | 
| Nenue@6 | 126 -- [(string) localized name] = { | 
| Nenue@6 | 127 --   [1] = (bool)   controlled channel | 
| Nenue@6 | 128 --   [2] = (number) minimum time to wait for next damage tick | 
| Nenue@6 | 129 --   [3] = (number) maximum time to log grouped spell hits | 
| Nenue@6 | 130 --   [4] = (string) combatEvent full string | 
| Nenue@6 | 131 --   [5] = (string) combatEvent short prefix (as opposed to SPELL_PERIODIC) | 
| Nenue@6 | 132 --   [6] = (string) combatEvent short suffix (as opposed to AURA_APPLIED) | 
| Nenue@6 | 133 -- } | 
| Nenue@6 | 134 -- tailing fields are set to reduce the number of arguments passed around | 
| Nenue@6 | 135 local groupedSpells = { | 
| Nenue@6 | 136   global = {false, 1, 'SPELL_PERIODIC_DAMAGE', 'SPELL', 'DAMAGE'}, | 
| Nenue@6 | 137   ['Crimson Tempest'] = {false,   0.3,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- shockwave dot | 
| Nenue@6 | 138   ['Mind Sear'] =       {false,   1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- scanning aoe | 
| Nenue@6 | 139   ['Searing Insanity'] =       {false,   1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- scanning aoe | 
| Nenue@6 | 140   ['Ice Nova'] =        {false, 0.4,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- shockwave aoe | 
| Nenue@6 | 141   ['Blizzard'] =        {false,   1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- scanning aoe | 
| Nenue@6 | 142   ['Frozen Orb'] =      {false,   1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, | 
| Nenue@6 | 143   ['Ice Bomb'] =        {false, 0.4,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- shockwave aoe | 
| Nenue@6 | 144   ['Comet Storm'] =     {false, 2.5,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- staggered multi-hit | 
| Nenue@6 | 145   ['Barrage'] =         {false, 1.1,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- projectile vommit | 
| Nenue@6 | 146   ['Glaive Toss'] =     {false,   3,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'}, -- path projectile, | 
| Nenue@6 | 147   ['Doom Nova'] =       {false, 0.4,  'SPELL_DAMAGE', 'SPELL', 'DAMAGE'} | 
| Nenue@6 | 148 } | 
| Nenue@6 | 149 | 
| Nenue@6 | 150 local dotSpellIndex = { | 
| Nenue@6 | 151   ['Shadow Word: Pain'] = {'HARMFUL|PLAYER'}, | 
| Nenue@6 | 152   ['Vampiric Touch'] = {'HARMFUL|PLAYER'} | 
| Nenue@6 | 153 } | 
| Nenue@6 | 154 | 
| Nenue@6 | 155 local offhandSpellIndex = { | 
| Nenue@6 | 156   ['Execute'] = {'Execute Off-Hand'}, | 
| Nenue@6 | 157   ['Mutilate'] = {'Mutilate Off-Hand'}, | 
| Nenue@6 | 158   ['Stormstrike'] = {'Stormstrike Off-Hand'}, | 
| Nenue@6 | 159 } | 
| Nenue@6 | 160 | 
| Nenue@6 | 161 --- stored as a list of field representations to be concatenated together within a loadstring def | 
| Nenue@6 | 162 | 
| Nenue@6 | 163 | 
| Nenue@6 | 164 | 
| Nenue@6 | 165 --- Tracking tables | 
| Nenue@6 | 166 local groupedQueue = {} | 
| Nenue@6 | 167 local groupedEvents = {} | 
| Nenue@6 | 168 local offhandQueue = {} | 
| Nenue@6 | 169 local sct_format = {} | 
| Nenue@6 | 170 | 
| Nenue@6 | 171 | 
| Nenue@6 | 172 local function GetGUIDInfo(guid) | 
| Nenue@6 | 173   local unitType, flags1, flags2 = guid:match('(%a+)\-(%x+)\-(%x+)') | 
| Nenue@6 | 174   return concat({unitType, flags1, flags2},'|r::|cFF0088FF')..'|r' | 
| Nenue@6 | 175 end | 
| Nenue@6 | 176 | 
| Nenue@6 | 177 local SpellSchoolColors = { | 
| Nenue@6 | 178   [1] = {255, 255,0},     -- physical | 
| Nenue@6 | 179   [2] = {255, 230, 128},  -- holy | 
| Nenue@6 | 180   [3] = {255, 255, 128},  -- holy+phys | 
| Nenue@6 | 181   [4] = {255, 128, 0},    -- fire | 
| Nenue@6 | 182   [8] = {77, 255, 77},    -- nature | 
| Nenue@6 | 183   [16] = {128, 255, 255}, -- frost | 
| Nenue@6 | 184   [20] = {255, 192, 128},   -- frostfire | 
| Nenue@6 | 185   [32] = {128, 128, 255}, -- shadow | 
| Nenue@6 | 186   [48] = {128, 192, 255}, -- shadow+frost | 
| Nenue@6 | 187   [64] = {255, 128, 255}, -- arcane, | 
| Nenue@6 | 188   [72] = {255, 128, 128}, -- spellstorm, | 
| Nenue@6 | 189 } | 
| Nenue@6 | 190 | 
| Nenue@6 | 191 local h = '|cFF%02X%02X%02X' | 
| Nenue@6 | 192 local SpellSchoolColor = function (flags, spellName) | 
| Nenue@6 | 193   local i = 64 | 
| Nenue@6 | 194   local rA, gA, bA | 
| Nenue@6 | 195   if SpellSchoolColors[flags] then | 
| Nenue@6 | 196     print(flags, 'match') | 
| Nenue@6 | 197     print(format('%02X%02X%02X', unpack(SpellSchoolColors[flags]))) | 
| Nenue@6 | 198     return format(h, unpack(SpellSchoolColors[flags])) | 
| Nenue@6 | 199   end | 
| Nenue@6 | 200 | 
| Nenue@6 | 201   repeat | 
| Nenue@6 | 202     local rB, gB, bB = unpack(SpellSchoolColors[i]) | 
| Nenue@6 | 203     if i <= flags then | 
| Nenue@6 | 204       if not rA then | 
| Nenue@6 | 205         rA = rB | 
| Nenue@6 | 206         gA = gB | 
| Nenue@6 | 207         bA = bB | 
| Nenue@6 | 208       else | 
| Nenue@6 | 209         rA = (rA+rB)/2 | 
| Nenue@6 | 210         gA = (gA+gB)/2 | 
| Nenue@6 | 211         bA = (bA+bB)/2 | 
| Nenue@6 | 212       end | 
| Nenue@6 | 213       print('test:', cWord(i), '<=', cKey(flags), '=', (i <= flags), cPink(rA), cNum(gA), cText(bA)) | 
| Nenue@6 | 214       flags = flags - i | 
| Nenue@6 | 215     else | 
| Nenue@6 | 216       print(i, 'skip') | 
| Nenue@6 | 217     end | 
| Nenue@6 | 218     i = i/2 | 
| Nenue@6 | 219   until (i == 1) | 
| Nenue@6 | 220   SpellSchoolColors[flags] = {rA, gA, bA } | 
| Nenue@6 | 221   --print(string.format('%02X%02X%02X', unpack(SpellSchoolColors[flags]))) | 
| Nenue@6 | 222   return format(h, unpack(SpellSchoolColors[flags])) | 
| Nenue@6 | 223 end | 
| Nenue@6 | 224 local myGUID | 
| Nenue@6 | 225 | 
| Nenue@6 | 226 mod.NewCombatMessage = function(frame, index) | 
| Nenue@6 | 227   local ct = CreateFrame('Frame', SCT_MESSAGE_NAME:format(frame.lineID), frame, SCT_MESSAGE_TEMPLATE) | 
| Nenue@6 | 228   index = index and 'active' or 'expired' | 
| Nenue@6 | 229   frame.lineID = frame.lineID + 1 | 
| Nenue@6 | 230   frame.active[#frame.active+1] = ct | 
| Nenue@6 | 231   ct.index = #frame.active | 
| Nenue@6 | 232   ct.x = 0 | 
| Nenue@6 | 233   ct.y = 0 | 
| Nenue@6 | 234   ct.point = 'BOTTOMLEFT' | 
| Nenue@6 | 235   return ct | 
| Nenue@6 | 236 end | 
| Nenue@6 | 237 | 
| Nenue@6 | 238 --- frame interaction logic | 
| Nenue@6 | 239 mod.AddCombatMessage = function(frame, text, icon, animationType, fontKey) | 
| Nenue@6 | 240   local line | 
| Nenue@6 | 241   print(fontKey) | 
| Nenue@6 | 242 | 
| Nenue@6 | 243   local expired = frame.expired | 
| Nenue@6 | 244   local active = frame.active | 
| Nenue@6 | 245   local a = 1 | 
| Nenue@6 | 246   local shiftY, shiftX | 
| Nenue@6 | 247   local lastFrame | 
| Nenue@6 | 248   --- If animation has overlap delta values, find the last active frame of that animation type and check for overlaps. | 
| Nenue@6 | 249   --- Frames are considered overlapping when the last member's ((height - distance traveled) * delta) is over 0. | 
| Nenue@6 | 250   --- This assumes the same string height for the upcoming frame since wordwrap isn't enabled. | 
| Nenue@6 | 251   print(animationType) | 
| Nenue@6 | 252   if animation[animationType].dx or animation[animationType].dy then | 
| Nenue@6 | 253     print('animation has displacement') | 
| Nenue@6 | 254     if frame.last then | 
| Nenue@6 | 255       lastFrame = frame.last | 
| Nenue@6 | 256       local dp = lastFrame[animationType]:GetProgress() | 
| Nenue@6 | 257       if animation[animationType].dx then | 
| Nenue@6 | 258         local dx = dp * animation[animationType].x | 
| Nenue@6 | 259         shiftX = (lastFrame.string:GetStringWidth() - dx) * animation[animationType].dx | 
| Nenue@6 | 260       end | 
| Nenue@6 | 261       if animation[animationType].dy then | 
| Nenue@6 | 262         local dy = dp * animation[animationType].y | 
| Nenue@6 | 263         shiftY = (lastFrame.string:GetStringHeight() - dy) * animation[animationType].dy | 
| Nenue@6 | 264         print('  ', 'h=', floor(lastFrame.string:GetStringHeight()), 'dY=', dy, 'offsetY=', shiftY) | 
| Nenue@6 | 265       end | 
| Nenue@6 | 266       print(cWord('lastFrame hit:'), lastFrame and lastFrame:GetName(), cNum(shiftX), cNum(shiftY)) | 
| Nenue@6 | 267     end | 
| Nenue@6 | 268   end | 
| Nenue@6 | 269 | 
| Nenue@6 | 270   -- find a usable frame | 
| Nenue@6 | 271   local currentFrame | 
| Nenue@6 | 272   for i, ct in ipairs(frame.active) do | 
| Nenue@6 | 273     if not currentFrame then | 
| Nenue@6 | 274       if ct.discard then | 
| Nenue@6 | 275         ct.discard=  nil | 
| Nenue@6 | 276         currentFrame = ct | 
| Nenue@6 | 277       end | 
| Nenue@6 | 278     end | 
| Nenue@6 | 279 | 
| Nenue@6 | 280     if lastFrame and ct.animationType == animationType and not ct.discard then | 
| Nenue@6 | 281       --print('lastFrame defined, check for overlap') | 
| Nenue@6 | 282       if shiftY > 0 then | 
| Nenue@6 | 283         print(cWord('  * vertical shift'), cNum('+'..floor(shiftY))) | 
| Nenue@6 | 284         ct.y = ct.y + shiftY | 
| Nenue@6 | 285       end | 
| Nenue@6 | 286       if shiftX > 0 then | 
| Nenue@6 | 287         print(cWord('  * horizontal shift'), cNum('+'..floor(shiftX))) | 
| Nenue@6 | 288         ct.x = ct.x + shiftX | 
| Nenue@6 | 289       end | 
| Nenue@6 | 290       ct:SetPoint(ct.point, frame, ct.point, ct.x, ct.y) | 
| Nenue@6 | 291     end | 
| Nenue@6 | 292   end | 
| Nenue@6 | 293   -- if no expired frames became available, make a new one (should max at 20 or so if groupings are right) | 
| Nenue@6 | 294   if  not currentFrame then | 
| Nenue@6 | 295     currentFrame = mod.NewCombatMessage(frame) | 
| Nenue@6 | 296     print(cNum('     creating new string object for the heap')) | 
| Nenue@6 | 297   end | 
| Nenue@6 | 298 | 
| Nenue@6 | 299   print(cText(' * Starting'), cPink(animationType), 'on', cKey(currentFrame:GetName()), ' ['..cNum(currentFrame.index)..']') | 
| Nenue@6 | 300 | 
| Nenue@6 | 301   if icon then | 
| Nenue@6 | 302     currentFrame.icon:Show() | 
| Nenue@6 | 303     currentFrame.icon:SetTexture(icon) | 
| Nenue@6 | 304   else | 
| Nenue@6 | 305     currentFrame.icon:Hide() | 
| Nenue@6 | 306   end | 
| Nenue@6 | 307   if fontKey  then | 
| Nenue@6 | 308     local newFont = fontKey and textFonts[fontKey] or defaultFont | 
| Nenue@6 | 309 | 
| Nenue@6 | 310     local path, size, flags = currentFrame.string:GetFont() | 
| Nenue@6 | 311     path = newFont[1] or path | 
| Nenue@6 | 312     size = newFont[2] or size | 
| Nenue@6 | 313     flags = newFont[3] or flags | 
| Nenue@6 | 314     print(cText('font ('..cWord(fontKey)..'):'), path, size, flags) | 
| Nenue@6 | 315     local result = currentFrame.string:SetFont(path, size, flags) | 
| Nenue@6 | 316     print(cNum('     result:'), cNum(result), currentFrame.string:GetFont()) | 
| Nenue@6 | 317     --currentFrame.fontKey = fontKey | 
| Nenue@6 | 318   end | 
| Nenue@6 | 319 | 
| Nenue@6 | 320   currentFrame.animationType = animationType | 
| Nenue@6 | 321   currentFrame.string:SetText(text) | 
| Nenue@6 | 322   local cHeight = currentFrame.string:GetStringHeight() | 
| Nenue@6 | 323   currentFrame:SetSize(currentFrame.string:GetStringWidth(), cHeight) | 
| Nenue@6 | 324   currentFrame.icon:SetSize(cHeight, cHeight) | 
| Nenue@6 | 325   currentFrame.y = 0 | 
| Nenue@6 | 326   currentFrame.x = 0 | 
| Nenue@6 | 327   currentFrame:SetPoint(currentFrame.point, frame, currentFrame.point, currentFrame.x, currentFrame.y) | 
| Nenue@6 | 328   currentFrame[animationType]:Play() | 
| Nenue@6 | 329   frame.last = currentFrame | 
| Nenue@6 | 330 end | 
| Nenue@6 | 331 | 
| Nenue@6 | 332 local GetTextFormatFunc = function(amount, overKill, absorbed, blocked, multistrike, spellName, sourceName, destName) | 
| Nenue@6 | 333   local sub_text = { | 
| Nenue@6 | 334     ['%d'] = amount, | 
| Nenue@6 | 335     ['%o'] = overKill, | 
| Nenue@6 | 336     ['%a'] = absorbed, | 
| Nenue@6 | 337     ['%b'] = blocked, | 
| Nenue@6 | 338     ['%m'] = multistrike, | 
| Nenue@6 | 339     ['%s'] = spellName, | 
| Nenue@6 | 340     ['%n'] = sourceName, | 
| Nenue@6 | 341     ['%t'] = destName, | 
| Nenue@6 | 342   } | 
| Nenue@6 | 343   local func = function(token) | 
| Nenue@6 | 344     print(cPink('gsub run:'), token) | 
| Nenue@6 | 345     return sub_text[token] | 
| Nenue@6 | 346   end | 
| Nenue@6 | 347   return func | 
| Nenue@6 | 348 end | 
| Nenue@6 | 349 | 
| Nenue@6 | 350 --- builds CT messages from combat log event data | 
| Nenue@6 | 351 --  separated from frame interaction for auxiliary events such as combat/loot/etc | 
| Nenue@6 | 352 local CreateDamageText = function(frame, timestamp, combatEvent, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike, ticks) | 
| Nenue@6 | 353   if combatEvent:match('^SWING') then | 
| Nenue@6 | 354     spellID = 1 | 
| Nenue@6 | 355     spellName = 'Attack' | 
| Nenue@6 | 356   elseif combatEvent:match('^RANGE') then | 
| Nenue@6 | 357     spellID = 2 | 
| Nenue@6 | 358     spellName = 'Auto Shot' | 
| Nenue@6 | 359   end | 
| Nenue@6 | 360   if not combatEvent:match('DAMAGE') then | 
| Nenue@6 | 361     return | 
| Nenue@6 | 362   end | 
| Nenue@6 | 363 | 
| Nenue@6 | 364 | 
| Nenue@6 | 365   local fontKey = 'font1' | 
| Nenue@6 | 366   local icon = GetSpellTexture(spellID) | 
| Nenue@6 | 367   local animationType = defaultAnimation | 
| Nenue@6 | 368   local text = amount | 
| Nenue@6 | 369   if textFormat[combatEvent] then | 
| Nenue@6 | 370     local tString, fontKey = unpack(textFormat[combatEvent]) | 
| Nenue@6 | 371     if amount > 1000000 then | 
| Nenue@6 | 372       amount = (floor(amount/100000)/10) ..'M' | 
| Nenue@6 | 373     elseif amount > 1000 then | 
| Nenue@6 | 374       amount = (floor(amount/100)/10) ..'k' | 
| Nenue@6 | 375     end | 
| Nenue@6 | 376 | 
| Nenue@6 | 377     text = tString:gsub('%%[doabsnt]', GetTextFormatFunc(amount, overKill, absorbed, blocked, multistrike, spellName, sourceName, destName)) | 
| Nenue@6 | 378     print("** font override:", '"'..cText(tString)..'",', cPink(font), cNum(size), cWord(outline)) | 
| Nenue@6 | 379   end | 
| Nenue@6 | 380 | 
| Nenue@6 | 381   print('** getting color data', cText(spellName)) | 
| Nenue@6 | 382   if type(effectSchool) == 'number' then | 
| Nenue@6 | 383     text = SpellSchoolColor(effectSchool) ..text..'|r' | 
| Nenue@6 | 384   end | 
| Nenue@6 | 385 | 
| Nenue@6 | 386   if overKill > 0 then | 
| Nenue@6 | 387     text = overKillModifier:format(text, overKill) | 
| Nenue@6 | 388     animationType = overKillAnimation | 
| Nenue@6 | 389     fontKey = overKillFont or fontKey | 
| Nenue@6 | 390   end | 
| Nenue@6 | 391   if critical then | 
| Nenue@6 | 392     text = criticalModifier:format(text) | 
| Nenue@6 | 393     animationType = criticalAnimation | 
| Nenue@6 | 394     fontKey = criticalFont or fontKey | 
| Nenue@6 | 395   end | 
| Nenue@6 | 396   if absorbed then | 
| Nenue@6 | 397     text = absorbedModifier:format(text, absorbed) | 
| Nenue@6 | 398     animationType = absorbedAnimation | 
| Nenue@6 | 399     fontKey = absorbedFont or fontKey | 
| Nenue@6 | 400   end | 
| Nenue@6 | 401   if blocked then | 
| Nenue@6 | 402     text = blockedModifier:format(text, blocked) | 
| Nenue@6 | 403     animationType = blockedAnimation | 
| Nenue@6 | 404     fontKey = blockedFont or fontKey | 
| Nenue@6 | 405   end | 
| Nenue@6 | 406   if multistrike then | 
| Nenue@6 | 407     text = multistrikeModifier:format(text, multistrike) | 
| Nenue@6 | 408     animationType = multistrikeAnimation | 
| Nenue@6 | 409     fontKey = multistrikeFont or fontKey | 
| Nenue@6 | 410   end | 
| Nenue@6 | 411 | 
| Nenue@6 | 412   print(ticks) | 
| Nenue@6 | 413   if ticks then | 
| Nenue@6 | 414     text = groupedModifier:format(text, ticks) | 
| Nenue@6 | 415     animationType = groupedAnimation | 
| Nenue@6 | 416     fontKey = groupedFont or fontKey | 
| Nenue@6 | 417   end | 
| Nenue@6 | 418 | 
| Nenue@6 | 419 | 
| Nenue@6 | 420   print('** sending format to SCT:', text, icon, animationType, fontKey) | 
| Nenue@6 | 421 | 
| Nenue@6 | 422 | 
| Nenue@6 | 423   mod.AddCombatMessage(frame, text .. '|r', icon, animationType, fontKey) | 
| Nenue@6 | 424 end | 
| Nenue@6 | 425 | 
| Nenue@6 | 426 local CT_ShowConsolidated = function() | 
| Nenue@6 | 427   for spellName, queuedSpell in pairs(groupedQueue) do | 
| Nenue@6 | 428     local isChannel, minDelay, combatEvent, sourceName, destName, spellID, school, SCT, timestamp = unpack(queuedSpell) | 
| Nenue@6 | 429     for i, v  in ipairs(queuedSpell) do | 
| Nenue@6 | 430       print('   ', i, '=', v) | 
| Nenue@6 | 431     end | 
| Nenue@6 | 432 | 
| Nenue@6 | 433     print(spellName, 'vars:', spellID, spellName, school, SCT, timestamp) | 
| Nenue@6 | 434     if groupedEvents[spellName] and #groupedEvents[spellName] ~= 0 then | 
| Nenue@6 | 435       local amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, hit = 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 | 
| Nenue@6 | 436       for i, line in ipairs(groupedEvents[spellName]) do | 
| Nenue@6 | 437         -- extra strike? | 
| Nenue@6 | 438         --{amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike} | 
| Nenue@6 | 439         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) | 
| Nenue@6 | 440 | 
| Nenue@6 | 441         amount = amount + amount_n | 
| Nenue@6 | 442         if overKill_n > 0   then overKill = overKill + overKill_n end | 
| Nenue@6 | 443         if blocked_n        then blocked = blocked + line[4] end | 
| Nenue@6 | 444         if absorbed_n       then absorbed = absorbed + line[5] end | 
| Nenue@6 | 445         if critical_n       then critical = critical + 1 end | 
| Nenue@6 | 446         if multi_n          then multistrike = multistrike + 1  end | 
| Nenue@6 | 447 | 
| Nenue@6 | 448         hit = hit + 1 | 
| Nenue@6 | 449       end | 
| Nenue@6 | 450       if overKill == -1 then | 
| Nenue@6 | 451         overKill = overKill + 1 | 
| Nenue@6 | 452       end | 
| Nenue@6 | 453       wipe(groupedEvents[spellName]) | 
| Nenue@6 | 454       groupedQueue[spellName] = nil | 
| Nenue@6 | 455       print('   expelling', spellName, cText('A:'), cNum(amount), cText('O:'), cNum(overKill), cText('Ticks:'), cNum(hit), cText('Crits:'), cNum(critical), cText(school)) | 
| Nenue@6 | 456       print(SCT, timestamp, combatEvent, sourceName, destName, spellID, spellName, school, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, hit) | 
| Nenue@6 | 457       CreateDamageText(SCT, timestamp, combatEvent, sourceName, destName, spellID, spellName, school, amount, -1, nil, nil, nil, false, glancing, crushing, false, multistrike, hit) | 
| Nenue@6 | 458     end | 
| Nenue@6 | 459   end | 
| Nenue@6 | 460 end | 
| Nenue@6 | 461 | 
| Nenue@6 | 462 local CT_ConsolidateText = function (self, timestamp, sourceGUID, destGUID, sourceName, destName, spellID, spellName, school, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike) | 
| Nenue@6 | 463   local archive = groupedSpells[spellName] and groupedSpells[spellName] or groupedSpells.global | 
| Nenue@6 | 464   local isChannel, minDelay, combatEvent = unpack(archive) | 
| Nenue@6 | 465   if not groupedEvents[spellName] then | 
| Nenue@6 | 466     groupedEvents[spellName] = {} | 
| Nenue@6 | 467   end | 
| Nenue@6 | 468 | 
| Nenue@6 | 469   if #groupedEvents[spellName] == 0 then | 
| Nenue@6 | 470     groupedQueue[spellName] = {isChannel, minDelay, combatEvent, sourceName, destName, spellID, school, self, timestamp} | 
| Nenue@6 | 471     T:ScheduleTimer(CT_ShowConsolidated, minDelay) | 
| Nenue@6 | 472     print('  starting archive for', select(7, unpack(archive))) | 
| Nenue@6 | 473   else | 
| Nenue@6 | 474     print('  recording into archive', cText(spellName)) | 
| Nenue@6 | 475   end | 
| Nenue@6 | 476   tinsert(groupedEvents[spellName], {amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, sourceGUID, destGUID, sourceName, destName, sourceName, destName,}) | 
| Nenue@6 | 477 end | 
| Nenue@6 | 478 | 
| Nenue@6 | 479 local CT_OnCast = function(self, event, unit, spellName, lineID, spellID) | 
| Nenue@6 | 480   if unit ~= 'player' and unit ~= 'pet' then | 
| Nenue@6 | 481     return | 
| Nenue@6 | 482   end | 
| Nenue@6 | 483 | 
| Nenue@6 | 484   if groupedSpells[spellName] then | 
| Nenue@6 | 485     print(cText('** Spell casting info received')) | 
| Nenue@6 | 486     local isChannel, delay = unpack(groupedSpells[spellName]) | 
| Nenue@6 | 487     if isChannel and event == 'CHANNEL_STOP' and groupedEvents[spellName] then | 
| Nenue@6 | 488       T:ScheduleTimer(CT_ShowConsolidated, delay) | 
| Nenue@6 | 489     else | 
| Nenue@6 | 490       if not groupedEvents[spellName] then | 
| Nenue@6 | 491         groupedEvents[spellName] = {} | 
| Nenue@6 | 492       end | 
| Nenue@6 | 493     end | 
| Nenue@6 | 494   end | 
| Nenue@6 | 495 end | 
| Nenue@6 | 496 | 
| Nenue@6 | 497 local CT_Unlock = function(str) | 
| Nenue@6 | 498   for i, CT in pairs(mod.CombatTextFrames) do | 
| Nenue@6 | 499       local frame = CT.frame | 
| Nenue@6 | 500       frame.configMode = (frame.configMode == nil) and true or nil | 
| Nenue@6 | 501       frame:RegisterForDrag(frame.configMode and 'LeftButton' or nil) | 
| Nenue@6 | 502       frame:EnableMouse(frame.configMode and true or false) | 
| Nenue@6 | 503       print(i, frame.configMode and 'ON' or 'OFF') | 
| Nenue@6 | 504       for _, reg in ipairs(frame.configRegions) do | 
| Nenue@6 | 505         if frame.configMode then | 
| Nenue@6 | 506           reg:Show() | 
| Nenue@6 | 507         else | 
| Nenue@6 | 508           reg:Hide() | 
| Nenue@6 | 509         end | 
| Nenue@6 | 510       end | 
| Nenue@6 | 511 | 
| Nenue@6 | 512       if frame.configMode then | 
| Nenue@6 | 513         CT.configTimer = T:ScheduleRepeatingTimer(function() | 
| Nenue@6 | 514           print(i, 'config tick') | 
| Nenue@6 | 515           for i, s in ipairs(CT.sample) do | 
| Nenue@6 | 516             CT.OnEvent(frame, unpack(s)) | 
| Nenue@6 | 517           end | 
| Nenue@6 | 518         end, 2) | 
| Nenue@6 | 519       else | 
| Nenue@6 | 520         print(CT.configTimer) | 
| Nenue@6 | 521         T:CancelTimer(CT.configTimer) | 
| Nenue@6 | 522       end | 
| Nenue@6 | 523 | 
| Nenue@6 | 524   end | 
| Nenue@6 | 525 end | 
| Nenue@6 | 526 | 
| Nenue@6 | 527 --- check for sectors | 
| Nenue@6 | 528 local queue = {} | 
| Nenue@6 | 529 mod.OnDamage = function(self, event, ...) | 
| Nenue@6 | 530   local isVisible | 
| Nenue@6 | 531 | 
| Nenue@6 | 532   local timestamp, combatEvent = ... | 
| Nenue@6 | 533   print(cText('* CT'), cKey(combatEvent)) | 
| Nenue@6 | 534   if combatEvent == 'UNIT_DIED' then | 
| Nenue@6 | 535     return | 
| Nenue@6 | 536   end | 
| Nenue@6 | 537   local sourceGUID, sourceName, sourceFlags, _, destGUID, destName, destFlags, _, spellID, spellName, spellSchool = select(4, ...) | 
| Nenue@6 | 538   print('  from', cPink(sourceGUID), 'spell', cNum(spellID), cText(spellName), '->', cKey(destGUID)) | 
| Nenue@6 | 539 | 
| Nenue@6 | 540   -- SWING starts at arg 10, SPELL/RANGE start at arg 13, ENVIRONMENTAL starts at arg 8 | 
| Nenue@6 | 541   local offset = 15 | 
| Nenue@6 | 542   print(combatEvent:sub(0,3)) | 
| Nenue@6 | 543   if combatEvent:sub(0,3) == 'SWI' then | 
| Nenue@6 | 544     offset = 10 | 
| Nenue@6 | 545   elseif combatEvent:sub(0,3) == 'ENV' then | 
| Nenue@6 | 546     offset = 8 | 
| Nenue@6 | 547   end | 
| Nenue@6 | 548   local amount, overKill, effectSchool, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike = select(offset, ...) | 
| Nenue@6 | 549 | 
| Nenue@6 | 550   print('  dmg', amount, overKill) | 
| Nenue@6 | 551 | 
| Nenue@6 | 552   local sc = SpellSchoolColors[effectSchool] | 
| Nenue@6 | 553   if groupedSpells[spellName] then | 
| Nenue@6 | 554     print('* ', cText(spellName), 'to consolidator') | 
| Nenue@6 | 555     CT_ConsolidateText(self, timestamp, sourceGUID, destGUID, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike) | 
| Nenue@6 | 556     return | 
| Nenue@6 | 557   end | 
| Nenue@6 | 558 | 
| Nenue@6 | 559   print('displaying on', cWord(self:GetName())) | 
| Nenue@6 | 560   CreateDamageText(self, timestamp, combatEvent, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike) | 
| Nenue@6 | 561 end | 
| Nenue@6 | 562 | 
| Nenue@6 | 563 mod.maxDotFrame = 1 | 
| Nenue@6 | 564 mod.OnDotEvent = function (self, event, timestamp, combatEvent, ...) | 
| Nenue@6 | 565   print(cWord('DOT'), combatEvent) | 
| Nenue@6 | 566   local sourceGUID, sourceName, sourceFlags, _, destGUID, destName, destFlags, destExtraFlags, spellID, spellName, castSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike = select(2, ...) | 
| Nenue@6 | 567   print(cText('dot from'), cWord(sourceGUID), 'to', cKey(destGUID)) | 
| Nenue@6 | 568   if sourceGUID == T.GUID then | 
| Nenue@6 | 569     local p = mod.PeriodicTable | 
| Nenue@6 | 570     p[destGUID] = p[destGUID] or {} | 
| Nenue@6 | 571     local unit = p[destGUID] | 
| Nenue@6 | 572     if not p[spellID] then | 
| Nenue@6 | 573       if not p.frames[#p.frames] then | 
| Nenue@6 | 574         unit[spellID] = CreateFrame('Frame', 'DotBar'..mod.maxDotFrame, p, 'TkDotBarTemplate') | 
| Nenue@6 | 575         mod.maxDotFrame = mod.maxDotFrame + 1 | 
| Nenue@6 | 576         unit[spellID].dotID = mod.maxDotFrame | 
| Nenue@6 | 577 | 
| Nenue@6 | 578         print('  create new frame ['..cKey(unit[spellID]:GetID(), '] for'), cWord(spellName), cKey(destGUID)) | 
| Nenue@6 | 579       else | 
| Nenue@6 | 580         unit[spellID] = p.frames[#p.frames] | 
| Nenue@6 | 581         p.frames[#p.frames] = nil -- remove that entry | 
| Nenue@6 | 582         print('  recycling frame ['..cKey(unit[spellID].dotID, '] for'), cWord(spellName), cKey(destGUID)) | 
| Nenue@6 | 583       end | 
| Nenue@6 | 584 | 
| Nenue@6 | 585       unit[spellID]:Show() | 
| Nenue@6 | 586     end | 
| Nenue@6 | 587     local dot = unit[spellID] | 
| Nenue@6 | 588     local time = GetTime() | 
| Nenue@6 | 589   end | 
| Nenue@6 | 590 end | 
| Nenue@6 | 591 | 
| Nenue@6 | 592 | 
| Nenue@6 | 593 | 
| Nenue@6 | 594 | 
| Nenue@6 | 595 function mod:OnInitialize() | 
| Nenue@6 | 596   print('This is a thing.') | 
| Nenue@6 | 597   self.UNIT_SPELLCAST_SUCCEEDED = CT_OnCast | 
| Nenue@6 | 598   self.UNIT_SPELLCAST_CHANNEL_START = CT_OnCast | 
| Nenue@6 | 599   self.UNIT_SPELLCAST_CHANNEL_STOP = CT_OnCast | 
| Nenue@6 | 600 end | 
| Nenue@6 | 601 | 
| Nenue@6 | 602 function mod:OnInitialize() | 
| Nenue@6 | 603   mod.db = TurokData.CombatText | 
| Nenue@6 | 604   db = TurokData.CombatText | 
| Nenue@6 | 605   myGUID = T.GUID | 
| Nenue@6 | 606   mod.PeriodicTable = mod.PeriodicTable or CreateFrame('Frame', 'TurokPeriodicFrame', UIParent) | 
| Nenue@6 | 607   mod.PeriodicTable.frames = {} | 
| Nenue@6 | 608 | 
| Nenue@6 | 609   local m = mod.db.textModifiers | 
| Nenue@6 | 610   --- These values are going to be looked up a lot, so cache as close as possible | 
| Nenue@6 | 611   criticalModifier = m.critical[1] or '%s CRIT' | 
| Nenue@6 | 612   criticalAnimation = m.critical[2] or 'slide' | 
| Nenue@6 | 613   criticalFont = m.critical[3] | 
| Nenue@6 | 614 | 
| Nenue@6 | 615   absorbedModifier = m.absorbed[1] or '%s ABS' | 
| Nenue@6 | 616   absorbedAnimation = m.absorbed[2] or 'slide' | 
| Nenue@6 | 617   absorbedFont = m.absorbed[3] | 
| Nenue@6 | 618 | 
| Nenue@6 | 619   blockedModifier = m.blocked[1] or '%s BLK' | 
| Nenue@6 | 620   blockedAnimation = m.blocked[2] or 'slide' | 
| Nenue@6 | 621   blockedFont = m.blocked[3] | 
| Nenue@6 | 622 | 
| Nenue@6 | 623   overKillModifier = m.overKill[1] or '%s KILL' | 
| Nenue@6 | 624   overKillAnimation = m.overKill[2] or 'slide' | 
| Nenue@6 | 625   overKillFont = m.overKill[3] | 
| Nenue@6 | 626 | 
| Nenue@6 | 627   multistrikeModifier = m.multistrike[1] or '%s MS' | 
| Nenue@6 | 628   multistrikeAnimation = m.multistrike[2] or 'slide' | 
| Nenue@6 | 629   multistrikeFont = m.multistrike[3] | 
| Nenue@6 | 630 | 
| Nenue@6 | 631   groupedModifier = m.grouped[1] or '%s' | 
| Nenue@6 | 632   groupedAnimation = m.grouped[2] or 'slide' | 
| Nenue@6 | 633   groupedFont = m.grouped[3] | 
| Nenue@6 | 634 | 
| Nenue@6 | 635   --- Same as above, but for specific table values, key is determined by the combat event | 
| Nenue@6 | 636   defaultAnimation = mod.db.defaultAnimation | 
| Nenue@6 | 637   defaultFont = mod.db.defaultFont | 
| Nenue@6 | 638   for k,v in pairs(mod.db.textFormat) do | 
| Nenue@6 | 639     textFormat[k] = {v[1], v[2]} | 
| Nenue@6 | 640     print('imported textFormat.'..k, cText(textFormat[k][1]), cNum(textFormat[k][2])) | 
| Nenue@6 | 641   end | 
| Nenue@6 | 642   for k,v in pairs(mod.db.textFonts) do | 
| Nenue@6 | 643     textFonts[k] = {v[1] or defaultFont[1], v[2] or defaultFont[2], v[3] or defaultFont[3]} | 
| Nenue@6 | 644     print('imported font.'..k, cText(textFonts[k][1]), cNum(textFonts[k][2]), cWord(textFonts[k][3])) | 
| Nenue@6 | 645   end | 
| Nenue@6 | 646 | 
| Nenue@6 | 647   for k,v in pairs(mod.db.animation) do | 
| Nenue@6 | 648     animation[k] = {} | 
| Nenue@6 | 649     animation[k].x = v.x | 
| Nenue@6 | 650     animation[k].y = v.y | 
| Nenue@6 | 651     animation[k].dx = v.dx | 
| Nenue@6 | 652     animation[k].dy = v.dy | 
| Nenue@6 | 653     animation[k].fromScale = v.fromScale | 
| Nenue@6 | 654     animation[k].toScale = v.toScale | 
| Nenue@6 | 655     animation[k].deviation = v.deviation | 
| Nenue@6 | 656     animation[k].change = v.change | 
| Nenue@6 | 657     animation[k].fromAlpha = v.fromAlpha | 
| Nenue@6 | 658     animation[k].toAlpha = v.toAlpha | 
| Nenue@6 | 659     animation[k].duration = v.duration | 
| Nenue@6 | 660   end | 
| Nenue@6 | 661 end | 
| Nenue@6 | 662 | 
| Nenue@6 | 663 function mod:OnEnable() | 
| Nenue@6 | 664   T:RegisterChatCommand('tkc', CT_Unlock) | 
| Nenue@6 | 665 | 
| Nenue@6 | 666   --- Populate CT frames | 
| Nenue@6 | 667   for name, CT in pairs(mod.CombatTextFrames) do | 
| Nenue@6 | 668     print('create CT', name) | 
| Nenue@6 | 669     -- make frame | 
| Nenue@6 | 670     CT.frame = CT.frame or CreateFrame('Frame', SCT_NAME:format(name), UIParent, SCT_TEMPLATE) | 
| Nenue@6 | 671 | 
| Nenue@6 | 672     -- local vars | 
| Nenue@6 | 673     local db = db[name] or db | 
| Nenue@6 | 674     local frame = CT.frame | 
| Nenue@6 | 675 | 
| Nenue@6 | 676     -- script defs | 
| Nenue@6 | 677     frame.IsFrameEvent = CT.trigger | 
| Nenue@6 | 678     frame.name = name | 
| Nenue@6 | 679     frame.lineID = 0 | 
| Nenue@6 | 680     frame.expired = {} | 
| Nenue@6 | 681     frame.active = {} | 
| Nenue@6 | 682 | 
| Nenue@6 | 683     -- frame defs | 
| Nenue@6 | 684     frame:SetPoint(db.anchor, db.parent, db.anchorTo, db.x, db.y) | 
| Nenue@6 | 685     frame:RegisterEvent('COMBAT_LOG_EVENT_UNFILTERED') | 
| Nenue@6 | 686     print('bound ', name, 'with', CT.OnEvent) | 
| Nenue@6 | 687     db.sample = {} | 
| Nenue@6 | 688     local logged = {} | 
| Nenue@6 | 689     frame:SetScript('OnEvent', function(self, e,...) | 
| Nenue@6 | 690       if CT.trigger(e, ...) then | 
| Nenue@6 | 691         print('event trigger fired', name) | 
| Nenue@6 | 692           if #db.sample < 5 and not logged[select(2,...)] then | 
| Nenue@6 | 693             logged[select(2,...)] = true | 
| Nenue@6 | 694             tinsert(db.sample, {e, ...}) | 
| Nenue@6 | 695           end | 
| Nenue@6 | 696 | 
| Nenue@6 | 697         CT.OnEvent(frame, e, ...) | 
| Nenue@6 | 698       end | 
| Nenue@6 | 699     end) | 
| Nenue@6 | 700 | 
| Nenue@6 | 701     -- configurators | 
| Nenue@6 | 702     frame.configHeader:SetText(name) | 
| Nenue@6 | 703     frame:EnableMouse(false) | 
| Nenue@6 | 704 | 
| Nenue@6 | 705     -- pre-pop some text frames | 
| Nenue@6 | 706     for i = (CT.lineID or 1), SCT_PREGAME do | 
| Nenue@6 | 707       print(frame:GetName(), 'pre-pop #'..i) | 
| Nenue@6 | 708       mod.NewCombatMessage(frame) | 
| Nenue@6 | 709     end | 
| Nenue@6 | 710   end | 
| Nenue@6 | 711 end | 
| Nenue@6 | 712 | 
| Nenue@6 | 713 | 
| Nenue@6 | 714 local damageEvents = { | 
| Nenue@6 | 715   ['SPELL_DAMAGE'] = true, | 
| Nenue@6 | 716   ['SPELL_PERIODIC_DAMAGE'] = true, | 
| Nenue@6 | 717   ['SWING_DAMAGE'] = true, | 
| Nenue@6 | 718   ['RANGE_DAMAGE'] = true, | 
| Nenue@6 | 719   ['SPELL_HEAL'] = true, | 
| Nenue@6 | 720 } | 
| Nenue@6 | 721 mod.CombatTextFrames = { | 
| Nenue@6 | 722   Incoming = { | 
| Nenue@6 | 723     trigger = function(e, _, c, _, _, _, _, _, d) return (d == T.GUID and damageEvents[c]) end, | 
| Nenue@6 | 724     OnEvent = mod.OnDamage, | 
| Nenue@6 | 725     sample = { | 
| Nenue@6 | 726       {"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, }, | 
| Nenue@6 | 727     } | 
| Nenue@6 | 728   }, | 
| Nenue@6 | 729   Outgoing = { | 
| Nenue@6 | 730     trigger = function(e, _,c,_, s) return (s == T.GUID and damageEvents[c]) end, | 
| Nenue@6 | 731     OnEvent = mod.OnDamage, | 
| Nenue@6 | 732     sample = { | 
| Nenue@6 | 733     { | 
| Nenue@6 | 734         "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, }, { | 
| Nenue@6 | 735         "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, }, { | 
| Nenue@6 | 736         "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, }, { | 
| Nenue@6 | 737         "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] | 
| Nenue@6 | 738     } | 
| Nenue@6 | 739   }, | 
| Nenue@6 | 740   DoTTracker = { | 
| Nenue@6 | 741     trigger = function(e, _, c, _, s) return (s == T.GUID and dotEvents[c]) end, | 
| Nenue@6 | 742     OnEvent = mod.OnDotEvent, | 
| Nenue@6 | 743     sample = { | 
| Nenue@6 | 744       {"COMBAT_LOG_EVENT_UNFILTERED", 1455887795.006, "SPELL_AURA_APPLIED",    false, | 
| Nenue@6 | 745         "Player-3684-07235A4E", "Klakyn", 1297, 0, | 
| Nenue@6 | 746         "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0, | 
| Nenue@6 | 747         589, "Shadow Word: Pain", 32, "DEBUFF", }, | 
| Nenue@6 | 748       {"COMBAT_LOG_EVENT_UNFILTERED", 1455887797.377, "SPELL_PERIODIC_DAMAGE", false, | 
| Nenue@6 | 749         "Player-3684-07235A4E", "Klakyn", 1297, 0, | 
| Nenue@6 | 750         "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0, | 
| Nenue@6 | 751         589, "Shadow Word: Pain", 32, 3944, -1, 32, nil, nil, nil, false, false, false, false, false, }, | 
| Nenue@6 | 752       {"COMBAT_LOG_EVENT_UNFILTERED", 1455887807.199, "SPELL_AURA_REMOVED",    false, | 
| Nenue@6 | 753         "Player-3684-07235A4E", "Klakyn", 1297, 0, | 
| Nenue@6 | 754         "Player-3684-07235A4E", "Klakyn", 1297, 0, | 
| Nenue@6 | 755         15473, "Shadowform", 32, "BUFF", }, | 
| Nenue@6 | 756     } | 
| Nenue@6 | 757   } | 
| Nenue@6 | 758 } |