annotate Turok/Modules/Combat/CombatLog.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 0b1a2f3dbfc4
rev   line source
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@9 15 _G.print('CT', ...)
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 defaultAnimation = 'slide',
Nenue@9 45 defaultFont = 'font0',
Nenue@6 46 textFonts = {
Nenue@9 47 font0 = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Regular.ttf", 18, 'OUTLINE'},
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@9 50 font3 = {"Interface\\Addons\\Turok\\Media\\font\\ArchivoNarrow-Regular.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@9 70 multistrike = {'%s', 'slide', 'font3'},
Nenue@6 71 absorbed = {'%s (%d)', 'slide'},
Nenue@6 72 blocked = {'%s {%d}', 'slide'},
Nenue@9 73 pet = {'(%s)', 'slide'},
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@9 235
Nenue@9 236 ct.string:SetFont(unpack(textFonts[defaultFont]))
Nenue@9 237 ct.fontKey = 'font0'
Nenue@6 238 return ct
Nenue@6 239 end
Nenue@6 240
Nenue@6 241 --- frame interaction logic
Nenue@6 242 mod.AddCombatMessage = function(frame, text, icon, animationType, fontKey)
Nenue@6 243 local line
Nenue@6 244 print(fontKey)
Nenue@6 245
Nenue@6 246 local expired = frame.expired
Nenue@6 247 local active = frame.active
Nenue@6 248 local a = 1
Nenue@6 249 local shiftY, shiftX
Nenue@6 250 local lastFrame
Nenue@6 251 --- If animation has overlap delta values, find the last active frame of that animation type and check for overlaps.
Nenue@6 252 --- Frames are considered overlapping when the last member's ((height - distance traveled) * delta) is over 0.
Nenue@6 253 --- This assumes the same string height for the upcoming frame since wordwrap isn't enabled.
Nenue@6 254 print(animationType)
Nenue@6 255 if animation[animationType].dx or animation[animationType].dy then
Nenue@6 256 print('animation has displacement')
Nenue@6 257 if frame.last then
Nenue@6 258 lastFrame = frame.last
Nenue@6 259 local dp = lastFrame[animationType]:GetProgress()
Nenue@6 260 if animation[animationType].dx then
Nenue@6 261 local dx = dp * animation[animationType].x
Nenue@6 262 shiftX = (lastFrame.string:GetStringWidth() - dx) * animation[animationType].dx
Nenue@6 263 end
Nenue@6 264 if animation[animationType].dy then
Nenue@6 265 local dy = dp * animation[animationType].y
Nenue@6 266 shiftY = (lastFrame.string:GetStringHeight() - dy) * animation[animationType].dy
Nenue@6 267 print(' ', 'h=', floor(lastFrame.string:GetStringHeight()), 'dY=', dy, 'offsetY=', shiftY)
Nenue@6 268 end
Nenue@6 269 print(cWord('lastFrame hit:'), lastFrame and lastFrame:GetName(), cNum(shiftX), cNum(shiftY))
Nenue@6 270 end
Nenue@6 271 end
Nenue@6 272
Nenue@6 273 -- find a usable frame
Nenue@6 274 local currentFrame
Nenue@6 275 for i, ct in ipairs(frame.active) do
Nenue@6 276 if not currentFrame then
Nenue@6 277 if ct.discard then
Nenue@6 278 ct.discard= nil
Nenue@6 279 currentFrame = ct
Nenue@6 280 end
Nenue@6 281 end
Nenue@6 282
Nenue@6 283 if lastFrame and ct.animationType == animationType and not ct.discard then
Nenue@6 284 --print('lastFrame defined, check for overlap')
Nenue@6 285 if shiftY > 0 then
Nenue@6 286 print(cWord(' * vertical shift'), cNum('+'..floor(shiftY)))
Nenue@6 287 ct.y = ct.y + shiftY
Nenue@6 288 end
Nenue@6 289 if shiftX > 0 then
Nenue@6 290 print(cWord(' * horizontal shift'), cNum('+'..floor(shiftX)))
Nenue@6 291 ct.x = ct.x + shiftX
Nenue@6 292 end
Nenue@6 293 ct:SetPoint(ct.point, frame, ct.point, ct.x, ct.y)
Nenue@6 294 end
Nenue@6 295 end
Nenue@6 296 -- if no expired frames became available, make a new one (should max at 20 or so if groupings are right)
Nenue@6 297 if not currentFrame then
Nenue@6 298 currentFrame = mod.NewCombatMessage(frame)
Nenue@6 299 print(cNum(' creating new string object for the heap'))
Nenue@6 300 end
Nenue@6 301
Nenue@6 302 print(cText(' * Starting'), cPink(animationType), 'on', cKey(currentFrame:GetName()), ' ['..cNum(currentFrame.index)..']')
Nenue@6 303
Nenue@6 304 if icon then
Nenue@6 305 currentFrame.icon:Show()
Nenue@6 306 currentFrame.icon:SetTexture(icon)
Nenue@6 307 else
Nenue@6 308 currentFrame.icon:Hide()
Nenue@6 309 end
Nenue@9 310 if fontKey and fontKey ~= currentFrame.fontKey then
Nenue@6 311 local newFont = fontKey and textFonts[fontKey] or defaultFont
Nenue@6 312
Nenue@6 313 local path, size, flags = currentFrame.string:GetFont()
Nenue@6 314 path = newFont[1] or path
Nenue@6 315 size = newFont[2] or size
Nenue@6 316 flags = newFont[3] or flags
Nenue@9 317 print(cText('swapping font to "'..cWord(fontKey)..')":'), path, size, flags)
Nenue@9 318 currentFrame.string:SetFont(path, size, flags)
Nenue@9 319 currentFrame.fontKey = fontKey
Nenue@6 320 end
Nenue@6 321
Nenue@6 322 currentFrame.animationType = animationType
Nenue@6 323 currentFrame.string:SetText(text)
Nenue@6 324 local cHeight = currentFrame.string:GetStringHeight()
Nenue@6 325 currentFrame:SetSize(currentFrame.string:GetStringWidth(), cHeight)
Nenue@6 326 currentFrame.icon:SetSize(cHeight, cHeight)
Nenue@6 327 currentFrame.y = 0
Nenue@6 328 currentFrame.x = 0
Nenue@6 329 currentFrame:SetPoint(currentFrame.point, frame, currentFrame.point, currentFrame.x, currentFrame.y)
Nenue@6 330 currentFrame[animationType]:Play()
Nenue@6 331 frame.last = currentFrame
Nenue@6 332 end
Nenue@6 333
Nenue@6 334 local GetTextFormatFunc = function(amount, overKill, absorbed, blocked, multistrike, spellName, sourceName, destName)
Nenue@6 335 local sub_text = {
Nenue@6 336 ['%d'] = amount,
Nenue@6 337 ['%o'] = overKill,
Nenue@6 338 ['%a'] = absorbed,
Nenue@6 339 ['%b'] = blocked,
Nenue@6 340 ['%m'] = multistrike,
Nenue@6 341 ['%s'] = spellName,
Nenue@6 342 ['%n'] = sourceName,
Nenue@6 343 ['%t'] = destName,
Nenue@6 344 }
Nenue@6 345 local func = function(token)
Nenue@6 346 print(cPink('gsub run:'), token)
Nenue@6 347 return sub_text[token]
Nenue@6 348 end
Nenue@6 349 return func
Nenue@6 350 end
Nenue@6 351
Nenue@6 352 --- builds CT messages from combat log event data
Nenue@6 353 -- separated from frame interaction for auxiliary events such as combat/loot/etc
Nenue@6 354 local CreateDamageText = function(frame, timestamp, combatEvent, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike, ticks)
Nenue@6 355 if combatEvent:match('^SWING') then
Nenue@6 356 spellID = 1
Nenue@6 357 spellName = 'Attack'
Nenue@6 358 elseif combatEvent:match('^RANGE') then
Nenue@6 359 spellID = 2
Nenue@6 360 spellName = 'Auto Shot'
Nenue@6 361 end
Nenue@6 362 if not combatEvent:match('DAMAGE') then
Nenue@6 363 return
Nenue@6 364 end
Nenue@6 365
Nenue@9 366 local fontKey = defaultFont
Nenue@6 367 local icon = GetSpellTexture(spellID)
Nenue@6 368 local animationType = defaultAnimation
Nenue@6 369 local text = amount
Nenue@6 370 if textFormat[combatEvent] then
Nenue@6 371 local tString, fontKey = unpack(textFormat[combatEvent])
Nenue@6 372 if amount > 1000000 then
Nenue@6 373 amount = (floor(amount/100000)/10) ..'M'
Nenue@6 374 elseif amount > 1000 then
Nenue@6 375 amount = (floor(amount/100)/10) ..'k'
Nenue@6 376 end
Nenue@6 377
Nenue@6 378 text = tString:gsub('%%[doabsnt]', GetTextFormatFunc(amount, overKill, absorbed, blocked, multistrike, spellName, sourceName, destName))
Nenue@6 379 print("** font override:", '"'..cText(tString)..'",', cPink(font), cNum(size), cWord(outline))
Nenue@6 380 end
Nenue@6 381
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 if ticks then
Nenue@6 412 text = groupedModifier:format(text, ticks)
Nenue@6 413 animationType = groupedAnimation
Nenue@6 414 fontKey = groupedFont or fontKey
Nenue@6 415 end
Nenue@6 416
Nenue@6 417
Nenue@9 418 print(cText('** AddCombatMessage(')..cKey(frame:GetName())..cText('):'), text, cWord(tostring(icon):sub(-20,-1)), cWord(animationType), cKey(fontKey))
Nenue@6 419
Nenue@6 420
Nenue@6 421 mod.AddCombatMessage(frame, text .. '|r', icon, animationType, fontKey)
Nenue@6 422 end
Nenue@6 423
Nenue@6 424 local CT_ShowConsolidated = function()
Nenue@6 425 for spellName, queuedSpell in pairs(groupedQueue) do
Nenue@6 426 local isChannel, minDelay, combatEvent, sourceName, destName, spellID, school, SCT, timestamp = unpack(queuedSpell)
Nenue@6 427 for i, v in ipairs(queuedSpell) do
Nenue@6 428 print(' ', i, '=', v)
Nenue@6 429 end
Nenue@6 430
Nenue@6 431 print(spellName, 'vars:', spellID, spellName, school, SCT, timestamp)
Nenue@6 432 if groupedEvents[spellName] and #groupedEvents[spellName] ~= 0 then
Nenue@6 433 local amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, hit = 0, -1, 0, 0, 0, 0, 0, 0, 0, 0
Nenue@6 434 for i, line in ipairs(groupedEvents[spellName]) do
Nenue@6 435 -- extra strike?
Nenue@6 436 --{amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike}
Nenue@6 437 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 438
Nenue@6 439 amount = amount + amount_n
Nenue@6 440 if overKill_n > 0 then overKill = overKill + overKill_n end
Nenue@6 441 if blocked_n then blocked = blocked + line[4] end
Nenue@6 442 if absorbed_n then absorbed = absorbed + line[5] end
Nenue@6 443 if critical_n then critical = critical + 1 end
Nenue@6 444 if multi_n then multistrike = multistrike + 1 end
Nenue@6 445
Nenue@6 446 hit = hit + 1
Nenue@6 447 end
Nenue@6 448 if overKill == -1 then
Nenue@6 449 overKill = overKill + 1
Nenue@6 450 end
Nenue@6 451 wipe(groupedEvents[spellName])
Nenue@6 452 groupedQueue[spellName] = nil
Nenue@6 453 print(' expelling', spellName, cText('A:'), cNum(amount), cText('O:'), cNum(overKill), cText('Ticks:'), cNum(hit), cText('Crits:'), cNum(critical), cText(school))
Nenue@6 454 print(SCT, timestamp, combatEvent, sourceName, destName, spellID, spellName, school, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, hit)
Nenue@6 455 CreateDamageText(SCT, timestamp, combatEvent, sourceName, destName, spellID, spellName, school, amount, -1, nil, nil, nil, false, glancing, crushing, false, multistrike, hit)
Nenue@6 456 end
Nenue@6 457 end
Nenue@6 458 end
Nenue@6 459
Nenue@6 460 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 461 local archive = groupedSpells[spellName] and groupedSpells[spellName] or groupedSpells.global
Nenue@6 462 local isChannel, minDelay, combatEvent = unpack(archive)
Nenue@6 463 if not groupedEvents[spellName] then
Nenue@6 464 groupedEvents[spellName] = {}
Nenue@6 465 end
Nenue@6 466
Nenue@6 467 if #groupedEvents[spellName] == 0 then
Nenue@6 468 groupedQueue[spellName] = {isChannel, minDelay, combatEvent, sourceName, destName, spellID, school, self, timestamp}
Nenue@6 469 T:ScheduleTimer(CT_ShowConsolidated, minDelay)
Nenue@6 470 print(' starting archive for', select(7, unpack(archive)))
Nenue@6 471 else
Nenue@6 472 print(' recording into archive', cText(spellName))
Nenue@6 473 end
Nenue@6 474 tinsert(groupedEvents[spellName], {amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, multistrike, sourceGUID, destGUID, sourceName, destName, sourceName, destName,})
Nenue@6 475 end
Nenue@6 476
Nenue@6 477 local CT_OnCast = function(self, event, unit, spellName, lineID, spellID)
Nenue@6 478 if unit ~= 'player' and unit ~= 'pet' then
Nenue@6 479 return
Nenue@6 480 end
Nenue@6 481
Nenue@6 482 if groupedSpells[spellName] then
Nenue@6 483 print(cText('** Spell casting info received'))
Nenue@6 484 local isChannel, delay = unpack(groupedSpells[spellName])
Nenue@6 485 if isChannel and event == 'CHANNEL_STOP' and groupedEvents[spellName] then
Nenue@6 486 T:ScheduleTimer(CT_ShowConsolidated, delay)
Nenue@6 487 else
Nenue@6 488 if not groupedEvents[spellName] then
Nenue@6 489 groupedEvents[spellName] = {}
Nenue@6 490 end
Nenue@6 491 end
Nenue@6 492 end
Nenue@6 493 end
Nenue@6 494
Nenue@6 495 local CT_Unlock = function(str)
Nenue@6 496 for i, CT in pairs(mod.CombatTextFrames) do
Nenue@6 497 local frame = CT.frame
Nenue@6 498 frame.configMode = (frame.configMode == nil) and true or nil
Nenue@6 499 frame:RegisterForDrag(frame.configMode and 'LeftButton' or nil)
Nenue@6 500 frame:EnableMouse(frame.configMode and true or false)
Nenue@6 501 print(i, frame.configMode and 'ON' or 'OFF')
Nenue@6 502 for _, reg in ipairs(frame.configRegions) do
Nenue@6 503 if frame.configMode then
Nenue@6 504 reg:Show()
Nenue@6 505 else
Nenue@6 506 reg:Hide()
Nenue@6 507 end
Nenue@6 508 end
Nenue@6 509
Nenue@6 510 if frame.configMode then
Nenue@6 511 CT.configTimer = T:ScheduleRepeatingTimer(function()
Nenue@6 512 print(i, 'config tick')
Nenue@6 513 for i, s in ipairs(CT.sample) do
Nenue@6 514 CT.OnEvent(frame, unpack(s))
Nenue@6 515 end
Nenue@6 516 end, 2)
Nenue@6 517 else
Nenue@6 518 print(CT.configTimer)
Nenue@6 519 T:CancelTimer(CT.configTimer)
Nenue@6 520 end
Nenue@6 521
Nenue@6 522 end
Nenue@6 523 end
Nenue@6 524
Nenue@6 525 --- check for sectors
Nenue@6 526 local queue = {}
Nenue@6 527 mod.OnDamage = function(self, event, ...)
Nenue@6 528 local isVisible
Nenue@6 529
Nenue@6 530 local timestamp, combatEvent = ...
Nenue@9 531 print(cText('OnDamage'), cKey(combatEvent))
Nenue@6 532 if combatEvent == 'UNIT_DIED' then
Nenue@6 533 return
Nenue@6 534 end
Nenue@6 535 local sourceGUID, sourceName, sourceFlags, _, destGUID, destName, destFlags, _, spellID, spellName, spellSchool = select(4, ...)
Nenue@9 536 --print(' from', cPink(sourceGUID), 'spell', cNum(spellID), cText(spellName), '->', cKey(destGUID))
Nenue@6 537
Nenue@6 538 -- SWING starts at arg 10, SPELL/RANGE start at arg 13, ENVIRONMENTAL starts at arg 8
Nenue@6 539 local offset = 15
Nenue@6 540 if combatEvent:sub(0,3) == 'SWI' then
Nenue@6 541 offset = 10
Nenue@6 542 elseif combatEvent:sub(0,3) == 'ENV' then
Nenue@6 543 offset = 8
Nenue@6 544 end
Nenue@6 545 local amount, overKill, effectSchool, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike = select(offset, ...)
Nenue@6 546
Nenue@6 547
Nenue@6 548 local sc = SpellSchoolColors[effectSchool]
Nenue@6 549 if groupedSpells[spellName] then
Nenue@6 550 print('* ', cText(spellName), 'to consolidator')
Nenue@6 551 CT_ConsolidateText(self, timestamp, sourceGUID, destGUID, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike)
Nenue@6 552 return
Nenue@6 553 end
Nenue@6 554
Nenue@6 555 print('displaying on', cWord(self:GetName()))
Nenue@6 556 CreateDamageText(self, timestamp, combatEvent, sourceName, destName, spellID, spellName, effectSchool, amount, overKill, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand, multistrike)
Nenue@6 557 end
Nenue@6 558
Nenue@6 559 mod.maxDotFrame = 1
Nenue@6 560 mod.OnDotEvent = function (self, event, timestamp, combatEvent, ...)
Nenue@6 561 print(cWord('DOT'), combatEvent)
Nenue@6 562 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 563 print(cText('dot from'), cWord(sourceGUID), 'to', cKey(destGUID))
Nenue@6 564 if sourceGUID == T.GUID then
Nenue@6 565 local p = mod.PeriodicTable
Nenue@6 566 p[destGUID] = p[destGUID] or {}
Nenue@6 567 local unit = p[destGUID]
Nenue@6 568 if not p[spellID] then
Nenue@6 569 if not p.frames[#p.frames] then
Nenue@6 570 unit[spellID] = CreateFrame('Frame', 'DotBar'..mod.maxDotFrame, p, 'TkDotBarTemplate')
Nenue@6 571 mod.maxDotFrame = mod.maxDotFrame + 1
Nenue@6 572 unit[spellID].dotID = mod.maxDotFrame
Nenue@6 573
Nenue@6 574 print(' create new frame ['..cKey(unit[spellID]:GetID(), '] for'), cWord(spellName), cKey(destGUID))
Nenue@6 575 else
Nenue@6 576 unit[spellID] = p.frames[#p.frames]
Nenue@6 577 p.frames[#p.frames] = nil -- remove that entry
Nenue@6 578 print(' recycling frame ['..cKey(unit[spellID].dotID, '] for'), cWord(spellName), cKey(destGUID))
Nenue@6 579 end
Nenue@6 580
Nenue@6 581 unit[spellID]:Show()
Nenue@6 582 end
Nenue@6 583 local dot = unit[spellID]
Nenue@6 584 local time = GetTime()
Nenue@6 585 end
Nenue@6 586 end
Nenue@6 587
Nenue@6 588
Nenue@6 589
Nenue@6 590
Nenue@6 591 function mod:OnInitialize()
Nenue@6 592 print('This is a thing.')
Nenue@6 593 self.UNIT_SPELLCAST_SUCCEEDED = CT_OnCast
Nenue@6 594 self.UNIT_SPELLCAST_CHANNEL_START = CT_OnCast
Nenue@6 595 self.UNIT_SPELLCAST_CHANNEL_STOP = CT_OnCast
Nenue@6 596 end
Nenue@6 597
Nenue@6 598 function mod:OnInitialize()
Nenue@6 599 mod.db = TurokData.CombatText
Nenue@6 600 db = TurokData.CombatText
Nenue@6 601 myGUID = T.GUID
Nenue@6 602 mod.PeriodicTable = mod.PeriodicTable or CreateFrame('Frame', 'TurokPeriodicFrame', UIParent)
Nenue@6 603 mod.PeriodicTable.frames = {}
Nenue@6 604
Nenue@6 605 local m = mod.db.textModifiers
Nenue@6 606 --- These values are going to be looked up a lot, so cache as close as possible
Nenue@6 607 criticalModifier = m.critical[1] or '%s CRIT'
Nenue@6 608 criticalAnimation = m.critical[2] or 'slide'
Nenue@6 609 criticalFont = m.critical[3]
Nenue@6 610
Nenue@6 611 absorbedModifier = m.absorbed[1] or '%s ABS'
Nenue@6 612 absorbedAnimation = m.absorbed[2] or 'slide'
Nenue@6 613 absorbedFont = m.absorbed[3]
Nenue@6 614
Nenue@6 615 blockedModifier = m.blocked[1] or '%s BLK'
Nenue@6 616 blockedAnimation = m.blocked[2] or 'slide'
Nenue@6 617 blockedFont = m.blocked[3]
Nenue@6 618
Nenue@6 619 overKillModifier = m.overKill[1] or '%s KILL'
Nenue@6 620 overKillAnimation = m.overKill[2] or 'slide'
Nenue@6 621 overKillFont = m.overKill[3]
Nenue@6 622
Nenue@6 623 multistrikeModifier = m.multistrike[1] or '%s MS'
Nenue@6 624 multistrikeAnimation = m.multistrike[2] or 'slide'
Nenue@6 625 multistrikeFont = m.multistrike[3]
Nenue@6 626
Nenue@6 627 groupedModifier = m.grouped[1] or '%s'
Nenue@6 628 groupedAnimation = m.grouped[2] or 'slide'
Nenue@6 629 groupedFont = m.grouped[3]
Nenue@6 630
Nenue@6 631 --- Same as above, but for specific table values, key is determined by the combat event
Nenue@6 632 defaultAnimation = mod.db.defaultAnimation
Nenue@6 633 for k,v in pairs(mod.db.textFormat) do
Nenue@6 634 textFormat[k] = {v[1], v[2]}
Nenue@6 635 print('imported textFormat.'..k, cText(textFormat[k][1]), cNum(textFormat[k][2]))
Nenue@6 636 end
Nenue@6 637 for k,v in pairs(mod.db.textFonts) do
Nenue@6 638 textFonts[k] = {v[1] or defaultFont[1], v[2] or defaultFont[2], v[3] or defaultFont[3]}
Nenue@6 639 print('imported font.'..k, cText(textFonts[k][1]), cNum(textFonts[k][2]), cWord(textFonts[k][3]))
Nenue@6 640 end
Nenue@9 641 defaultFont = mod.db.defaultFont
Nenue@6 642
Nenue@6 643 for k,v in pairs(mod.db.animation) do
Nenue@6 644 animation[k] = {}
Nenue@6 645 animation[k].x = v.x
Nenue@6 646 animation[k].y = v.y
Nenue@6 647 animation[k].dx = v.dx
Nenue@6 648 animation[k].dy = v.dy
Nenue@6 649 animation[k].fromScale = v.fromScale
Nenue@6 650 animation[k].toScale = v.toScale
Nenue@6 651 animation[k].deviation = v.deviation
Nenue@6 652 animation[k].change = v.change
Nenue@6 653 animation[k].fromAlpha = v.fromAlpha
Nenue@6 654 animation[k].toAlpha = v.toAlpha
Nenue@6 655 animation[k].duration = v.duration
Nenue@6 656 end
Nenue@6 657 end
Nenue@6 658
Nenue@6 659 function mod:OnEnable()
Nenue@6 660 T:RegisterChatCommand('tkc', CT_Unlock)
Nenue@6 661
Nenue@9 662 local fontPath, fontSize, fontFlags = unpack(db.textFonts.font0)
Nenue@6 663 --- Populate CT frames
Nenue@6 664 for name, CT in pairs(mod.CombatTextFrames) do
Nenue@6 665 print('create CT', name)
Nenue@6 666 -- make frame
Nenue@6 667 CT.frame = CT.frame or CreateFrame('Frame', SCT_NAME:format(name), UIParent, SCT_TEMPLATE)
Nenue@6 668
Nenue@6 669 -- local vars
Nenue@6 670 local db = db[name] or db
Nenue@6 671 local frame = CT.frame
Nenue@6 672
Nenue@6 673 -- script defs
Nenue@6 674 frame.IsFrameEvent = CT.trigger
Nenue@6 675 frame.name = name
Nenue@6 676 frame.lineID = 0
Nenue@6 677 frame.expired = {}
Nenue@6 678 frame.active = {}
Nenue@6 679
Nenue@6 680 -- frame defs
Nenue@6 681 frame:SetPoint(db.anchor, db.parent, db.anchorTo, db.x, db.y)
Nenue@6 682 frame:RegisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
Nenue@6 683 print('bound ', name, 'with', CT.OnEvent)
Nenue@6 684 db.sample = {}
Nenue@6 685 local logged = {}
Nenue@6 686 frame:SetScript('OnEvent', function(self, e,...)
Nenue@6 687 if CT.trigger(e, ...) then
Nenue@6 688 print('event trigger fired', name)
Nenue@6 689 if #db.sample < 5 and not logged[select(2,...)] then
Nenue@6 690 logged[select(2,...)] = true
Nenue@6 691 tinsert(db.sample, {e, ...})
Nenue@6 692 end
Nenue@6 693
Nenue@6 694 CT.OnEvent(frame, e, ...)
Nenue@6 695 end
Nenue@6 696 end)
Nenue@6 697
Nenue@6 698 -- configurators
Nenue@6 699 frame.configHeader:SetText(name)
Nenue@6 700 frame:EnableMouse(false)
Nenue@6 701
Nenue@6 702 -- pre-pop some text frames
Nenue@6 703 for i = (CT.lineID or 1), SCT_PREGAME do
Nenue@6 704 print(frame:GetName(), 'pre-pop #'..i)
Nenue@6 705 mod.NewCombatMessage(frame)
Nenue@6 706 end
Nenue@6 707 end
Nenue@6 708 end
Nenue@6 709
Nenue@6 710
Nenue@6 711 local damageEvents = {
Nenue@6 712 ['SPELL_DAMAGE'] = true,
Nenue@6 713 ['SPELL_PERIODIC_DAMAGE'] = true,
Nenue@6 714 ['SWING_DAMAGE'] = true,
Nenue@6 715 ['RANGE_DAMAGE'] = true,
Nenue@6 716 ['SPELL_HEAL'] = true,
Nenue@6 717 }
Nenue@6 718 mod.CombatTextFrames = {
Nenue@6 719 Incoming = {
Nenue@6 720 trigger = function(e, _, c, _, _, _, _, _, d) return (d == T.GUID and damageEvents[c]) end,
Nenue@6 721 OnEvent = mod.OnDamage,
Nenue@6 722 sample = {
Nenue@6 723 {"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 724 }
Nenue@6 725 },
Nenue@6 726 Outgoing = {
Nenue@6 727 trigger = function(e, _,c,_, s) return (s == T.GUID and damageEvents[c]) end,
Nenue@6 728 OnEvent = mod.OnDamage,
Nenue@6 729 sample = {
Nenue@6 730 {
Nenue@6 731 "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 732 "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 733 "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 734 "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 735 }
Nenue@6 736 },
Nenue@6 737 DoTTracker = {
Nenue@6 738 trigger = function(e, _, c, _, s) return (s == T.GUID and dotEvents[c]) end,
Nenue@6 739 OnEvent = mod.OnDotEvent,
Nenue@6 740 sample = {
Nenue@6 741 {"COMBAT_LOG_EVENT_UNFILTERED", 1455887795.006, "SPELL_AURA_APPLIED", false,
Nenue@6 742 "Player-3684-07235A4E", "Klakyn", 1297, 0,
Nenue@6 743 "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0,
Nenue@6 744 589, "Shadow Word: Pain", 32, "DEBUFF", },
Nenue@6 745 {"COMBAT_LOG_EVENT_UNFILTERED", 1455887797.377, "SPELL_PERIODIC_DAMAGE", false,
Nenue@6 746 "Player-3684-07235A4E", "Klakyn", 1297, 0,
Nenue@6 747 "Creature-0-3684-1116-7-87761-0000C32119", "Dungeoneer's Training Dummy", 68136, 0,
Nenue@6 748 589, "Shadow Word: Pain", 32, 3944, -1, 32, nil, nil, nil, false, false, false, false, false, },
Nenue@6 749 {"COMBAT_LOG_EVENT_UNFILTERED", 1455887807.199, "SPELL_AURA_REMOVED", false,
Nenue@6 750 "Player-3684-07235A4E", "Klakyn", 1297, 0,
Nenue@6 751 "Player-3684-07235A4E", "Klakyn", 1297, 0,
Nenue@6 752 15473, "Shadowform", 32, "BUFF", },
Nenue@6 753 }
Nenue@6 754 }
Nenue@6 755 }