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