vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 
2 local mod = klhtm
3 local me = { }
4 mod.my = me
5  
6 --[[
7 KTM_My.lua
8  
9 This file stores data that specifically relates to the current player, and methods that change these data sets.
10 Most of the data sets are updated by polling, that is frequently recalculating their values.
11  
12 me.states - a set of flags, e.g. "player in combat" / "afk" / "feigning death"
13 me.spellranks - list of ranks the player has for the important threat causing spells / abilities
14 me.mods - currently active modifiers to threat / abilities from talents, gear, buffs, etc
15 me.globalthreat - buffs that affect all threat the player generates
16 ]]
17  
18 ----------------------------------------------------------------------------------------
19  
20 me.feigndeathresisttime = 0 -- return value of GetTime()
21 me.lastfadevalue = 0
22  
23 -- mod.my.class is the unlocalised lower case representation. e.g. "warrior", "rogue", no matter what locale you are in.
24 _, me.class = UnitClass("player")
25 me.class = string.lower(me.class)
26  
27 -----------------------------------------
28 -- Special Methods from Core.lua --
29 -----------------------------------------
30  
31 me.parserset = { }
32  
33 -- onupdate
34 me.onload = function()
35  
36 -- make our parser
37 local parserdata
38  
39 for _, parserdata in me.parserconstructor do
40 mod.regex.addparsestring(me.parserset, parserdata[1], parserdata[2], parserdata[3])
41 end
42  
43 end
44  
45 me.parserconstructor =
46 {
47 {"buffstart", "AURAADDEDSELFHELPFUL", "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"}, -- "You gain %s."
48 {"debuffstart", "AURAADDEDSELFHARMFUL", "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"}, -- "You are afflicated by %s."
49 {"weaponbuff", "ITEMENCHANTMENTADDSELFSELF", "CHAT_MSG_SPELL_ITEM_ENCHANTMENTS"}, -- "You cast %s on your %s."
50 {"buffend", "AURAREMOVEDSELF", "CHAT_MSG_SPELL_AURA_GONE_SELF"}, -- "%s fades from you."
51 }
52  
53 -- The "UI_ERROR_MESSAGE" event is used to detect Feign Death resists.
54 me.myevents = { "UI_ERROR_MESSAGE", "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE", "CHAT_MSG_SPELL_AURA_GONE_SELF",
55 "CHAT_MSG_SPELL_ITEM_ENCHANTMENTS", "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"}
56  
57 me.onevent = function()
58  
59 local ability
60  
61 -- 1) Check for Feign Death resisted
62 if event == "UI_ERROR_MESSAGE" then
63  
64 if arg1 == ERR_FEIGN_DEATH_RESISTED then
65 if mod.out.checktrace("info", me, "feigndeath") then
66 mod.out.printtrace("Feign Death Resist message intercepted.")
67 end
68 me.feigndeathresisttime = GetTime()
69 end
70  
71 return
72 end
73  
74 -- 2) Check for various buffs
75 local output = mod.regex.parse(me.parserset, arg1, event)
76  
77 if output.hit == nil then
78 return
79 end
80  
81 me.parserstagetwo[output.parser.identifier](output.final[1], output.final[2], output.final[3], output.final[4])
82  
83 end
84  
85 --[[
86 This is the continuation of the combat log parsing from me.onevent above. From that method, we know the identifier of the event that has been parsed, and all the arguments that go with it. We put all the identifiers into a table whose value is a method to handle that event.
87 ]]
88 me.parserstagetwo =
89 {
90 ["debuffstart"] = function(buff)
91 if buff == mod.string.get("boss", "spell", "burningadrenaline") then
92 me.setstate("burningadrenaline", true)
93 end
94  
95 end,
96  
97 ["buffend"] = function(buff)
98 if buff == mod.string.get("boss", "spell", "burningadrenaline") then
99 me.setstate("burningadrenaline", false)
100  
101 elseif buff == mod.string.get("spell", "arcaneshroud") then
102 me.setstate("arcaneshroud", false)
103  
104 elseif buff == mod.string.get("spell", "fade") then
105 -- retract fade
106 mod.table.raidthreatoffset = mod.table.raidthreatoffset + me.lastfadevalue
107 me.setstate("fade", false)
108 end
109  
110 end,
111  
112 ["weaponbuff"] = function(buff, weapon)
113 -- only care about rockbiter == shaman
114 if me.class ~= "shaman" then
115 return
116 end
117  
118 if string.find(buff, mod.string.get("spell", "rockbiter")) then
119  
120 -- get rank
121 local rank
122 _, _, rank = string.find(buff, ".-(%d+).-")
123  
124 -- set rank in mods
125 me.mods.shaman.rockbiter = tonumber(rank)
126  
127 else
128  
129 -- added an enchant that was not rockbiter
130 me.mods.shaman.rockbiter = 0
131 end
132  
133 end,
134  
135 ["buffstart"] = function(buff)
136 if buff == mod.string.get("spell", "arcaneshroud") then -- fetish of the sand reaver activation
137 mod.my.setstate("arcaneshroud", true)
138  
139 elseif buff == mod.string.get("spell", "vanish") then
140 mod.table.resetraidthreat()
141  
142 elseif buff == mod.string.get("spell", "fade") then
143  
144 local threat = mod.my.ability("fade", "threat")
145 mod.table.raidthreatoffset = mod.table.raidthreatoffset - threat
146 mod.my.lastfadevalue = threat
147  
148 mod.my.setstate("fade", true)
149 end
150 end,
151 }
152  
153  
154 me.lastupdatetime = 0.0 -- return value of GetTime()
155 me.longupdateinterval = 1.0 -- at least this time in seconds will pass between long updates
156  
157 me.lastmegaupdatetime = 0.0
158 me.megaupdateinterval = 10.0
159  
160 --[[
161 Special onupdate() function, called from Core.lua
162 me.states - frequent update
163 me.spellranks - long time update
164 me.mods - long time update
165 me.globalthreat - frequent update
166 ]]
167 me.onupdate = function()
168  
169 -- short updates
170 me.redostates()
171  
172 -- check for long updates
173 local timenow = GetTime()
174  
175 -- long updates
176 if timenow > me.lastupdatetime + me.longupdateinterval then
177 me.lastupdatetime = timenow
178 me.redomods()
179 me.redoglobalthreat()
180 end
181  
182 -- mega updates
183 if timenow > me.lastmegaupdatetime + me.megaupdateinterval then
184 me.lastmegaupdatetime = timenow
185 me.redospellranks()
186 end
187  
188 -- check status of rockbiter enchant for Shaman, i.e. whether it has run out.
189 if GetWeaponEnchantInfo() == nil then
190 me.mods.shaman.rockbiter = 0
191 end
192 end
193  
194 ----------------------------------------------------------------------------------------
195  
196 ----------------------------
197 -- General Methods --
198 ----------------------------
199  
200  
201 --[[
202 mod.my.ability(name, value)
203 Returns the current value for some property of your spells and abilities.
204 <name> is the mod's internal name for the ability.
205 <value> is the name of the property, e.g. "threat", "multiplier", "rage". These are the indexes of values in
206 the mod.data.spells structure.
207 --> multiplier: return nil if nil
208 --> rage: return 0 if nil
209 --> threat / nextattack: throw error if nil
210  
211 This method will use me.spellranks to get the data that applies to your specific rank of the ability. Then
212 it will apply any modifiers from me.mods that are appropriate.
213 ]]
214 me.ability = function(spellid, parameter)
215  
216 if (spellid == nil) or (mod.data.spells[spellid] == nil) then
217 if mod.out.checktrace("error", me, "ability") then
218 mod.out.printtrace(string.format("No ability |cffffff00%s|r found.", tostring(spellid)))
219 end
220 return 0
221 end
222  
223 local value
224 local data = mod.data.spells[spellid]
225  
226 -- "rage" and "multiplier" parameters are easy, since they
227 if parameter == "rage" then
228 if data.rage == nil then
229 return 0
230 else
231 return data.rage
232 end
233  
234 elseif parameter == "multiplier" then
235 return data.multiplier -- may be nil
236 end
237  
238 -- to get here, <parameter> is either "threat" or "nextattack"
239  
240 -- Item abilities only have one value for threat, no ranks.
241 if data.class == "item" then
242 return data.threat
243 end
244  
245 -- Now check the spell has a known rank
246 local spellrank = me.spellranks[spellid]
247  
248 if spellrank == nil then
249 if mod.out.checktrace("error", me, "ability") then
250 mod.out.printtrace(string.format("No spell rank defined for |cffffff00%s.", tostring(spellid)))
251 end
252  
253 return 0
254 end
255  
256 -- Check there is a value in data for our parameter (should always be)
257 value = data[spellrank][parameter]
258  
259 if value == nil then
260 if mod.out.checktrace("error", me, "ability") then
261 mod.out.printtrace(string.format("No value of |cffffff00%s for rank |cffffff00%s of |cffffff00%s.", tostring(parameter), tostring(spellrankg), tostring(spellid)))
262 end
263  
264 return 0
265 end
266  
267 -- to get here, there were no errors. we got a value. Now we have to look for class mods that would affect it.
268 if spellid == "sunder" then
269 if parameter == "rage" then
270 value = value + me.mods.warrior.sundercost
271 elseif parameter == "threat" then
272 value = value * me.mods.warrior.sunderthreat
273 end
274  
275 elseif spellid == "heroicstrike" then
276 if parameter == "rage" then
277 value = value + me.mods.warrior.heroicstrikecost
278 end
279  
280 elseif spellid == "feint" then
281 if parameter == "threat" then
282 value = value * me.mods.rogue.feintthreat
283 end
284  
285 elseif spellid == "maul" then
286 if parameter == "rage" then
287 value = value + me.mods.druid.ferocity
288 end
289  
290 elseif spellid == "swipe" then
291 if parameter == "rage" then
292 value = value + me.mods.druid.ferocity
293 end
294 end
295  
296 return value
297 end
298  
299 --[[
300 mod.my.testthreat()
301 Print out your threat properties for debug purposes.
302 ]]
303 me.testthreat = function()
304  
305 -- 1) Print out spell ranks
306 local key
307 local value
308  
309 for key, value in me.spellranks do
310 mod.out.print(string.format(mod.string.get("print", "data", "abilityrank"), key, value))
311 end
312  
313 -- 2) Print out global threat
314 mod.out.print(string.format(mod.string.get("print", "data", "globalthreat"), me.globalthreat.value))
315 for key, value in me.globalthreat.modifiers do
316 if value.isactive == false then
317 break
318 end
319  
320 mod.out.print(string.format(mod.string.get("print", "data", "globalthreatmod"), value.reason, value.value))
321 end
322  
323 -- 3) Print out threat for specific abilities
324 if me.class == "priest" then
325 mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), mod.string.get("talent", "silentresolve"), 1.0 + me.mods.priest.silentresolve))
326 mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), SPELL_SCHOOL5_CAP, me.mods.priest.shadowaffinity))
327  
328 elseif me.class == "warlock" then
329 mod.out.print(string.format(mod.string.get("print", "data", "setactive"), mod.string.get("sets", "nemesis"), 8, mod.out.booltostring(me.mods.warlock.nemesis)))
330  
331 elseif me.class == "mage" then
332 mod.out.print(string.format(mod.string.get("print", "data", "setactive"), mod.string.get("sets", "netherwind"), 3, mod.out.booltostring(me.mods.mage.netherwind)))
333 mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), SPELL_SCHOOL6_CAP, 1.0 + me.mods.mage.arcanethreat))
334 mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), SPELL_SCHOOL4_CAP, 1.0 + me.mods.mage.frostthreat))
335 mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), SPELL_SCHOOL2_CAP, 1.0 + me.mods.mage.firethreat))
336  
337 elseif me.class == "paladin" then
338 mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), mod.string.get("print", "data", "holyspell"), me.mods.paladin.righteousfury))
339 mod.out.print(string.format(mod.string.get("print", "data", "healing"), mod.data.threatconstants.healing * me.mods.paladin.healing))
340  
341 elseif me.class == "warrior" then
342 mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), mod.string.get("spell", "sunder"), me.mods.warrior.sunderthreat))
343  
344 elseif me.class == "druid" then
345 mod.out.print(string.format(mod.string.get("print", "data", "multiplier"), UnitClass("player"), mod.string.get("talent", "tranquility"), me.mods.druid.tranquilitythreat))
346  
347 elseif me.class == "shaman" then
348 mod.out.print(string.format(mod.string.get("print", "data", "healing"), mod.data.threatconstants.healing * me.mods.shaman.healing))
349  
350 if me.mods.shaman.rockbiter > 0 then
351 mod.out.print(string.format(mod.string.get("print", "data", "rockbiter"), me.mods.shaman.rockbiter, UnitAttackSpeed("player") * mod.data.rockbiter[me.mods.shaman.rockbiter]))
352 end
353 end
354 end
355  
356  
357 ----------------------------------------------------------------------------------------
358  
359 ----------------------------
360 -- States --
361 ----------------------------
362  
363 --[[
364 each state is represented by a key-value pair. The key is a string, which is a description of the state,
365 e.g. "incombat", "afk". The value is a list with properties
366 ["value"], boolean, whether the state is on or off
367 ["lastchange"], return value of GetTime() (seconds + decimal), when the value last changed
368 ["duration"], OPTIONAL, known duration of the state. This is used to turn the state off just in case
369 the normal mechanism to detect it does not work.
370  
371 To set a state's value, call the method mod.my.setstate(<string name>, <boolean value>). If this causes the
372 state to change, the method will return non-nil, and an event KLHTM_STATECHANGE_<NAME> will be raised.
373 ]]
374 me.states =
375 {
376 ["feigndeath"] =
377 {
378 ["value"] = false,
379 ["lastchange"] = 0,
380 },
381 ["arcaneshroud"] = -- from Fetish of the Sandreaver
382 {
383 ["value"] = false,
384 ["lastchange"] = 0,
385 ["duration"] = 20.0
386 },
387 ["incombat"] =
388 {
389 ["value"] = false,
390 ["lastchange"] = 0,
391 },
392 ["playercharmed"] = -- whether you have been mind controlled
393 {
394 ["value"] = false,
395 ["lastchange"] = 0,
396 },
397 ["burningadrenaline"] =
398 {
399 ["value"] = false,
400 ["lastchange"] = 0,
401 ["duration"] = 20.0,
402 },
403 ["afk"] =
404 {
405 ["value"] = false,
406 ["lastchange"] = 0,
407 },
408 ["fade"] =
409 {
410 ["value"] = false,
411 ["lastchange"] = 0,
412 ["duration"] = 10.0,
413 },
414 }
415  
416  
417 --[[
418 mod.my.setstate(state, value)
419 Sets the value of one of the state variables above.
420 <state> is a string, a key to the me.states list, e.g. "afk", "incombat", etc.
421 <value> is a boolean, true or false.
422 Return: true if the set represents a change, otherwise nil
423 ]]
424 me.setstate = function(state, value)
425  
426 -- verify <state>
427 if me.states[state] == nil then
428 if mod.out.checktrace("error", me, "state") then
429 mod.out.printtrace(string.format("There is no state |cffffff00%s|r.", tostring(state)))
430 end
431 return
432 end
433  
434 -- update
435 if me.states[state].value ~= value then
436 me.states[state].value = value
437 me.states[state].lastchange = GetTime()
438  
439 return true
440 end
441  
442 end
443  
444 --[[
445 me.redostates()
446 Checks for changes to state variables that we poll for (e.g. "incombat" / "ischarmed").
447 Also checks for states that have run out, but whose end events were not detected (e.g. burning adrenaline). This
448 last should not be relied upon though.
449 ]]
450 me.redostates = function()
451  
452 -- hunter: check for FD
453 if mod.my.class == "hunter" then
454  
455 -- Check for feign death debuff
456 local currentfd = mod.data.isbuffpresent("Interface\\Icons\\Ability_Rogue_FeignDeath")
457  
458 if me.setstate("feigndeath", currentfd) and (currentfd == true) then
459  
460 -- first check for resist.
461 if math.abs(me.feigndeathresisttime - GetTime()) < 1.0 then
462 --resisted. Nothing happens
463  
464 if mod.out.checktrace("info", me, "feigndeath") then
465 mod.out.printtrace("feigndeathdebug", "Feign Death was resisted!")
466 end
467  
468 else -- wipe threat
469  
470 mod.table.resetraidthreat()
471  
472 end
473  
474 end
475 end
476  
477 -- update charmed state
478 if UnitIsCharmed("player") == 1 then
479 me.setstate("playercharmed", true)
480 else
481 me.setstate("playercharmed", false)
482 end
483  
484 -- update combat state
485 if UnitAffectingCombat("player") == 1 then
486  
487 if me.setstate("incombat", true) then
488  
489 -- player has just joined combat
490 if mod.out.checktrace("info", me, "incombat") then
491 mod.out.printtrace("You entered combat.")
492 end
493  
494 mod.table.resetraidthreat()
495  
496 local key
497 local value
498  
499 for key, value in mod.combat.recentattacks do
500 mod.table.raidthreatoffset = mod.table.raidthreatoffset + value[2]
501 end
502 end
503  
504 else
505 if (me.states.playercharmed.value == true) or (GetTime() - me.states.playercharmed.lastchange < 2.0) then
506 me.setstate("incombat", true) -- should not be a change
507 else
508 if me.setstate("incombat", false) then
509 if mod.out.checktrace("info", me, "incombat") then
510 mod.out.printtrace("You left combat.")
511 end
512 me.lastfadevalue = 0 -- stop fade
513 end
514 end
515 end
516  
517 -- update states with a "duration" parameter. They are all designed to deactivate on their own, but e.g.
518 -- burning adrenaline isn't working, so we put in this mechanism as a backup
519 local state
520 local data
521  
522 for state, data in me.states do
523 if data.duration and (data.value == true) and (GetTime() > data.lastchange + data.duration + 1.0) then
524 me.setstate(state, false)
525  
526 if mod.out.checktrace("info", me, "state") then
527 mod.out.printtrace(string.format("Deactivated the state %s because it had passed its duration.", state))
528 end
529 end
530 end
531 end
532  
533 ----------------------------------------------------------------------------------------
534  
535 ----------------------------
536 -- Spell Ranks --
537 ----------------------------
538  
539 --[[
540 A collection of key-value pairs. The key is a string, the name of a spell / ability, e.g. "Sunder Armor". It
541 is localised. The value is an integer, the rank of that spell you have.
542 The only spells recorded are those which are cause or remove threat by known amounts, i.e. Sunder Armor,
543 Revenge, Maul, Feint, etc.
544 We want to know the what rank of the spell the player has, because most abilities have different numbers for
545 different spell ranks. e.g. Heroic Strike does more damage and threat for each new rank. Some players will have the
546 extra rank from AQ20, some will not.
547 These ranks will probably not change over the course of a session, but we poll just in case.
548 ]]
549 me.spellranks = { }
550  
551 --[[
552 me.redospellranks()
553 Redetermines the ranks of all special threat abilities you have.
554 ]]
555 me.redospellranks = function()
556  
557 local index = 0
558 local name, rankstring, rank, spellid
559 local rankpattern = mod.string.get("misc", "spellrank") -- e.g. "Rank %d" in english.
560  
561 while true do
562 index = index + 1
563 name, rankstring = GetSpellName(index, "spell")
564  
565 if name == nil then
566 break
567 end
568  
569 -- get the internal spell ID
570 spellid = mod.string.unlocalise("spell", name)
571  
572 if spellid and mod.data.spells[spellid] then
573 rank = 0
574  
575 _, _, rank = string.find(rankstring, rankpattern)
576 if rank then
577 me.spellranks[spellid] = rank
578 end
579 end
580 end
581  
582 end
583  
584  
585 ----------------------------------------------------------------------------------------
586  
587 ---------------------------------------
588 -- Threat And Ability Modifers --
589 ---------------------------------------
590  
591 me.mods =
592 {
593 ["warrior"] =
594 {
595 ["defiance"] = 0.0, -- modifier to global threat. e.g. "+0.15" for 5 points.
596 ["sunderthreat"] = 1.0, -- multiplier. e.g. "1.15" for 8/8 Might.
597 ["sundercost"] = 0.0, -- modifier of rage cost. e.g. "-3" for 3/3 improved sunder talent.
598 ["heroicstrikecost"] = 0.0,-- modifier of rage cost. e.g. "-3" for 3/3 improved heroic strike talent.
599 ["impale"] = 0.0 -- critical strike bonus damage for abilities. e.g. "0.2" for 2/2 impale talent.
600 },
601 ["rogue"] =
602 {
603 ["feintthreat"] = 1.0, -- multiplier. e.g. "1.25" for 5/8 Bloodfang.
604 },
605 ["druid"] =
606 {
607 ["feralinstinct"] = 0.0, -- defiance for druids.
608 ["savagefury"] = 1.0, -- multiplier to damage for druid abilities
609 ["subtlety"] = 1.0, -- multiplier to healing threat. e.g. "0.8" for all talents.
610 ["ferocity"] = 0.0, -- modifier to rage cost of maul and swipe. e.g. "-5" for all talents.
611 ["tranquilitythreat"] = 1.0-- multiplier, from Improved Tranquility Talent
612 },
613 ["mage"] =
614 {
615 ["arcanist"] = false, -- 8 piece bonus
616 ["netherwind"] = false, -- 3 piece
617 ["arcanethreat"] = 0.0, -- almost a modifier to global threat (spells only) e.g. "-0.4" for 2/2 talent points.
618 ["frostthreat"] = 0.0, -- almost a modifier to global threat (spells only) e.g. "-0.3" for 3/3 talent points.
619 ["firethreat"] = 0.0, -- almost a modifier to global threat (spells only) e.g. "-0.3" for 2/2 talent points.
620 },
621 ["warlock"] =
622 {
623 ["masterdemo"] = 0.0, -- modifier to global threat. e.g. "-0.2" for imp out, 5/5.
624 ["nemesis"] = false, -- 8 piece bonus
625 },
626 ["priest"] =
627 {
628 ["silentresolve"] = 0.0, -- almost a modifier to global threat (spells only) e.g. "-0.2" for fully talented.
629 ["shadowaffinity"] = 1.0, -- multiplier for shadow damage. e.g. "0.8".
630 },
631 ["paladin"] =
632 {
633 ["righteousfury"] = 1.0, -- multiplier for threat from holy damage / abilities.
634 ["healing"] = 0.5, -- this is actually constant, but really fits best in this variable.
635 },
636 ["shaman"] =
637 {
638 ["rockbiter"] = 0, -- current rank of rockbiter that is active, 0 for none.
639 ["healing"] = 1.0, -- multiplier. e.g. 0.85 for 3/3 Healing Grace.
640 }
641 }
642  
643 me.redomods = function()
644  
645 -- talent / gear searching
646 local info
647 local rank
648 local key
649 local value
650  
651 -- warrior
652 if mod.my.class == "warrior" then
653  
654 -- might 8/8
655 if mod.data.getsetpieces("might") == 8 then
656 me.mods.warrior.sunderthreat = 1.15
657 else
658 me.mods.warrior.sunderthreat = 1.0
659 end
660  
661 -- improved sunder armor
662 rank = mod.data.gettalentrank("sunder")
663 me.mods.warrior.sundercost = -rank
664  
665 -- improved heroic strike
666 rank = mod.data.gettalentrank("heroicstrike")
667 me.mods.warrior.heroicstrikecost = -rank
668  
669 -- defiance
670 rank = mod.data.gettalentrank("defiance")
671 me.mods.warrior.defiance = 0.03 * rank
672  
673 -- impale
674 rank = mod.data.gettalentrank("impale")
675 me.mods.warrior.impale = 0.05 * rank
676  
677 -- rogue
678 elseif mod.my.class == "rogue" then
679  
680 if mod.data.getsetpieces("bloodfang") > 4 then
681 me.mods.rogue.feint = 1.25
682 else
683 me.mods.rogue.feint = 1.0
684 end
685  
686 -- priest
687 elseif mod.my.class == "priest" then
688  
689 -- silent resolve
690 rank = mod.data.gettalentrank("silentresolve")
691 me.mods.priest.silentresolve = -0.04 * rank
692  
693 -- shadow affinity
694 rank = mod.data.gettalentrank("shadowaffinity")
695 me.mods.priest.shadowaffinity = 1 - rank * 0.25 / 3
696  
697 -- druid
698 elseif mod.my.class == "druid" then
699  
700 -- subtlety
701 rank = mod.data.gettalentrank("druidsubtlety")
702 me.mods.druid.subtlety = 1 - 0.04 * rank
703  
704 -- feral instinct
705 rank = mod.data.gettalentrank("feralinstinct")
706 me.mods.druid.feralinstinct = 0.03 * rank
707  
708 -- ferocity
709 rank = mod.data.gettalentrank("ferocity")
710 me.mods.druid.ferocity = -rank
711  
712 -- savage fury
713 rank = mod.data.gettalentrank("savagefury")
714 me.mods.druid.savagefury = rank * 0.1
715  
716 -- improved tranquility
717 rank = mod.data.gettalentrank("tranquility")
718 me.mods.druid.tranquilitythreat = 1.0 - rank * 0.4
719  
720 -- mage
721 elseif mod.my.class == "mage" then
722  
723 -- arcanist 8 piece
724 if mod.data.getsetpieces("arcanist") == 8 then
725 me.mods.mage.arcanist = true
726 else
727 me.mods.mage.arcanist = false
728 end
729  
730 -- arcane subtelty
731 rank = mod.data.gettalentrank("arcanesubtlety")
732 me.mods.mage.arcanethreat = 0.0 - 0.2 * rank
733  
734 -- frost channeling
735 rank = mod.data.gettalentrank("frostchanneling")
736 me.mods.mage.frostthreat = 0.0 - 0.1 * rank
737  
738 -- burning soul
739 rank = mod.data.gettalentrank("burningsoul")
740 me.mods.mage.firethreat = 0.0 - 0.15 * rank
741  
742 -- netherwind 3 piece
743 if mod.data.getsetpieces("netherwind") > 2 then
744 me.mods.mage.netherwind = true
745 else
746 me.mods.mage.netherwind = false
747 end
748  
749 -- warlock
750 elseif mod.my.class == "warlock" then
751 -- master demonologist
752 rank = mod.data.gettalentrank("masterdemonologist")
753  
754 -- check for imp
755 if UnitCreatureFamily("pet") == mod.string.get("misc", "imp") then
756 me.mods.warlock.masterdemo = -0.04 * rank
757 else
758 me.mods.warlock.masterdemo = 0
759 end
760  
761 -- nemesis 8 piece
762 if mod.data.getsetpieces("nemesis") == 8 then
763 me.mods.warlock.nemesis = true
764 else
765 me.mods.warlock.nemesis = false
766 end
767  
768 -- shaman
769 elseif mod.my.class == "shaman" then
770  
771 -- healing grace
772 rank = mod.data.gettalentrank("healinggrace")
773 me.mods.shaman.healing = 1.0 - 0.05 * rank
774  
775 -- paladin
776 elseif mod.my.class == "paladin" then
777  
778 local multi = 1.0
779  
780 -- righteous fury buff:
781 if mod.data.isbuffpresent("Interface\\Icons\\Spell_Holy_SealOfFury") then
782 multi = multi + 0.6
783  
784 -- talents
785 rank = mod.data.gettalentrank("righteousfury")
786 multi = multi + (0.5 * rank / 3)
787 end
788  
789 me.mods.paladin.righteousfury = multi
790 end
791  
792 end
793  
794  
795 ----------------------------------------------------------------------------------------
796  
797 ----------------------------
798 -- Global Threat Mods --
799 ----------------------------
800  
801 --[[
802 There are certain threat modifiers that are applied to all threat done, and that stack additively.
803 For instance with the "Blessing of Salvation" buff, all your threat is reduced by 30%. Wit the Arcanist
804 8 piece bonus, threat is reduced by 15%, but these bonuses add directly, so that with both bonuses, your
805 threat is reduced by 45%.
806 mod.my.globalthreat provides a list of all your global bonuses that are currently active. The data is
807 designed to be easily printed to the user.
808 The data is updated frequently, because the modifiers can come on and off at any time. This means once
809 every OnUpdate, roughly 40 times per second. As a result we take pains to avoid the creation of new lists,
810 which would otherwise generate excess heap memory.
811 me.globalthreat.modifiers is an ARRAY, i.e. a list keyed by [1], [2], [3], etc. Each item of the array the value
812 is a list with properties
813 ["value"] - the fractional reduction in threat. -30% from Blessing of Salvation would be "-0.3".
814 ["reason"] - a description of the effect, for the user's benefit. e.g. "Blessing of Salvation".
815 ["isactive"] - if false, this value and all further values should be ignored. Since we don't want to keep
816 recreating lists in a frequently called procedure, when a threat modifier is no longer active,
817 <isactive> is set to false, and that value is ignored. A newly added buff will overwrite the
818 other data fields, but set <isactive> to true.
819 There is one final part me.globalthreat, a key-value pair. The key is "value" and the value is your total
820 modifier, i.e. the sum of all the modifiers. For a normal character with no buffs / talents / items, this
821 value would be 1.0, e.g.
822 me.globalthreat =
823 {
824 ["value"] = 1.0,
825 ["modifiers"] =
826 {
827 [1] =
828 {
829 ["value"] = 1.0,
830 ["reason"] = "Base Value",
831 ["isactive"] = true,
832 }
833 }
834 }
835 ]]
836 me.globalthreat =
837 {
838 ["value"] = 0.0,
839 ["modifiers"] = { }
840 }
841  
842 --[[
843 me.modifyglobalthreat(value, reason)
844 Adds a global threat modifier to me.globalthreat.
845 <value> and <reason> are as defined in me.globalthreat (see comments above).
846 This method will first look for a value in the me.globalthreat array that is labeled inactive (unused memory)
847 and write the values there. Otherwise it will create a new value and append it to the array.
848 Heap memory creation occurs a maximum of n times, where n is the largest number of +- threat buffs you get.
849 This is on the order of 5, and for the whole running time, so negligable.
850 ]]
851 me.modifyglobalthreat = function(value, reason)
852  
853 -- update the total / sum
854  
855 -- 1.12: multiplicative threat
856 if mod.isnewwowversion then
857 me.globalthreat.value = me.globalthreat.value * (1.0 + value)
858 value = 1.0 + value
859 else
860 me.globalthreat.value = me.globalthreat.value + value
861 end
862  
863 -- look for an unused array position
864 local x
865 local count = table.getn(me.globalthreat.modifiers)
866  
867 for x = 1, count do
868  
869 if me.globalthreat.modifiers[x].isactive == false then
870  
871 -- found an inactive array index. Activate it and write values
872 me.globalthreat.modifiers[x].value = value
873 me.globalthreat.modifiers[x].reason = reason
874 me.globalthreat.modifiers[x].isactive = true
875 return
876 end
877 end
878  
879 -- all the slots in the array are being used. Add a new value to the end
880 table.insert(me.globalthreat.modifiers, {["value"] = value, ["reason"] = reason, ["isactive"] = true})
881  
882 end
883  
884 --[[
885 me.redoglobalthreat()
886 Recalculates all your global threat modifiers.
887 ]]
888 me.redoglobalthreat = function()
889  
890 -- 1) reset
891 local x
892 for x = 1, table.getn(me.globalthreat.modifiers) do
893 me.globalthreat.modifiers[x].isactive = false
894 end
895  
896 -- 1.12 change: all threat is multiplicative
897 if mod.isnewwowversion then
898 me.globalthreat.value = 1.0
899 me.modifyglobalthreat(0.0, mod.string.get("threatmod", "basevalue"))
900  
901 else
902 me.globalthreat.value = 0.0
903 me.modifyglobalthreat(1.0, mod.string.get("threatmod", "basevalue"))
904 end
905  
906 -- 2) rebuild
907  
908 -- Tranquil Air
909 if mod.data.isbuffpresent("Interface\\Icons\\Spell_Nature_Brilliance") == true then
910 me.modifyglobalthreat(-0.2, mod.string.get("threatmod", "tranquilair"))
911 end
912  
913 -- Blessing of Salvation
914 if mod.data.isbuffpresent("Interface\\Icons\\Spell_Holy_SealOfSalvation") == true then
915 me.modifyglobalthreat(-0.3, mod.string.get("threatmod", "salvation"))
916  
917 elseif mod.data.isbuffpresent("Interface\\Icons\\Spell_Holy_GreaterBlessingofSalvation") == true then
918 me.modifyglobalthreat(-0.3, mod.string.get("threatmod", "salvation"))
919 end
920  
921 -- Burning Adrenaline
922 if me.states["burningadrenaline"].value == true then
923 me.modifyglobalthreat(-0.75, mod.string.get("boss", "spell", "burningadrenaline"))
924 end
925  
926 -- Fetish of the Sand Reaver
927 if me.states["arcaneshroud"].value == true then
928 me.modifyglobalthreat(-0.7, mod.string.get("spell", "arcaneshroud"))
929 end
930  
931 local linkstring
932 local enchant
933  
934 -- +2% threat enchant to gloves
935 linkstring = GetInventoryItemLink("player", 10) or ""
936 _, _, enchant = string.find(linkstring, ".-item:%d+:(%d+).*")
937  
938 if enchant == "2613" then
939 me.modifyglobalthreat(0.02, mod.string.get("threatmod", "glovethreatenchant"))
940 end
941  
942 -- -2% threat enchant to back
943 linkstring = GetInventoryItemLink("player", 15) or ""
944 _, _, enchant = string.find(linkstring, ".-item:%d+:(%d+).*")
945  
946 if enchant == "2621" then
947 me.modifyglobalthreat(-0.02, mod.string.get("threatmod", "backthreatenchant"))
948 end
949  
950 local stance
951  
952 -- Warrior
953 if mod.my.class == "warrior" then
954 stance = me.getstanceindex()
955  
956 if stance == 1 then
957 me.modifyglobalthreat(-0.2, mod.string.get("threatmod", "battlestance"))
958  
959 elseif stance == 2 then
960 me.modifyglobalthreat(0.3, mod.string.get("threatmod", "defensivestance"))
961 me.modifyglobalthreat(me.mods.warrior.defiance, mod.string.get("talent", "defiance"))
962  
963 elseif stance == 3 then
964 me.modifyglobalthreat(-0.2, mod.string.get("threatmod", "berserkerstance"))
965 end
966  
967 -- Druid Stances
968 elseif mod.my.class == "druid" then
969 stance = me.getstanceindex()
970  
971 if stance == 1 then
972 me.modifyglobalthreat(0.3, mod.string.get("threatmod", "bearform"))
973 me.modifyglobalthreat(me.mods.druid.feralinstinct, mod.string.get("talent", "feralinstinct"))
974  
975 elseif stance == 3 then
976 me.modifyglobalthreat(-0.2, mod.string.get("threatmod", "catform"))
977  
978 end
979  
980 -- Rogue
981 elseif mod.my.class == "rogue" then
982 me.modifyglobalthreat(-0.2, UnitClass("player"))
983  
984 -- Arcanist
985 elseif (mod.my.class == "mage") and (me.mods.mage.arcanist == true) then
986 me.modifyglobalthreat(-0.15, mod.string.get("sets", "arcanist") .. " 8/8")
987  
988 -- Warlock
989 elseif (mod.my.class == "warlock") and (me.mods.warlock.masterdemo ~= 0) then
990 me.modifyglobalthreat(me.mods.warlock.masterdemo, mod.string.get("talent", "masterdemonologist"))
991 end
992 end
993  
994  
995 --[[
996 me.getstanceindex()
997 For Druids and Warriors, returns an integer saying which stance you are in.
998 ]]
999 me.getstanceindex = function()
1000  
1001 local x = 0
1002 local isactive
1003 local texture
1004  
1005 while true do
1006 x = x + 1
1007 texture, _, isactive = GetShapeshiftFormInfo(x)
1008  
1009 if texture == nil then
1010 return 0
1011 end
1012  
1013 if isactive == 1 then
1014 return x
1015 end
1016 end
1017  
1018 end