vanilla-wow-addons – Blame information for rev 1
?pathlinks?
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 |