vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 
2 -- Add the module to the tree
3 local mod = klhtm
4 local me = {}
5 mod.combat = me
6  
7 --[[
8 KTM_Combat.lua
9  
10 The combat module parses combat log events for damage and abilities done.
11 ]]
12  
13 -- These are the events we would like to be notified of
14 me.myevents = { "CHAT_MSG_SPELL_FAILED_LOCALPLAYER", "CHAT_MSG_COMBAT_FRIENDLY_DEATH"}
15  
16 -- these are kept for debug purposes. We need two because next attack abilities are split into two.
17 me.lastattack = nil
18 me.secondlastattack = nil
19  
20 --[[
21 This is a record of attacks in the last second while out of combat. We keep this because when you
22 go into combat by initiating an attack, the +combat event can come after the actual attack.
23 ]]
24 me.recentattacks = { }
25  
26 me.onupdate = function()
27  
28 local timenow = GetTime()
29 local key
30 local value
31  
32 for key, value in me.recentattacks do
33 if value[1] < timenow - 1 then
34 me.recentattacks[key] = nil
35 end
36 end
37  
38 end
39  
40  
41 -- This is a method level temporary variable. Declared at file level because it is a list,
42 -- and we don't want to keep paging heap memory every time he is created.
43 me.event =
44 {
45 ["hits"] = 0,
46 ["damage"] = 0,
47 ["rage"] = 0,
48 ["threat"] = 0,
49 ["name"] = 0,
50 }
51  
52 --[[
53 Special onevent() method that will be called by Core.lua:onevent()
54 ]]
55 me.onevent = function()
56  
57 if me.oneventinternal() then
58 KLHTM_RequestRedraw("self")
59 end
60  
61 end
62  
63 -- Returns non-nil if the event causes our threat to change
64 me.oneventinternal = function()
65  
66 local ability
67 local target
68 local amount
69 local damagetype
70  
71 if event == "CHAT_MSG_SPELL_FAILED_LOCALPLAYER" then
72 if string.find(arg1, mod.string.get("spell", "sunder")) then
73 me.retractsundercast()
74 return true
75 else
76 return
77 end
78  
79 elseif event == "CHAT_MSG_COMBAT_FRIENDLY_DEATH" then
80  
81 if arg1 == UNITDIESSELF then -- UNITDIESSELF = "You die."
82 -- death is a threat wipe
83 mod.table.resetraidthreat()
84 return true
85 end
86  
87 end
88  
89 end
90  
91 --[[
92 mod.combat.specialattack(abilityid, target, damage, iscrit, spellschool)
93 This handles any attack from a spell that has special threat properties, i.e. all the spells in mod.data.spells .
94 <abilityid> is the internal identifier for these abilities. i.e. Sunder Armor has <abilityid> = "sunder". This is locale
95 independent.
96 <damage>, <iscrit>, and <spellschool> are optional. <spellschool> is localised, and will have the value either nil
97 or "" or one of SPELL_SCHOOL1_CAP, SPELL_SCHOOL2_CAP, etc.
98 <iscrit> is only accepted if it has the boolean value true.
99 ]]
100 me.specialattack = function(abilityid, target, damage, iscrit, spellschool)
101  
102 -- 1) check the attack is directed at the master target. If not, ignore.
103 if mod.boss.targetismaster(target) == nil then
104 return
105 end
106  
107 -- 2) get the player's global threat modifiers (defensive stance, blessing of salvation, etc)
108 local threatmodifier = mod.my.globalthreat.value
109  
110 --[[
111 Now, most attacks can be handled gracefully by the table. However, for abilities that modify your autoattack,
112 we would prefer to decouple the ability from the autoattack, so we have to handle these cases individually.
113 ]]
114  
115 -- reset me.event
116 me.event.hits = 1
117 me.event.rage = 0
118 me.event.damage = damage
119  
120 -- 3) Handle Autoattack modifying abilities separately
121 if abilityid == "whitedamage" then
122  
123 -- shaman special: check for rockbiter
124 if mod.my.mods.shaman.rockbiter > 0 then
125  
126 -- make a separate event for the rockbiter
127 local weaponspeed = UnitAttackSpeed("player")
128 local rockbiterdps = mod.data.rockbiter[mod.my.mods.shaman.rockbiter]
129  
130 -- note: we are sending the threat value of rockbiter as the damage argument
131 me.specialattack("rockbiter", target, rockbiterdps * weaponspeed, nil, nil)
132  
133 -- the above call to me.specialattack will have overwritten some parts of me.event. Set them back!
134 me.event.damage = damage
135 end
136  
137 -- normal behaviour
138 me.event.threat = damage * threatmodifier
139  
140 -- Special case: Rockbiter Weapon
141 elseif abilityid == "rockbiter" then
142  
143 -- this will only come from a "whitedamage" call to this method (see above)
144 me.event.threat = me.event.damage * threatmodifier
145 me.event.damage = 0
146  
147 -- Special case: Heroic Strike
148 elseif abilityid == "heroicstrike" then
149  
150 local preimpaledamage = damage
151 if iscrit == true then
152 preimpaledamage = damage / (1 + mod.my.mods.warrior.impale)
153 end
154  
155 local addeddamage = mod.my.ability("heroicstrike", "nextattack")
156 local myaveragedamage = me.averagemainhanddamage()
157 local whitedamage = preimpaledamage * (myaveragedamage / (addeddamage + myaveragedamage))
158  
159 -- Now make a separate method call for the autoattack component
160 me.specialattack("whitedamage", target, whitedamage, nil, nil)
161  
162 -- The above method will have overwritten some parts of me.event, so change them back
163 me.event.damage = damage - whitedamage
164 me.event.threat = (me.event.damage + mod.my.ability("heroicstrike", "threat")) * threatmodifier
165 me.event.rage = mod.my.ability("heroicstrike", "rage") + whitedamage / (UnitLevel("player") / 2)
166  
167 -- Special Case: Maul
168 elseif abilityid == "maul" then
169  
170 -- same as heroic strike, but a bit different
171 local presavagefurydamage = damage / (1 + mod.my.mods.druid.savagefury)
172 local addeddamage = mod.my.ability("maul", "nextattack")
173 local myaveragedamage = me.averagemainhanddamage()
174 local whitedamage = presavagefurydamage * (myaveragedamage / (addeddamage + myaveragedamage))
175  
176 -- Now make a separate method call for the autoattack component
177 me.specialattack("whitedamage", target, whitedamage, nil, nil)
178  
179 -- The above method will have overwritten some parts of me.event, so change them back
180 me.event.damage = damage - whitedamage
181 me.event.threat = threatmodifier * (damage * mod.my.ability("maul", "multiplier") - whitedamage)
182 me.event.rage = mod.my.ability("maul", "rage") + whitedamage / (UnitLevel("player") / 2)
183  
184 -- Default Case: all other abilities
185 else
186  
187 -- 1) Check for rage
188 me.event.rage = mod.my.ability(abilityid, "rage")
189  
190 local multiplier = mod.my.ability(abilityid, "multiplier")
191  
192 -- 2) Check for multiplier
193 if multiplier then
194 me.event.threat = me.event.damage * multiplier
195  
196 else
197 me.event.threat = me.event.damage + mod.my.ability(abilityid, "threat")
198 end
199  
200 -- 3) Multiply by global modifiers
201 me.event.threat = me.event.threat * threatmodifier
202  
203 end
204  
205 -- Paladin righteous fury (can affect holy shield)
206 if mod.my.class == "paladin" then
207  
208 -- righteous fury
209 if spellschool == SPELL_SCHOOL1_CAP then -- holy
210 me.event.threat = me.event.threat * mod.my.mods.paladin.righteousfury
211 end
212  
213 -- warlock Nemesis 8/8 (can affect searing pain)
214 elseif mod.my.class == "warlock" then
215  
216 -- Nemesis 8/8
217 if (mod.my.mods.warlock.nemesis == true) and (mod.data.spellmatchesset("Warlock Destruction", abilityid) == true) then
218 me.event.threat = me.event.threat * 0.8
219 end
220 end
221  
222 -- check for >= 0 threat
223 if me.event.threat + mod.table.getraidthreat() < 0 then
224 me.event.threat = - mod.table.getraidthreat()
225 end
226  
227 -- relocalise.
228 if abilityid == "whitedamage" then
229 me.event.name = mod.string.get("threatsource", "whitedamage")
230  
231 else
232 me.event.name = mod.string.get("spell", abilityid)
233 end
234  
235 -- Add to data
236 me.addattacktodata(me.event.name, me.event)
237 me.addattacktodata(mod.string.get("threatsource", "total"), me.event)
238  
239 end
240  
241 -- to work out which part of a next attack ability was from white damage
242 -- used by nextattack abilities like maul and heroic strike.
243 me.averagemainhanddamage = function()
244  
245 local min, max = UnitDamage("player")
246 return (min + max) / 2
247  
248 end
249  
250 --[[
251 me.normalattack(spellname, damage, target, iscrit, spellschool)
252 Handles a damage-causing ability with no special threat properties. We often have to make modifiers for gear or talents here.
253 <spellname> is the name of the ability.
254 <spellid> is the internal name for "special" spells or abilities, i.e. those we have to look out for because there are specific set bonuses or talents that affect them.
255 <damage> is the damage done.
256 <target> is the name of the mob you hit.
257 <iscrit> is a boolean, whether the hit was a critical one. Triggers iff it is the boolean value true.
258 <spellschool> is a string, one of SPELL_SCHOOL1_CAP, SPELL_SCHOOL2_CAP, etc, or possibly nil or "".
259 ]]
260 me.normalattack = function(spellname, spellid, damage, isdot, target, iscrit, spellschool)
261  
262 -- check the attack is directed at the master target. If not, ignore.
263 if mod.boss.targetismaster(target) == nil then
264 return
265 end
266  
267 -- threatmodifier includes global things, like defensive stance, tranquil air totem, rogue passive modifier, etc.
268 local threatmodifier = mod.my.globalthreat.value
269  
270 -- Special threat mod: priest silent resolve (spells only)
271 if mod.my.class == "priest" then
272  
273 -- 1.12: multiplicative threat
274 if mod.isnewwowversion then
275 threatmodifier = threatmodifier * (1.0 + mod.my.mods.priest.silentresolve)
276 else
277 threatmodifier = threatmodifier + mod.my.mods.priest.silentresolve
278 end
279  
280 end
281  
282 -- Special threat modifiers for mages
283 if mod.my.class == "mage" then
284 if spellschool == SPELL_SCHOOL6_CAP then -- arcane
285  
286 -- 1.12 override check
287 if mod.isnewwowversion then
288 threatmodifier = threatmodifier * (1.0 + mod.my.mods.mage.arcanethreat)
289 else
290 threatmodifier = threatmodifier + mod.my.mods.mage.arcanethreat
291 end
292  
293 elseif spellschool == SPELL_SCHOOL4_CAP then -- frost
294  
295 -- 1.12 override check
296 if mod.isnewwowversion then
297 threatmodifier = threatmodifier * (1.0 + mod.my.mods.mage.frostthreat)
298 else
299 threatmodifier = threatmodifier + mod.my.mods.mage.frostthreat
300 end
301  
302 elseif spellschool == SPELL_SCHOOL2_CAP then -- fire
303  
304 -- 1.12 override check
305 if mod.isnewwowversion then
306 threatmodifier = threatmodifier * (1.0 + mod.my.mods.mage.firethreat)
307 else
308 threatmodifier = threatmodifier + mod.my.mods.mage.firethreat
309 end
310 end
311 end
312  
313 -- Default values for me.event:
314 me.event.hits = 1
315 me.event.rage = 0
316 me.event.damage = damage
317 me.event.threat = damage * threatmodifier
318  
319 -- now get the name:
320 if spellid == "dot" then
321  
322 me.event.hits = 0
323 me.event.name = mod.string.get("threatsource", "dot")
324  
325 elseif spellid == "damageshield" then
326 me.event.name = mod.string.get("threatsource", "damageshield")
327  
328 else
329 me.event.name = mod.string.get("threatsource", "special")
330 me.event.threat = damage * threatmodifier
331 end
332  
333 -- Now apply class-specific filters
334  
335 -- warlock
336 if mod.my.class == "warlock" then
337  
338 -- Nemesis 8/8
339 if (mod.my.mods.warlock.nemesis == true) and (mod.data.spellmatchesset("Warlock Destruction", spellid) == true) then
340 me.event.threat = me.event.threat * 0.8
341 end
342  
343 -- Priest
344 elseif mod.my.class == "priest" then
345  
346 -- shadow affinity
347 if spellschool == SPELL_SCHOOL5_CAP then
348 me.event.threat = me.event.threat * mod.my.mods.priest.shadowaffinity
349 end
350  
351 -- holy nova: no threat
352 if spellid == "holynova" then
353 me.event.threat = 0
354 end
355  
356 -- Mage
357 elseif mod.my.class == "mage" then
358  
359 -- netherwind
360 if mod.my.mods.mage.netherwind == true then
361  
362 if (spellid == "frostbolt") or (spellid == "scorch") or (spellid == "fireball") then
363 -- note that this won't trigger off the dot part of fireball, because then spellid will be "dot"
364 me.event.threat = math.max(0, (me.event.threat - 100))
365  
366 elseif spellid == "arcanemissiles" then
367 me.event.threat = math.max(0, (me.event.threat - 20))
368 end
369 end
370  
371 -- Paladin
372 elseif mod.my.class == "paladin" then
373  
374 -- righteous fury
375 if spellschool == SPELL_SCHOOL1_CAP then -- holy
376 me.event.threat = me.event.threat * mod.my.mods.paladin.righteousfury
377 end
378 end
379  
380 -- special: blood siphon no threat vs Hakkar. This may or may not be correct.
381 if spellid == "bloodsiphon" then
382 me.event.threat = 0
383 end
384  
385 -- now add me.event to individual and totals
386 me.addattacktodata(me.event.name, me.event)
387 me.addattacktodata(mod.string.get("threatsource", "total"), me.event)
388  
389 end
390  
391 --[[
392 me.registertaunt()
393 Called when you succesfully casts Taunt or Growl on a mob.
394 <target> is the name of the mob you have taunted.
395 ]]
396 me.taunt = function(target)
397  
398 if mod.out.checktrace("info", me, "taunt") then
399 mod.out.printtrace(string.format("Taunting %s!", target))
400 end
401  
402 --[[
403 OK, new idea. If targettarget has greater threat than you, assume they are the aggro target.
404 ]]
405  
406 if mod.boss.mastertarget and (target ~= mod.boss.mastertarget) then
407 -- you taunted a mob that is not the master target
408 return
409 end
410  
411 -- here, you either taunted the master target, or there is no master target
412 -- check if you are still targetting that mob (likely, if you just taunted it)
413 if UnitName("target") == target then
414  
415 -- Check for tt
416 local targettarget = UnitName("targettarget")
417  
418 if mod.table.raiddata[targettarget] and (mod.table.raiddata[targettarget] > mod.table.getraidthreat()) then
419 -- your current target is targetting another player, and they have more threat than you
420  
421 local gain = mod.table.raiddata[targettarget] - mod.table.getraidthreat()
422  
423 me.event.hits = 1
424 me.event.damage = 0
425 me.event.rage = 0
426 me.event.threat = gain
427 me.event.name = mod.string.get("spell", "taunt")
428  
429 me.addattacktodata(mod.string.get("spell", "taunt"), me.event)
430 me.addattacktodata(mod.string.get("threatsource", "total"), me.event)
431  
432 if mod.out.checktrace("info", me, "taunt") then
433 mod.out.printtrace(string.format("You taunt %s from %s, gaining %d threat.", target, targettarget, gain))
434 end
435  
436 return
437 end
438 end
439  
440 -- If that didn't work, use the old code:
441  
442 if target == mod.boss.mastertarget then
443 local previoustargetthreat = mod.table.raiddata[mod.boss.mttruetarget]
444  
445 if (previoustargetthreat ~= nil) and (previoustargetthreat > mod.table.getraidthreat()) then
446 local tauntgain = previoustargetthreat - mod.table.getraidthreat()
447  
448 me.event.hits = 1
449 me.event.damage = 0
450 me.event.rage = 0
451 me.event.threat = tauntgain
452 me.event.name = mod.string.get("spell", "taunt")
453  
454 me.addattacktodata(mod.string.get("spell", "taunt"), me.event)
455 me.addattacktodata(mod.string.get("threatsource", "total"), me.event)
456  
457 if mod.out.checktrace("info", me, "taunt") then
458 mod.out.printtrace(string.format("You taunt %s from %s, gaining %d threat.", target, mod.boss.mttruetarget or "<nil>", tauntgain))
459 end
460  
461 else
462 if mod.out.checktrace("info", me, "taunt") then
463 mod.out.printtrace(string.format("You taunt %s from %s, but he had a lower threat, so you gain no threat.", target, mod.boss.mttruetarget or "<nil>"))
464 end
465 end
466 end
467  
468 end
469  
470 --[[
471 me.possibleoverheal(spellname, spellid, amount, target)
472 Works out the threat from a heal.
473 <spellname> is the localised name of the spell
474 <spellid> is the mod's internal name for the spell, if it is special (i.e. affected by talents / sets bonuses), otherwise "".
475 <amount> is the healed amount only (no overheal)
476 <target> is the name of the target
477 Called when you heal someone. Deducts the overhealing from the total, then calls the Heal method
478 ]]
479 me.possibleoverheal = function(spellname, spellid, amount, target)
480  
481 -- we can check the target's health, which will be the health before the heal. Then we work out what the
482 -- heal did, and we can calculate overheal.
483  
484 local unit = mod.unit.findunitidfromname(target)
485  
486 if unit == nil then
487 if mod.out.checktrace("info", me, "healtarget") then
488 mod.out.printtrace(string.format("Could not find a UnitID for the name %s.", target))
489 end
490 -- (and assume there was no overheal)
491 else
492 local hpvoid = UnitHealthMax(unit) - UnitHealth(unit)
493 amount = math.min(amount, hpvoid)
494 end
495  
496 me.registerheal(spellname, spellid, amount, target)
497  
498 end
499  
500 --[[
501 me.registerheal(spellname, spellid, amount, target)
502 Works out the threat from a heal.
503 <spellname> is the localised name of the spell
504 <spellid> is the mod's internal name for the spell, if it is special (i.e. affected by talents / sets bonuses), otherwise "".
505 <amount> is the healed amount only (no overheal)
506 <target> is the name of the target
507 ]]
508 me.registerheal = function(spellname, spellid, amount, target)
509  
510 -- in general, don't count heals towards the master target
511 if mod.boss.mastertarget and mod.boss.targetismaster(target) then
512 return
513 end
514  
515 me.event.hits = 1
516 me.event.rage = 0
517 me.event.damage = amount
518  
519 local threatmod = mod.my.globalthreat.value
520  
521 -- Special threat mod: priest silent resolve (spells only)
522 if mod.my.class == "priest" then
523  
524 -- 1.12+ multiplicative threat
525 if mod.isnewwowversion then
526 threatmod = threatmod * (1 + mod.my.mods.priest.silentresolve)
527 else
528 threatmod = threatmod + mod.my.mods.priest.silentresolve
529 end
530 end
531  
532 me.event.threat = amount * threatmod * mod.data.threatconstants.healing
533  
534 -- class-based healing multipliers
535 if mod.my.class == "paladin" then
536 me.event.threat = me.event.threat * mod.my.mods.paladin.healing
537  
538 elseif mod.my.class == "druid" then
539 me.event.threat = me.event.threat * mod.my.mods.druid.subtlety
540  
541 if spellid == "tranquility" then
542 me.event.threat = me.event.threat * mod.my.mods.druid.tranquilitythreat
543 end
544  
545 elseif mod.my.class == "shaman" then
546 me.event.threat = me.event.threat * mod.my.mods.shaman.healing
547  
548 end
549  
550 -- Special: healing abilities which don't cause threat
551 if spellid == "holynova" then
552 me.event.threat = 0
553 elseif spellid == "siphonlife" then
554 me.event.threat = 0
555 elseif spellid == "drainlife" then
556 me.event.threat = 0
557 elseif spellid == "deathcoil" then
558 me.event.threat = 0
559 end
560  
561 me.addattacktodata(mod.string.get("threatsource", "healing"), me.event)
562  
563 me.event.damage = 0
564 me.addattacktodata(mod.string.get("threatsource", "total"), me.event)
565 end
566  
567 --[[
568 me.powergain(amount, powertype)
569 Calculates the threat from gaining energy / mana / rage.
570 <amount> is the amount of power gained.
571 <powertype> is "Mana" or "Rage" or "Energy", but the localised versions.
572 <spellid> is the spell or effect that caused the power gain.
573 ]]
574 me.powergain = function(amount, powertype, spellid)
575  
576 me.event.damage = amount
577 me.event.hits = 1
578 me.event.rage = 0
579  
580 -- 1) Prevent "overheal" for power gain
581 local maxgain = UnitManaMax("player") - UnitMana("player")
582 amount = math.min(maxgain, amount)
583  
584 if powertype == mod.string.get("power", "rage") then
585 me.event.threat = amount * mod.data.threatconstants.ragegain
586  
587 elseif powertype == mod.string.get("power", "energy") then
588 me.event.threat = amount * mod.data.threatconstants.energygain
589  
590 elseif powertype == mod.string.get("power", "mana") then
591 me.event.threat = amount * mod.data.threatconstants.managain
592  
593 else
594 return
595 end
596  
597 -- Special: abilities which don't cause threat
598 if spellid == "lifetap" then
599 me.event.threat = 0
600 end
601  
602 me.addattacktodata(mod.string.get("threatsource", "powergain"), me.event)
603  
604 -- now mod it a bit to work into total better
605 me.event.damage = 0
606 me.event.hits = 0
607  
608 me.addattacktodata(mod.string.get("threatsource", "total"), me.event)
609  
610 end
611  
612 --[[
613 me.addattacktodata(name, data)
614 Once the threat from an attack has been worked, add it.
615 <name> is the category to add the threat to
616 <data> is me.event, i think...
617 Heap Memory will be created when you call this method when out of combat (mostly healing).
618 From the size of the list (two numbers in it), will be 50 bytes tops each time.
619 ]]
620 me.addattacktodata = function(name, data)
621  
622 -- Ignore if charmed
623 if mod.my.states.playercharmed.value == true then
624 return
625 end
626  
627 if name ~= mod.string.get("threatsource", "total") then
628 me.secondlastattack = me.lastattack
629 me.lastattack = data
630  
631 -- Add this to the recent attacks list, if we are not in combat
632 if mod.my.states.incombat.value == false then
633 table.insert(me.recentattacks, {GetTime(), data.threat})
634 end
635 end
636  
637 -- Add a new column to mod.table.mydata, if it does not exist already
638 if mod.table.mydata[name] == nil then
639 mod.table.mydata[name] = mod.table.newdatastruct()
640 end
641  
642 mod.table.mydata[name].hits = mod.table.mydata[name].hits + data.hits
643 mod.table.mydata[name].threat = mod.table.mydata[name].threat + data.threat
644 mod.table.mydata[name].rage = mod.table.mydata[name].rage + data.rage
645 mod.table.mydata[name].damage = mod.table.mydata[name].damage + data.damage
646  
647 end
648  
649  
650 ----------------------------------------------------------
651 -- Handling Sunder --
652 ----------------------------------------------------------
653  
654  
655 --[[
656 klhtu.combat.submitsundercast()
657 When you think you have just cast a sunder, call this method to make sure the addon knows. There's no way to
658 reliably detect a sunder hit, so we assume it has been cast, then look for failure signs (misses, spell errors)
659 It's highly recommended to use klhtm.combat.sunder() or KLHTM_Sunder() instead, because it will make sure
660 that threat is correct when you spam the button, which may not happen otherwise.
661 ]]
662 me.submitsundercast = function()
663  
664 -- check for Master Target
665 if mod.boss.targetismaster(UnitName("target")) == nil then
666 return
667 end
668  
669 me.specialattack("sunder", UnitName("target"), 0)
670 KLHTM_RequestRedraw("self")
671 end
672  
673 -- Call this from a macro, to replace your normal sunder button
674 me.spellbooksunderindex = 1
675  
676 -- Kept for compatibility
677 function KLHTM_Sunder()
678  
679 me.sunder()
680  
681 end
682  
683 --[[
684 me.sunder()
685 Casts Sunder Armor if most checks reveal it is castable, and calls me.sumbitsundercast().
686 This method is safe to be spammed. It will only cast if the global cooldown is off, which means once you try to
687 cast it, you won't try again unless the server says "failed due to <x>". So it's like full good and stuff.
688 ]]
689 me.sunder = function()
690  
691 if mod.my.class ~= "warrior" then
692 return
693 end
694  
695 -- 1) Check if the old position is fine
696 if GetSpellName(me.spellbooksunderindex, "spell") ~= mod.string.get("spell", "sunder") then
697  
698 -- get spell number of spells
699 local spelltabs = GetNumSpellTabs()
700 local numspells = 0
701 local i
702 local temp
703  
704 for i = 1, spelltabs do
705 _, _, _, temp = GetSpellTabInfo(i)
706 numspells = numspells + temp
707 end
708  
709 -- 2) locate sunder armor in the spell book
710 for i = 1, numspells do
711 if GetSpellName(me.spellbooksunderindex + i, "spell") == mod.string.get("spell", "sunder") then
712 me.spellbooksunderindex = me.spellbooksunderindex + i
713 break
714  
715 elseif i == numspells then
716 if mod.out.checktrace("warning", me, "sunder") then
717 mod.out.printtrace("Can't find sunder in your spellbook!")
718 end
719 return -- can't find sunder
720 end
721 end
722 end
723  
724 -- Now we've found sunder. Check the cooldown
725 if GetSpellCooldown(me.spellbooksunderindex, "spell") ~= 0 then
726 return
727 end
728  
729 -- Test for target
730 if UnitCanAttack("player", "target") == nil then
731 return
732 end
733  
734 -- Cast
735 CastSpellByName(mod.string.get("spell", "sunder") .. "()")
736 me.submitsundercast()
737  
738 end
739  
740 --[[
741 me.retractsundercast()
742 Called when an attempted cast of Sunder Armor was found to have failed. i.e. we received a message like
743 "Your sunder armor failed: not enough rage" in the combat log.
744 ]]
745 me.retractsundercast = function()
746  
747 -- 1) check for master target
748 if mod.boss.targetismaster(UnitName("target")) == nil then
749 return
750 end
751  
752 me.event.hits = -1
753 me.event.damage = 0
754 me.event.rage = - mod.my.ability("sunder", "rage")
755  
756 local threatmodifier = mod.my.globalthreat.value
757 me.event.threat = - mod.my.ability("sunder", "threat") * threatmodifier
758  
759 me.addattacktodata(mod.string.get("spell", "sunder"), me.event)
760 me.addattacktodata(mod.string.get("threatsource", "total"), me.event)
761  
762 KLHTM_RequestRedraw("self")
763 end
764  
765 --[[
766 This code hooks UseAction, intercepts the user casting Sunder Armor, and runs me.sunder() instead.
767 ]]
768  
769 me.saveduseaction = UseAction
770  
771 me.newuseaction = function(actionindex, x, y)
772  
773 -- Check Sunder
774 if (mod.my.class == "warrior") and actionindex and (GetActionText(actionindex) == nil) and (GetActionTexture(actionindex) == "Interface\\Icons\\Ability_Warrior_Sunder") then
775  
776 if GetActionCooldown(actionindex) > 0 then
777 if mod.out.checktrace("info", me, "sunder") then
778 mod.out.printtrace("Preventing Sunder cast due to cooldown!")
779 end
780 return
781  
782 else
783 -- Now check we have a decent target. Otherwise press "tab" - this is what the UI does anyway
784 if UnitCanAttack("player", "target") ~= 1 then
785  
786 if mod.out.checktrace("info", me, "sunder") then
787 mod.out.printtrace("Preventing Sunder cast due to invalid target!")
788 end
789  
790 TargetNearestEnemy()
791 return
792 end
793  
794 me.submitsundercast()
795 -- At the bottom of this method the sunder will be cast
796 end
797  
798 end
799  
800 -- Call the original function
801 me.saveduseaction(actionindex, x, y)
802  
803 end
804  
805 UseAction = me.newuseaction