Advertisement
AsuranDex

Untitled

Nov 25th, 2020
555
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 97.31 KB | None | 0 0
  1. --------------------------------------------------------------------------------
  2. -- CONFIGURATION
  3. --
  4. -- The configuration options have moved to the "Author Options" tab as of
  5. -- WeakAuras Version 2.10.
  6. --
  7. -- DO NOT EDIT THIS CODE!
  8. --------------------------------------------------------------------------------
  9. local ZT = aura_env
  10.  
  11. -- Local versions of commonly used functions
  12. local ipairs = ipairs
  13. local pairs = pairs
  14. local print = print
  15. local select = select
  16. local tonumber = tonumber
  17. local tinsert = tinsert
  18.  
  19. local IsInGroup = IsInGroup
  20. local IsInRaid = IsInRaid
  21. local UnitGUID = UnitGUID
  22.  
  23. -- Turns on/off debugging messages
  24. local DEBUG_EVENT = { isEnabled = false, color = "FF2281F4" }
  25. local DEBUG_MESSAGE = { isEnabled = false, color = "FF11D825" }
  26. local DEBUG_TIMER = { isEnabled = false, color = "FFF96D27" }
  27. local DEBUG_TRACKING = { isEnabled = false, color = "FFA53BF7" }
  28.  
  29. -- Turns on/off testing of combatlog-based tracking for the player
  30. -- (Note: This will disable sharing of player CD updates over addon messages)
  31. local TEST_CLEU = false
  32.  
  33. local function prdebug(type, ...)
  34. if type.isEnabled then
  35. print("|c"..type.color.."[ZT-Debug]", ...)
  36. end
  37. end
  38.  
  39. local function prerror(...)
  40. print("|cFFFF0000[ZT-Error]", ...)
  41. end
  42.  
  43. -- Utility functions for creating tables/maps
  44. local function DefaultTable_Create(genDefaultFunc)
  45. local metatable = {}
  46. metatable.__index = function(table, key)
  47. local value = genDefaultFunc()
  48. rawset(table, key, value)
  49. return value
  50. end
  51.  
  52. return setmetatable({}, metatable)
  53. end
  54.  
  55. local function Map_FromTable(table)
  56. local map = {}
  57. for _,value in ipairs(table) do
  58. map[value] = true
  59. end
  60. return map
  61. end
  62.  
  63. --##############################################################################
  64. -- Class and Spec Information
  65.  
  66. local DH = {ID=12, name="DEMONHUNTER", Havoc=577, Veng=581}
  67. local DK = {ID=6, name="DEATHKNIGHT", Blood=250, Frost=251, Unholy=252}
  68. local Druid = {ID=11, name="DRUID", Balance=102, Feral=103, Guardian=104, Resto=105}
  69. local Hunter = {ID=3, name="HUNTER", BM=253, MM=254, SV=255}
  70. local Mage = {ID=8, name="MAGE", Arcane=62, Fire=63, Frost=64}
  71. local Monk = {ID=10, name="MONK", BRM=268, WW=269, MW=270}
  72. local Paladin = {ID=2, name="PALADIN", Holy=65, Prot=66, Ret=70}
  73. local Priest = {ID=5, name="PRIEST", Disc=256, Holy=257, Shadow=258}
  74. local Rogue = {ID=4, name="ROGUE", Sin=259, Outlaw=260, Sub=261}
  75. local Shaman = {ID=7, name="SHAMAN", Ele=262, Enh=263, Resto=264}
  76. local Warlock = {ID=9, name="WARLOCK", Affl=265, Demo=266, Destro=267}
  77. local Warrior = {ID=1, name="WARRIOR", Arms=71, Fury=72, Prot=73}
  78.  
  79. local AllClasses = {
  80. [DH.name] = DH, [DK.name] = DK, [Druid.name] = Druid, [Hunter.name] = Hunter,
  81. [Mage.name] = Mage, [Monk.name] = Monk, [Paladin.name] = Paladin,
  82. [Priest.name] = Priest, [Rogue.name] = Rogue, [Shaman.name] = Shaman,
  83. [Warlock.name] = Warlock, [Warrior.name] = Warrior
  84. }
  85.  
  86. --##############################################################################
  87. -- Spell Requirements
  88.  
  89. local function Requirement(type, check, indices)
  90. return { type = type, check = check, indices = indices }
  91. end
  92.  
  93. local function LevelReq(minLevel)
  94. return Requirement("level", function(member) return member.level >= minLevel end, {minLevel})
  95. end
  96.  
  97. local function RaceReq(race)
  98. return Requirement("race", function(member) return member.race == race end, {race})
  99. end
  100.  
  101. local function ClassReq(class)
  102. return Requirement("class", function(member) return member.classID == class.ID end, {class.ID})
  103. end
  104.  
  105. local function SpecReq(ids)
  106. local idsMap = Map_FromTable(ids)
  107. return Requirement("spec", function(member) return idsMap[member.specID] ~= nil end, ids)
  108. end
  109.  
  110. local function TalentReq(id)
  111. return Requirement("talent", function(member) return member.talents[id] ~= nil end, {id})
  112. end
  113.  
  114. local function NoTalentReq(id)
  115. return Requirement("notalent", function(member) return member.talents[id] == nil end, {id})
  116. end
  117.  
  118. -- local function ItemReq(id)
  119. -- return Requirement("items", function(member) return false end)
  120. -- end
  121.  
  122. local function CovenantReq(name)
  123. return Requirement("covenant", function(member) return false end)
  124. end
  125.  
  126. --##############################################################################
  127. -- Spell Modifiers (Static and Dynamic)
  128.  
  129. local function StaticMod(func)
  130. return { type = "Static", func = func }
  131. end
  132.  
  133. local function SubtractMod(amount)
  134. return StaticMod(function(watchInfo) watchInfo.duration = watchInfo.duration - amount end)
  135. end
  136.  
  137. local function MultiplyMod(coeff)
  138. return StaticMod(function(watchInfo) watchInfo.duration = watchInfo.duration * coeff end)
  139. end
  140.  
  141. local function ChargesMod(amount)
  142. return StaticMod(function(watchInfo)
  143. watchInfo.charges = amount
  144. watchInfo.maxCharges = amount
  145. end)
  146. end
  147.  
  148.  
  149. local function DynamicMod(handlers)
  150. if handlers.type then
  151. handlers = { handlers }
  152. end
  153.  
  154. return { type = "Dynamic", handlers = handlers }
  155. end
  156.  
  157. local function EventDeltaMod(type, spellID, delta)
  158. return DynamicMod({
  159. type = type,
  160. spellID = spellID,
  161. handler = function(watchInfo)
  162. watchInfo:updateCDDelta(delta)
  163. end
  164. })
  165. end
  166.  
  167. local function CastDeltaMod(spellID, delta)
  168. return EventDeltaMod("SPELL_CAST_SUCCESS", spellID, delta)
  169. end
  170.  
  171. local function EventRemainingMod(type, spellID, remaining)
  172. return DynamicMod({
  173. type = type,
  174. spellID = spellID,
  175. handler = function(watchInfo)
  176. watchInfo:updateCDRemaining(remaining)
  177. end
  178. })
  179. end
  180.  
  181. local function CastRemainingMod(spellID, remaining)
  182. return EventRemainingMod("SPELL_CAST_SUCCESS", spellID, remaining)
  183. end
  184.  
  185. -- If Shockwave 3+ targets hit then reduces cooldown by 15 seconds
  186. local RumblingEarthMod = DynamicMod({
  187. {
  188. type = "SPELL_CAST_SUCCESS", spellID = 46968,
  189. handler = function(watchInfo)
  190. watchInfo.numHits = 0
  191. end
  192. },
  193. {
  194. type = "SPELL_AURA_APPLIED", spellID = 132168,
  195. handler = function(watchInfo)
  196. watchInfo.numHits = watchInfo.numHits + 1
  197. if watchInfo.numHits == 3 then
  198. watchInfo:updateCDDelta(-15)
  199. end
  200. end
  201. }
  202. })
  203.  
  204. -- Each target hit by Capacitor Totem reduces cooldown by 5 seconds (up to 4 targets hit)
  205. local function StaticChargeAuraHandler(watchInfo)
  206. watchInfo.numHits = watchInfo.numHits + 1
  207. if watchInfo.numHits <= 4 then
  208. watchInfo:updateCDDelta(-5)
  209. end
  210. end
  211.  
  212. local StaticChargeMod = DynamicMod({
  213. type = "SPELL_SUMMON", spellID = 192058,
  214. handler = function(watchInfo)
  215. watchInfo.numHits = 0
  216.  
  217. if watchInfo.totemGUID then
  218. ZT.eventHandlers:remove("SPELL_AURA_APPLIED", 118905, watchInfo.totemGUID, StaticChargeAuraHandler)
  219. end
  220.  
  221. watchInfo.totemGUID = select(8, CombatLogGetCurrentEventInfo())
  222. ZT.eventHandlers:add("SPELL_AURA_APPLIED", 118905, watchInfo.totemGUID, StaticChargeAuraHandler, watchInfo)
  223. end
  224. })
  225.  
  226.  
  227. -- Guardian Spirit: If expires watchInfothout healing then reset to 60 seconds
  228. local GuardianAngelMod = DynamicMod({
  229. {
  230. type = "SPELL_HEAL", spellID = 48153,
  231. handler = function(watchInfo)
  232. watchInfo.spiritHeal = true
  233. end
  234. },
  235. {
  236. type = "SPELL_AURA_REMOVED", spellID = 47788,
  237. handler = function(watchInfo)
  238. if not watchInfo.spiritHeal then
  239. watchInfo:updateCDRemaining(60)
  240. end
  241. watchInfo.spiritHeal = false
  242. end
  243. }
  244. })
  245.  
  246. -- Dispels: Go on cooldown only if a debuff is dispelled
  247. local function DispelMod(spellID)
  248. return DynamicMod({
  249. type = "SPELL_DISPEL",
  250. spellID = spellID,
  251. handler = function(watchInfo)
  252. watchInfo:updateCDRemaining(8)
  253. end
  254. })
  255. end
  256.  
  257. -- Resource Spending: For every spender, reduce cooldown by (coefficient * cost) seconds
  258. -- Note: By default, I try to use minimum cost values as to not over-estimate the cooldown reduction
  259. local specIDToSpenderInfo = {
  260. [DK.Blood] = {
  261. [47541] = 40, -- Death Coil
  262. [49998] = 40, -- Death Strike (Assumes -5 due to Ossuary)
  263. [61999] = 30, -- Raise Ally
  264. [327574] = 20, -- Sacrificial Pact
  265. },
  266. [Warrior.Arms] = {
  267. [845] = 20, -- Cleave
  268. [163201] = 20, -- Execute (Ignores Sudden Death)
  269. [1715] = 10, -- Hamstring
  270. [202168] = 10, -- Impending Victory
  271. [12294] = 30, -- Moral Strike
  272. [772] = 30, -- Rend
  273. [1464] = 20, -- Slam
  274. [1680] = 30, -- Whirlwind
  275. [190456] = 40, -- Ignore Pain
  276. },
  277. [Warrior.Fury] = {
  278. [202168] = 10, -- Impending Victory
  279. [184367] = 75, -- Rampage (Assumes -10 from Carnage)
  280. [12323] = 10, -- Piercing Howl
  281. [190456] = 40, -- Ignore Pain
  282. },
  283. [Warrior.Prot] = {
  284. [190456] = 40, -- Ignore Pain (Ignores Vengeance)
  285. [202168] = 10, -- Impending Victory
  286. [6572] = 30, -- Revenge (Ignores Vengeance)
  287. [2565] = 30, -- Shield Block
  288. },
  289. [Hunter.BM] = { -- TODO
  290. },
  291. [Hunter.MM] = { -- TODO
  292. },
  293. [Hunter.SV] = { -- TODO
  294. },
  295. [Paladin] = {
  296. [85673] = 3, -- Word of Glory
  297. [85222] = 3, -- Light of Dawn
  298. [152262] = 3, -- Seraphim
  299. [53600] = 3, -- Shield of the Righteous
  300. [85256] = 3, -- Templar's Verdict
  301. [53385] = 3, -- Divine Storm
  302. [343527] = 3, -- Execution Sentence
  303. },
  304. [Paladin.Holy] = {
  305. [85673] = 3, -- Word of Glory
  306. [85222] = 3, -- Light of Dawn
  307. [152262] = 3, -- Seraphim
  308. },
  309. [Paladin.Prot] = {
  310. [85673] = 3, -- Word of Glory
  311. [53600] = 3, -- Shield of the Righteous
  312. [152262] = 3, -- Seraphim
  313. },
  314. [Paladin.Ret] = {
  315. [85673] = 3, -- Word of Glory
  316. [85256] = 3, -- Templar's Verdict
  317. [53385] = 3, -- Divine Storm
  318. [343527] = 3, -- Execution Sentence
  319. [152262] = 3, -- Seraphim
  320. },
  321. }
  322.  
  323. local function ResourceSpendingMods(specID, coefficient)
  324. local handlers = {}
  325. local spenderInfo = specIDToSpenderInfo[specID]
  326.  
  327. for spellID,cost in pairs(spenderInfo) do
  328. local delta = -(coefficient * cost)
  329.  
  330. handlers[#handlers+1] = {
  331. type = "SPELL_CAST_SUCCESS",
  332. spellID = spellID,
  333. handler = function(watchInfo)
  334. watchInfo:updateCDDelta(delta)
  335. end
  336. }
  337. end
  338.  
  339. return DynamicMod(handlers)
  340. end
  341.  
  342. --##############################################################################
  343. -- List of Tracked Spells
  344. -- TODO: Denote which spells should be modified by UnitSpellHaste(...)
  345.  
  346. ZT.spellListVersion = 102
  347. ZT.spellList = {
  348. -- Racials
  349. {type="HARDCC", id=255654, cd=120, reqs={RaceReq("HighmountainTauren")}}, -- Bull Rush
  350. {type="HARDCC", id=20549, cd=90, reqs={RaceReq("Tauren")}}, -- War Stomp
  351. {type="STHARDCC", id=287712, cd=150, reqs={RaceReq("KulTiran")}}, -- Haymaker
  352. {type="STSOFTCC", id=107079, cd=120, reqs={RaceReq("Pandaren")}}, -- Quaking Palm
  353. {type="DISPEL", id=202719, cd=120, reqs={RaceReq("BloodElf"), ClassReq(DH)}}, -- Arcane Torrent
  354. {type="DISPEL", id=50613, cd=120, reqs={RaceReq("BloodElf"), ClassReq(DK)}}, -- Arcane Torrent
  355. {type="DISPEL", id=80483, cd=120, reqs={RaceReq("BloodElf"), ClassReq(Hunter)}}, -- Arcane Torrent
  356. {type="DISPEL", id=28730, cd=120, reqs={RaceReq("BloodElf"), ClassReq(Mage)}}, -- Arcane Torrent
  357. {type="DISPEL", id=129597, cd=120, reqs={RaceReq("BloodElf"), ClassReq(Monk)}}, -- Arcane Torrent
  358. {type="DISPEL", id=155145, cd=120, reqs={RaceReq("BloodElf"), ClassReq(Paladin)}}, -- Arcane Torrent
  359. {type="DISPEL", id=232633, cd=120, reqs={RaceReq("BloodElf"), ClassReq(Priest)}}, -- Arcane Torrent
  360. {type="DISPEL", id=25046, cd=120, reqs={RaceReq("BloodElf"), ClassReq(Rogue)}}, -- Arcane Torrent
  361. {type="DISPEL", id=28730, cd=120, reqs={RaceReq("BloodElf"), ClassReq(Warlock)}}, -- Arcane Torrent
  362. {type="DISPEL", id=69179, cd=120, reqs={RaceReq("BloodElf"), ClassReq(Warrior)}}, -- Arcane Torrent
  363. {type="DISPEL", id=20594, cd=120, reqs={RaceReq("Dwarf")}, mods={{mod=EventRemainingMod("SPELL_AURA_APPLIED",65116,120)}}}, -- Stoneform
  364. {type="DISPEL", id=265221, cd=120, reqs={RaceReq("DarkIronDwarf")}, mods={{mod=EventRemainingMod("SPELL_AURA_APPLIED",265226,120)}}}, -- Fireblood
  365. {type="UTILITY", id=58984, cd=120, reqs={RaceReq("NightElf")}}, -- Shadowmeld
  366.  
  367. -- Covenants
  368. {type="UTILITY", id=324739, cd=300, reqs={CovenantReq("Kyrian")}, version=101},-- Summon Steward
  369. {type="UTILITY", id=300728, cd=60, reqs={CovenantReq("Venthyr")}, version=101},-- Door of Shadows
  370. {type="UTILITY", id=310143, cd=90, reqs={CovenantReq("NightFae")}, version=101},-- Soulshape
  371. {type="PERSONAL", id=324631, cd=90, reqs={CovenantReq("Necrolord")}, version=101},-- Fleshcraft
  372.  
  373. -- DH
  374. ---- Base
  375. {type="INTERRUPT", id=183752, cd=15, reqs={ClassReq(DH)}}, -- Disrupt
  376. {type="UTILITY", id=188501, cd=60, reqs={ClassReq(DH)}, mods={{reqs={ClassReq(DH), LevelReq(42)}, mod=SubtractMod(30)}}}, -- Spectral Sight
  377. {type="TANK", id=185245, cd=8, reqs={ClassReq(DH), LevelReq(9)}}, -- Torment
  378. {type="DISPEL", id=278326, cd=10, reqs={ClassReq(DH), LevelReq(17)}}, -- Consume Magic
  379. {type="STSOFTCC", id=217832, cd=45, reqs={ClassReq(DH), LevelReq(34)}}, -- Imprison
  380. ---- DH.Havoc
  381. {type="HARDCC", id=179057, cd=60, reqs={SpecReq({DH.Havoc})}, mods={{reqs={TalentReq(206477)}, mod=SubtractMod(20)}}}, -- Chaos Nova
  382. {type="PERSONAL", id=198589, cd=60, reqs={SpecReq({DH.Havoc}), LevelReq(21)}}, -- Blur
  383. {type="RAIDCD", id=196718, cd=300, reqs={SpecReq({DH.Havoc}), LevelReq(39)}, mods={{reqs={LevelReq(47)}, mod=SubtractMod(120)}}}, -- Darkness
  384. {type="DAMAGE", id=191427, cd=300, reqs={SpecReq({DH.Havoc})}, mods={{reqs={LevelReq(48)}, mod=SubtractMod(60)}}}, -- Metamorphosis
  385. ---- DH.Veng
  386. {type="TANK", id=204021, cd=60, reqs={SpecReq({DH.Veng})}}, -- Fiery Brand
  387. {type="TANK", id=212084, cd=45, reqs={SpecReq({DH.Veng}), LevelReq(11)}}, -- Fel Devastation
  388. {type="SOFTCC", id=207684, cd=180, reqs={SpecReq({DH.Veng}), LevelReq(21)}, mods={{reqs={LevelReq(33)}, mod=SubtractMod(90)}, {reqs={TalentReq(209281)}, mod=MultiplyMod(0.8)}}}, -- Sigil of Misery
  389. {type="SOFTCC", id=202137, cd=120, reqs={SpecReq({DH.Veng}), LevelReq(39)}, mods={{reqs={LevelReq(48)}, mod=SubtractMod(60)}, {reqs={TalentReq(209281)}, mod=MultiplyMod(0.8)}}}, -- Sigil of Silence
  390. {type="TANK", id=187827, cd=300, reqs={SpecReq({DH.Veng})}, mods={{reqs={LevelReq(20)}, mod=SubtractMod(60)}, {reqs={LevelReq(48)}, mod=SubtractMod(60)}}}, -- Metamorphosis
  391. ---- Talents
  392. {type="IMMUNITY", id=196555, cd=180, reqs={TalentReq(196555)}}, -- Netherwalk
  393. {type="SOFTCC", id=202138, cd=90, reqs={TalentReq(202138)}}, -- Sigil of Chains
  394. {type="STHARDCC", id=211881, cd=30, reqs={TalentReq(211881)}}, -- Fel Eruption
  395. {type="TANK", id=263648, cd=30, reqs={TalentReq(263648)}}, -- Soul Barrier
  396. {type="DAMAGE", id=258925, cd=60, reqs={TalentReq(258925)}}, -- Fel Barrage
  397. {type="TANK", id=320341, cd=90, reqs={TalentReq(320341)}}, -- Bulk Extraction
  398.  
  399. -- DK
  400. -- TODO: Raise Ally (Brez support)
  401. ---- Base
  402. {type="INTERRUPT", id=47528, cd=15, reqs={ClassReq(DK), LevelReq(7)}}, -- Mind Freeze
  403. {type="PERSONAL", id=48707, cd=60, reqs={ClassReq(DK), LevelReq(9)}, mods={{reqs={TalentReq(205727)}, mod=SubtractMod(20)}}}, -- Anti-Magic Shell
  404. {type="TANK", id=56222, cd=8, reqs={ClassReq(DK), LevelReq(14)}}, -- Dark Command
  405. {type="PERSONAL", id=49039, cd=120, reqs={ClassReq(DK), LevelReq(33)}}, -- Lichborne
  406. {type="PERSONAL", id=48792, cd=180, reqs={ClassReq(DK), LevelReq(38)}}, -- Icebound Fortitude
  407. {type="BREZ", id=61999, cd=600, reqs={ClassReq(DK), LevelReq(39)}}, -- Raise Ally
  408. {type="RAIDCD", id=51052, cd=120, reqs={ClassReq(DK), LevelReq(47)}}, -- Anti-Magic Zone
  409. {type="PERSONAL", id=327574, cd=120, reqs={ClassReq(DK), LevelReq(54)}}, -- Sacrificial Pact
  410. ---- DK.Blood
  411. {type="STHARDCC", id=221562, cd=45, reqs={SpecReq({DK.Blood}), LevelReq(13)}}, -- Asphyxiate
  412. {type="TANK", id=55233, cd=90, reqs={SpecReq({DK.Blood}), LevelReq(29)}, mods={{reqs={TalentReq(205723)}, mod=ResourceSpendingMods(DK.Blood, 0.15)}}}, -- Vampiric Blood
  413. {type="SOFTCC", id=108199, cd=120, reqs={SpecReq({DK.Blood}), LevelReq(44)}, mods={{reqs={TalentReq(206970)}, mod=SubtractMod(30)}}}, -- Gorefiend's Grasp
  414. {type="TANK", id=49028, cd=120, reqs={SpecReq({DK.Blood}), LevelReq(34)}}, -- Dancing Rune Weapon
  415. ---- DK.Frost
  416. {type="DAMAGE", id=51271, cd=45, reqs={SpecReq({DK.Frost}), LevelReq(29)}}, -- Pillar of Frost
  417. {type="DAMAGE", id=279302, cd=180, reqs={SpecReq({DK.Frost}), LevelReq(44)}}, -- Frostwyrm's Fury
  418. ---- DK.Unholy
  419. {type="DAMAGE", id=275699, cd=90, reqs={SpecReq({DK.Unholy}), LevelReq(19)}, mods={{reqs={LevelReq(49)}, mod=SubtractMod(15)}, {reqs={TalentReq(276837)}, mod=CastDeltaMod(47541,-1)}, {reqs={TalentReq(276837)}, mod=CastDeltaMod(207317,-1)}}}, -- Apocalypse
  420. {type="DAMAGE", id=63560, cd=60, reqs={SpecReq({DK.Unholy}), LevelReq(32)}, mods={{reqs={LevelReq(41)}, mod=CastDeltaMod(47541,-1)}}}, -- Dark Transformation
  421. {type="DAMAGE", id=42650, cd=480, reqs={SpecReq({DK.Unholy}), LevelReq(44)}, mods={{reqs={TalentReq(276837)}, mod=CastDeltaMod(47541,-5)}, {reqs={TalentReq(276837)}, mod=CastDeltaMod(207317,-5)}}}, -- Army of the Dead
  422. ---- Talents
  423. {type="TANK", id=219809, cd=60, reqs={TalentReq(219809)}}, -- Tombstone
  424. {type="DAMAGE", id=115989, cd=45, reqs={TalentReq(115989)}}, -- Unholy Blight
  425. {type="STHARDCC", id=108194, cd=45, reqs={TalentReq(108194)}}, -- Asphyxiate
  426. {type="SOFTCC", id=207167, cd=60, reqs={TalentReq(207167)}}, -- Blinding Sleet
  427. {type="PERSONAL", id=48743, cd=120, reqs={TalentReq(48743)}}, -- Death Pact
  428. {type="TANK", id=194844, cd=60, reqs={TalentReq(194844)}}, -- Bonestorm
  429. {type="DAMAGE", id=152279, cd=120, reqs={TalentReq(152279)}}, -- Breath of Sindragosa
  430. {type="DAMAGE", id=49206, cd=180, reqs={TalentReq(49206)}}, -- Summon Gargoyle
  431. {type="DAMAGE", id=207289, cd=75, reqs={TalentReq(207289)}}, -- Unholy Assault
  432.  
  433. -- Druid
  434. -- TODO: Rebirth (Brez support)
  435. ---- Base
  436. {type="TANK", id=6795, cd=8, reqs={ClassReq(Druid), LevelReq(14)}}, -- Growl
  437. {type="PERSONAL", id=22812, cd=60, reqs={ClassReq(Druid), LevelReq(24)}, mods={{reqs={TalentReq(203965)}, mod=MultiplyMod(0.67)}}}, -- Barkskin
  438. {type="BREZ", id=20484, cd=600, reqs={ClassReq(Druid), LevelReq(29)}}, -- Rebirth
  439. {type="DISPEL", id=2908, cd=10, reqs={ClassReq(Druid), LevelReq(41)}}, -- Soothe
  440. {type="UTILITY", id=106898, cd=120, reqs={ClassReq(Druid), LevelReq(43)}, mods={{reqs={SpecReq({Druid.Guardian}), LevelReq(49)}, mod=SubtractMod(60)}}}, -- Stampeding Roar
  441. ---- Shared
  442. {type="DISPEL", id=2782, cd=8, reqs={SpecReq({Druid.Balance, Druid.Feral, Druid.Guardian}), LevelReq(19)}, mods={{mod=DispelMod(2782)}}, ignoreCast=true}, -- Remove Corruption
  443. {type="INTERRUPT", id=106839, cd=15, reqs={SpecReq({Druid.Feral, Druid.Guardian}), LevelReq(26)}}, -- Skull Bash
  444. {type="PERSONAL", id=61336, cd=180, reqs={SpecReq({Druid.Feral, Druid.Guardian}), LevelReq(32)}, mods={{reqs={SpecReq({Druid.Guardian}), LevelReq(47)}, mod=ChargesMod(2)}}}, -- Survival Instincts
  445. {type="UTILITY", id=29166, cd=180, reqs={SpecReq({Druid.Balance, Druid.Resto}), LevelReq(42)}}, -- Innervate
  446. ---- Druid.Balance
  447. {type="INTERRUPT", id=78675, cd=60, reqs={SpecReq({Druid.Balance}), LevelReq(26)}}, -- Solar Beam
  448. {type="SOFTCC", id=132469, cd=30, reqs={SpecReq({Druid.Balance}), LevelReq(28)}}, -- Typhoon
  449. {type="DAMAGE", id=194223, cd=180, reqs={SpecReq({Druid.Balance}, NoTalentReq(102560)), LevelReq(39)}}, -- Celestial Alignment
  450. ---- Druid.Feral
  451. {type="STHARDCC", id=22570, cd=20, reqs={SpecReq({Druid.Feral}), LevelReq(28)}}, -- Maim
  452. {type="DAMAGE", id=106951, cd=180, reqs={SpecReq({Druid.Feral}), LevelReq(34)}}, -- Berserk
  453. ---- Druid.Guardian
  454. {type="SOFTCC", id=99, cd=30, reqs={SpecReq({Druid.Guardian}), LevelReq(28)}}, -- Incapacitating Roar
  455. {type="TANK", id=50334, cd=180, reqs={SpecReq({Druid.Guardian}), LevelReq(34)}}, -- Berserk
  456. ---- Druid.Resto
  457. {type="EXTERNAL", id=102342, cd=90, reqs={SpecReq({Druid.Resto}), LevelReq(12)}}, -- Ironbark
  458. {type="DISPEL", id=88423, cd=8, reqs={SpecReq({Druid.Resto}), LevelReq(19)}, mods={{mod=DispelMod(88423)}}, ignoreCast=true}, -- Remove Corruption
  459. {type="SOFTCC", id=102793, cd=60, reqs={SpecReq({Druid.Resto}), LevelReq(28)}}, -- Ursol's Vortex
  460. {type="HEALING", id=740, cd=180, reqs={SpecReq({Druid.Resto}), LevelReq(37)}, mods={{reqs={SpecReq({Druid.Resto}), TalentReq(197073)}, mod=SubtractMod(60)}}}, -- Tranquility
  461. {type="UTILITY", id=132158, cd=60, reqs={SpecReq({Druid.Resto}), LevelReq(58)}}, -- Nature's Swiftness
  462. ---- Talents
  463. {type="HEALING", id=102351, cd=30, reqs={TalentReq(102351)}}, -- Cenarion Ward
  464. {type="UTILITY", id=205636, cd=60, reqs={TalentReq(205636)}}, -- Force of Nature
  465. {type="PERSONAL", id=108238, cd=90, reqs={TalentReq(108238)}}, -- Renewal
  466. {type="STHARDCC", id=5211, cd=60, reqs={TalentReq(5211)}}, -- Mighty Bash
  467. {type="SOFTCC", id=102359, cd=30, reqs={TalentReq(102359)}}, -- Mass Entanglement
  468. {type="SOFTCC", id=132469, cd=30, reqs={TalentReq(197632)}}, -- Typhoon
  469. {type="SOFTCC", id=132469, cd=30, reqs={TalentReq(197488)}}, -- Typhoon
  470. {type="SOFTCC", id=102793, cd=60, reqs={TalentReq(197492)}}, -- Ursol's Vortex
  471. {type="SOFTCC", id=99, cd=30, reqs={TalentReq(197491)}}, -- Incapacitating Roar
  472. {type="SOFTCC", id=99, cd=30, reqs={TalentReq(217615)}}, -- Incapacitating Roar
  473. {type="DAMAGE", id=319454, cd=300, reqs={TalentReq(319454), TalentReq(202157)}}, -- Heart of the Wild
  474. {type="PERSONAL", id=319454, cd=300, reqs={TalentReq(319454), TalentReq(197491)}}, -- Heart of the Wild
  475. {type="HEALING", id=319454, cd=300, reqs={TalentReq(319454), TalentReq(197492)}}, -- Heart of the Wild
  476. {type="DAMAGE", id=319454, cd=300, reqs={TalentReq(319454), TalentReq(197488)}}, -- Heart of the Wild
  477. {type="PERSONAL", id=319454, cd=300, reqs={TalentReq(319454), TalentReq(217615)}}, -- Heart of the Wild
  478. {type="DAMAGE", id=319454, cd=300, reqs={TalentReq(319454), TalentReq(202155)}}, -- Heart of the Wild
  479. {type="DAMAGE", id=319454, cd=300, reqs={TalentReq(319454), TalentReq(197632)}}, -- Heart of the Wild
  480. {type="DAMAGE", id=319454, cd=300, reqs={TalentReq(319454), TalentReq(197490)}}, -- Heart of the Wild
  481. {type="DAMAGE", id=102543, cd=180, reqs={TalentReq(102543)}}, -- Incarnation: King of the Jungle
  482. {type="DAMAGE", id=102560, cd=180, reqs={TalentReq(102560)}}, -- Incarnation: Chosen of Elune
  483. {type="TANK", id=102558, cd=180, reqs={TalentReq(102558)}}, -- Incarnation: Guardian of Ursoc
  484. {type="HEALING", id=33891, cd=180, reqs={TalentReq(33891)}, mods={{mod=EventRemainingMod("SPELL_AURA_APPLIED",117679,180)}}, ignoreCast=true}, -- Incarnation: Tree of Life
  485. {type="HEALING", id=203651, cd=60, reqs={TalentReq(203651)}}, -- Overgrowth
  486. {type="DAMAGE", id=202770, cd=60, reqs={TalentReq(202770)}}, -- Fury of Elune
  487. {type="TANK", id=204066, cd=75, reqs={TalentReq(204066)}}, -- Lunar Beam
  488. {type="HEALING", id=197721, cd=90, reqs={TalentReq(197721)}}, -- Flourish
  489. {type="TANK", id=80313, cd=30, reqs={TalentReq(80313)}}, -- Pulverize
  490.  
  491. -- Hunter
  492. -- TODO: Update ResourceSpendingMod to include spender costs
  493. ---- Base
  494. {type="UTILITY", id=186257, cd=180, reqs={ClassReq(Hunter), LevelReq(5)}, mods={{reqs={ClassReq(Hunter), TalentReq(266921)}, mod=MultiplyMod(0.8)}}}, -- Aspect of the Cheetah
  495. {type="UTILITY", id=5384, cd=30, reqs={ClassReq(Hunter), LevelReq(6)}}, -- Feign Death
  496. {type="IMMUNITY", id=186265, cd=180, reqs={ClassReq(Hunter), LevelReq(8)}, mods={{reqs={ClassReq(Hunter), TalentReq(266921)}, mod=MultiplyMod(0.8)}}}, -- Aspect of the Turtle
  497. {type="PERSONAL", id=109304, cd=120, reqs={ClassReq(Hunter), LevelReq(9)}, mods={{reqs={SpecReq({Hunter.BM}), TalentReq(270581)}, mod=ResourceSpendingMods(Hunter.BM, 0.033)}, {reqs={SpecReq({Hunter.MM}), TalentReq(270581)}, mod=ResourceSpendingMods(Hunter.MM, 0.05)}, {reqs={SpecReq({Hunter.SV}), TalentReq(270581)}, mod=ResourceSpendingMods(Hunter.SV, 0.05)}}}, -- Exhilaration
  498. {type="STSOFTCC", id=187650, cd=30, reqs={ClassReq(Hunter), LevelReq(10)}, mods={{reqs={ClassReq(Hunter), LevelReq(56)}, mod=SubtractMod(5)}}}, -- Freezing Trap
  499. {type="UTILITY", id=34477, cd=30, reqs={ClassReq(Hunter), LevelReq(27)}}, -- Misdirection
  500. {type="DISPEL", id=19801, cd=10, reqs={ClassReq(Hunter), LevelReq(37)}}, -- Tranquilizing Shot
  501. ---- Shared
  502. {type="INTERRUPT", id=147362, cd=24, reqs={SpecReq({Hunter.BM, Hunter.MM}), LevelReq(18)}}, -- Counter Shot
  503. {type="STHARDCC", id=19577, cd=60, reqs={SpecReq({Hunter.BM, Hunter.SV}), LevelReq(33)}}, -- Intimidation
  504. ---- Hunter.BM
  505. {type="DAMAGE", id=19574, cd=90, reqs={SpecReq({Hunter.BM}), LevelReq(20)}}, -- Bestial Wrath
  506. {type="DAMAGE", id=193530, cd=120, reqs={SpecReq({Hunter.BM}), LevelReq(38)}}, -- Aspect of the Wild
  507. ---- Hunter.MM
  508. {type="STSOFTCC", id=186387, cd=30, reqs={SpecReq({Hunter.MM}), LevelReq(12)}}, -- Bursting Shot
  509. {type="HARDCC", id=109248, cd=45, reqs={SpecReq({Hunter.MM}), LevelReq(33)}}, -- Binding Shot
  510. {type="DAMAGE", id=288613, cd=120, reqs={SpecReq({Hunter.MM}), LevelReq(34)}}, -- Trueshot
  511. ---- Hunter.SV
  512. {type="INTERRUPT", id=187707, cd=15, reqs={SpecReq({Hunter.SV}), LevelReq(18)}}, -- Muzzle
  513. {type="DAMAGE", id=266779, cd=120, reqs={SpecReq({Hunter.SV}), LevelReq(34)}}, -- Coordinated Assault
  514. ---- Talents
  515. {type="UTILITY", id=199483, cd=60, reqs={TalentReq(199483)}}, -- Camouflage
  516. {type="SOFTCC", id=162488, cd=30, reqs={TalentReq(162488)}}, -- Steel Trap
  517. {type="HARDCC", id=109248, cd=45, reqs={SpecReq({Hunter.BM, Hunter.SV}), TalentReq(109248)}}, -- Binding Shot
  518. {type="DAMAGE", id=201430, cd=120, reqs={TalentReq(201430)}}, -- Stampede
  519. {type="DAMAGE", id=260402, cd=60, reqs={TalentReq(260402)}}, -- Double Tap
  520. {type="DAMAGE", id=321530, cd=60, reqs={TalentReq(321530)}}, -- Bloodshed
  521.  
  522. -- Mage
  523. -- TODO: Invisibility seemed to be bugged (even w/o combat log tracking)
  524. -- TODO: Arcane should have Invisibility from 34 to 46, then Greater Invisibility from 47 onward
  525. ---- Base
  526. {type="INTERRUPT", id=2139, cd=24, reqs={ClassReq(Mage), LevelReq(7)}}, -- Counterspell
  527. {type="DISPEL", id=475, cd=8, reqs={ClassReq(Mage), LevelReq(21)}, mods={{mod=DispelMod(475)}}, ignoreCast=true}, -- Remove Curse
  528. {type="IMMUNITY", id=45438, cd=240, reqs={ClassReq(Mage), LevelReq(22)}, mods={{mod=CastRemainingMod(235219, 0)}}}, -- Ice Block
  529. {type="DAMAGE", id=55342, cd=120, reqs={ClassReq(Mage), LevelReq(44)}}, -- Mirror Image
  530. ---- Shared
  531. {type="UTILITY", id=66, cd=300, reqs={SpecReq({Mage.Fire, Mage.Frost}), LevelReq(34)}}, -- Invisibility
  532. {type="PERSONAL", id=108978, cd=60, reqs={SpecReq({Mage.Fire, Mage.Frost}), LevelReq(58)}}, -- Alter Time
  533. ---- Mage.Arcane
  534. {type="PERSONAL", id=342245, cd=60, reqs={SpecReq({Mage.Arcane}), LevelReq(19)}, mods={{reqs={TalentReq(342249)}, mod=SubtractMod(30)}}}, -- Alter Time
  535. {type="PERSONAL", id=235450, cd=25, reqs={SpecReq({Mage.Arcane}), LevelReq(28)}}, -- Prismatic Barrier
  536. {type="DAMAGE", id=12042, cd=120, reqs={SpecReq({Mage.Arcane}), LevelReq(29)}}, -- Arcane Power
  537. {type="DAMAGE", id=321507, cd=45, reqs={SpecReq({Mage.Arcane}), LevelReq(33)}}, -- Touch of the Magi
  538. {type="UTILITY", id=205025, cd=60, reqs={SpecReq({Mage.Arcane}), LevelReq(42)}}, -- Presence of Mind
  539. {type="UTILITY", id=110959, cd=120, reqs={SpecReq({Mage.Arcane}), LevelReq(47)}}, -- Greater Invisibility
  540. ---- Mage.Fire
  541. {type="SOFTCC", id=31661, cd=20, reqs={SpecReq({Mage.Fire}), LevelReq(27)}, mods={{reqs={SpecReq({Mage.Fire}), LevelReq(38)}, mod=SubtractMod(2)}}}, -- Dragon's Breath
  542. {type="PERSONAL", id=235313, cd=25, reqs={SpecReq({Mage.Fire}), LevelReq(28)}}, -- Blazing Barrier
  543. {type="DAMAGE", id=190319, cd=120, reqs={SpecReq({Mage.Fire}), LevelReq(29)}}, -- Combustion
  544. ---- Mage.Frost
  545. {type="PERSONAL", id=11426, cd=25, reqs={SpecReq({Mage.Frost}), LevelReq(28)}}, -- Ice Barrier
  546. {type="DAMAGE", id=12472, cd=180, reqs={SpecReq({Mage.Frost}), LevelReq(29)}}, -- Icy Veins
  547. {type="DAMAGE", id=84714, cd=60, reqs={SpecReq({Mage.Frost}), LevelReq(38)}}, -- Frozen Orb
  548. {type="UTILITY", id=235219, cd=300, reqs={SpecReq({Mage.Frost}), LevelReq(42)}, mods={{reqs={SpecReq({Mage.Frost}), LevelReq(54)}, mod=SubtractMod(30)}}}, -- Cold Snap
  549. ---- Talents
  550. {type="SOFTCC", id=113724, cd=45, reqs={TalentReq(113724)}}, -- Ring of Frost
  551.  
  552. -- Monk
  553. -- TODO: Spiritual Focus (280197) as a ResourceSpendingMod
  554. -- TODO: Black Ox Brew modifiers
  555. -- TODO: Blackout Combo modifiers
  556. ---- Base
  557. {type="DAMAGE", id=322109, cd=180, reqs={ClassReq(Monk)}}, -- Touch of Death
  558. {type="TANK", id=115546, cd=8, reqs={ClassReq(Monk), LevelReq(14)}}, -- Provoke
  559. {type="STSOFTCC", id=115078, cd=45, reqs={ClassReq(Monk), LevelReq(22)}, mods={{reqs={ClassReq(Monk), LevelReq(56)}, mod=SubtractMod(15)}}}, -- Paralysis
  560. {type="HARDCC", id=119381, cd=60, reqs={ClassReq(Monk), LevelReq(6)}, mods={{reqs={ClassReq(Monk), TalentReq(264348)}, mod=SubtractMod(10)}}}, -- Leg Sweep
  561. ---- Shared
  562. {type="INTERRUPT", id=116705, cd=15, reqs={SpecReq({Monk.BRM, Monk.WW}), LevelReq(18)}}, -- Spear Hand Strike
  563. {type="PERSONAL", id=243435, cd=420, reqs={SpecReq({Monk.MW, Monk.WW}), LevelReq(28)}, mods={{reqs={LevelReq(48)}, mod=SubtractMod(240)}}}, -- Fortifying Brew
  564. ---- Monk.BRM
  565. {type="TANK", id=322507, cd=60, reqs={SpecReq({Monk.BRM}), LevelReq(27)}, mods={{reqs={SpecReq({Monk.BRM}), TalentReq(325093)}, mod=MultiplyMod(0.8)}, {mod=CastDeltaMod(121253, -3)}, {mod=CastDeltaMod(100780, -1)}}}, -- Celestial Brew
  566. {type="PERSONAL", id=115203, cd=360, reqs={SpecReq({Monk.BRM}), LevelReq(28)}}, -- Fortifying Brew
  567. {type="TANK", id=115176, cd=300, reqs={SpecReq({Monk.BRM}), LevelReq(34)}}, -- Zen Meditation
  568. {type="SOFTCC", id=324312, cd=30, reqs={SpecReq({Monk.BRM}), LevelReq(54)}}, -- Clash
  569. {type="TANK", id=132578, cd=180, reqs={SpecReq({Monk.BRM}), LevelReq(42)}}, -- Invoke Niuzao, the Black Ox
  570. ---- Monk.MW
  571. {type="HEALING", id=322118, cd=180, reqs={SpecReq({Monk.MW}), LevelReq(42)}}, -- Invoke Yu'lon, the Jade Serpent
  572. {type="HEALING", id=115310, cd=180, reqs={SpecReq({Monk.MW}), LevelReq(46)}}, -- Revival
  573. {type="EXTERNAL", id=116849, cd=120, reqs={SpecReq({Monk.MW}), LevelReq(27)}}, -- Life Cocoon
  574. ---- Monk.WW
  575. {type="PERSONAL", id=122470, cd=90, reqs={SpecReq({Monk.WW}), LevelReq(29)}}, -- Touch of Karma
  576. {type="DAMAGE", id=137639, cd=90, reqs={SpecReq({Monk.WW}), LevelReq(27), NoTalentReq(152173)}, mods={{reqs={LevelReq(47)}, mod=ChargesMod(2)}}}, -- Storm, Earth, and Fire
  577. {type="DAMAGE", id=123904, cd=120, reqs={SpecReq({Monk.WW}), LevelReq(42)}}, -- Invoke Xuen, the White Tiger
  578. {type="DAMAGE", id=113656, cd=24, reqs={SpecReq({Monk.WW}), LevelReq(12)}}, -- Fists of Fury
  579. ---- Talents
  580. {type="UTILITY", id=116841, cd=30, reqs={TalentReq(116841)}}, -- Tiger's Lust
  581. {type="TANK", id=115399, cd=120, reqs={TalentReq(115399)}}, -- Black Ox Brew
  582. {type="SOFTCC", id=198898, cd=30, reqs={TalentReq(198898)}}, -- Song of Chi-Ji
  583. {type="SOFTCC", id=116844, cd=45, reqs={TalentReq(116844)}}, -- Ring of Peace
  584. {type="PERSONAL", id=122783, cd=90, reqs={TalentReq(122783)}}, -- Diffuse Magic
  585. {type="PERSONAL", id=122278, cd=120, reqs={TalentReq(122278)}}, -- Dampen Harm
  586. {type="TANK", id=325153, cd=60, reqs={TalentReq(325153)}}, -- Exploding Keg
  587. {type="HEALING", id=325197, cd=120, reqs={TalentReq(325197)}}, -- Invoke Chi-Ji, the Red Crane
  588. {type="DAMAGE", id=152173, cd=90, reqs={TalentReq(152173)}}, -- Serenity
  589.  
  590. -- Paladin
  591. -- TODO: Prot should have Divine Protection from 28 to 41, then Ardent Defender from 42 onward
  592. ---- Base
  593. {type="IMMUNITY", id=642, cd=300, reqs={ClassReq(Paladin)}, mods={{reqs={TalentReq(114154)}, mod=MultiplyMod(0.7)}}}, -- Divine Shield
  594. {type="STHARDCC", id=853, cd=60, reqs={ClassReq(Paladin), LevelReq(5)}, mods={{reqs={TalentReq(234299)}, mod=ResourceSpendingMods(Paladin, 2)}}}, -- Hammer of Justice
  595. {type="EXTERNAL", id=633, cd=600, reqs={ClassReq(Paladin), LevelReq(9)}, mods={{reqs={TalentReq(114154)}, mod=MultiplyMod(0.3)}}}, -- Lay on Hands
  596. {type="UTILITY", id=1044, cd=25, reqs={ClassReq(Paladin), LevelReq(22)}, version=101}, -- Blessing of Freedom
  597. {type="EXTERNAL", id=6940, cd=120, reqs={ClassReq(Paladin), LevelReq(32)}}, -- Blessing of Sacrifice
  598. {type="EXTERNAL", id=1022, cd=300, reqs={ClassReq(Paladin), LevelReq(41), NoTalentReq(204018)}}, -- Blessing of Protection
  599. ---- Shared
  600. {type="DISPEL", id=213644, cd=8, reqs={SpecReq({Paladin.Prot, Paladin.Ret}), LevelReq(12)}}, -- Cleanse Toxins
  601. {type="INTERRUPT", id=96231, cd=15, reqs={SpecReq({Paladin.Prot, Paladin.Ret}), LevelReq(23)}}, -- Rebuke
  602. {type="DAMAGE", id=31884, cd=180, reqs={SpecReq({Paladin.Prot, Paladin.Ret}), LevelReq(37), NoTalentReq(231895)}, mods={{reqs={LevelReq(49)}, mod=SubtractMod(60)}}}, -- Avenging Wrath
  603. ---- Paladin.Holy
  604. {type="DISPEL", id=4987, cd=8, reqs={SpecReq({Paladin.Holy}), LevelReq(12)}, mods={{mod=DispelMod(4987)}}, ignoreCast=true}, -- Cleanse
  605. {type="PERSONAL", id=498, cd=60, reqs={SpecReq({Paladin.Holy}), LevelReq(26)}, mods={{reqs={TalentReq(114154)}, mod=MultiplyMod(0.7)}}}, -- Divine Protection
  606. {type="HEALING", id=31884, cd=180, reqs={SpecReq({Paladin.Holy}), LevelReq(37), NoTalentReq(216331)}, mods={{reqs={LevelReq(49)}, mod=SubtractMod(60)}}}, -- Avenging Wrath
  607. {type="RAIDCD", id=31821, cd=180, reqs={SpecReq({Paladin.Holy}), LevelReq(39)}}, -- Aura Mastery
  608. ---- Paladin.Prot
  609. {type="INTERRUPT", id=31935, cd=15, reqs={SpecReq({Paladin.Prot}), LevelReq(10)}}, -- Avenger's Shield
  610. {type="TANK", id=62124, cd=8, reqs={SpecReq({Paladin.Prot}), LevelReq(14)}, version=102}, -- Hand of Reckoning
  611. {type="TANK", id=86659, cd=300, reqs={SpecReq({Paladin.Prot}), LevelReq(39)}}, -- Guardian of Ancient Kings
  612. {type="TANK", id=31850, cd=120, reqs={SpecReq({Paladin.Prot}), LevelReq(42)}, mods={{reqs={TalentReq(114154)}, mod=MultiplyMod(0.7)}}}, -- Ardent Defender
  613. ---- Paladin.Ret
  614. {type="PERSONAL", id=184662, cd=120, reqs={SpecReq({Paladin.Ret}), LevelReq(26)}, mods={{reqs={TalentReq(114154)}, mod=MultiplyMod(0.7)}}}, -- Shield of Vengeance
  615. ---- Talents
  616. {type="STSOFTCC", id=20066, cd=15, reqs={TalentReq(20066)}}, -- Repentance
  617. {type="SOFTCC", id=115750, cd=90, reqs={TalentReq(115750)}}, -- Blinding Light
  618. {type="PERSONAL", id=205191, cd=60, reqs={TalentReq(205191)}}, -- Eye for an Eye
  619. {type="EXTERNAL", id=204018, cd=180, reqs={TalentReq(204018)}}, -- Blessing of Spellwarding
  620. {type="HEALING", id=105809, cd=180, reqs={TalentReq(105809), SpecReq({Paladin.Holy})}}, -- Holy Avenger
  621. {type="TANK", id=105809, cd=180, reqs={TalentReq(105809), SpecReq({Paladin.Prot})}}, -- Holy Avenger
  622. {type="DAMAGE", id=105809, cd=180, reqs={TalentReq(105809), SpecReq({Paladin.Ret})}}, -- Holy Avenger
  623. {type="HEALING", id=216331, cd=120, reqs={TalentReq(216331)}}, -- Avenging Crusader
  624. {type="DAMAGE", id=231895, cd=20, reqs={TalentReq(231895)}}, -- Crusade
  625. {type="DAMAGE", id=343721, cd=60, reqs={TalentReq(343721)}}, -- Final Reckoning
  626. {type="HEALING", id=200025, cd=15, reqs={TalentReq(200025)}}, -- Beacon of Virtue
  627.  
  628. -- Priest
  629. ---- Base
  630. {type="SOFTCC", id=8122, cd=60, reqs={ClassReq(Priest), LevelReq(7)}, mods={{reqs={TalentReq(196704)}, mod=SubtractMod(30)}}}, -- Psychic Scream
  631. {type="PERSONAL", id=19236, cd=90, reqs={ClassReq(Priest), LevelReq(8)}}, -- Desperate Prayer
  632. {type="DISPEL", id=32375, cd=45, reqs={ClassReq(Priest), LevelReq(42)}}, -- Mass Dispel
  633. {type="UTILITY", id=73325, cd=90, reqs={ClassReq(Priest), LevelReq(49)}}, -- Leap of Faith
  634. ---- Shared
  635. {type="DISPEL", id=527, cd=8, reqs={SpecReq({Priest.Disc, Priest.Holy}), LevelReq(18)}, mods={{mod=DispelMod(4987)}}, ignoreCast=true}, -- Purify
  636. {type="HEALING", id=10060, cd=120, reqs={SpecReq({Priest.Disc, Priest.Holy}), LevelReq(58)}}, -- Power Infusion
  637. ---- Priest.Disc
  638. {type="EXTERNAL", id=33206, cd=180, reqs={SpecReq({Priest.Disc}), LevelReq(38)}}, -- Pain Suppression
  639. {type="HEALING", id=47536, cd=90, reqs={SpecReq({Priest.Disc}), LevelReq(41), NoTalentReq(109964)}}, -- Rapture
  640. {type="RAIDCD", id=62618, cd=180, reqs={SpecReq({Priest.Disc}), LevelReq(44)}}, -- Power Word: Barrier
  641. ---- Priest.Holy
  642. {type="STSOFTCC", id=88625, cd=60, reqs={SpecReq({Priest.Holy}), LevelReq(23), NoTalentReq(200199)}, mods={{mod=CastDeltaMod(585, -4)}, {reqs={TalentReq(196985)}, mod=CastDeltaMod(585, -1.3333)}}}, -- Holy Word: Chastise
  643. {type="STHARDCC", id=88625, cd=60, reqs={SpecReq({Priest.Holy}), LevelReq(23), TalentReq(200199)}, mods={{mod=CastDeltaMod(585, -4)}, {reqs={TalentReq(196985)}, mod=CastDeltaMod(585, -1.3333)}}}, -- Holy Word: Chastise
  644. {type="EXTERNAL", id=47788, cd=180, reqs={SpecReq({Priest.Holy}), LevelReq(38)}, mods={{reqs={TalentReq(200209)}, mod=GuardianAngelMod}}}, -- Guardian Spirit
  645. {type="HEALING", id=64843, cd=180, reqs={SpecReq({Priest.Holy}), LevelReq(44)}}, -- Divine Hymn
  646. {type="UTILITY", id=64901, cd=300, reqs={SpecReq({Priest.Holy}), LevelReq(47)}}, -- Symbol of Hope
  647. ---- Priest.Shadow
  648. {type="PERSONAL", id=47585, cd=120, reqs={SpecReq({Priest.Shadow}), LevelReq(16)}, mods={{reqs={TalentReq(288733)}, mod=SubtractMod(30)}}}, -- Dispersion
  649. {type="DISPEL", id=213634, cd=8, reqs={SpecReq({Priest.Shadow}), LevelReq(18)}}, -- Purify Disease
  650. {type="DAMAGE", id=228260, cd=90, reqs={SpecReq({Priest.Shadow}), LevelReq(23)}}, -- Void Eruption
  651. {type="HEALING", id=15286, cd=120, reqs={SpecReq({Priest.Shadow}), LevelReq(38)}, mods={{reqs={TalentReq(199855)}, mod=SubtractMod(45)}}}, -- Vampiric Embrace
  652. {type="INTERRUPT", id=15487, cd=45, reqs={SpecReq({Priest.Shadow}), LevelReq(41)}, mods={{reqs={TalentReq(263716)}, mod=SubtractMod(15)}}}, -- Silence
  653. {type="DAMAGE", id=10060, cd=120, reqs={SpecReq({Priest.Shadow}), LevelReq(58)}}, -- Power Infusion
  654. ---- Talents
  655. {type="HARDCC", id=205369, cd=30, reqs={TalentReq(205369)}}, -- Mind Bomb
  656. {type="SOFTCC", id=204263, cd=45, reqs={TalentReq(204263)}}, -- Shining Force
  657. {type="STHARDCC", id=64044, cd=45, reqs={TalentReq(64044)}}, -- Psychic Horror
  658. {type="HEALING", id=109964, cd=60, reqs={TalentReq(109964)}}, -- Spirit Shell
  659. {type="HEALING", id=200183, cd=120, reqs={TalentReq(200183)}}, -- Apotheosis
  660. {type="HEALING", id=246287, cd=90, reqs={TalentReq(246287)}}, -- Evangelism
  661. {type="HEALING", id=265202, cd=720, reqs={TalentReq(265202)}, mods={{mod=CastDeltaMod(34861,-30)}, {mod=CastDeltaMod(2050,-30)}}}, -- Holy Word: Salvation
  662. {type="DAMAGE", id=319952, cd=90, reqs={TalentReq(319952)}}, -- Surrender to Madness
  663.  
  664. -- Rogue
  665. ---- Base
  666. {type="UTILITY", id=57934, cd=30, reqs={ClassReq(Rogue), LevelReq(44)}}, -- Tricks of the Trade
  667. {type="UTILITY", id=114018, cd=360, reqs={ClassReq(Rogue), LevelReq(47)}}, -- Shroud of Concealment
  668. {type="UTILITY", id=1856, cd=120, reqs={ClassReq(Rogue), LevelReq(31)}}, -- Vanish
  669. {type="IMMUNITY", id=31224, cd=120, reqs={ClassReq(Rogue), LevelReq(49)}}, -- Cloak of Shadows
  670. {type="STHARDCC", id=408, cd=20, reqs={ClassReq(Rogue), LevelReq(20)}}, -- Kidney Shot
  671. {type="UTILITY", id=1725, cd=30, reqs={ClassReq(Rogue), LevelReq(36)}}, -- Distract
  672. {type="STSOFTCC", id=2094, cd=120, reqs={ClassReq(Rogue), LevelReq(41)}, mods={{reqs={TalentReq(256165)}, mod=SubtractMod(30)}}}, -- Blind
  673. {type="PERSONAL", id=5277, cd=120, reqs={ClassReq(Rogue), LevelReq(23)}}, -- Evasion
  674. {type="INTERRUPT", id=1766, cd=15, reqs={ClassReq(Rogue), LevelReq(6)}}, -- Kick
  675. {type="PERSONAL", id=185311, cd=30, reqs={ClassReq(Rogue), LevelReq(8)}}, -- Crimson Vial
  676. ---- Rogue.Sin
  677. {type="DAMAGE", id=79140, cd=120, reqs={SpecReq({Rogue.Sin}), LevelReq(34)}}, -- Vendetta
  678. ---- Rogue.Outlaw
  679. {type="DAMAGE", id=13877, cd=30, reqs={SpecReq({Rogue.Outlaw}), LevelReq(33)}, mods={{reqs={SpecReq({Rogue.Outlaw}), TalentReq(272026)}, mod=SubtractMod(-3)}}}, -- Blade Flurry
  680. {type="DAMAGE", id=13750, cd=180, reqs={SpecReq({Rogue.Outlaw}), LevelReq(34)}}, -- Adrenaline Rush
  681. {type="STSOFTCC", id=1776, cd=15, reqs={SpecReq({Rogue.Outlaw}), LevelReq(46)}, version=101}, -- Gouge
  682. ---- Rogue.Sub
  683. {type="DAMAGE", id=121471, cd=180, reqs={SpecReq({Rogue.Sub}), LevelReq(34)}}, -- Shadow Blades
  684. ---- Talents
  685. {type="DAMAGE", id=343142, cd=90, reqs={SpecReq({Rogue.Outlaw}), TalentReq(343142)}}, -- Dreadblades
  686. {type="DAMAGE", id=271877, cd=45, reqs={SpecReq({Rogue.Outlaw}), TalentReq(271877)}}, -- Blade Rush
  687. {type="DAMAGE", id=51690, cd=120, reqs={SpecReq({Rogue.Outlaw}), TalentReq(51690)}}, -- Killing Spree
  688. {type="DAMAGE", id=277925, cd=60, reqs={SpecReq({Rogue.Sub}), TalentReq(277925)}}, -- Shuriken Tornado
  689.  
  690. -- Shaman
  691. -- TODO: Add support for Reincarnation
  692. ---- Base
  693. {type="UTILITY", id=20608, cd=1800, reqs={ClassReq(Shaman), LevelReq(8)}}, -- Reincarnation
  694. {type="INTERRUPT", id=57994, cd=12, reqs={ClassReq(Shaman), LevelReq(12)}}, -- Wind Shear
  695. {type="HARDCC", id=192058, cd=60, reqs={ClassReq(Shaman), LevelReq(23)}, mods={{reqs={TalentReq(265046)}, mod=StaticChargeMod}}}, -- Capacitor Totem
  696. {type="UTILITY", id=198103, cd=300, reqs={ClassReq(Shaman), LevelReq(37)}}, -- Earth Elemental
  697. {type="STSOFTCC", id=51514, cd=30, reqs={ClassReq(Shaman), LevelReq(41)}, mods={{reqs={LevelReq(56)}, mod=SubtractMod(10)}}}, -- Hex
  698. {type="PERSONAL", id=108271, cd=90, reqs={ClassReq(Shaman), LevelReq(42)}}, -- Astral Shift
  699. {type="DISPEL", id=8143, cd=60, reqs={ClassReq(Shaman), LevelReq(47)}}, -- Tremor Totem
  700. ---- Shared
  701. {type="DISPEL", id=51886, cd=8, reqs={SpecReq({Shaman.Ele, Shaman.Enh}), LevelReq(18)}, mods={{mod=DispelMod(51886)}}, ignoreCast=true}, -- Cleanse Spirit
  702. {type="UTILITY", id=79206, cd=120, reqs={SpecReq({Shaman.Ele, Shaman.Resto}), LevelReq(44)}, mods={{reqs={TalentReq(192088)}, mod=SubtractMod(60)}}}, -- Spiritwalker's Grace
  703. ---- Shaman.Ele
  704. {type="DAMAGE", id=198067, cd=150, reqs={SpecReq({Shaman.Ele}), LevelReq(34), NoTalentReq(192249)}}, -- Fire Elemental
  705. ---- Shaman.Enh
  706. {type="DAMAGE", id=51533, cd=120, reqs={SpecReq({Shaman.Enh}), LevelReq(34)}, mods={{reqs={SpecReq({Shaman.Enh}), TalentReq(262624)}, mod=SubtractMod(30)}}}, -- Feral Spirit
  707. ---- Shaman.Resto
  708. {type="DISPEL", id=77130, cd=8, reqs={SpecReq({Shaman.Resto}), LevelReq(18)}, mods={{mod=DispelMod(77130)}}, ignoreCast=true}, -- Purify Spirit
  709. {type="UTILITY", id=16191, cd=180, reqs={SpecReq({Shaman.Resto}), LevelReq(38)}}, -- Mana Tide Totem
  710. {type="RAIDCD", id=98008, cd=180, reqs={SpecReq({Shaman.Resto}), LevelReq(43)}, version=101}, -- Spirit Link Totem
  711. {type="UTILITY", id=79206, cd=120, reqs={SpecReq({Shaman.Resto}), LevelReq(44)}, mods={{reqs={SpecReq({Shaman.Resto}), TalentReq(192088)}, mod=SubtractMod(60)}}}, -- Spiritwalker's Grace
  712. {type="HEALING", id=108280, cd=180, reqs={SpecReq({Shaman.Resto}), LevelReq(49)}}, -- Healing Tide Totem
  713. ---- Talents
  714. {type="SOFTCC", id=51485, cd=30, reqs={SpecReq({Shaman.Resto}), TalentReq(51485)}}, -- Earthgrab Totem
  715. {type="HEALING", id=198838, cd=60, reqs={SpecReq({Shaman.Resto}), TalentReq(198838)}}, -- Earthen Wall Totem
  716. {type="DAMAGE", id=192249, cd=150, reqs={SpecReq({Shaman.Ele}), TalentReq(192249)}}, -- Fire Elemental
  717. {type="EXTERNAL", id=207399, cd=300, reqs={SpecReq({Shaman.Resto}), TalentReq(207399)}}, -- Ancestral Protection Totem
  718. {type="HEALING", id=108281, cd=120, reqs={SpecReq({Shaman.Ele}), TalentReq(108281)}}, -- Ancestral Guidance
  719. {type="UTILITY", id=192077, cd=120, reqs={ClassReq(Shaman), TalentReq(192077)}}, -- Wind Rush Totem
  720. {type="DAMAGE", id=191634, cd=60, reqs={SpecReq({Shaman.Ele}), TalentReq(191634)}}, -- Stormkeeper
  721. {type="HEALING", id=114052, cd=180, reqs={SpecReq({Shaman.Resto}), TalentReq(114052)}}, -- Ascendance
  722. {type="DAMAGE", id=114050, cd=180, reqs={SpecReq({Shaman.Ele}), TalentReq(114050)}}, -- Ascendance
  723. {type="DAMAGE", id=114051, cd=180, reqs={SpecReq({Shaman.Enh}), TalentReq(114051)}}, -- Ascendance
  724.  
  725. -- Warlock
  726. -- TODO: Soulstone (Brez Support)
  727. -- TODO: PetReq for Spell Lock and Axe Toss
  728. ---- Base
  729. {type="PERSONAL", id=104773, cd=180, reqs={ClassReq(Warlock), LevelReq(4)}}, -- Unending Resolve
  730. {type="UTILITY", id=333889, cd=180, reqs={ClassReq(Warlock), LevelReq(22)}}, -- Fel Domination
  731. {type="BREZ", id=20707, cd=600, reqs={ClassReq(Warlock), LevelReq(48)}}, -- Soulstone
  732. {type="HARDCC", id=30283, cd=60, reqs={ClassReq(Warlock), LevelReq(38)}, mods={{reqs={TalentReq(264874)}, mod=SubtractMod(15)}}}, -- Shadowfury
  733. ---- Shared
  734. {type="INTERRUPT", id=19647, cd=24, reqs={SpecReq({Warlock.Affl, Warlock.Destro}), LevelReq(29)}}, -- Spell Lock
  735. ---- Warlock.Affl
  736. {type="DAMAGE", id=205180, cd=180, reqs={SpecReq({Warlock.Affl}), LevelReq(42)}, mods={{reqs={TalentReq(334183)}, mod=SubtractMod(60)}}}, -- Summon Darkglare
  737. ---- Warlock.Demo
  738. {type="INTERRUPT", id=89766, cd=30, reqs={SpecReq({Warlock.Demo}), LevelReq(29)}}, -- Axe Toss
  739. {type="DAMAGE", id=265187, cd=90, reqs={SpecReq({Warlock.Demo}), LevelReq(42)}}, -- Summon Demonic Tyrant
  740. ---- Warlock.Destro
  741. {type="DAMAGE", id=1122, cd=180, reqs={SpecReq({Warlock.Destro}), LevelReq(42)}}, -- Summon Infernal
  742. ---- Talents
  743. {type="PERSONAL", id=108416, cd=60, reqs={ClassReq(Warlock), TalentReq(108416)}}, -- Dark Pact
  744. {type="DAMAGE", id=152108, cd=30, reqs={SpecReq({Warlock.Destro}), TalentReq(152108)}}, -- Cataclysm
  745. {type="STHARDCC", id=6789, cd=45, reqs={ClassReq(Warlock), TalentReq(6789)}}, -- Mortal Coil
  746. {type="SOFTCC", id=5484, cd=40, reqs={ClassReq(Warlock), TalentReq(5484)}}, -- Howl of Terror
  747. {type="DAMAGE", id=111898, cd=120, reqs={SpecReq({Warlock.Demo}), TalentReq(111898)}}, -- Grimoire: Felguard
  748. {type="DAMAGE", id=113858, cd=120, reqs={SpecReq({Warlock.Destro}), TalentReq(113858)}}, -- Dark Soul: Instability
  749. {type="DAMAGE", id=267217, cd=180, reqs={SpecReq({Warlock.Demo}), TalentReq(267217)}}, -- Nether Portal
  750. {type="DAMAGE", id=113860, cd=120, reqs={SpecReq({Warlock.Affl}), TalentReq(113860)}}, -- Dark Soul: Misery
  751.  
  752. -- Warrior
  753. -- Note: Spell Reflection is separated to generate REMOVE/ADD events (Request from Nnoggie)
  754. ---- Base
  755. {type="INTERRUPT", id=6552, cd=15, reqs={ClassReq(Warrior), LevelReq(7)}}, -- Pummel
  756. {type="TANK", id=355, cd=8, reqs={ClassReq(Warrior), LevelReq(14)}}, -- Taunt
  757. {type="SOFTCC", id=5246, cd=90, reqs={ClassReq(Warrior), LevelReq(34)}}, -- Intimidating Shout
  758. {type="UTILITY", id=64382, cd=180, reqs={ClassReq(Warrior), LevelReq(41)}}, -- Shattering Throw
  759. {type="EXTERNAL", id=3411, cd=30, reqs={ClassReq(Warrior), LevelReq(43)}}, -- Intervene
  760. {type="RAIDCD", id=97462, cd=180, reqs={ClassReq(Warrior), LevelReq(46)}}, -- Rallying Cry
  761. {type="TANK", id=1161, cd=240, reqs={ClassReq(Warrior), LevelReq(54)}}, -- Challenging Shout
  762. ---- Shared
  763. {type="PERSONAL", id=23920, cd=25, reqs={SpecReq({Warrior.Arms, Warrior.Fury}), LevelReq(47)}}, -- Spell Reflection
  764. ---- Warrior.Arms
  765. {type="PERSONAL", id=118038, cd=180, reqs={SpecReq({Warrior.Arms}), LevelReq(23)}, mods={{reqs={LevelReq(52)}, mod=SubtractMod(60)}}}, -- Die by the Sword
  766. {type="DAMAGE", id=227847, cd=90, reqs={SpecReq({Warrior.Arms}), LevelReq(38)}, mods={{reqs={TalentReq(152278)}, mod=ResourceSpendingMods(Warrior.Arms, 0.05)}}}, -- Bladestorm
  767. ---- Warrior.Fury
  768. {type="PERSONAL", id=184364, cd=180, reqs={SpecReq({Warrior.Fury}), LevelReq(23)}, mods={{reqs={LevelReq(32)}, mod=SubtractMod(60)}}}, -- Enraged Regeneration
  769. {type="DAMAGE", id=1719, cd=90, reqs={SpecReq({Warrior.Fury}), LevelReq(38)}, mods={{reqs={TalentReq(152278)}, mod=ResourceSpendingMods(Warrior.Arms, 0.05)}}}, -- Recklessness
  770. ---- Warrior.Prot
  771. {type="HARDCC", id=46968, cd=40, reqs={SpecReq({Warrior.Prot}), LevelReq(21)}, mods={{reqs={TalentReq(275339)}, mod=RumblingEarthMod}}}, -- Shockwave
  772. {type="TANK", id=871, cd=240, reqs={SpecReq({Warrior.Prot}), LevelReq(23)}, mods={{reqs={TalentReq(152278)}, mod=ResourceSpendingMods(Warrior.Arms, 0.1)}}}, -- Shield Wall
  773. {type="TANK", id=1160, cd=45, reqs={SpecReq({Warrior.Prot}), LevelReq(27)}}, -- Demoralizing Shout
  774. {type="DAMAGE", id=107574, cd=90, reqs={SpecReq({Warrior.Prot}), LevelReq(32)}, mods={{reqs={TalentReq(152278)}, mod=ResourceSpendingMods(Warrior.Arms, 0.1)}}}, -- Avatar
  775. {type="TANK", id=12975, cd=180, reqs={SpecReq({Warrior.Prot}), LevelReq(38)}, mods={{reqs={TalentReq(280001)}, mod=SubtractMod(60)}}}, -- Last Stand
  776. {type="PERSONAL", id=23920, cd=25, reqs={SpecReq({Warrior.Prot}), LevelReq(47)}}, -- Spell Reflection
  777. ---- Talents
  778. {type="STHARDCC", id=107570, cd=30, reqs={ClassReq(Warrior), TalentReq(107570)}}, -- Storm Bolt
  779. {type="DAMAGE", id=107574, cd=90, reqs={SpecReq({Warrior.Arms}), TalentReq(107574)}}, -- Avatar
  780. {type="DAMAGE", id=262228, cd=60, reqs={SpecReq({Warrior.Arms}), TalentReq(262228)}}, -- Deadly Calm
  781. {type="DAMAGE", id=228920, cd=45, reqs={SpecReq({Warrior.Prot}), TalentReq(228920)}}, -- Ravager
  782. {type="DAMAGE", id=46924, cd=60, reqs={SpecReq({Warrior.Fury}), TalentReq(46924)}}, -- Bladestorm
  783. {type="DAMAGE", id=152277, cd=45, reqs={SpecReq({Warrior.Arms}), TalentReq(152277)}}, -- Ravager
  784. {type="DAMAGE", id=280772, cd=30, reqs={SpecReq({Warrior.Fury}), TalentReq(280772)}}, -- Siegebreaker
  785. }
  786.  
  787. ZT.linkedSpellIDs = {
  788. [19647] = {119910, 132409, 115781}, -- Spell Lock
  789. [89766] = {119914, 347008}, -- Axe Toss
  790. [51514] = {211004, 211015, 277778, 309328, 210873, 211010, 269352, 277784}, -- Hex
  791. [132469] = {61391}, -- Typhoon
  792. [191427] = {200166}, -- Metamorphosis
  793. [106898] = {77761, 77764}, -- Stampeding Roar
  794. [86659] = {212641}, -- Guardian of the Ancient Kings (+Glyph)
  795. }
  796.  
  797. ZT.separateLinkedSpellIDs = {
  798. [86659] = {212641}, -- Guardian of the Ancient Kings (+Glyph)
  799. }
  800.  
  801. --##############################################################################
  802. -- Handling custom spells specified by the user in the configuration
  803.  
  804. local spellConfigPrefix = "return function(DH,DK,Druid,Hunter,Mage,Monk,Paladin,Priest,Rogue,Shaman,Warlock,Warrior,LevelReq,RaceReq,ClassReq,SpecReq,TalentReq,NoTalentReq,SubtractMod,MultiplyMod,ChargesMod,DynamicMod,EventDeltaMod,CastDeltaMod,EventRemainingMod,CastRemainingMod,DispelMod) return "
  805. local spellConfigSuffix = "end"
  806.  
  807. local function trim(s) -- From PiL2 20.4
  808. if s ~= nil then
  809. return s:gsub("^%s*(.-)%s*$", "%1")
  810. end
  811. return ""
  812. end
  813.  
  814. local function addCustomSpell(spellConfig, i)
  815. if not spellConfig or type(spellConfig) ~= "table" then
  816. prerror("Custom Spell", i, "is not represented as a valid table")
  817. return
  818. end
  819.  
  820. if type(spellConfig.type) ~= "string" then
  821. prerror("Custom Spell", i, "does not have a valid 'type' entry")
  822. return
  823. end
  824.  
  825. if type(spellConfig.id) ~= "number" then
  826. prerror("Custom Spell", i, "does not have a valid 'id' entry")
  827. return
  828. end
  829.  
  830. if type(spellConfig.cd) ~= "number" then
  831. prerror("Custom Spell", i, "does not have a valid 'cd' entry")
  832. return
  833. end
  834.  
  835. spellConfig.version = 10000
  836. spellConfig.isCustom = true
  837.  
  838. ZT.spellList[#ZT.spellList + 1] = spellConfig
  839. end
  840.  
  841. for i = 1,16 do
  842. local spellConfig = trim(ZT.config["custom"..i])
  843. if spellConfig ~= "" then
  844. local spellConfigFunc = WeakAuras.LoadFunction(spellConfigPrefix..spellConfig..spellConfigSuffix, "ZenTracker Custom Spell "..i)
  845. if spellConfigFunc then
  846. local spell = spellConfigFunc(DH,DK,Druid,Hunter,Mage,Monk,Paladin,Priest,Rogue,Shaman,Warlock,Warrior,LevelReq,RaceReq,ClassReq,SpecReq,TalentReq,NoTalentReq,SubtractMod,MultiplyMod,ChargesMod,DynamicMod,EventDeltaMod,CastDeltaMod,EventRemainingMod,CastRemainingMod,DispelMod)
  847. addCustomSpell(spell, i)
  848. end
  849. end
  850. end
  851.  
  852. --##############################################################################
  853. -- Compiling the complete indexed tables of spells
  854.  
  855. ZT.spells = DefaultTable_Create(function() return DefaultTable_Create(function() return {} end) end)
  856.  
  857. for _,spellInfo in ipairs(ZT.spellList) do
  858. spellInfo.version = spellInfo.version or 100
  859. spellInfo.isRegistered = false
  860. spellInfo.frontends = {}
  861.  
  862. -- Indexing for faster lookups based on the info/requirements
  863. if spellInfo.reqs and (#spellInfo.reqs > 0) then
  864. for _,req in ipairs(spellInfo.reqs) do
  865. if req.indices then
  866. for _,index in ipairs(req.indices) do
  867. tinsert(ZT.spells[req.type][index], spellInfo)
  868. end
  869. end
  870. end
  871. else
  872. tinsert(ZT.spells["generic"], spellInfo)
  873. end
  874.  
  875. if spellInfo.mods then
  876. for _,mod in ipairs(spellInfo.mods) do
  877. if mod.reqs then
  878. for _,req in ipairs(mod.reqs) do
  879. for _,index in ipairs(req.indices) do
  880. tinsert(ZT.spells[req.type][index], spellInfo)
  881. end
  882. end
  883. end
  884. end
  885. end
  886.  
  887. tinsert(ZT.spells["type"][spellInfo.type], spellInfo)
  888. tinsert(ZT.spells["id"][spellInfo.id], spellInfo)
  889. end
  890.  
  891. --##############################################################################
  892. -- Handling combatlog and WeakAura events by invoking specified callbacks
  893.  
  894. ZT.eventHandlers = { handlers = {} }
  895.  
  896. function ZT.eventHandlers:add(type, spellID, sourceGUID, func, data)
  897. local types = self.handlers[spellID]
  898. if not types then
  899. types = {}
  900. self.handlers[spellID] = types
  901. end
  902.  
  903. local sources = types[type]
  904. if not sources then
  905. sources = {}
  906. types[type] = sources
  907. end
  908.  
  909. local handlers = sources[sourceGUID]
  910. if not handlers then
  911. handlers = {}
  912. sources[sourceGUID] = handlers
  913. end
  914.  
  915. handlers[func] = data
  916. end
  917.  
  918. function ZT.eventHandlers:remove(type, spellID, sourceGUID, func)
  919. local types = self.handlers[spellID]
  920. if types then
  921. local sources = types[type]
  922. if sources then
  923. local handlers = sources[sourceGUID]
  924. if handlers then
  925. handlers[func] = nil
  926. end
  927. end
  928. end
  929. end
  930.  
  931. function ZT.eventHandlers:removeAll(sourceGUID)
  932. for _,spells in pairs(self.eventHandlers) do
  933. for _,sources in pairs(spells) do
  934. for GUID,handlers in pairs(sources) do
  935. if GUID == sourceGUID then
  936. wipe(handlers)
  937. end
  938. end
  939. end
  940. end
  941. end
  942.  
  943. local function fixSourceGUID(sourceGUID) -- Based on https://wago.io/p/Nnogga
  944. local type = strsplit("-", sourceGUID)
  945. if type == "Pet" then
  946. for unit in WA_IterateGroupMembers() do
  947. if UnitGUID(unit.."pet") == sourceGUID then
  948. sourceGUID = UnitGUID(unit)
  949. break
  950. end
  951. end
  952. end
  953.  
  954. return sourceGUID
  955. end
  956.  
  957. function ZT.eventHandlers:handle(type, spellID, sourceGUID)
  958. local types = self.handlers[spellID]
  959. if not types then
  960. return
  961. end
  962.  
  963. local sources = types[type]
  964. if not sources then
  965. return
  966. end
  967.  
  968. local handlers = sources[sourceGUID]
  969. if not handlers then
  970. sourceGUID = fixSourceGUID(sourceGUID)
  971. handlers = sources[sourceGUID]
  972. if not handlers then
  973. return
  974. end
  975. end
  976.  
  977. for func,data in pairs(handlers) do
  978. func(data, spellID)
  979. end
  980. end
  981.  
  982. --##############################################################################
  983. -- Managing timer callbacks in a way that allows for updates/removals
  984.  
  985. ZT.timers = { heap={}, callbackTimes={} }
  986.  
  987. function ZT.timers:fixHeapUpwards(index)
  988. local heap = self.heap
  989. local timer = heap[index]
  990.  
  991. local parentIndex, parentTimer
  992. while index > 1 do
  993. parentIndex = floor(index / 2)
  994. parentTimer = heap[parentIndex]
  995. if timer.time >= parentTimer.time then
  996. break
  997. end
  998.  
  999. parentTimer.index = index
  1000. heap[index] = parentTimer
  1001. index = parentIndex
  1002. end
  1003.  
  1004. if timer.index ~= index then
  1005. timer.index = index
  1006. heap[index] = timer
  1007. end
  1008. end
  1009.  
  1010. function ZT.timers:fixHeapDownwards(index)
  1011. local heap = self.heap
  1012. local timer = heap[index]
  1013.  
  1014. local childIndex, minChildTimer, leftChildTimer, rightChildTimer
  1015. while true do
  1016. childIndex = 2 * index
  1017.  
  1018. leftChildTimer = heap[childIndex]
  1019. if leftChildTimer then
  1020. rightChildTimer = heap[childIndex + 1]
  1021. if rightChildTimer and (rightChildTimer.time < leftChildTimer.time) then
  1022. minChildTimer = rightChildTimer
  1023. else
  1024. minChildTimer = leftChildTimer
  1025. end
  1026. else
  1027. break
  1028. end
  1029.  
  1030. if timer.time <= minChildTimer.time then
  1031. break
  1032. end
  1033.  
  1034. childIndex = minChildTimer.index
  1035. minChildTimer.index = index
  1036. heap[index] = minChildTimer
  1037. index = childIndex
  1038. end
  1039.  
  1040. if timer.index ~= index then
  1041. timer.index = index
  1042. heap[index] = timer
  1043. end
  1044. end
  1045.  
  1046. function ZT.timers:setupCallback()
  1047. local minTimer = self.heap[1]
  1048. if minTimer then
  1049. local timeNow = GetTime()
  1050. local remaining = minTimer.time - timeNow
  1051. if remaining <= 0 then
  1052. self:handle()
  1053. elseif not self.callbackTimes[minTimer.time] then
  1054. for time,_ in pairs(self.callbackTimes) do
  1055. if time < timeNow then
  1056. self.callbackTimes[time] = nil
  1057. end
  1058. end
  1059. self.callbackTimes[minTimer.time] = true
  1060.  
  1061. -- Note: This 0.001 avoids early callbacks that I ran into
  1062. remaining = remaining + 0.001
  1063. prdebug(DEBUG_TIMER, "Setting callback for handling timers after", remaining, "seconds")
  1064. C_Timer.After(remaining, function() self:handle() end)
  1065. end
  1066. end
  1067. end
  1068.  
  1069. function ZT.timers:handle()
  1070. local timeNow = GetTime()
  1071. local heap = self.heap
  1072. local minTimer = heap[1]
  1073.  
  1074. prdebug(DEBUG_TIMER, "Handling timers at time", timeNow, "( Min @", minTimer and minTimer.time or "NONE", ")")
  1075. while minTimer and minTimer.time <= timeNow do
  1076. local heapSize = #heap
  1077. if heapSize > 1 then
  1078. heap[1] = heap[heapSize]
  1079. heap[1].index = 1
  1080. heap[heapSize] = nil
  1081. self:fixHeapDownwards(1)
  1082. else
  1083. heap[1] = nil
  1084. end
  1085.  
  1086. minTimer.index = -1
  1087. minTimer.callback()
  1088.  
  1089. minTimer = heap[1]
  1090. end
  1091.  
  1092. self:setupCallback()
  1093. end
  1094.  
  1095. function ZT.timers:add(time, callback)
  1096. local heap = self.heap
  1097.  
  1098. local index = #heap + 1
  1099. local timer = {time=time, callback=callback, index=index}
  1100. heap[index] = timer
  1101.  
  1102. self:fixHeapUpwards(index)
  1103. self:setupCallback()
  1104.  
  1105. return timer
  1106. end
  1107.  
  1108. function ZT.timers:cancel(timer)
  1109. local index = timer.index
  1110. if index == -1 then
  1111. return
  1112. end
  1113.  
  1114. timer.index = -1
  1115.  
  1116. local heap = self.heap
  1117. local heapSize = #heap
  1118. if heapSize ~= index then
  1119. heap[index] = heap[heapSize]
  1120. heap[index].index = index
  1121. heap[heapSize] = nil
  1122. self:fixHeapDownwards(index)
  1123. self:setupCallback()
  1124. else
  1125. heap[index] = nil
  1126. end
  1127. end
  1128.  
  1129. function ZT.timers:update(timer, time)
  1130. local fixHeapFunc = (time <= timer.time) and self.fixHeapUpwards or self.fixHeapDownwards
  1131. timer.time = time
  1132.  
  1133. fixHeapFunc(self, timer.index)
  1134. self:setupCallback()
  1135. end
  1136.  
  1137. --##############################################################################
  1138. -- Managing the set of spells that are being watched
  1139.  
  1140. local WatchInfo = { nextID = 1 }
  1141. local WatchInfoMT = { __index = WatchInfo }
  1142.  
  1143. ZT.watching = {}
  1144.  
  1145. function WatchInfo:create(member, spellInfo, isHidden)
  1146. local watchInfo = {
  1147. id = self.nextID,
  1148. member = member,
  1149. spellInfo = spellInfo,
  1150. duration = spellInfo.cd,
  1151. expiration = GetTime(),
  1152. charges = spellInfo.charges,
  1153. maxCharges = spellInfo.charges,
  1154. isHidden = isHidden,
  1155. isLazy = spellInfo.isLazy,
  1156. ignoreSharing = false,
  1157. }
  1158. self.nextID = self.nextID + 1
  1159.  
  1160. watchInfo = setmetatable(watchInfo, WatchInfoMT)
  1161. watchInfo:updateModifiers()
  1162.  
  1163. return watchInfo
  1164. end
  1165.  
  1166. function WatchInfo:updateModifiers()
  1167. if not self.spellInfo.mods then
  1168. return
  1169. end
  1170.  
  1171. self.duration = self.spellInfo.cd
  1172. self.charges = self.spellInfo.charges
  1173. self.maxCharges = self.spellInfo.charges
  1174.  
  1175. for _,modifier in ipairs(self.spellInfo.mods) do
  1176. if modifier.mod.type == "Static" then
  1177. if self.member:checkRequirements(modifier.reqs) then
  1178. modifier.mod.func(self)
  1179. end
  1180. end
  1181. end
  1182. end
  1183.  
  1184. function WatchInfo:sendAddEvent()
  1185. if not self.isLazy and not self.isHidden then
  1186. local spellInfo = self.spellInfo
  1187. prdebug(DEBUG_EVENT, "Sending ZT_ADD", spellInfo.type, self.id, self.member.name, spellInfo.id, self.duration, self.charges)
  1188. WeakAuras.ScanEvents("ZT_ADD", spellInfo.type, self.id, self.member, spellInfo.id, self.duration, self.charges)
  1189.  
  1190. if self.expiration > GetTime() then
  1191. self:sendTriggerEvent()
  1192. end
  1193. end
  1194. end
  1195.  
  1196. function WatchInfo:sendTriggerEvent()
  1197. if self.isLazy then
  1198. self.isLazy = false
  1199. self:sendAddEvent()
  1200. end
  1201.  
  1202. if not self.isHidden then
  1203. prdebug(DEBUG_EVENT, "Sending ZT_TRIGGER", self.spellInfo.type, self.id, self.duration, self.expiration, self.charges)
  1204. WeakAuras.ScanEvents("ZT_TRIGGER", self.spellInfo.type, self.id, self.duration, self.expiration, self.charges)
  1205. end
  1206. end
  1207.  
  1208. function WatchInfo:sendRemoveEvent()
  1209. if not self.isLazy and not self.isHidden then
  1210. prdebug(DEBUG_EVENT, "Sending ZT_REMOVE", self.spellInfo.type, self.id)
  1211. WeakAuras.ScanEvents("ZT_REMOVE", self.spellInfo.type, self.id)
  1212. end
  1213. end
  1214.  
  1215. function WatchInfo:hide()
  1216. if not self.isHidden then
  1217. self:sendRemoveEvent()
  1218. self.isHidden = true
  1219. end
  1220. end
  1221.  
  1222. function WatchInfo:unhide(suppressAddEvent)
  1223. if self.isHidden then
  1224. self.isHidden = false
  1225. if not suppressAddEvent then
  1226. self:sendAddEvent()
  1227. end
  1228. end
  1229. end
  1230.  
  1231. function WatchInfo:toggleHidden(toggle, suppressAddEvent)
  1232. if toggle then
  1233. self:hide()
  1234. else
  1235. self:unhide(suppressAddEvent)
  1236. end
  1237. end
  1238.  
  1239. function WatchInfo:handleReadyTimer()
  1240. if self.charges then
  1241. self.charges = self.charges + 1
  1242.  
  1243. -- If we are not at max charges, update expiration and start a ready timer
  1244. if self.charges < self.maxCharges then
  1245. self.expiration = self.expiration + self.duration
  1246. prdebug(DEBUG_TIMER, "Adding ready timer of", self.expiration, "for spellID", self.spellInfo.id)
  1247. self.readyTimer = ZT.timers:add(self.expiration, function() self:handleReadyTimer() end)
  1248. else
  1249. self.readyTimer = nil
  1250. end
  1251. else
  1252. self.readyTimer = nil
  1253. end
  1254.  
  1255. self:sendTriggerEvent()
  1256. end
  1257.  
  1258. function WatchInfo:updateReadyTimer() -- Returns true if a timer was set, false if handled immediately
  1259. if self.expiration > GetTime() then
  1260. if self.readyTimer then
  1261. prdebug(DEBUG_TIMER, "Updating ready timer from", self.readyTimer.time, "to", self.expiration, "for spellID", self.spellInfo.id)
  1262. ZT.timers:update(self.readyTimer, self.expiration)
  1263. else
  1264. prdebug(DEBUG_TIMER, "Adding ready timer of", self.expiration, "for spellID", self.spellInfo.id)
  1265. self.readyTimer = ZT.timers:add(self.expiration, function() self:handleReadyTimer() end)
  1266. end
  1267.  
  1268. return true
  1269. else
  1270. if self.readyTimer then
  1271. prdebug(DEBUG_TIMER, "Canceling ready timer for spellID", self.spellInfo.id)
  1272. ZT.timers:cancel(self.readyTimer)
  1273. self.readyTimer = nil
  1274. end
  1275.  
  1276. self:handleReadyTimer(self.expiration)
  1277. return false
  1278. end
  1279. end
  1280.  
  1281. function WatchInfo:startCD()
  1282. if self.charges then
  1283. if self.charges == 0 or self.charges == self.maxCharges then
  1284. self.expiration = GetTime() + self.duration
  1285. self:updateReadyTimer()
  1286. end
  1287.  
  1288. if self.charges > 0 then
  1289. self.charges = self.charges - 1
  1290. end
  1291. else
  1292. self.expiration = GetTime() + self.duration
  1293. self:updateReadyTimer()
  1294. end
  1295.  
  1296. self:sendTriggerEvent()
  1297. end
  1298.  
  1299. function WatchInfo:updateCDDelta(delta)
  1300. self.expiration = self.expiration + delta
  1301.  
  1302. local time = GetTime()
  1303. local remaining = self.expiration - time
  1304.  
  1305. if self.charges and remaining <= 0 then
  1306. local chargesGained = 1 - floor(remaining / self.duration)
  1307. self.charges = max(self.charges + chargesGained, self.maxCharges)
  1308. if self.charges == self.maxCharges then
  1309. self.expiration = time
  1310. else
  1311. self.expiration = self.expiration + (chargesGained * self.duration)
  1312. end
  1313. end
  1314.  
  1315. if self:updateReadyTimer() then
  1316. self:sendTriggerEvent()
  1317. end
  1318. end
  1319.  
  1320. function WatchInfo:updateCDRemaining(remaining)
  1321. -- Note: This assumes that when remaining is 0 and the spell uses charges then it gains a charge
  1322. if self.charges and remaining == 0 then
  1323. if self.charges < self.maxCharges then
  1324. self.charges = self.charges + 1
  1325. end
  1326.  
  1327. -- Below maximum charges the expiration time doesn't change
  1328. if self.charges < self.maxCharges then
  1329. self:sendTriggerEvent()
  1330. else
  1331. self.expiration = GetTime()
  1332. self:updateReadyTimer()
  1333. end
  1334. else
  1335. self.expiration = GetTime() + remaining
  1336. if self:updateReadyTimer() then
  1337. self:sendTriggerEvent()
  1338. end
  1339. end
  1340. end
  1341.  
  1342. function WatchInfo:updatePlayerCharges()
  1343. local charges = GetSpellCharges(self.spellInfo.id)
  1344. if charges then
  1345. self.charges = charges
  1346. end
  1347. end
  1348.  
  1349. function WatchInfo:updatePlayerCD(spellID, ignoreIfReady)
  1350. local startTime, duration, enabled
  1351. if self.charges then
  1352. self.charges, self.maxCharges, startTime, duration = GetSpellCharges(spellID)
  1353. if self.charges == self.maxCharges then
  1354. startTime = 0
  1355. end
  1356. enabled = 1
  1357. else
  1358. startTime, duration, enabled = GetSpellCooldown(spellID)
  1359. end
  1360.  
  1361. if enabled ~= 0 then
  1362. local ignoreRateLimit
  1363. if startTime ~= 0 then
  1364. ignoreRateLimit = (self.expiration < GetTime())
  1365. self.duration = duration
  1366. self.expiration = startTime + duration
  1367. else
  1368. ignoreRateLimit = true
  1369. self.expiration = GetTime()
  1370. end
  1371.  
  1372. if (not ignoreIfReady) or (startTime ~= 0) then
  1373. ZT:sendCDUpdate(self, ignoreRateLimit)
  1374. self:sendTriggerEvent()
  1375. end
  1376. end
  1377. end
  1378.  
  1379. function ZT:togglePlayerHandlers(watchInfo, enable)
  1380. local spellID = watchInfo.spellInfo.id
  1381. local toggleHandlerFunc = enable and self.eventHandlers.add or self.eventHandlers.remove
  1382.  
  1383. if enable then
  1384. WeakAuras.WatchSpellCooldown(spellID)
  1385. end
  1386. toggleHandlerFunc(self.eventHandlers, "SPELL_COOLDOWN_CHANGED", spellID, 0, watchInfo.updatePlayerCD, watchInfo)
  1387.  
  1388. local links = self.separateLinkedSpellIDs[spellID]
  1389. if links then
  1390. for _,linkedSpellID in ipairs(links) do
  1391. if enable then
  1392. WeakAuras.WatchSpellCooldown(linkedSpellID)
  1393. end
  1394. toggleHandlerFunc(self.eventHandlers, "SPELL_COOLDOWN_CHANGED", linkedSpellID, 0, watchInfo.updatePlayerCD, watchInfo)
  1395. end
  1396. end
  1397. end
  1398.  
  1399. function ZT:toggleCombatLogHandlers(watchInfo, enable)
  1400. local spellInfo = watchInfo.spellInfo
  1401. local spellID = spellInfo.id
  1402. local member = watchInfo.member
  1403. local toggleHandlerFunc = enable and self.eventHandlers.add or self.eventHandlers.remove
  1404.  
  1405. if not spellInfo.ignoreCast then
  1406. toggleHandlerFunc(self.eventHandlers, "SPELL_CAST_SUCCESS", spellID, member.GUID, watchInfo.startCD, watchInfo)
  1407.  
  1408. local links = self.linkedSpellIDs[spellID]
  1409. if links then
  1410. for _,linkedSpellID in ipairs(links) do
  1411. toggleHandlerFunc(self.eventHandlers, "SPELL_CAST_SUCCESS", linkedSpellID, member.GUID, watchInfo.startCD, watchInfo)
  1412. end
  1413. end
  1414. end
  1415.  
  1416. if spellInfo.mods then
  1417. for _,modifier in ipairs(spellInfo.mods) do
  1418. if modifier.mod.type == "Dynamic" then
  1419. if not enable or member:checkRequirements(modifier.reqs) then
  1420. for _,handlerInfo in ipairs(modifier.mod.handlers) do
  1421. toggleHandlerFunc(self.eventHandlers, handlerInfo.type, handlerInfo.spellID, member.GUID, handlerInfo.handler, watchInfo)
  1422. end
  1423. end
  1424. end
  1425. end
  1426. end
  1427. end
  1428.  
  1429. function ZT:watch(spellInfo, member)
  1430. -- Only handle registered spells (or those for the player)
  1431. if not spellInfo.isRegistered and not member.isPlayer then
  1432. return
  1433. end
  1434.  
  1435. -- Only handle spells that meet all the requirements for the member
  1436. if not member:checkRequirements(spellInfo.reqs) then
  1437. return
  1438. end
  1439.  
  1440. local spellID = spellInfo.id
  1441. local spells = self.watching[spellID]
  1442. if not spells then
  1443. spells = {}
  1444. self.watching[spellID] = spells
  1445. end
  1446.  
  1447. local isHidden = (member.isPlayer and not spellInfo.isRegistered) or member.isHidden
  1448.  
  1449. local watchInfo = spells[member.GUID]
  1450. local isNew = (watchInfo == nil)
  1451. if not watchInfo then
  1452. watchInfo = WatchInfo:create(member, spellInfo, isHidden)
  1453. spells[member.GUID] = watchInfo
  1454. member.watching[spellID] = watchInfo
  1455. else
  1456. -- If the type changed, send a remove event
  1457. if not isHidden and spellInfo.type ~= watchInfo.spellInfo.type then
  1458. watchInfo:sendRemoveEvent()
  1459. end
  1460. watchInfo.spellInfo = spellInfo
  1461. watchInfo:updateModifiers()
  1462. watchInfo:toggleHidden(isHidden, true) -- We will send the ZT_ADD event later
  1463. end
  1464.  
  1465. if member.isPlayer then
  1466. watchInfo:updatePlayerCharges()
  1467. watchInfo:sendAddEvent()
  1468.  
  1469. watchInfo:updatePlayerCD(spellID, true)
  1470.  
  1471. local links = self.separateLinkedSpellIDs[spellID]
  1472. if links then
  1473. for _,linkedSpellID in ipairs(links) do
  1474. watchInfo:updatePlayerCD(linkedSpellID, true)
  1475. end
  1476. end
  1477. else
  1478. watchInfo:sendAddEvent()
  1479. end
  1480.  
  1481. if member.isPlayer and not TEST_CLEU then
  1482. if isNew then
  1483. self:togglePlayerHandlers(watchInfo, true)
  1484. end
  1485. elseif member.tracking == "CombatLog" or (member.tracking == "Sharing" and member.spellsVersion < spellInfo.version) then
  1486. watchInfo.ignoreSharing = true
  1487. if not isNew then
  1488. self:toggleCombatLogHandlers(watchInfo, false)
  1489. end
  1490. self:toggleCombatLogHandlers(watchInfo, true)
  1491. else
  1492. watchInfo.ignoreSharing = false
  1493. end
  1494. end
  1495.  
  1496. function ZT:unwatch(spellInfo, member)
  1497. -- Only handle registered spells (or those for the player)
  1498. if not spellInfo.isRegistered and not member.isPlayer then
  1499. return
  1500. end
  1501.  
  1502. local spellID = spellInfo.id
  1503. local sources = self.watching[spellID]
  1504. if not sources then
  1505. return
  1506. end
  1507.  
  1508. local watchInfo = sources[member.GUID]
  1509. if not watchInfo then
  1510. return
  1511. end
  1512.  
  1513. -- Ignoring unwatch requests if the spellInfo doesn't match (yet spellID does)
  1514. -- (Note: This serves to ease updating after spec/talent changes)
  1515. if watchInfo.spellInfo ~= spellInfo then
  1516. return
  1517. end
  1518.  
  1519. if member.isPlayer and not TEST_CLEU then
  1520. -- If called due to front-end unregistration, only hide it to allow continued sharing of updates
  1521. -- Otherwise, called due to a spec/talent change, so actually unwatch it
  1522. if not spellInfo.isRegistered then
  1523. watchInfo:hide()
  1524. return
  1525. end
  1526.  
  1527. self:togglePlayerHandlers(watchInfo, false)
  1528. elseif member.tracking == "CombatLog" or (member.tracking == "Sharing" and member.spellsVersion < spellInfo.version) then
  1529. self:toggleCombatLogHandlers(watchInfo, false)
  1530. end
  1531.  
  1532. if watchInfo.readyTimer then
  1533. self.timers:cancel(watchInfo.readyTimer)
  1534. end
  1535.  
  1536. sources[member.GUID] = nil
  1537. member.watching[spellID] = nil
  1538.  
  1539. watchInfo:sendRemoveEvent()
  1540. end
  1541.  
  1542. --##############################################################################
  1543. -- Tracking types registered by front-end WAs
  1544.  
  1545. function ZT:registerSpells(frontendID, spells)
  1546. for _,spellInfo in ipairs(spells) do
  1547. local frontends = spellInfo.frontends
  1548. if next(frontends, nil) ~= nil then
  1549. -- Some front-end already registered for this spell, so just send ADD events
  1550. local watched = self.watching[spellInfo.id]
  1551. if watched then
  1552. for _,watchInfo in pairs(watched) do
  1553. if watchInfo.spellInfo == spellInfo then
  1554. watchInfo:sendAddEvent()
  1555. end
  1556. end
  1557. end
  1558. else
  1559. -- No front-end was registered for this spell, so watch as needed
  1560. spellInfo.isRegistered = true
  1561. for _,member in pairs(self.members) do
  1562. if not member.isIgnored then
  1563. self:watch(spellInfo, member)
  1564. end
  1565. end
  1566. end
  1567.  
  1568. frontends[frontendID] = true
  1569. end
  1570. end
  1571.  
  1572. function ZT:unregisterSpells(frontendID, spells)
  1573. for _,spellInfo in ipairs(spells) do
  1574. local frontends = spellInfo.frontends
  1575. frontends[frontendID] = nil
  1576.  
  1577. if next(frontends, nil) == nil then
  1578. local watched = self.watching[spellInfo.id]
  1579. if watched then
  1580. for _,watchInfo in pairs(watched) do
  1581. if watchInfo.spellInfo == spellInfo then
  1582. self:unwatch(spellInfo, watchInfo.member)
  1583. end
  1584. end
  1585. end
  1586. spellInfo.isRegistered = false
  1587. end
  1588. end
  1589. end
  1590.  
  1591. function ZT:toggleFrontEndRegistration(frontendID, info, toggle)
  1592. local infoType = type(info)
  1593. local registerFunc = toggle and self.registerSpells or self.unregisterSpells
  1594.  
  1595. if infoType == "string" then -- Registration info is a type
  1596. prdebug(DEBUG_EVENT, "Received", toggle and "ZT_REGISTER" or "ZT_UNREGISTER", "from", frontendID, "for type", info)
  1597. registerFunc(self, frontendID, self.spells["type"][info])
  1598. elseif infoType == "number" then -- Registration info is a spellID
  1599. prdebug(DEBUG_EVENT, "Received", toggle and "ZT_REGISTER" or "ZT_UNREGISTER", "from", frontendID, "for spellID", info)
  1600. registerFunc(self, frontendID, self.spells["id"][info])
  1601. elseif infoType == "table" then -- Registration info is a table of types or spellIDs
  1602. infoType = type(info[1])
  1603.  
  1604. if infoType == "string" then
  1605. prdebug(DEBUG_EVENT, "Received", toggle and "ZT_REGISTER" or "ZT_UNREGISTER", "from", frontendID, "for multiple types")
  1606. for _,type in ipairs(info) do
  1607. registerFunc(self, frontendID, self.spells["type"][type])
  1608. end
  1609. elseif infoType == "number" then
  1610. prdebug(DEBUG_EVENT, "Received", toggle and "ZT_REGISTER" or "ZT_UNREGISTER", "from", frontendID, "for multiple spells")
  1611. for _,spellID in ipairs(info) do
  1612. registerFunc(self, frontendID, self.spells["id"][spellID])
  1613. end
  1614. end
  1615. end
  1616. end
  1617.  
  1618. function ZT:registerFrontEnd(frontendID, info)
  1619. self:toggleFrontEndRegistration(frontendID, info, true)
  1620. end
  1621.  
  1622. function ZT:unregisterFrontEnd(frontendID, info)
  1623. self:toggleFrontEndRegistration(frontendID, info, false)
  1624. end
  1625.  
  1626. --##############################################################################
  1627. -- Managing member information (e.g., spec, talents) for all group members
  1628.  
  1629. local Member = { }
  1630. local MemberMT = { __index = Member }
  1631.  
  1632. ZT.members = {}
  1633. ZT.inEncounter = false
  1634.  
  1635. local membersToIgnore = {}
  1636. if ZT.config["ignoreList"] then
  1637. local ignoreListStr = trim(ZT.config["ignoreList"])
  1638. if ignoreListStr ~= "" then
  1639. ignoreListStr = "return "..ignoreListStr
  1640. local ignoreList = WeakAuras.LoadFunction(ignoreListStr, "ZenTracker Ignore List")
  1641. if ignoreList and (type(ignoreList) == "table") then
  1642. for i,name in ipairs(ignoreList) do
  1643. if type(name) == "string" then
  1644. membersToIgnore[strlower(name)] = true
  1645. else
  1646. prerror("Ignore list entry", i, "is not a string. Skipping...")
  1647. end
  1648. end
  1649. else
  1650. prerror("Ignore list is not in the form of a table. Usage: {\"Zenlia\", \"Cistara\"}")
  1651. end
  1652. end
  1653. end
  1654.  
  1655. function Member:create(memberInfo)
  1656. local member = memberInfo
  1657. member.watching = {}
  1658. member.tracking = member.tracking and member.tracking or "CombatLog"
  1659. member.isPlayer = (member.GUID == UnitGUID("player"))
  1660. member.isHidden = false
  1661. member.isReady = false
  1662.  
  1663. return setmetatable(member, MemberMT)
  1664. end
  1665.  
  1666. function Member:update(memberInfo)
  1667. if memberInfo.level then
  1668. self.level = memberInfo.level
  1669. end
  1670. self.specID = memberInfo.specID
  1671. self.talents = memberInfo.talents
  1672. self.talentsStr = memberInfo.talentsStr
  1673. if memberInfo.unit then
  1674. self.unit = memberInfo.unit
  1675. end
  1676. end
  1677.  
  1678. function Member:gatherInfo()
  1679. local _,className,_,race,_,name = GetPlayerInfoByGUID(self.GUID)
  1680. self.name = name and gsub(name, "%-[^|]+", "") or nil
  1681. self.class = className and AllClasses[className] or nil
  1682. self.classID = className and AllClasses[className].ID or nil
  1683. self.classColor = className and RAID_CLASS_COLORS[className] or nil
  1684. self.race = race
  1685. self.level = self.unit and UnitLevel(self.unit) or -1
  1686.  
  1687. if (self.tracking == "Sharing") and self.name then
  1688. prdebug(DEBUG_TRACKING, self.name, "is using ZenTracker with spellsVersion", self.spellsVersion)
  1689. end
  1690.  
  1691. if self.name and membersToIgnore[strlower(self.name)] then
  1692. self.isIgnored = true
  1693. return false
  1694. end
  1695.  
  1696. self.isReady = (self.name ~= nil) and (self.classID ~= nil) and (self.race ~= nil) and (self.level >= 1)
  1697. return self.isReady
  1698. end
  1699.  
  1700. function Member:checkRequirements(reqs)
  1701. if not reqs then
  1702. return true
  1703. end
  1704.  
  1705. for _,req in ipairs(reqs) do
  1706. if not req.check(self) then
  1707. return false
  1708. end
  1709. end
  1710. return true
  1711. end
  1712.  
  1713. function Member:hide()
  1714. if not self.isHidden and not self.isPlayer then
  1715. self.isHidden = true
  1716. for _,watchInfo in pairs(self.watching) do
  1717. watchInfo:hide()
  1718. end
  1719. end
  1720. end
  1721.  
  1722. function Member:unhide()
  1723. if self.isHidden and not self.isPlayer then
  1724. self.isHidden = false
  1725. for _,watchInfo in pairs(self.watching) do
  1726. watchInfo:unhide()
  1727. end
  1728. end
  1729. end
  1730.  
  1731. -- TODO: Fix rare issue where somehow only talented spells are being shown?
  1732. function ZT:addOrUpdateMember(memberInfo)
  1733. local member = self.members[memberInfo.GUID]
  1734. if not member then
  1735. member = Member:create(memberInfo)
  1736. self.members[member.GUID] = member
  1737. end
  1738.  
  1739. if member.isIgnored then
  1740. return
  1741. end
  1742.  
  1743. -- Determining which properties of the member have updated
  1744. local isInitialUpdate = not member.isReady and member:gatherInfo()
  1745. local isLevelUpdate = memberInfo.level and (memberInfo.level ~= member.level)
  1746. local isSpecUpdate = memberInfo.specID and (memberInfo.specID ~= member.specID)
  1747. local isTalentUpdate = isSpecUpdate
  1748. for talent,_ in pairs(memberInfo.talents) do
  1749. if member.talents[talent] == nil then
  1750. isTalentUpdate = true
  1751. break
  1752. end
  1753. end
  1754.  
  1755. if member.isReady and (isInitialUpdate or isLevelUpdate or isSpecUpdate or isTalentUpdate) then
  1756. local prevSpecID = member.specID
  1757. local prevTalents = member.talents
  1758. member:update(memberInfo)
  1759.  
  1760. -- This handshake should come before any cooldown updates for newly watched spells
  1761. if member.isPlayer then
  1762. self:sendHandshake()
  1763. end
  1764.  
  1765. -- If we are in an encounter, hide the member if they are outside the player's instance
  1766. -- (Note: Previously did this on member creation, which seemed to introduce false positives)
  1767. if isInitialUpdate and self.inEncounter and (not member.isPlayer) then
  1768. local _,_,_,instanceID = UnitPosition("player")
  1769. local _,_,_,mInstanceID = UnitPosition(member.unit)
  1770. if instanceID ~= mInstanceID then
  1771. member:hide()
  1772. end
  1773. end
  1774.  
  1775. -- Generic Spells + Class Spells + Race Spells
  1776. -- Note: These are set once and never change
  1777. if isInitialUpdate then
  1778. for _,spellInfo in ipairs(self.spells["generic"]) do
  1779. self:watch(spellInfo, member)
  1780. end
  1781. for _,spellInfo in ipairs(self.spells["race"][member.race]) do
  1782. self:watch(spellInfo, member)
  1783. end
  1784. for _,spellInfo in ipairs(self.spells["class"][member.classID]) do
  1785. self:watch(spellInfo, member)
  1786. end
  1787. end
  1788.  
  1789. -- Specialization Spells
  1790. if (isInitialUpdate or isSpecUpdate) and member.specID then
  1791. for _,spellInfo in ipairs(self.spells["spec"][member.specID]) do
  1792. self:watch(spellInfo, member)
  1793. end
  1794.  
  1795. if isSpecUpdate and prevSpecID then
  1796. for _,spellInfo in ipairs(self.spells["spec"][prevSpecID]) do
  1797. if not member:checkRequirements(spellInfo.reqs) then
  1798. self:unwatch(spellInfo, member)
  1799. end
  1800. end
  1801. end
  1802. end
  1803.  
  1804. -- Talented Spells
  1805. if isInitialUpdate or isTalentUpdate then
  1806. for talent,_ in pairs(member.talents) do
  1807. for _,spellInfo in ipairs(self.spells["talent"][talent]) do
  1808. self:watch(spellInfo, member)
  1809. end
  1810. for _,spellInfo in ipairs(self.spells["notalent"][talent]) do
  1811. if not member:checkRequirements(spellInfo.reqs) then
  1812. self:unwatch(spellInfo, member)
  1813. end
  1814. end
  1815. end
  1816.  
  1817. if isTalentUpdate then
  1818. for talent,_ in pairs(prevTalents) do
  1819. if not member.talents[talent] then
  1820. for _,spellInfo in ipairs(self.spells["talent"][talent]) do
  1821. if not member:checkRequirements(spellInfo.reqs) then
  1822. self:unwatch(spellInfo, member) -- Talent was required
  1823. else
  1824. self:watch(spellInfo, member) -- Talent was a modifier
  1825. end
  1826. end
  1827. for _,spellInfo in ipairs(self.spells["notalent"][talent]) do
  1828. self:watch(spellInfo, member)
  1829. end
  1830. end
  1831. end
  1832. end
  1833. end
  1834. end
  1835.  
  1836. -- If tracking changed from "CombatLog" to "Sharing", remove unnecessary event handlers and send a handshake/updates
  1837. if (member.tracking == "CombatLog") and (memberInfo.tracking == "Sharing") then
  1838. member.tracking = "Sharing"
  1839. member.spellsVersion = memberInfo.spellsVersion
  1840.  
  1841. if member.name then
  1842. prdebug(DEBUG_TRACKING, member.name, "is using ZenTracker with spell list version", member.spellsVersion)
  1843. end
  1844.  
  1845. for _,watchInfo in pairs(member.watching) do
  1846. if watchInfo.spellInfo.version <= member.spellsVersion then
  1847. watchInfo.ignoreSharing = false
  1848. self:toggleCombatLogHandlers(watchInfo, false)
  1849. end
  1850. end
  1851.  
  1852. self:sendHandshake()
  1853. local time = GetTime()
  1854. for _,watchInfo in pairs(self.members[UnitGUID("player")].watching) do
  1855. if watchInfo.expiration > time then
  1856. self:sendCDUpdate(watchInfo)
  1857. end
  1858. end
  1859. end
  1860. end
  1861.  
  1862. --##############################################################################
  1863. -- Handling raid and M+ encounters
  1864.  
  1865. function ZT:resetEncounterCDs()
  1866. for _,member in pairs(self.members) do
  1867. local resetMemberCDs = not member.isPlayer and member.tracking ~= "Sharing"
  1868.  
  1869. for _,watchInfo in pairs(member.watching) do
  1870. if resetMemberCDs and watchInfo.duration >= 180 then
  1871. watchInfo.charges = watchInfo.maxCharges
  1872. watchInfo:updateCDRemaining(0)
  1873. end
  1874.  
  1875. -- If spell uses lazy tracking and it was triggered, reset lazy tracking at this point
  1876. if watchInfo.spellInfo.isLazy and not watchInfo.isLazy then
  1877. watchInfo:sendRemoveEvent()
  1878. watchInfo.isLazy = true
  1879. end
  1880. end
  1881. end
  1882. end
  1883.  
  1884. function ZT:startEncounter(event)
  1885. self.inEncounter = true
  1886.  
  1887. local _,_,_,instanceID = UnitPosition("player")
  1888. for _,member in pairs(self.members) do
  1889. local _,_,_,mInstanceID = UnitPosition(self.inspectLib:GuidToUnit(member.GUID))
  1890. if mInstanceID ~= instanceID then
  1891. member:hide()
  1892. else
  1893. member:unhide() -- Note: Shouldn't be hidden, but just in case...
  1894. end
  1895. end
  1896.  
  1897. if event == "CHALLENGE_MODE_START" then
  1898. self:resetEncounterCDs()
  1899. end
  1900. end
  1901.  
  1902. function ZT:endEncounter(event)
  1903. if self.inEncounter then
  1904. self.inEncounter = false
  1905. for _,member in pairs(self.members) do
  1906. member:unhide()
  1907. end
  1908. end
  1909.  
  1910. if event == "ENCOUNTER_END" then
  1911. self:resetEncounterCDs()
  1912. end
  1913. end
  1914.  
  1915. --##############################################################################
  1916. -- Public functions for other addons/auras to query ZenTracker information
  1917. -- TODO: Under heavy construction
  1918.  
  1919. local function Public_Query(typeOrID)
  1920. local results = {}
  1921. if type(typeOrID) == "string" then
  1922. local type = tostring(typeOrID)
  1923. for _,member in pairs(ZT.members) do
  1924. for _,watchInfo in pairs(member.watching) do
  1925. if watchInfo.spellInfo.type == type then
  1926. tinsert(results, {spellID = watchInfo.spellInfo.id, member = member, expiration = watchInfo.expiration, charges = watchInfo.charges})
  1927. end
  1928. end
  1929. end
  1930. elseif type(typeOrID) == "number" then
  1931. local id = tonumber(typeOrID)
  1932. for _,member in pairs(ZT.members) do
  1933. local watchInfo = member.watching[id]
  1934. if watchInfo then
  1935. tinsert(results, {spellID = watchInfo.spellInfo.id, member = member, expiration = watchInfo.expiration, charges = watchInfo.charges})
  1936. end
  1937. end
  1938. end
  1939.  
  1940. return results
  1941. end
  1942.  
  1943. setglobal("ZenTracker_PublicFunctions", { query = Public_Query })
  1944.  
  1945. --##############################################################################
  1946. -- Handling the exchange of addon messages with other ZT clients
  1947. --
  1948. -- Message Format = <Protocol Version (%d)>:<Message Type (%s)>:<Member GUID (%s)>...
  1949. -- Type = "H" (Handshake)
  1950. -- ...:<Spec ID (%d)>:<Talents (%s)>:<IsInitial? (%d) [2]>:<Spells Version (%d) [2]>
  1951. -- Type = "U" (CD Update)
  1952. -- ...:<Spell ID (%d)>:<Duration (%f)>:<Remaining (%f)>:<#Charges (%d) [3]>
  1953.  
  1954. ZT.protocolVersion = 4
  1955.  
  1956. ZT.timeBetweenHandshakes = 5 --seconds
  1957. ZT.timeOfNextHandshake = 0
  1958. ZT.handshakeTimer = nil
  1959.  
  1960. ZT.timeBetweenCDUpdates = 5 --seconds (per spellID)
  1961. ZT.timeOfNextCDUpdate = {}
  1962. ZT.updateTimers = {}
  1963.  
  1964. local function sendMessage(message)
  1965. prdebug(DEBUG_MESSAGE, "Sending message '"..message.."'")
  1966.  
  1967. if not IsInGroup() and not IsInRaid() then
  1968. return
  1969. end
  1970.  
  1971. local channel = IsInGroup(2) and "INSTANCE_CHAT" or "RAID"
  1972. C_ChatInfo.SendAddonMessage("ZenTracker", message, channel)
  1973. end
  1974.  
  1975. ZT.hasSentHandshake = false
  1976. function ZT:sendHandshake()
  1977. local time = GetTime()
  1978. if time < self.timeOfNextHandshake then
  1979. if not self.handshakeTimer then
  1980. self.handshakeTimer = self.timers:add(self.timeOfNextHandshake, function() self:sendHandshake() end)
  1981. end
  1982. return
  1983. end
  1984.  
  1985. local GUID = UnitGUID("player")
  1986. if not self.members[GUID] then
  1987. return -- This may happen when rejoining a group after login, so ignore this attempt to send a handshake
  1988. end
  1989.  
  1990. local member = self.members[GUID]
  1991. local specID = member.specID or 0
  1992. local talents = member.talentsStr or ""
  1993. local isInitial = self.hasSentHandshake and 0 or 1
  1994. local message = string.format("%d:H:%s:%d:%s:%d:%d", self.protocolVersion, GUID, specID, talents, isInitial, self.spellListVersion)
  1995. sendMessage(message)
  1996.  
  1997. self.hasSentHandshake = true
  1998. self.timeOfNextHandshake = time + self.timeBetweenHandshakes
  1999. if self.handshakeTimer then
  2000. self.timers:cancel(self.handshakeTimer)
  2001. self.handshakeTimer = nil
  2002. end
  2003. end
  2004.  
  2005. function ZT:sendCDUpdate(watchInfo, ignoreRateLimit)
  2006. local spellID = watchInfo.spellInfo.id
  2007. local time = GetTime()
  2008.  
  2009. local timer = self.updateTimers[spellID]
  2010. if ignoreRateLimit then
  2011. if timer then
  2012. self.timers:cancel(timer)
  2013. self.updateTimers[spellID] = nil
  2014. end
  2015. elseif timer then
  2016. return
  2017. else
  2018. local timeOfNextCDUpdate = self.timeOfNextCDUpdate[spellID]
  2019. if timeOfNextCDUpdate and (time < timeOfNextCDUpdate) then
  2020. self.updateTimers[spellID] = self.timers:add(timeOfNextCDUpdate, function() self:sendCDUpdate(watchInfo, true) end)
  2021. return
  2022. end
  2023. end
  2024.  
  2025. local GUID = watchInfo.member.GUID
  2026. local duration = watchInfo.duration
  2027. local remaining = watchInfo.expiration - time
  2028. if remaining < 0 then
  2029. remaining = 0
  2030. end
  2031. local charges = watchInfo.charges and tostring(watchInfo.charges) or "-"
  2032. local message = string.format("%d:U:%s:%d:%0.2f:%0.2f:%s", self.protocolVersion, GUID, spellID, duration, remaining, charges)
  2033. sendMessage(message)
  2034.  
  2035. self.timeOfNextCDUpdate[spellID] = time + self.timeBetweenCDUpdates
  2036. end
  2037.  
  2038. function ZT:handleHandshake(version, mGUID, specID, talentsStr, isInitial, spellsVersion)
  2039. -- Protocol V4: Ignore any earlier versions due to substantial changes (talents)
  2040. if version < 4 then
  2041. return
  2042. end
  2043.  
  2044. specID = tonumber(specID)
  2045. if specID == 0 then
  2046. specID = nil
  2047. end
  2048.  
  2049. local talents = {}
  2050. if talents ~= "" then
  2051. for index in talentsStr:gmatch("%d+") do
  2052. index = tonumber(index)
  2053. talents[index] = true
  2054. end
  2055. end
  2056.  
  2057. -- Protocol V2: Assume false if not present
  2058. if isInitial == "1" then
  2059. isInitial = true
  2060. else
  2061. isInitial = false
  2062. end
  2063.  
  2064. -- Protocol V2: Assume spellsVersion is 1 if not present
  2065. if spellsVersion then
  2066. spellsVersion = tonumber(spellsVersion)
  2067. if not spellsVersion then
  2068. spellsVersion = 1
  2069. end
  2070. else
  2071. spellsVersion = 1
  2072. end
  2073.  
  2074. local memberInfo = {
  2075. GUID = mGUID,
  2076. specID = specID,
  2077. talents = talents,
  2078. talentsStr = talentsStr,
  2079. tracking = "Sharing",
  2080. spellsVersion = spellsVersion,
  2081. }
  2082.  
  2083. self:addOrUpdateMember(memberInfo)
  2084. if isInitial then
  2085. self:sendHandshake()
  2086. end
  2087. end
  2088.  
  2089. function ZT:handleCDUpdate(version, mGUID, spellID, duration, remaining, charges)
  2090. local member = self.members[mGUID]
  2091. if not member or not member.isReady then
  2092. return
  2093. end
  2094.  
  2095. spellID = tonumber(spellID)
  2096. duration = tonumber(duration)
  2097. remaining = tonumber(remaining)
  2098. if not spellID or not duration or not remaining then
  2099. return
  2100. end
  2101.  
  2102. local sources = self.watching[spellID]
  2103. if sources then
  2104. local watchInfo = sources[member.GUID]
  2105. if not watchInfo or watchInfo.ignoreSharing then
  2106. return
  2107. end
  2108.  
  2109. -- Protocol V3: Ignore charges if not present
  2110. -- (Note that this shouldn't happen because of spell list version handling)
  2111. if charges then
  2112. charges = tonumber(charges)
  2113. if charges then
  2114. watchInfo.charges = charges
  2115. end
  2116. end
  2117.  
  2118. watchInfo.duration = duration
  2119. watchInfo.expiration = GetTime() + remaining
  2120. watchInfo:sendTriggerEvent()
  2121. end
  2122. end
  2123.  
  2124. function ZT:handleMessage(message)
  2125. local version, type, mGUID, arg1, arg2, arg3, arg4, arg5 = strsplit(":", message)
  2126. version = tonumber(version)
  2127.  
  2128. -- Ignore any messages sent by the player
  2129. if mGUID == UnitGUID("player") then
  2130. return
  2131. end
  2132.  
  2133. prdebug(DEBUG_MESSAGE, "Received message '"..message.."'")
  2134.  
  2135. if type == "H" then -- Handshake
  2136. self:handleHandshake(version, mGUID, arg1, arg2, arg3, arg4, arg5)
  2137. elseif type == "U" then -- CD Update
  2138. self:handleCDUpdate(version, mGUID, arg1, arg2, arg3, arg4, arg5)
  2139. else
  2140. return
  2141. end
  2142. end
  2143.  
  2144. if not C_ChatInfo.RegisterAddonMessagePrefix("ZenTracker") then
  2145. prerror("Could not register addon message prefix. Defaulting to local-only cooldown tracking.")
  2146. end
  2147.  
  2148. --##############################################################################
  2149. -- Callback functions for libGroupInspecT for updating/removing members
  2150.  
  2151. ZT.delayedUpdates = {}
  2152.  
  2153. function ZT:libInspectUpdate(_, GUID, _, info)
  2154. local specID = info.global_spec_id
  2155. if specID == 0 then
  2156. specID = nil
  2157. end
  2158.  
  2159. local talents = {}
  2160. local talentsStr = ""
  2161. if info.talents then
  2162. for _,talent in pairs(info.talents) do
  2163. if talent.spell_id then -- This is rarely nil, not sure why...
  2164. talents[talent.spell_id] = true
  2165. talentsStr = talentsStr..talent.spell_id..","
  2166. end
  2167. end
  2168. end
  2169.  
  2170. local memberInfo = {
  2171. GUID = GUID,
  2172. unit = info.lku,
  2173. specID = specID,
  2174. talents = talents,
  2175. talentsStr = strsub(talentsStr, 0, -2),
  2176. }
  2177.  
  2178. if not self.delayedUpdates then
  2179. self:addOrUpdateMember(memberInfo)
  2180. else
  2181. self.delayedUpdates[GUID] = memberInfo
  2182. end
  2183. end
  2184.  
  2185. function ZT:libInspectRemove(_, GUID)
  2186. local member = self.members[GUID]
  2187. if not member then
  2188. return
  2189. end
  2190.  
  2191. for _,watchInfo in pairs(member.watching) do
  2192. self:unwatch(watchInfo.spellInfo, member)
  2193. end
  2194. self.members[GUID] = nil
  2195. end
  2196.  
  2197. function ZT:handleDelayedUpdates()
  2198. if self.delayedUpdates then
  2199. for _,memberInfo in pairs(self.delayedUpdates) do
  2200. self:addOrUpdateMember(memberInfo)
  2201. end
  2202. self.delayedUpdates = nil
  2203. end
  2204. end
  2205.  
  2206. ZT.inspectLib = LibStub:GetLibrary("LibGroupInSpecT-1.1", true)
  2207.  
  2208. if ZT.inspectLib then
  2209. local prevZT = _G["ZenTracker_AuraEnv"]
  2210. if prevZT then
  2211. ZT.inspectLib.UnregisterAllCallbacks(prevZT)
  2212. if prevZT.timers then
  2213. prevZT.timers.heap = {}
  2214. end
  2215. end
  2216. _G["ZenTracker_AuraEnv"] = ZT
  2217.  
  2218. -- If prevZT exists, we know it wasn't a login or reload. If it doesn't exist,
  2219. -- it still might not be a login or reload if the user is installing ZenTracker
  2220. -- for the first time. IsLoginFinished() takes care of the second case.
  2221. if prevZT or WeakAuras.IsLoginFinished() then
  2222. ZT.delayedUpdates = nil
  2223. end
  2224.  
  2225. ZT.inspectLib.RegisterCallback(ZT, "GroupInSpecT_Update", "libInspectUpdate")
  2226. ZT.inspectLib.RegisterCallback(ZT, "GroupInSpecT_Remove", "libInspectRemove")
  2227.  
  2228. for unit in WA_IterateGroupMembers() do
  2229. local GUID = UnitGUID(unit)
  2230. if GUID then
  2231. local info = ZT.inspectLib:GetCachedInfo(GUID)
  2232. if info then
  2233. ZT:libInspectUpdate("Init", GUID, unit, info)
  2234. else
  2235. ZT.inspectLib:Rescan(GUID)
  2236. end
  2237. end
  2238. end
  2239. else
  2240. prerror("LibGroupInSpecT-1.1 not found")
  2241. end
  2242.  
  2243.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement