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.boss = me |
||
5 | |||
6 | --[[ |
||
7 | KTM_Bosses.lua |
||
8 | |||
9 | This module contains all the code for special boss encounters, determining who has aggro, etc. |
||
10 | |||
11 | The old functions from the old KLHTMTargetting.lua are a bit scrappy and due for a major buff in R17, as well |
||
12 | as the entire rest of this module, with lots more boss encounters being added. |
||
13 | ]] |
||
14 | |||
15 | me.mastertarget = nil |
||
16 | me.ismtworldboss = false |
||
17 | |||
18 | me.isspellreportingactive = false |
||
19 | me.istrackingspells = false |
||
20 | me.bosstarget = "" |
||
21 | |||
22 | -- me.onload() - called by Core.lua. |
||
23 | me.onload = function() |
||
24 | |||
25 | -- Let's create our parser! |
||
26 | me.createparser() |
||
27 | |||
28 | end |
||
29 | |||
30 | me.myevents = { "CHAT_MSG_MONSTER_EMOTE", "CHAT_MSG_MONSTER_YELL", "CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE", "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS", "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE", "CHAT_MSG_COMBAT_HOSTILE_DEATH", } |
||
31 | |||
32 | me.onevent = function() |
||
33 | |||
34 | if event == "CHAT_MSG_MONSTER_EMOTE" then |
||
35 | |||
36 | if string.find(arg1, mod.string.get("boss", "speech", "razorphase2")) then |
||
37 | |||
38 | -- clear threat when phase 2 starts |
||
39 | mod.table.resetraidthreat() |
||
40 | |||
41 | -- set the master target to Razorgore, but only if a localised version of him exists. |
||
42 | local bossname = mod.string.get("boss", "name", "razorgore") |
||
43 | |||
44 | if mod.string.unlocalise("boss", "name", bossname) then |
||
45 | me.automastertarget(bossname) |
||
46 | end |
||
47 | |||
48 | return |
||
49 | end |
||
50 | |||
51 | elseif event == "CHAT_MSG_MONSTER_YELL" then |
||
52 | |||
53 | -- Nef Phase 2 |
||
54 | if string.find(arg1, mod.string.get("boss", "speech", "nefphase2")) then |
||
55 | |||
56 | -- reset threat in phase 2 |
||
57 | mod.table.resetraidthreat() |
||
58 | |||
59 | -- boss name is given by the arg2 |
||
60 | me.automastertarget(arg2) |
||
61 | |||
62 | return |
||
63 | end |
||
64 | |||
65 | -- ZG Tiger boss phase 2 |
||
66 | if string.find(arg1, mod.string.get("boss", "speech", "thekalphase2")) then |
||
67 | |||
68 | -- reset threat in phase 2 |
||
69 | mod.table.resetraidthreat() |
||
70 | |||
71 | -- boss name is given by the arg2 |
||
72 | me.automastertarget(arg2) |
||
73 | |||
74 | return |
||
75 | end |
||
76 | |||
77 | -- Rajaxx attacks |
||
78 | if string.find(arg1, mod.string.get("boss", "speech", "rajaxxfinal")) then |
||
79 | |||
80 | -- reset threat when he finally attacks |
||
81 | mod.table.resetraidthreat() |
||
82 | |||
83 | -- boss name is given by arg2 |
||
84 | me.automastertarget(arg2) |
||
85 | |||
86 | return |
||
87 | end |
||
88 | |||
89 | -- Azuregos Port |
||
90 | if string.find(arg1, mod.string.get("boss", "speech", "azuregosport")) then |
||
91 | |||
92 | -- 1) Find Azuregos |
||
93 | local bossfound = false |
||
94 | |||
95 | for x = 1, 40 do |
||
96 | |||
97 | if UnitClassification("raid" .. x .. "target") == "worldboss" then |
||
98 | if CheckInteractDistance("raid" .. x .. "target", 4) then |
||
99 | mod.table.resetraidthreat() |
||
100 | end |
||
101 | |||
102 | bossfound = true |
||
103 | break |
||
104 | end |
||
105 | end |
||
106 | |||
107 | -- couldn't find anyone targetting Azuregos. Better reset just to be sure. |
||
108 | if bossfound == false then |
||
109 | mod.table.resetraidthreat() |
||
110 | end |
||
111 | |||
112 | return |
||
113 | end |
||
114 | |||
115 | elseif event == "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS" then |
||
116 | |||
117 | -- 1) Scan for casting pattern |
||
118 | local output = mod.regex.parse(me.parserset, arg1, event) |
||
119 | |||
120 | if (output.hit == nil) or (output.parser.identifier ~= "mobbuffgain") then |
||
121 | return |
||
122 | end |
||
123 | |||
124 | -- 2) Get Boss, Spell |
||
125 | local boss, spell = output.final[1], output.final[2] |
||
126 | |||
127 | -- noth blink |
||
128 | if spell == mod.string.get("boss", "spell", "nothblink") then |
||
129 | |||
130 | -- notify the raid, if this event isn't on cooldown |
||
131 | if GetTime() < me.bossevents.nothblink.lastoccurence + me.bossevents.nothblink.cooldown then |
||
132 | -- on cooldown. don't send |
||
133 | else |
||
134 | mod.net.sendevent("nothblink") |
||
135 | end |
||
136 | end |
||
137 | |||
138 | elseif event == "CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE" then |
||
139 | |||
140 | -- 1) Scan for casting pattern |
||
141 | local output = mod.regex.parse(me.parserset, arg1, event) |
||
142 | |||
143 | if (output.hit == nil) or (output.parser.identifier ~= "mobspellcast") then |
||
144 | return |
||
145 | end |
||
146 | |||
147 | -- 2) Get Boss, Spell |
||
148 | local boss, spell = output.final[1], output.final[2] |
||
149 | |||
150 | -- twin teleport |
||
151 | if spell == mod.string.get("boss", "spell", "twinteleport") then |
||
152 | |||
153 | -- notify the raid, if this event isn't on cooldown |
||
154 | if GetTime() < me.bossevents.twinteleport.lastoccurence + me.bossevents.twinteleport.cooldown then |
||
155 | -- on cooldown. don't send |
||
156 | else |
||
157 | mod.net.sendevent("twinteleport") |
||
158 | end |
||
159 | |||
160 | -- gate of shazzrah |
||
161 | elseif spell == mod.string.get("boss", "spell", "shazzrahgate") then |
||
162 | |||
163 | -- notify the raid, if this event isn't on cooldown |
||
164 | if GetTime() < me.bossevents.shazzrahgate.lastoccurence + me.bossevents.shazzrahgate.cooldown then |
||
165 | -- on cooldown. don't send |
||
166 | else |
||
167 | mod.net.sendevent("shazzrahgate") |
||
168 | end |
||
169 | end |
||
170 | |||
171 | elseif event == "CHAT_MSG_COMBAT_HOSTILE_DEATH" then |
||
172 | |||
173 | -- 1) Scan for mob death |
||
174 | local output = mod.regex.parse(me.parserset, arg1, event) |
||
175 | |||
176 | if (output.hit == nil) or (output.parser.identifier ~= "mobdeath") then |
||
177 | return |
||
178 | end |
||
179 | |||
180 | local mobname = output.final[1] |
||
181 | |||
182 | if (mobname == me.mastertarget) and (me.ismtworldboss == true) and (mod.net.lastmtsender == UnitName("player")) and mod.unit.isplayerofficer(UnitName("player")) then |
||
183 | |||
184 | mod.net.clearmastertarget() |
||
185 | me.ismtworldboss = false |
||
186 | end |
||
187 | |||
188 | else |
||
189 | me.parsebossattack(arg1, event) |
||
190 | end |
||
191 | |||
192 | end |
||
193 | |||
194 | --[[ |
||
195 | me.onupdate is called by Core.lua. It currently has 3 different parts that are unrelated. |
||
196 | ]] |
||
197 | me.onupdate = function() |
||
198 | |||
199 | local key, value, key2 |
||
200 | |||
201 | -- 1) if we are out of combat, reset the ticks on all boss abilities that have them |
||
202 | if UnitAffectingCombat("player") == nil then |
||
203 | |||
204 | for key, value in me.tickcounters do |
||
205 | for key2 in value do |
||
206 | value[key2] = 0 |
||
207 | end |
||
208 | end |
||
209 | end |
||
210 | |||
211 | -- 2) clear out all old event reports - one report but no confirmations for 1.0 seconds. |
||
212 | local timenow = GetTime() |
||
213 | |||
214 | for key, value in me.bossevents do |
||
215 | if (value.reporter ~= "") and (timenow > value.reporttime + 1.0) then |
||
216 | |||
217 | -- debug |
||
218 | if mod.out.checktrace("warning", me, "event") then |
||
219 | mod.out.printtrace(string.format("The event %s has not been confirmed. It was reported by %s.", key, value.reporter)) |
||
220 | end |
||
221 | |||
222 | -- remove the report |
||
223 | value.reporter = "" |
||
224 | end |
||
225 | end |
||
226 | |||
227 | -- 3) spellreporting: check for target changes |
||
228 | if (me.isspellreportingactive == true) and (me.istrackingspells == true) and me.mastertarget then |
||
229 | |||
230 | -- 1) find mt |
||
231 | local x, newtarget |
||
232 | |||
233 | for x = 1, 40 do |
||
234 | name = UnitName("raid" .. x .. "target") |
||
235 | if name == me.mastertarget then |
||
236 | |||
237 | -- get target^2 |
||
238 | newtarget = UnitName("raid" .. x .. "targettarget") |
||
239 | |||
240 | if newtarget == nil then |
||
241 | newtarget = "<none>" |
||
242 | end |
||
243 | |||
244 | break |
||
245 | end |
||
246 | end |
||
247 | |||
248 | -- couldn't find the boss? |
||
249 | if newtarget == nil then |
||
250 | newtarget = "<unknown>" |
||
251 | end |
||
252 | |||
253 | -- report! |
||
254 | if newtarget ~= me.bosstarget then |
||
255 | |||
256 | -- find the threat of the old target |
||
257 | local oldthreat = mod.table.raiddata[me.bosstarget] |
||
258 | if oldthreat == nil then |
||
259 | oldthreat = "?" |
||
260 | end |
||
261 | |||
262 | -- threat of the boss' new target |
||
263 | local newthreat = mod.table.raiddata[me.bosstarget] |
||
264 | if newthreat == nil then |
||
265 | newthreat = "?" |
||
266 | end |
||
267 | |||
268 | -- print |
||
269 | mod.out.print(string.format(mod.string.get("print", "boss", "bosstargetchange"), me.mastertarget, me.bosstarget, oldthreat, newtarget, newthreat)) |
||
270 | |||
271 | -- update bosstarget |
||
272 | me.bosstarget = newtarget |
||
273 | end |
||
274 | end |
||
275 | |||
276 | -- 4) Check triggers |
||
277 | me.checktriggers() |
||
278 | |||
279 | end |
||
280 | |||
281 | --[[ |
||
282 | ------------------------------------------------------------------------------------------------ |
||
283 | Boss Events - Sending and Receiving |
||
284 | ------------------------------------------------------------------------------------------------ |
||
285 | |||
286 | Boss Events are when a mob changes his threat against everyone after taking some action. Some players may be out of (combat log) range of the boss action, so we have nearby players report these special events to the rest of the raid. |
||
287 | There is a potential for abuse if someone in the raid group sends false boss event reports, which could make the raid group incorrectly reset their threat. To counter this we require two people to report the same event within a small time interval for it to be activated. |
||
288 | For each event in <me.bossevents>, we keep track of the person who first reported it, and the time they reported it. If the trace key "boss.fireevent" is enabled, the mod will print out who first reported the event and who confirmed it. |
||
289 | Insertion: players in the raid report events in the network channel, and <me.reportevent(...)> is called from the <netin> module. |
||
290 | Maintenance: no OnUpdate maintenance necessary. |
||
291 | ]] |
||
292 | |||
293 | me.bossevents = { } |
||
294 | |||
295 | --[[ |
||
296 | me.addevent(eventid, cooldown) |
||
297 | Defines a new event in <me.bossevents>. This is just a helper method to create <me.bossevents>. Called at file load. |
||
298 | <eventid> is a localisation key in the "boss"-"spell" set. |
||
299 | <cooldown> is the minimum time between casts, extreme lower bound. Want it large enough to avoid spams. 1 sec would probably do. |
||
300 | ]] |
||
301 | me.addevent = function(eventid, cooldown) |
||
302 | |||
303 | me.bossevents[eventid] = |
||
304 | { |
||
305 | ["cooldown"] = cooldown, |
||
306 | lastoccurence = 0, -- GetTime() |
||
307 | reporter = "", |
||
308 | reporttime = 0, -- GetTime() |
||
309 | ["eventid"] = eventid, |
||
310 | } |
||
311 | |||
312 | end |
||
313 | |||
314 | -- define all possible events. These methods are called at file read time. |
||
315 | me.addevent("shazzrahgate", 5.0) |
||
316 | me.addevent("twinteleport", 5.0) |
||
317 | me.addevent("wrathofragnaros", 5.0) |
||
318 | me.addevent("nothblink", 5.0) |
||
319 | |||
320 | --[[ |
||
321 | mod.boss.reportevent(eventid, player) |
||
322 | Called when someone in the raid reports a boss event. |
||
323 | <eventid> is the internal name of the event. |
||
324 | <player> is the name of the player who reported it. |
||
325 | ]] |
||
326 | me.reportevent = function(eventid, player) |
||
327 | |||
328 | local eventdata = me.bossevents[eventid] |
||
329 | local timenow = GetTime() |
||
330 | |||
331 | -- ignore if the event is cooling down |
||
332 | if timenow < eventdata.lastoccurence + eventdata.cooldown then |
||
333 | return |
||
334 | end |
||
335 | |||
336 | -- has this been reported recently? If so it is now confirmed and we can run it. |
||
337 | if (eventdata.reporter ~= "") and (eventdata.reporter ~= player) then |
||
338 | me.fireevent(eventdata, player) |
||
339 | |||
340 | -- always trust reports from yourself |
||
341 | elseif player == UnitName("player") then |
||
342 | me.fireevent(eventdata, player) |
||
343 | |||
344 | -- some player reports a new event. wait for confirmation |
||
345 | else |
||
346 | eventdata.reporter = player |
||
347 | eventdata.reporttime = timenow |
||
348 | end |
||
349 | |||
350 | end |
||
351 | |||
352 | --[[ |
||
353 | me.fireevent(eventdata, player) |
||
354 | Run when an event is confirmed. Does whatever the event does. |
||
355 | <eventdata> is an structure in <me.bossevents> |
||
356 | <player> is the name of the player who confirmed the event |
||
357 | ]] |
||
358 | me.fireevent = function(eventdata, player) |
||
359 | |||
360 | -- debug |
||
361 | if mod.out.checktrace("info", me, "event") then |
||
362 | mod.out.printtrace(string.format("The event |cffffff00%s|r has occured. It was reported by %s and confirmed by %s.", eventdata.eventid, eventdata.reporter, player)) |
||
363 | end |
||
364 | |||
365 | -- first reset the event's timers |
||
366 | eventdata.lastoccurence = GetTime() |
||
367 | eventdata.reporter = "" |
||
368 | |||
369 | -- now actually do the event |
||
370 | if eventdata.eventid == "shazzrahgate" then |
||
371 | mod.table.resetraidthreat() |
||
372 | |||
373 | elseif eventdata.eventid == "twinteleport" then |
||
374 | mod.table.resetraidthreat() |
||
375 | |||
376 | -- activate the proximity aggro detection trigger |
||
377 | me.starttrigger("twinemps") |
||
378 | |||
379 | elseif eventdata.eventid == "wrathofragnaros" then |
||
380 | mod.table.resetraidthreat() |
||
381 | |||
382 | elseif eventdata.eventid == "nothblink" then |
||
383 | mod.table.resetraidthreat() |
||
384 | end |
||
385 | |||
386 | end |
||
387 | |||
388 | --[[ |
||
389 | ------------------------------------------------------------------------------------------------ |
||
390 | Triggers - Hard To Detect Events That Require Polling |
||
391 | ------------------------------------------------------------------------------------------------ |
||
392 | |||
393 | Some events have no easily defined actions such as a combat log event, and must be checked for periodically instead. |
||
394 | For example in the Twin Emperors encounter, after a teleport the closest person to each emperor is given a moderate amount of threat. The only way to see who received the threat is to see who the emperors target. However, after the teleport they become stunned and have no target, so we have to wait until the stun period has ended. So we make a trigger to periodically check them for new targets. |
||
395 | Each trigger has these properties: |
||
396 | <isactive> boolean, whether the mod is checking the trigger |
||
397 | <startdelay> time in seconds after the trigger is activated that the mod will start checking it. |
||
398 | <timeout> time in seconds after the trigger has started that the mod should give up on it |
||
399 | <mystarttime> when the most recent activation of the trigger occured. |
||
400 | |||
401 | The names and basic properties of triggers are defined in the variable <me.triggers>. This is a key-value list where the key is the internal name of the trigger, and the value is a structure with the variables described above. |
||
402 | To activate a trigger, call the <me.starttrigger(trigger)> function with the internal name of the trigger. |
||
403 | The code that will run each time a trigger is polled is contained in the variable <me.triggerfunctions>. This is a key-value list, where the key is the internal name and the value is the function that is run. |
||
404 | ]] |
||
405 | |||
406 | me.triggers = |
||
407 | { |
||
408 | twinemps = |
||
409 | { |
||
410 | isactive = false, |
||
411 | startdelay = 0.5, |
||
412 | timeout = 5.0, |
||
413 | mystarttime = 0, |
||
414 | data = 0, |
||
415 | }, |
||
416 | autotarget = |
||
417 | { |
||
418 | isactive = false, |
||
419 | startdelay = 1.0, |
||
420 | timeout = 300.0, |
||
421 | mystarttime = 0, |
||
422 | data = 0, |
||
423 | } |
||
424 | } |
||
425 | |||
426 | --[[ |
||
427 | me.starttrigger(trigger) |
||
428 | Activates a trigger. The mod will start periodically checking for it. |
||
429 | <trigger> is the mod's internal identifier of the trigger, and matches a key to me.triggers. |
||
430 | ]] |
||
431 | me.starttrigger = function(trigger) |
||
432 | |||
433 | -- debug check for trigger being defined. We should generalise this, since is happens in a flew different places in the mod. i.e. "badidentifierargument" |
||
434 | -- maybe also some kind of flood control to stop error messages spamming onupdate. |
||
435 | |||
436 | if (trigger == nil) or (me.triggers[trigger] == nil) then |
||
437 | |||
438 | -- report error |
||
439 | if mod.out.checktrace("error", me, "trigger") then |
||
440 | mod.out.printtrace(string.format("There is no trigger |cffffff00%s|r.", trigger or "<nil>")) |
||
441 | end |
||
442 | |||
443 | return |
||
444 | end |
||
445 | |||
446 | local triggerdata = me.triggers[trigger] |
||
447 | triggerdata.isactive = true |
||
448 | triggerdata.mystarttime = GetTime() + triggerdata.startdelay |
||
449 | triggerdata.data = 0 |
||
450 | |||
451 | -- debug |
||
452 | if mod.out.checktrace("info", me, "trigger") then |
||
453 | mod.out.printtrace(string.format("The |cffffff00%s|r trigger has been activated.", trigger)) |
||
454 | end |
||
455 | |||
456 | end |
||
457 | |||
458 | --[[ |
||
459 | This variable gives the code that runs when an active trigger is checked. The keys are the internal names of triggers, that match keys in <me.triggers>. |
||
460 | The values are functions. The functions should return non-nil if the trigger is to be deactivated. |
||
461 | ]] |
||
462 | me.triggerfunctions = |
||
463 | { |
||
464 | --[[ |
||
465 | We want to find out if we are being targetting by one of the emps. To do this we find the emperors by scanning the targets of everyone in the raid group. Then once we have an emps's target, we check whether that is us. |
||
466 | It might occur that one emps' target is known but the other is not (not sure if this could happen). In this case the trigger should not end; we should keep checking until we know both targets. |
||
467 | However, if one of the emps is targetting us, we can instantly give ourself threat and exit. |
||
468 | The threat gained is set at 2000. This isn't confirmed, and is instead a bit of a guess. |
||
469 | ]] |
||
470 | twinemps = function(triggerdata) |
||
471 | |||
472 | local x, name, firstbossname, unitid |
||
473 | local bosshits = 0 |
||
474 | local bosstargets = 0 |
||
475 | |||
476 | -- loop through everyone in the raid |
||
477 | for x = 1, 40 do |
||
478 | |||
479 | unitid = "raid" .. x .. "target" |
||
480 | if UnitExists(unitid) and (UnitClassification(unitid) == "worldboss") then |
||
481 | |||
482 | -- we've found an emperor. check if we've seen him before |
||
483 | name = UnitName(unitid) |
||
484 | |||
485 | if name ~= firstbossname then |
||
486 | bosshits = bosshits + 1 |
||
487 | |||
488 | -- if this is the first boss we've seen, put his name up |
||
489 | if bosshits == 1 then |
||
490 | firstbossname = name |
||
491 | end |
||
492 | |||
493 | -- now find the player the boss is targetting |
||
494 | unitid = unitid .. "target" |
||
495 | |||
496 | if UnitExists(unitid) then |
||
497 | bosstargets = bosstargets + 1 |
||
498 | |||
499 | if UnitIsUnit("player", unitid) then |
||
500 | -- an emp is targetting us. give us a bit of threat. |
||
501 | |||
502 | mod.combat.event.hits = 1 |
||
503 | mod.combat.event.threat = 2000 |
||
504 | mod.combat.event.damage = 0 |
||
505 | mod.combat.event.rage = 0 |
||
506 | |||
507 | mod.combat.addattacktodata(mod.string.get("threatsource", "threatwipe"), mod.combat.event) |
||
508 | |||
509 | -- clear hits for total column |
||
510 | mod.combat.event.hits = 0 |
||
511 | mod.combat.addattacktodata(mod.string.get("threatsource", "total"), mod.combat.event) |
||
512 | |||
513 | -- if an emperor is targetting us, he will be the only one, and we have all the information we need, so we want the trigger to deactivate |
||
514 | bosstargets = 2 |
||
515 | bosshits = 2 |
||
516 | end |
||
517 | end |
||
518 | |||
519 | -- if we have found 2 bosses now, there's no need to do more searching |
||
520 | if bosshits == 2 then |
||
521 | break |
||
522 | end |
||
523 | end |
||
524 | end |
||
525 | end |
||
526 | |||
527 | -- don't give up on the trigger until we have found both boss targets on one loop |
||
528 | if bosstargets == 2 then |
||
529 | return true |
||
530 | end |
||
531 | end, |
||
532 | |||
533 | --[[ |
||
534 | Autotarget trigger runs when you run the command "/ktm boss autoatarget". When you next target a world boss, you will set the target and clear the meter. |
||
535 | ]] |
||
536 | autotarget = function(triggerdata) |
||
537 | |||
538 | if UnitExists("target") and (UnitClassification("target") == "worldboss") then |
||
539 | |||
540 | -- found a target. now only activate if we've been targetting him for a while |
||
541 | if triggerdata.data == 0 then |
||
542 | triggerdata.data = GetTime() |
||
543 | return |
||
544 | |||
545 | else |
||
546 | -- 500 ms minimum. |
||
547 | if GetTime() < triggerdata.data + 0.5 then |
||
548 | return |
||
549 | end |
||
550 | end |
||
551 | |||
552 | -- found a target. Activate |
||
553 | if me.mastertarget == UnitName("target") then |
||
554 | |||
555 | -- someone has already set the master target to this mob. In this case don't do anything. |
||
556 | mod.out.print(string.format(mod.string.get("print", "boss", "autotargetabort"), UnitName("target"))) |
||
557 | |||
558 | else |
||
559 | mod.net.clearraidthreat() |
||
560 | mod.net.sendmastertarget() |
||
561 | end |
||
562 | |||
563 | return true |
||
564 | end |
||
565 | |||
566 | end |
||
567 | } |
||
568 | |||
569 | --[[ |
||
570 | me.checktriggers() |
||
571 | Loops through all possible triggers, checking for active ones and running them if need be. This is called in the OnUpdate() method. |
||
572 | ]] |
||
573 | me.checktriggers = function() |
||
574 | |||
575 | local key, data |
||
576 | local timenow = GetTime() |
||
577 | |||
578 | for key, data in me.triggers do |
||
579 | |||
580 | -- ignore inactive triggers |
||
581 | if data.isactive == true then |
||
582 | |||
583 | -- stop the trigger if it has timed out |
||
584 | if timenow > data.mystarttime + data.timeout then |
||
585 | |||
586 | data.isactive = false |
||
587 | |||
588 | -- debug |
||
589 | if mod.out.checktrace("warning", me, "trigger") then |
||
590 | mod.out.printtrace(string.format("The trigger |cffffff00%s|r timed out.", key)) |
||
591 | end |
||
592 | |||
593 | -- don't process the trigger if the start delay is not over |
||
594 | elseif timenow < data.mystarttime then |
||
595 | -- (do nothing) |
||
596 | |||
597 | else |
||
598 | -- ok, run a trigger check |
||
599 | if me.triggerfunctions[key](data) then |
||
600 | data.isactive = false |
||
601 | end |
||
602 | end |
||
603 | end |
||
604 | end |
||
605 | end |
||
606 | |||
607 | --[[ |
||
608 | ------------------------------------------------------------------------------------------------ |
||
609 | Parsing the Combat Log to Detect Boss Special Attacks and Spells |
||
610 | ------------------------------------------------------------------------------------------------ |
||
611 | ]] |
||
612 | |||
613 | -- me.parserset = { } -- defined in me.createparser |
||
614 | |||
615 | --[[ |
||
616 | me.createparser() |
||
617 | Called from me.onload() on startup. Creates the parser engine from the constructor. |
||
618 | ]] |
||
619 | me.createparser = function() |
||
620 | |||
621 | me.parserset = { } |
||
622 | |||
623 | local parserdata |
||
624 | |||
625 | for _, parserdata in me.parserconstructor do |
||
626 | mod.regex.addparsestring(me.parserset, parserdata[1], parserdata[2], parserdata[3]) |
||
627 | end |
||
628 | |||
629 | end |
||
630 | |||
631 | -- This describes all the combat log lines we are checking for |
||
632 | me.parserconstructor = |
||
633 | { |
||
634 | -- this is for school spells or debuffs |
||
635 | {"magicresist", "SPELLRESISTOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s was resisted." |
||
636 | |||
637 | -- these two are for school spells only |
||
638 | {"spellhit", "SPELLLOGSCHOOLOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s hits you for %d %s damage." |
||
639 | {"spellhit", "SPELLLOGCRITSCHOOLOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s crits you for %d %s damage." |
||
640 | |||
641 | -- spellboth is for abilities or school spells |
||
642 | {"attackabsorb", "SPELLLOGABSORBOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "You absorb %s's %s." |
||
643 | |||
644 | -- ability hit / miss only works for physical spells. |
||
645 | {"abilityhit", "SPELLLOGOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s hits you for %d." |
||
646 | {"abilityhit", "SPELLLOGCRITOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s hits you for %d." |
||
647 | {"abilityhit", "SPELLBLOCKEDOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s was blocked." |
||
648 | {"abilitymiss", "SPELLDODGEDOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s was dodged." |
||
649 | {"abilitymiss", "SPELLPARRIEDOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s was parried." |
||
650 | {"abilitymiss", "SPELLMISSOTHERSELF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"}, -- "%s's %s misses you." |
||
651 | |||
652 | {"debuffstart", "AURAADDEDSELFHARMFUL", "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"}, -- "You are afflicated by %s." |
||
653 | |||
654 | {"mobspellcast", "SPELLCASTGOOTHER", "CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE"}, -- "%s casts %s." |
||
655 | {"mobbuffgain", "AURAADDEDOTHERHELPFUL", "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS"}, -- "%s gains %s." |
||
656 | |||
657 | {"mobdeath", "UNITDIESOTHER", "CHAT_MSG_COMBAT_HOSTILE_DEATH"}, -- "%s dies." |
||
658 | } |
||
659 | |||
660 | me.tickcounters = { } |
||
661 | |||
662 | --[[ |
||
663 | me.parsebossattack(message, event) |
||
664 | |||
665 | Handles a combat log line that describes a boss's attack or spell against the player. |
||
666 | --> Stage one is to parse the message to find which formatting pattern the message matches, e.g. "magicresist" or |
||
667 | "spellhit" etc, or none (then just exit). |
||
668 | --> Stage two is to fill in <me.action>, whch descibes the important parts of the attack, using the formatting patter and the arguments captured by the pattern. |
||
669 | --> Then we check whether this attack is actually a threat modifying attack. For this to be the case, there would be a localisation string whose value is the attack name, and there would be an entry in <me.bossattacks> with the same key as the localisation key. |
||
670 | --> Next we identify the ability w.r.t. the mob. Does the ability only come from one mob, and if so is the mob who just attacked us the right one? This involves a check in the next level of <me.bossattacks>. |
||
671 | --> Now we know the specific attack performed against us, and we have to work out whether it triggered. If the ability does not trigger on a miss (e.g. Knock Away), we won't do anything. If the ability only triggers after a number of ticks (Time Lapse), it will only trigger if the correct number of ticks has passed. |
||
672 | --> If it triggers, we just change our threat by the right amount, then report the threat change in the <combat> and <table> modules. |
||
673 | |||
674 | <message> is the combat log line. |
||
675 | <event> is the chat message event <message> was received on. |
||
676 | Returns: nothing. |
||
677 | ]] |
||
678 | me.parsebossattack = function(message, event) |
||
679 | |||
680 | -- stage 1: regex |
||
681 | local output = mod.regex.parse(me.parserset, message, event) |
||
682 | |||
683 | if output.hit == nil then |
||
684 | return |
||
685 | end |
||
686 | |||
687 | -- interrupt: wrath of ragnaros |
||
688 | if output.final[2] == mod.string.get("boss", "spell", "wrathofragnaros") then |
||
689 | |||
690 | -- notify the raid, if this event isn't on cooldown |
||
691 | if GetTime() < me.bossevents.wrathofragnaros.lastoccurence + me.bossevents.wrathofragnaros.cooldown then |
||
692 | -- on cooldown. don't send |
||
693 | else |
||
694 | mod.net.sendevent("wrathofragnaros") |
||
695 | end |
||
696 | end |
||
697 | |||
698 | -- Set the mob and ability (always arg1 and arg2, except for debuffgain) |
||
699 | if output.parser.identifier == "debuffstart" then |
||
700 | me.resetaction("", output.final[1]) |
||
701 | else |
||
702 | me.resetaction(output.final[1], output.final[2]) |
||
703 | end |
||
704 | |||
705 | -- set the spell and hit types |
||
706 | local description = me.attackdescription[output.parser.identifier] |
||
707 | |||
708 | if description.ishit then me.action.ishit = true end |
||
709 | if description.isspell then me.action.isspell = true end |
||
710 | if description.isdebuff then me.action.isdebuff = true end |
||
711 | if description.isphysical then me.action.isphysical = true end |
||
712 | |||
713 | -- Now, is the attack known? |
||
714 | local spellid = mod.string.unlocalise("boss", "spell", me.action.ability) |
||
715 | if (spellid == nil) or (me.bossattacks[spellid] == nil) then |
||
716 | return |
||
717 | end |
||
718 | |||
719 | -- Check for a mob match |
||
720 | local spelldata |
||
721 | |||
722 | local mobid = mod.string.unlocalise("boss", "name", me.action.mobname) |
||
723 | |||
724 | if mobid and me.bossattacks[spellid][mobid] then |
||
725 | -- there is a specific version of this spell for this particular mob |
||
726 | spelldata = me.bossattacks[spellid][mobid] |
||
727 | |||
728 | elseif me.bossattacks[spellid].default == nil then |
||
729 | -- this mob does not match any of the mobs that have the ability |
||
730 | return |
||
731 | |||
732 | else |
||
733 | spelldata = me.bossattacks[spellid].default |
||
734 | end |
||
735 | |||
736 | -- Now process the spell |
||
737 | |||
738 | -- 1) Does the ability activate on a miss? |
||
739 | if (me.action.ishit == false) and (spelldata.effectonmiss == false) then |
||
740 | |||
741 | -- ability will not activate |
||
742 | if mod.out.checktrace("info", me, "attack") then |
||
743 | mod.out.printtrace(string.format("%s's attack %s did not activate because it missed.", me.action.mobname, me.action.ability)) |
||
744 | end |
||
745 | |||
746 | -- spell reporting |
||
747 | if me.isspellreportingactive == true then |
||
748 | mod.net.reportspelleffect(me.action.ability, me.action.mobname, "miss") |
||
749 | end |
||
750 | |||
751 | return |
||
752 | end |
||
753 | |||
754 | -- 2) Check number of ticks |
||
755 | local mytickdata |
||
756 | |||
757 | if spelldata.ticks ~= 1 then |
||
758 | |||
759 | -- create a list if none exists yet |
||
760 | if me.tickcounters[me.action.ability] == nil then |
||
761 | me.tickcounters[me.action.ability] = { } |
||
762 | end |
||
763 | |||
764 | if me.tickcounters[me.action.ability][me.action.mobname] == nil then |
||
765 | me.tickcounters[me.action.ability][me.action.mobname] = 0 |
||
766 | end |
||
767 | |||
768 | -- create an entry if none exists so far |
||
769 | me.tickcounters[me.action.ability][me.action.mobname] = me.tickcounters[me.action.ability][me.action.mobname] + 1 |
||
770 | |||
771 | -- now, have we gone enough ticks? |
||
772 | if me.tickcounters[me.action.ability][me.action.mobname] < spelldata.ticks then |
||
773 | |||
774 | -- not enough ticks |
||
775 | if mod.out.checktrace("info", me, "attack") then |
||
776 | mod.out.printtrace(string.format("This is tick number %d of %s; it will activate in another %d ticks.", me.tickcounters[me.action.ability][me.action.mobname], me.action.ability, spelldata.ticks - me.tickcounters[me.action.ability][me.action.mobname])) |
||
777 | end |
||
778 | |||
779 | -- spell reporting |
||
780 | local value1 = me.tickcounters[me.action.ability][me.action.mobname] |
||
781 | local value2 = spelldata.ticks - value1 |
||
782 | |||
783 | if me.isspellreportingactive then |
||
784 | mod.net.reportspelleffect(me.action.ability, me.action.mobname, "tick", value1, value2) |
||
785 | end |
||
786 | |||
787 | return |
||
788 | |||
789 | else |
||
790 | -- we just got enough ticks, so now reset to 0 |
||
791 | me.tickcounters[me.action.ability][me.action.mobname] = 0 |
||
792 | |||
793 | end |
||
794 | end |
||
795 | |||
796 | -- 3) To get here, the ability is definitely activating |
||
797 | if mod.out.checktrace("info", me, "attack") then |
||
798 | mod.out.printtrace(string.format("%s's %s activates, multiplying your threat by %s then adding %s.", me.action.mobname, me.action.ability, spelldata.multiplier, spelldata.addition)) |
||
799 | end |
||
800 | |||
801 | -- compute new threat |
||
802 | local newthreat = mod.table.getraidthreat() * spelldata.multiplier + spelldata.addition |
||
803 | |||
804 | -- remember threat can't go below 0 |
||
805 | newthreat = math.max(0, newthreat) |
||
806 | |||
807 | -- threat change is the (possibly negative) amount of threat that was added |
||
808 | local threatchange = newthreat - mod.table.getraidthreat() |
||
809 | |||
810 | -- spellreporting |
||
811 | if me.isspellreportingactive then |
||
812 | mod.net.reportspelleffect(me.action.ability, me.action.mobname, "proc", math.floor(0.5 + mod.table.getraidthreat()), math.floor(0.5 + newthreat)) |
||
813 | end |
||
814 | |||
815 | -- add to threat wipes section, but not to totals |
||
816 | mod.combat.event.hits = 1 |
||
817 | mod.combat.event.damage = 0 |
||
818 | mod.combat.event.rage = 0 |
||
819 | mod.combat.event.threat = threatchange |
||
820 | |||
821 | mod.combat.addattacktodata(mod.string.get("threatsource", "threatwipe"), mod.combat.event) |
||
822 | |||
823 | -- now add it to your raid threat total (but not your personal threat total) |
||
824 | mod.table.raidthreatoffset = mod.table.raidthreatoffset + threatchange |
||
825 | |||
826 | -- ask for a redraw of the personal window |
||
827 | KLHTM_RequestRedraw("self") |
||
828 | |||
829 | end |
||
830 | |||
831 | --[[ |
||
832 | me.resetaction() |
||
833 | Sets the values of me.action to their defaults. |
||
834 | ]] |
||
835 | me.resetaction = function(mobname, ability) |
||
836 | |||
837 | me.action.mobname = mobname |
||
838 | me.action.ability = ability |
||
839 | me.action.ishit = false |
||
840 | me.action.isphysical = false |
||
841 | me.action.isdebuff = false |
||
842 | me.action.isspell = false |
||
843 | |||
844 | end |
||
845 | |||
846 | me.action = |
||
847 | { |
||
848 | mobname = "", |
||
849 | ability = "", |
||
850 | ishit = false, |
||
851 | isphysical = false, |
||
852 | isdebuff = false, |
||
853 | isspell = false, |
||
854 | } |
||
855 | |||
856 | -- Note that <ishit> defaults to false, so we only set it when it is true |
||
857 | me.attackdescription = |
||
858 | { |
||
859 | ["magicresist"] = |
||
860 | { |
||
861 | isspell = true, |
||
862 | isdebuff = true, |
||
863 | }, |
||
864 | ["spellhit"] = |
||
865 | { |
||
866 | isspell = true, |
||
867 | ishit = true, |
||
868 | }, |
||
869 | ["attackabsorb"] = |
||
870 | { |
||
871 | isspell = true, |
||
872 | isphysical = true, |
||
873 | ishit = true, |
||
874 | }, |
||
875 | ["abilityhit"] = |
||
876 | { |
||
877 | isphysical = true, |
||
878 | ishit = true, |
||
879 | }, |
||
880 | ["abilitymiss"] = |
||
881 | { |
||
882 | isphysical = true, |
||
883 | }, |
||
884 | ["debuffstart"] = |
||
885 | { |
||
886 | ishit = true, |
||
887 | isdebuff = true, |
||
888 | }, |
||
889 | } |
||
890 | |||
891 | --[[ |
||
892 | Here is where you define all the boss' attacks that affect threat. |
||
893 | The first key in me.bossattacks is the identifier of the spell. That is, mod.string.get("boss", "spell", <first key>) |
||
894 | is the localised version. |
||
895 | The second key deep specifies which mob the attack comes from. You can choose "default" to make it apply to all mobs, |
||
896 | or you can specify a mob id, which will override the "default" value. Mob id's recognised are all the keys in the |
||
897 | "boss" -> "name" section of the localisation tree. |
||
898 | So if you want to define a new attack name or boss name, you'll have to add a new key to the localisation tree in the |
||
899 | "boss" -> "spell" and "boss" -> "name" sections respectively. |
||
900 | Inside each block, the follow parameters are defined: |
||
901 | <multiplier> - a value that your threat is multiplier by. e.g. the standard Knock Away is -50% threat, so this would be a |
||
902 | multiplier of 0.5. A complete threat wipe would be a multiplier of 0. |
||
903 | <addition> - a flat value that is added to your threat. Can be positive or negative or 0. |
||
904 | <effectonmiss> - a boolean value specifying whether the event triggers even when it is resisted or misses you. |
||
905 | <ticks> - the number of times you must suffer the attack before your threat is changed. e.g. most knockbacks happen every |
||
906 | time so <ticks> = 1, but Time Lapse reduces your threat only after a certain number of applications. |
||
907 | <type> - describes the attack. Can be "physical" or "spell" or "debuff". Not used by the mod at the moment: it will |
||
908 | assume that if the name matches, it has found the right ability. |
||
909 | ]] |
||
910 | me.bossattacks = |
||
911 | { |
||
912 | knockaway = |
||
913 | { |
||
914 | default = |
||
915 | { |
||
916 | multiplier = 0.5, |
||
917 | addition = 0, |
||
918 | effectonmiss = false, |
||
919 | ticks = 1, |
||
920 | type = "physical", |
||
921 | }, |
||
922 | onyxia = |
||
923 | { |
||
924 | multiplier = 0.67, |
||
925 | addition = 0, |
||
926 | effectonmiss = false, |
||
927 | ticks = 1, |
||
928 | type = "physical", |
||
929 | }, |
||
930 | }, |
||
931 | wingbuffet = |
||
932 | { |
||
933 | default = |
||
934 | { |
||
935 | multiplier = 0.5, |
||
936 | addition = 0, |
||
937 | effectonmiss = false, |
||
938 | ticks = 1, |
||
939 | type = "physical", |
||
940 | }, |
||
941 | onyxia = |
||
942 | { |
||
943 | multiplier = 1.0, |
||
944 | addition = 0, |
||
945 | effectonmiss = false, |
||
946 | ticks = 1, |
||
947 | type = "physical", |
||
948 | }, |
||
949 | }, |
||
950 | timelapse = |
||
951 | { |
||
952 | default = |
||
953 | { |
||
954 | multiplier = 0, |
||
955 | addition = 0, |
||
956 | effectonmiss = false, |
||
957 | ticks = 5, |
||
958 | type = "debuff" |
||
959 | } |
||
960 | }, |
||
961 | sandblast = |
||
962 | { |
||
963 | default = |
||
964 | { |
||
965 | multiplier = 0, |
||
966 | addition = 0, |
||
967 | effectonmiss = false, |
||
968 | ticks = 1, |
||
969 | type = "spell" |
||
970 | } |
||
971 | }, |
||
972 | } |
||
973 | |||
974 | --[[ |
||
975 | ------------------------------------------------------------------------------------------------ |
||
976 | + B + Normal Shit |
||
977 | ------------------------------------------------------------------------------------------------ |
||
978 | ]] |
||
979 | |||
980 | --[[ |
||
981 | me.automastertarget(target) |
||
982 | Called when the mod itself sets the mastertarget. |
||
983 | <target> is the localised name of the mob. |
||
984 | ]] |
||
985 | me.automastertarget = function(target) |
||
986 | |||
987 | me.mastertarget = target |
||
988 | |||
989 | -- stop network module autoupdating the master target |
||
990 | mod.net.lastmtsender = "" |
||
991 | |||
992 | -- explain to user |
||
993 | mod.out.print(string.format(mod.string.get("print", "boss", "automt"), target)) |
||
994 | |||
995 | end |
||
996 | |||
997 | --[[ |
||
998 | mod.boss.newmastertarget(author, target) |
||
999 | Handles a request to change the master target. |
||
1000 | <author> is the name of the officer in the group who changed the master target. |
||
1001 | <target> is the name of his current mob, localised to him. |
||
1002 | The problem is that if you have a different localisation to <author>, you will think his target is spelt differently to <target>! So we have to check for this, and override if necessary. |
||
1003 | ]] |
||
1004 | me.newmastertarget = function(author, target) |
||
1005 | |||
1006 | -- 1) Find the author's UnitID |
||
1007 | local officerunit = mod.unit.findunitidfromname(author) |
||
1008 | local officertarget = UnitName(tostring(officerunit) .. "target") |
||
1009 | |||
1010 | -- 2) Check for differences |
||
1011 | if officertarget == nil then |
||
1012 | mod.out.print(string.format(mod.string.get("print", "network", "newmttargetnil"), target, author)) |
||
1013 | |||
1014 | elseif officertarget ~= target then |
||
1015 | mod.out.print(string.format(mod.string.get("print", "network", "newmttargetmismatch"), author, target, officertarget)) |
||
1016 | target = officertarget |
||
1017 | end |
||
1018 | |||
1019 | -- 3) Check for worldboss target |
||
1020 | if author == UnitName("player") and UnitClassification("target") == "worldboss" then |
||
1021 | me.ismtworldboss = true |
||
1022 | else |
||
1023 | me.ismtworldboss = false |
||
1024 | end |
||
1025 | |||
1026 | -- 3) OK |
||
1027 | me.mastertarget = target |
||
1028 | mod.out.print(string.format(mod.string.get("print", "network", "newmt"), target, author)) |
||
1029 | |||
1030 | end |
||
1031 | |||
1032 | -- todo: stuff, maybe? |
||
1033 | me.clearmastertarget = function() |
||
1034 | |||
1035 | me.mastertarget = nil |
||
1036 | |||
1037 | end |
||
1038 | |||
1039 | --[[ |
||
1040 | mod.boss.targetismaster(target) |
||
1041 | Checks whether <target> is the master target. The master target is usually just a name / string, but it may be something |
||
1042 | more general in the future (e.g. tracking both bosses in the Twin Emps fight). |
||
1043 | <target> is the name of the mob being queried. |
||
1044 | Return: non-nil if <target> is a mastertarget. |
||
1045 | ]] |
||
1046 | me.targetismaster = function(target) |
||
1047 | |||
1048 | if me.mastertarget == nil then |
||
1049 | return true |
||
1050 | end |
||
1051 | |||
1052 | if target == me.mastertarget then |
||
1053 | return true |
||
1054 | end |
||
1055 | |||
1056 | -- insert other checks here, later. |
||
1057 | |||
1058 | return -- (nil) |
||
1059 | |||
1060 | end |
||
1061 | |||
1062 | ----------------------------------- |
||
1063 | -- Targeting Behaviour -- |
||
1064 | ----------------------------------- |
||
1065 | --[[ |
||
1066 | |||
1067 | True = True target. Who the mob would have aggro on, if we discount secondary targetting and taunts, etc. |
||
1068 | Curr = Current target. Who the mob's target unitid is |
||
1069 | New = New target. If the mob's current target has changed |
||
1070 | |||
1071 | x, y = players with known threat values |
||
1072 | nil = no target |
||
1073 | ? = player with unknown threat value |
||
1074 | |||
1075 | |||
1076 | True Curr New Result |
||
1077 | ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ |
||
1078 | nil nil x true and curr become x |
||
1079 | |||
1080 | x x y curr -> y. In 2 seconds with no change, true -> y. also, if their threat goes above 110 of yours in that time, put them up. |
||
1081 | x x ? curr -> ?. In 2 seconds with no change, true -> ? |
||
1082 | x x nil curr -> nil. |
||
1083 | |||
1084 | x y z curr -> z. In 2 secs, true -> z |
||
1085 | x y nil curr -> nil. |
||
1086 | x y ? curr -> ?. In 2 secs, true -> ? |
||
1087 | x y x curr -> x |
||
1088 | |||
1089 | x nil y If it's been nil for more than 1 second, true = y. Otherwise true -> y after 2 - (secs at nil) secs. |
||
1090 | x nil x curr -> x |
||
1091 | x nil ? same as x - nil - y |
||
1092 | |||
1093 | ? ? x true -> x. Easy enough. |
||
1094 | |||
1095 | ]] |
||
1096 | |||
1097 | -- Master Target Variables |
||
1098 | me.mttruetarget = nil -- The Name of the player who this mod thinks is the true target |
||
1099 | me.mtcurrenttarget = nil -- The Name of the player the mob is currently targetting |
||
1100 | me.mttargetswaptime = 0 -- The time when the mob last changed its target |
||
1101 | me.unknowntarget = "#unknown" |
||
1102 | me.mastertargettarget = nil |
||
1103 | |||
1104 | -- lm2: aggro gain is now calculated just before redrawing the raid frame |
||
1105 | me.updateaggrogain = function() |
||
1106 | |||
1107 | if mod.boss.mastertarget == nil then |
||
1108 | me.recalculateaggrogain() |
||
1109 | else |
||
1110 | me.updatetrueaggrotarget() |
||
1111 | end |
||
1112 | |||
1113 | end |
||
1114 | |||
1115 | |||
1116 | me.updatetrueaggrotarget = function() |
||
1117 | |||
1118 | -- 1) find a UnitID for the master target |
||
1119 | local mastertargetid = nil |
||
1120 | |||
1121 | if UnitName("target") == me.mastertarget then |
||
1122 | mastertargetid = "target" |
||
1123 | |||
1124 | else |
||
1125 | -- check everyone in the raid |
||
1126 | local x |
||
1127 | |||
1128 | for x = 1, 40 do |
||
1129 | if UnitName("raid" .. x .. "target") == me.mastertarget then |
||
1130 | mastertargetid = "raid" .. x .. "target" |
||
1131 | break |
||
1132 | end |
||
1133 | end |
||
1134 | end |
||
1135 | |||
1136 | -- 2) If noone can see the mob, give up. |
||
1137 | if mastertargetid == nil then |
||
1138 | |||
1139 | me.mttruetarget = me.unknowntarget |
||
1140 | me.mtcurrenttarget = me.unknownuarget |
||
1141 | mod.table.raiddata[mod.string.get("misc", "aggrogain")] = nil |
||
1142 | |||
1143 | return |
||
1144 | end |
||
1145 | |||
1146 | -- 3) Get the boss' current target |
||
1147 | local targetnow = UnitName(mastertargetid .. "target") |
||
1148 | |||
1149 | -- 4) Reevaluate True, Current, Time |
||
1150 | |||
1151 | -- a) Transitions from true=unknown |
||
1152 | if (me.mttruetarget == nil) or (me.mttruetarget == me.unknowntarget) or (mod.table.raiddata[me.mttruetarget] == nil) then |
||
1153 | |||
1154 | -- debug print |
||
1155 | if targetnow ~= me.mttruetarget then |
||
1156 | if targetnow == nil then |
||
1157 | if mod.out.checktrace("info", me, "target") then |
||
1158 | mod.out.printtrace("Target changed from bad to nil.") |
||
1159 | end |
||
1160 | else |
||
1161 | if mod.out.checktrace("info", me, "target") then |
||
1162 | mod.out.printtrace(string.format("Target changed from bad to %s.", targetnow)) |
||
1163 | end |
||
1164 | |||
1165 | if (me.isknockbackdiscoveryactive == true) and (targetnow == UnitName("player")) then |
||
1166 | mod.net.sendmessage("aggrogain " .. mod.table.getraidthreat()) |
||
1167 | end |
||
1168 | end |
||
1169 | end |
||
1170 | |||
1171 | me.mttruetarget = targetnow |
||
1172 | me.mtcurrenttarget = targetnow |
||
1173 | |||
1174 | -- b) Transitions from true = known |
||
1175 | elseif targetnow ~= me.mtcurrenttarget then |
||
1176 | |||
1177 | if me.mtcurrenttarget ~= nil then |
||
1178 | me.mttargetswaptime = GetTime() |
||
1179 | end |
||
1180 | |||
1181 | me.mtcurrenttarget = targetnow |
||
1182 | |||
1183 | if targetnow == nil then |
||
1184 | if mod.out.checktrace("info", me, "target") then |
||
1185 | mod.out.printtrace("CurrentTarget changed to nil.") |
||
1186 | end |
||
1187 | else |
||
1188 | if mod.out.checktrace("info", me, "target") then |
||
1189 | mod.out.printtrace(string.format("CurrentTarget changed from bad to %s.", targetnow)) |
||
1190 | end |
||
1191 | |||
1192 | if (me.isknockbackdiscoveryactive == true) and (targetnow == UnitName("player")) then |
||
1193 | mod.net.sendmessage("aggrogain " .. mod.table.getraidthreat()) |
||
1194 | end |
||
1195 | end |
||
1196 | end |
||
1197 | |||
1198 | -- 5) Check if CurrentTarget should become Truetarget |
||
1199 | if me.mttruetarget ~= me.mtcurrenttarget then |
||
1200 | -- to get here, true target is known. |
||
1201 | |||
1202 | if me.mtcurrenttarget == nil then |
||
1203 | -- do nothing |
||
1204 | |||
1205 | elseif mod.table.raiddata[me.mtcurrenttarget] == nil then |
||
1206 | -- switch to unknown if it's been more than 2 seconds |
||
1207 | |||
1208 | if GetTime() > me.mttargetswaptime + 2 then |
||
1209 | me.mttruetarget = me.mtcurrenttarget |
||
1210 | |||
1211 | if mod.out.checktrace("info", me, "target") then |
||
1212 | mod.out.printtrace(string.format("TrueTarget switches to the unknown %s after 2 seconds.", me.mttruetarget)) |
||
1213 | end |
||
1214 | end |
||
1215 | |||
1216 | else -- current target is a known user |
||
1217 | if GetTime() - me.mttargetswaptime > 2 then |
||
1218 | me.mttruetarget = me.mtcurrenttarget |
||
1219 | |||
1220 | if mod.out.checktrace("info", me, "target") then |
||
1221 | mod.out.printtrace(string.format("TrueTarget switches to the known player %s after 2 seconds.", me.mttruetarget)) |
||
1222 | end |
||
1223 | |||
1224 | elseif mod.table.raiddata[me.mtcurrenttarget] > mod.data.threatconstants.meleeaggrogain * mod.table.raiddata[me.mttruetarget] then |
||
1225 | me.mttruetarget = me.mtcurrenttarget |
||
1226 | |||
1227 | if mod.out.checktrace("info", me, "target") then |
||
1228 | mod.out.printtrace(string.format("TrueTarget switches to the known player %s due to high threat.", me.mttruetarget)) |
||
1229 | end |
||
1230 | end |
||
1231 | end |
||
1232 | end |
||
1233 | |||
1234 | -- update the AggroGain virtual player |
||
1235 | if ((me.mttruetarget ~= nil) and (me.truetarget ~= me.unknowntarget) and |
||
1236 | (mod.table.raiddata[me.mttruetarget] ~= nil)) then |
||
1237 | |||
1238 | local aggro = mod.table.raiddata[me.mttruetarget]; |
||
1239 | |||
1240 | if (UnitName("player") ~= me.mttruetarget) then |
||
1241 | if CheckInteractDistance(mastertargetid, 1) then |
||
1242 | aggro = math.ceil(aggro * mod.data.threatconstants.meleeaggrogain) |
||
1243 | else |
||
1244 | aggro = math.ceil(aggro * mod.data.threatconstants.rangeaggrogain) |
||
1245 | end |
||
1246 | end |
||
1247 | |||
1248 | mod.table.raiddata[mod.string.get("misc", "aggrogain")] = aggro; |
||
1249 | else |
||
1250 | mod.table.raiddata[mod.string.get("misc", "aggrogain")] = nil |
||
1251 | end |
||
1252 | end |
||
1253 | |||
1254 | me.recalculateaggrogain = function() |
||
1255 | |||
1256 | -- update aggro, and such |
||
1257 | local newaggrogain |
||
1258 | local targetname = "" |
||
1259 | local maxdepth = 5 |
||
1260 | local i |
||
1261 | local targetacquired = false |
||
1262 | |||
1263 | for i = 1, maxdepth do |
||
1264 | targetname = targetname .. "target" |
||
1265 | |||
1266 | if UnitName(targetname) == nil then |
||
1267 | break |
||
1268 | |||
1269 | elseif UnitIsFriend("player", targetname) == nil then |
||
1270 | targetacquired = true |
||
1271 | break |
||
1272 | end |
||
1273 | end |
||
1274 | |||
1275 | if targetacquired == false then |
||
1276 | -- remove aggro gain |
||
1277 | newaggrogain = nil |
||
1278 | |||
1279 | else |
||
1280 | local mobtarget = UnitName(targetname .. "target") |
||
1281 | if mobtarget == nil then |
||
1282 | mobtarget = "<nil>" |
||
1283 | end |
||
1284 | |||
1285 | if mod.table.raiddata[mobtarget] then |
||
1286 | -- aggro target has a known threat value |
||
1287 | |||
1288 | if UnitName("player") == mobtarget then |
||
1289 | newaggrogain = mod.table.raiddata[mobtarget] |
||
1290 | |||
1291 | else |
||
1292 | -- now check our range to the mob |
||
1293 | if CheckInteractDistance(targetname, 1) then |
||
1294 | -- we're in melee range |
||
1295 | newaggrogain = math.ceil(mod.table.raiddata[mobtarget] * mod.data.threatconstants.meleeaggrogain) |
||
1296 | |||
1297 | else |
||
1298 | -- there's a small region where we might be in melee range. for now, assume not |
||
1299 | newaggrogain = math.ceil(mod.table.raiddata[mobtarget] * mod.data.threatconstants.rangeaggrogain) |
||
1300 | end |
||
1301 | end |
||
1302 | else |
||
1303 | newaggrogain = nil |
||
1304 | end |
||
1305 | end |
||
1306 | |||
1307 | local currentaggrogain = mod.table.raiddata[mod.string.get("misc", "aggrogain")] |
||
1308 | if newaggrogain ~= currentaggrogain then |
||
1309 | mod.table.raiddata[mod.string.get("misc", "aggrogain")] = newaggrogain |
||
1310 | end |
||
1311 | |||
1312 | end |