vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 --[[---------------------------------------------------------------------------------
2 Detox
3 written by Maia/Proudmoore,
4 code based on the Decursive 1.9.7 core code by Archarodim and Quu.
5 The name 'Detox' was provided by Tem.
6 ------------------------------------------------------------------------------------]]
7  
8 local vmajor,vminor = "0.4", tonumber(string.sub("$Revision: 12266 $", 12, -3))
9  
10 --[[---------------------------------------------------------------------------------
11 locals
12 ------------------------------------------------------------------------------------]]
13  
14 local compost = AceLibrary("Compost-2.0")
15 local L = AceLibrary("AceLocale-2.0"):new("Detox")
16 local BS = AceLibrary("Babble-Spell-2.0")
17 local BC = AceLibrary("Babble-Class-2.0")
18 local tablet = AceLibrary("Tablet-2.0")
19 local aura = nil -- AceLibrary("SpecialEvents-Aura-2.0")
20 local RL = nil -- AceLibrary("RosterLib-2.0")
21  
22 local last_DemonType = nil
23 local curr_DemonType = nil
24 local castTarget = nil
25 local soundPlayed = false
26 local hasSpells = false
27 local blacklist = {}
28 local outofrange = {}
29 local spells = {} -- will be filled with available spells
30 local priorityIndex = {} -- this table references name to index of the priority table
31 local priority = {} -- this integer keyed table will hold subtables containing name, unitid and priority for each unit in our roster
32  
33 local DebuffTypeColor = {}
34 DebuffTypeColor["none"] = { r = 0.80, g = 0, b = 0 }
35 DebuffTypeColor["Magic"] = { r = 0.20, g = 0.60, b = 1.00 }
36 DebuffTypeColor["Curse"] = { r = 0.60, g = 0.00, b = 1.00 }
37 DebuffTypeColor["Disease"] = { r = 0.60, g = 0.40, b = 0 }
38 DebuffTypeColor["Poison"] = { r = 0.00, g = 0.60, b = 0 }
39  
40 local invisible = {
41 [BS["Prowl"]] = true,
42 [BS["Stealth"]] = true,
43 [BS["Shadowmeld"]] = true,
44 }
45  
46 local ignore = {
47 [BS["Banish"]] = true,
48 [BS["Phase Shift"]] = true,
49 [L["Frost Trap Aura"]] = true,
50 }
51  
52 local skip = {
53 [L["Dreamless Sleep"]] = true,
54 [L["Greater Dreamless Sleep"]] = true,
55 }
56  
57 local cleaningSpells = {
58 [BS["Cure Disease"]] = true,
59 [BS["Abolish Disease"]] = true,
60 [BS["Purify"]] = true,
61 [BS["Cleanse"]] = true,
62 [BS["Dispel Magic"]] = true,
63 [BS["Cure Poison"]] = true,
64 [BS["Abolish Poison"]] = true,
65 [BS["Remove Lesser Curse"]] = true,
66 [BS["Remove Curse"]] = true,
67 [BS["Purge"]] = true,
68 [BS["Devour Magic"]] = true,
69 [BS["Stoneform"]] = true,
70 }
71  
72 BINDING_HEADER_DETOX = "Detox"
73 BINDING_NAME_CLEAN = L["Clean group"]
74 BINDING_NAME_PRIORITYTOGGLE = L["Toggle target priority"]
75 BINDING_NAME_PRIORITYTOGGLEGROUP = L["Toggle target group priority"]
76 BINDING_NAME_PRIORITYTOGGLECLASS = L["Toggle target class priority"]
77  
78 --[[---------------------------------------------------------------------------------
79 defaults and AceOptions table
80 ------------------------------------------------------------------------------------]]
81  
82 local defaults = {
83 showPriorities = true,
84 ignoreStealthed = true,
85 blacklistTime = 5,
86 checkAbolish = true,
87 ignorePets = false,
88 liveDisplay = 5,
89 filter = true,
90 range = true,
91 sound = true,
92 feedback = true,
93 updateSpeed = 0.5,
94 priorityNames = {},
95 priorityGroups = {},
96 priorityClasses = {},
97 debuffsInSkipList = {
98 BS["Silence"],
99 L["Ancient Hysteria"],
100 L["Ignite Mana"],
101 L["Tainted Mind"],
102 L["Magma Shackles"],
103 L["Cripple"],
104 L["Dust Cloud"],
105 L["Widow's Embrace"],
106 L["Curse of Tongues"],
107 L["Sonic Burst"],
108 L["Thunderclap"],
109 L["Delusions of Jin'do"]
110 },
111 skipByClass = {
112 ["WARRIOR"] = {
113 [L["Ancient Hysteria"]] = true,
114 [L["Ignite Mana"]] = true,
115 [L["Tainted Mind"]] = true,
116 [L["Widow's Embrace"]] = true,
117 [L["Curse of Tongues"]] = true,
118 [L["Delusions of Jin'do"]]= true,
119 },
120 ["ROGUE"] = {
121 [BS["Silence"]] = true,
122 [L["Ancient Hysteria"]] = true,
123 [L["Ignite Mana"]] = true,
124 [L["Tainted Mind"]] = true,
125 [L["Widow's Embrace"]] = true,
126 [L["Curse of Tongues"]] = true,
127 [L["Sonic Burst"]] = true,
128 [L["Delusions of Jin'do"]]= true,
129 },
130 ["HUNTER"] = {
131 [L["Magma Shackles"]] = true,
132 [L["Delusions of Jin'do"]]= true,
133 },
134 ["MAGE"] = {
135 [L["Magma Shackles"]] = true,
136 [L["Cripple"]] = true,
137 [L["Dust Cloud"]] = true,
138 [L["Thunderclap"]] = true,
139 [L["Delusions of Jin'do"]]= true,
140 },
141 ["WARLOCK"] = {
142 [L["Cripple"]] = true,
143 [L["Dust Cloud"]] = true,
144 [L["Thunderclap"]] = true,
145 [L["Delusions of Jin'do"]]= true,
146 },
147 ["DRUID"] = {
148 [L["Cripple"]] = true,
149 [L["Dust Cloud"]] = true,
150 [L["Thunderclap"]] = true,
151 [L["Delusions of Jin'do"]]= true,
152 },
153 ["PALADIN"] = {
154 [L["Cripple"]] = true,
155 [L["Dust Cloud"]] = true,
156 [L["Delusions of Jin'do"]]= true,
157 },
158 ["PRIEST"] = {
159 [L["Cripple"]] = true,
160 [L["Dust Cloud"]] = true,
161 [L["Thunderclap"]] = true,
162 [L["Delusions of Jin'do"]]= true,
163 },
164 ["SHAMAN"] = {
165 [L["Cripple"]] = true,
166 [L["Dust Cloud"]] = true,
167 [L["Delusions of Jin'do"]]= true,
168 }
169 }
170 }
171  
172 local options = {
173 type = 'group',
174 args = {
175 clean = {
176 type = 'execute',
177 name = L["Clean group"],
178 desc = L["Will attempt to clean a player in your raid/party."],
179 func = function()
180 Detox:Clean()
181 end,
182 order = 101,
183 },
184 spacer = { type = "header", order = 102 },
185 livelistoptions = {
186 type = 'group',
187 name = L["Live list"],
188 desc = L["Options for the live list."],
189 order = 110,
190 args = {
191 show = {
192 type = 'toggle',
193 name = L["Show live list"],
194 desc = L["Detaches the live list from the Detox icon."],
195 get = function() return Detox:IsTooltipDetached() end,
196 set = function() Detox:ToggleTooltipDetached() end,
197 order = 100
198 },
199 sound = {
200 type = 'toggle',
201 name = L["Play sound if unit needs decursing"],
202 desc = L["Play sound if unit needs decursing"]..".",
203 get = function() return Detox.db.profile.sound end,
204 set = function()
205 Detox.db.profile.sound = not Detox.db.profile.sound
206 end,
207 order = 101
208 },
209 showpriorizations = {
210 type = 'toggle',
211 name = L["Show priorities"],
212 desc = L["Displays who is prioritized in the live list."],
213 get = function() return Detox.db.profile.showPriorities end,
214 set = function() Detox.db.profile.showPriorities = not Detox.db.profile.showPriorities end,
215 order = 102,
216 },
217 live = {
218 type = 'range',
219 name = L["Max debuffs shown"],
220 desc = L["Defines the max number of debuffs to display in the live list."],
221 get = function() return Detox.db.profile.liveDisplay end,
222 set = function(v)
223 Detox.db.profile.liveDisplay = v
224 end,
225 min = 0,
226 max = 20,
227 step = 1,
228 isPercent = false,
229 order = 103,
230 },
231 speed = {
232 type = 'range',
233 name = L["Update speed"],
234 desc = L["Defines the speed the live list is updated, in seconds."],
235 get = function() return Detox.db.profile.updateSpeed end,
236 set = function(v)
237 Detox.db.profile.updateSpeed = v
238 Detox:CancelScheduledEvent("liveList")
239 Detox:ScheduleRepeatingEvent("liveList", Detox.Update, v, Detox)
240 end,
241 min = 0.2,
242 max = 2,
243 step = 0.1,
244 isPercent = false,
245 order = 104,
246 },
247 },
248 },
249 priority = {
250 type = 'group',
251 name = L["Priority"],
252 desc = L["These units will be priorized when curing."],
253 order = 120,
254 args = {}
255 },
256 filter = {
257 type = 'group',
258 name = L["Filter"],
259 desc = L["Options for filtering various debuffs and conditions."],
260 order = 130,
261 args = {
262 filter = {
263 type = 'toggle',
264 name = L["Filter by type"],
265 desc = L["Only show debuffs you can cure."],
266 get = function() return Detox.db.profile.filter end,
267 set = function()
268 Detox.db.profile.filter = not Detox.db.profile.filter
269 end,
270 order = 103,
271 },
272 range = {
273 type = 'toggle',
274 name = L["Filter by range"],
275 desc = L["Only show units in range."],
276 get = function() return Detox.db.profile.range end,
277 set = function()
278 Detox.db.profile.range = not Detox.db.profile.range
279 end,
280 order = 104
281 },
282 stealth = {
283 type = 'toggle',
284 name = L["Filter stealthed units"],
285 desc = L["It is recommended not to cure stealthed units."],
286 get = function() return Detox.db.profile.ignoreStealthed end,
287 set = function()
288 Detox.db.profile.ignoreStealthed = not Detox.db.profile.ignoreStealthed
289 end,
290 order = 105
291 },
292 abolish = {
293 type = 'toggle',
294 name = L["Filter Abolished units"],
295 desc = L["Skip units that have an active Abolish buff."],
296 get = function() return Detox.db.profile.checkAbolish end,
297 set = function()
298 Detox.db.profile.checkAbolish = not Detox.db.profile.checkAbolish
299 end,
300 order = 106
301 },
302 pets = {
303 type = 'toggle',
304 name = L["Filter pets"],
305 desc = L["Pets are also your friends."],
306 get = function() return Detox.db.profile.ignorePets end,
307 set = function()
308 Detox.db.profile.ignorePets = not Detox.db.profile.ignorePets
309 Detox:UpdatePriority()
310 end,
311 order = 107
312 },
313 debuff = {
314 type = 'group',
315 name = L["Debuff"],
316 desc = L["Filter by debuff and class."],
317 order = 400,
318 args = {}
319 },
320 },
321 },
322 blacklist = {
323 type = 'range',
324 name = L["Seconds to blacklist"],
325 desc = L["Units that are out of Line of Sight will be blacklisted for the set duration."],
326 get = function() return Detox.db.profile.blacklistTime end,
327 set = function(v)
328 Detox.db.profile.blacklistTime = v
329 end,
330 min = 1,
331 max = 20,
332 step = 1,
333 isPercent = false,
334 order = 140
335 },
336 feedback = {
337 type = 'toggle',
338 name = L["Show detoxing in scrolling combat frame"],
339 desc = L["This will use SCT5 when available, otherwise Blizzards Floating Combat Text."],
340 get = function() return Detox.db.profile.feedback end,
341 set = function()
342 Detox.db.profile.feedback = not Detox.db.profile.feedback
343 end,
344 order = 150
345 },
346 },
347 }
348  
349 --[[---------------------------------------------------------------------------------
350 Initialization
351 ------------------------------------------------------------------------------------]]
352  
353 Detox = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceDB-2.0", "AceConsole-2.0", "AceDebug-2.0", "FuBarPlugin-2.0")
354  
355 -- stuff for FuBar:
356 Detox.hasIcon = true
357 Detox.defaultPosition = "LEFT"
358 Detox.defaultMinimapPosition = 250
359 Detox.cannotDetachTooltip = false
360 Detox.tooltipHiddenWhenEmpty = true
361 Detox.hideWithoutStandby = true
362 Detox.clickableTooltip = true
363 Detox.independentProfile = true
364  
365 function Detox:OnInitialize()
366 self:RegisterDB("DetoxDB")
367 self:RegisterDefaults('profile', defaults )
368 self:RegisterChatCommand({'/detox'}, options )
369 self.OnMenuRequest = options
370 self.OnMenuRequest.args.lockTooltip.hidden = true
371 self.OnMenuRequest.args.detachTooltip.hidden = true
372 if not FuBar then
373 self.OnMenuRequest.args.hide.guiName = "Hide minimap icon"
374 self.OnMenuRequest.args.hide.desc = "Hide minimap icon"
375 end
376 end
377  
378  
379 function Detox:OnEnable()
380 if not aura then aura = AceLibrary("SpecialEvents-Aura-2.0") end
381 if not RL then RL = AceLibrary("RosterLib-2.0") end
382  
383 self:RegisterEvent("SPELLCAST_STOP")
384 self:RegisterEvent("UNIT_PET",1)
385 self:RegisterEvent("SPELLS_CHANGED","RescanSpellbook",1)
386 self:RegisterEvent("LEARNED_SPELL_IN_TAB","RescanSpellbook",1)
387 self:RegisterEvent("UI_ERROR_MESSAGE")
388 self:RegisterEvent("RosterLib_RosterChanged")
389 self:ParseSpellbook()
390 self:ScheduleRepeatingEvent(self.OnUpdate, 1, self)
391 self:ScheduleRepeatingEvent("liveList", self.Update, Detox.db.profile.updateSpeed or 0.5, self) -- this is the FuBar live list update!
392 self.debugging = self:IsDebugging()
393 self:PopulateSkipList()
394 end
395  
396  
397 function Detox:OnDisable()
398 local detached = self:IsTooltipDetached()
399 if detached then
400 self:ReattachTooltip()
401 self.db.profile.detachedTooltip.detached = true
402 end
403 end
404  
405  
406 function Detox:OnClick() -- FuBarPlugin
407 self:Clean()
408 end
409  
410 function Detox:IsDebugging() return self.db.profile.debugging end
411 function Detox:SetDebugging(debugging) self.db.profile.debugging = debugging; self.debugging = debugging; end
412  
413 --[[---------------------------------------------------------------------------------
414 Events
415 ------------------------------------------------------------------------------------]]
416  
417 function Detox:OnUpdate()
418 self:UpdateBlacklist()
419 end
420  
421  
422 function Detox:UpdateBlacklist()
423 for unit in pairs(blacklist) do
424 blacklist[unit] = blacklist[unit] - 1
425 if blacklist[unit] < 0 then
426 blacklist[unit] = nil
427 end
428 end
429 end
430  
431  
432 function Detox:RosterLib_RosterChanged(tbl)
433 for name in pairs(tbl) do
434 local u = tbl[name]
435 if not u.name then -- someone left the raid
436 for i = table.getn(priority), 1, -1 do
437 if not RL.roster[priority[i].name] then
438 priorityIndex[priority[i].name] = nil
439 compost:Reclaim(priority[i])
440 priority[i] = nil
441 table.remove(priority, i)
442 end
443 end
444 end
445 end
446 self:UpdatePriority()
447 end
448  
449  
450 function Detox:SPELLCAST_STOP()
451 castTarget = nil
452 end
453  
454  
455 function Detox:UNIT_PET(unit)
456 if unit == "player" then
457 self:ScheduleEvent(self.CheckPet, 2, self)
458 end
459 end
460  
461  
462 function Detox:UI_ERROR_MESSAGE(arg1)
463 if arg1 == SPELL_FAILED_LINE_OF_SIGHT then
464 if castTarget and not UnitIsUnit(castTarget, "player") then
465 blacklist[castTarget] = Detox.db.profile.blacklistTime
466 end
467 end
468 end
469  
470  
471 --[[---------------------------------------------------------------------------------
472 Spellbook
473 ------------------------------------------------------------------------------------]]
474  
475  
476 function Detox:ParseSpellbook()
477 compost:Reclaim(spells,1)
478 spells = compost:Acquire()
479 hasSpells = false
480 local BookType = BOOKTYPE_SPELL
481 local break_flag = false
482 local i = 1
483 local maxRankMagic = 0
484 local maxRankEnemyMagic = 0
485 while not break_flag do
486 while (true) do -- HUH?
487 local name, rank = GetSpellName(i, BookType)
488 if not name then
489 if BookType == BOOKTYPE_PET then
490 break_flag = true
491 break
492 end
493 BookType = BOOKTYPE_PET
494 i = 1
495 break
496 end
497 if cleaningSpells[name] then
498 hasSpells = true
499 spells["cooldown"] = compost:Acquire(i, BookType)
500 if name == BS["Cure Disease"] or name == BS["Purify"] then
501 spells["disease1"] = compost:Acquire(i, BookType, name)
502 end
503 if name == BS["Abolish Disease"] or name == BS["Cleanse"] then
504 spells["disease2"] = compost:Acquire(i, BookType, name)
505 end
506 if name == BS["Cure Poison"] or name == BS["Purify"] then
507 spells["poison1"] = compost:Acquire(i, BookType, name)
508 end
509 if name == BS["Abolish Poison"] or name == BS["Cleanse"] then
510 spells["poison2"] = compost:Acquire(i, BookType, name)
511 end
512 if name == BS["Remove Curse"] or name == BS["Remove Lesser Curse"] then
513 spells["curse"] = compost:Acquire(i, BookType, name)
514 end
515 if name == BS["Stoneform"] then
516 spells["poison3"] = compost:Acquire(i, BookType, name)
517 end
518 if name == BS["Cleanse"] or name == BS["Dispel Magic"] or name == BS["Devour Magic"] then
519 if rank and string.find(rank, L["Rank (%d+)"]) then
520 local ranknum = string.gsub(rank, L["Rank (%d+)"], "%1")
521 if tonumber(ranknum) > maxRankMagic then
522 spells["magic"] = compost:Acquire(i, BookType, name)
523 maxRankMagic = tonumber(ranknum)
524 end
525 else
526 spells["magic"] = compost:Acquire(i, BookType, name)
527 end
528 end
529 if name == BS["Dispel Magic"] or name == BS["Purge"] or name == BS["Devour Magic"] then
530 if rank and string.find(rank, L["Rank (%d+)"]) then
531 local ranknum = string.gsub(rank, L["Rank (%d+)"], "%1")
532 if tonumber(ranknum) > maxRankEnemyMagic then
533 spells["enemymagic"] = compost:Acquire(i, BookType, name)
534 maxRankEnemyMagic = tonumber(ranknum)
535 end
536 else
537 spells["enemymagic"] = compost:Acquire(i, BookType, name)
538 end
539 end
540 end
541 i = i + 1
542 end
543 end
544 end
545  
546  
547 function Detox:RescanSpellbook()
548 if not hasSpells then return end
549 local valid = true
550 for spell in pairs(spells) do
551 if GetSpellName(spells[spell][1], spells[spell][2]) ~= spells[spell][3] then
552 valid = false
553 end
554 end
555 if not valid then
556 self:ParseSpellbook()
557 end
558 end
559  
560  
561 function Detox:CheckPet()
562 curr_DemonType = UnitCreatureFamily("pet")
563 if last_DemonType ~= curr_DemonType then
564 last_DemonType = curr_DemonType
565 self:ParseSpellbook()
566 end
567 end
568  
569  
570 --[[---------------------------------------------------------------------------------
571 Cleaning
572 ------------------------------------------------------------------------------------]]
573  
574  
575 function Detox:Clean()
576 if not hasSpells then
577 self:ParseSpellbook()
578 if not hasSpells then
579 return false
580 end
581 end
582 -- check for cooldown
583 local _, cooldown = GetSpellCooldown(spells["cooldown"][1], spells["cooldown"][2])
584 if cooldown ~= 0 then
585 return false
586 end
587 -- temporarily disable selfcasting
588 local selfCast = GetCVar("autoSelfCast")
589 SetCVar("autoSelfCast", "0")
590 local cleanedType = nil
591 local cleanedName = nil
592 castTarget = nil
593 -- save original target
594 local targetIsEnemy = false
595 local targetFriendlyName = nil
596 if UnitExists("target") then
597 if UnitIsFriend("target","player") then
598 targetFriendlyName = UnitName("target")
599 else
600 targetIsEnemy = true
601 end
602 end
603 if SpellIsTargeting() then SpellStopTargeting() end
604 -- try to clean target first
605 if UnitExists("target") then
606 cleanedType = self:CureUnit("target")
607 if cleanedType then
608 cleanedName = UnitName("target")
609 end
610 end
611 -- couldn't clean target, check for MCd units
612 if not cleanedType and spells["enemymagic"] then
613 for k,v in ipairs(priority) do
614 if v.pri == 0 then break end
615 local unit = v.unitid
616 if UnitIsCharmed(unit)
617 and not blacklist[unit] then
618 cleanedType = self:CureUnit(unit)
619 if cleanedType then
620 cleanedName = UnitName(unit)
621 break
622 end
623 end
624 end
625 end
626 -- now do the rest (this is duplicated code, gah!)
627 if not cleanedType then
628 for k,v in ipairs(priority) do
629 if v.pri == 0 then break end
630 local unit = v.unitid
631 if not blacklist[unit]
632 and not UnitIsCharmed(unit) then
633 cleanedType = self:CureUnit(unit)
634 if cleanedType then
635 cleanedName = UnitName(unit)
636 break
637 end
638 end
639 end
640 end
641 -- now try blacklist
642 if not cleanedType then
643 for unit in pairs(blacklist) do
644 cleanedType = self:CureUnit(unit)
645 if cleanedType then
646 cleanedName = UnitName(unit)
647 blacklist[unit] = nil
648 break
649 end
650 end
651 end
652 -- now try enemies
653 if not cleanedType and spells["enemymagic"] then
654 for n, u in pairs(RL.roster) do
655 if UnitExists(u.unitid.."target") then
656 cleanedType = self:CureUnit(u.unitid.."target")
657 if cleanedType then
658 cleanedName = UnitName(u.unitid.."target")
659 self:Debug("dispelled enemy",cleanedName)
660 break
661 end
662 end
663 end
664 end
665 --restore original target
666 if targetIsEnemy then
667 if not UnitIsEnemy("target","player") then
668 TargetLastEnemy()
669 end
670 elseif targetFriendlyName then
671 if targetFriendlyName ~= UnitName("target") then
672 TargetByName(targetFriendlyName)
673 end
674 elseif UnitExists("target") then
675 ClearTarget()
676 end
677 -- restore self-cast
678 SetCVar("autoSelfCast", selfCast) -- maybe not do this here but on a delayed timer?
679 -- scrolling combat text notification
680 if cleanedType and cleanedName and self.db.profile.feedback then
681 local text = string.format(L["Cleaned %s"], cleanedName)
682 local color = DebuffTypeColor[cleanedType]
683 if SCT and SCT_MSG_FRAME then -- SCT 5.x
684 SCT_MSG_FRAME:AddMessage( text, color.r, color.g, color.b, 1 )
685 elseif MikSBT then -- Mik's Scrolling Battle Text(MSBT)
686 MikSBT.DisplayMessage(text, MikSBT.DISPLAYTYPE_NOTIFICATION, true, color.r * 255, color.g * 255, color.b * 255)
687 elseif CombatText_AddMessage then
688 -- CHECK FOR COMBAT TEXT
689 CombatText_AddMessage(text, COMBAT_TEXT_SCROLL_FUNCTION, color.r, color.g, color.b, nil, nil)
690 end
691 end
692 return cleanedType and true or false
693 end
694  
695  
696 function Detox:UnitCurable(unit)
697 if not UnitExists(unit) then return false end
698 if not UnitIsVisible(unit) then return false end
699 if Detox.db.profile.range and not CheckInteractDistance(unit, 4) and not curr_DemonType then return false end
700 -- check if unit is stealthed
701 if Detox.db.profile.ignoreStealthed then
702 for buff in pairs(invisible) do
703 if aura:UnitHasBuff(unit, buff) then return false end
704 end
705 end
706 -- check if we need to ignore unit
707 for debuff in pairs(ignore) do
708 if aura:UnitHasDebuff(unit, debuff) then return false end
709 end
710 -- check for debuffs that are buffs
711 for debuff in pairs(skip) do
712 if aura:UnitHasDebuff(unit, debuff) then return false end
713 end
714 -- check for debuffs we want to skip for specific classes when in combat
715 if UnitAffectingCombat("player") then
716 local _, class = UnitClass(unit)
717 if Detox.db.profile.skipByClass[class] then
718 for debuff in Detox.db.profile.skipByClass[class] do
719 if Detox.db.profile.skipByClass[class][debuff] then
720 if aura:UnitHasDebuff(unit, debuff) then return false end
721 end
722 end
723 end
724 end
725 if Detox.db.profile.checkAbolish and aura:UnitHasBuff(unit, BS["Abolish Poison"]) then return false end
726 if Detox.db.profile.checkAbolish and aura:UnitHasBuff(unit, BS["Abolish Disease"]) then return false end
727 return true
728 end
729  
730  
731 function Detox:CureUnit(unit)
732 if not self:UnitCurable(unit) then return false end
733 local cleaned = false
734 local skip
735 local type
736 -- check if we can clean a magic debuff
737 if (spells["magic"] or spells["enemymagic"]) and aura:UnitHasDebuffType(unit, "Magic") then
738 type = "Magic"
739 -- check for MC
740 if spells["enemymagic"] and UnitIsCharmed(unit) and UnitCanAttack("player", unit) then
741 cleaned = self:CastCuringSpell(spells["enemymagic"], unit)
742 elseif spells["magic"] and not UnitCanAttack("player", unit) then
743 cleaned = self:CastCuringSpell(spells["magic"], unit)
744 end
745 end
746 -- check if we can clean a curse
747 if not cleaned and spells["curse"] and not UnitIsCharmed(unit) and aura:UnitHasDebuffType(unit, "Curse") then
748 type = "Curse"
749 cleaned = self:CastCuringSpell(spells["curse"], unit)
750 end
751 -- check if we can clean poison
752 skip = ( Detox.db.profile.checkAbolish and aura:UnitHasBuff(unit, BS["Abolish Poison"]) ) or false
753 if not cleaned and ( spells["poison1"] or spells["poison2"] ) and not UnitIsCharmed(unit) and not skip and aura:UnitHasDebuffType(unit, "Poison") then
754 type = "Poison"
755 if spells["poison2"] and not aura:UnitHasBuff(unit, BS["Abolish Poison"]) then
756 cleaned = self:CastCuringSpell(spells["poison2"], unit)
757 else
758 cleaned = self:CastCuringSpell(spells["poison1"], unit)
759 end
760 end
761 -- check if we can use stoneform to clean our own poison
762 if not cleaned and unit == "player" and spells["poison3"] and aura:UnitHasDebuffType(unit, "Poison") then
763 type = "Poison"
764 cleaned = self:CastCuringSpell(spells["poison3"], unit)
765 end
766 -- check if we can clean disease
767 skip = ( Detox.db.profile.checkAbolish and aura:UnitHasBuff(unit, BS["Abolish Disease"]) ) or false
768 if not cleaned and ( spells["disease1"] or spells["disease2"] ) and not UnitIsCharmed(unit) and not skip and aura:UnitHasDebuffType(unit, "Disease") then
769 type = "Disease"
770 if spells["disease2"] and not aura:UnitHasBuff(unit, BS["Abolish Disease"]) then
771 cleaned = self:CastCuringSpell(spells["disease2"], unit)
772 else
773 cleaned = self:CastCuringSpell(spells["disease1"], unit)
774 end
775 end
776 if cleaned then return type else return nil end
777 end
778  
779  
780 function Detox:CastCuringSpell(spellID, unit)
781 if UnitIsDeadOrGhost("player") then return false end
782 if spellID[1] == 0 then return false end
783 if spellID[2] ~= BOOKTYPE_PET and not CheckInteractDistance(unit, 4) then return false end
784 local spellCasted = false
785 local castingOnTarget = false
786 -- check if we plan to clean our current target
787 if unit ~= "target" then
788 ClearTarget()
789 end
790 -- cast spell
791 castTarget = unit
792 CastSpell(spellID[1], spellID[2])
793 if SpellIsTargeting() then -- check if spell is usable
794 SpellTargetUnit(unit)
795 end
796 spellCasted = true
797 -- if spell is still targetting, we had a problem with casting on the unit
798 if SpellIsTargeting() then
799 SpellStopTargeting()
800 spellCasted = false
801 end
802 -- we're done
803 return spellCasted
804 end
805  
806  
807 --[[---------------------------------------------------------------------------------
808 Debuff skipping
809 ------------------------------------------------------------------------------------]]
810  
811  
812 function Detox:AddSkippedDebuff(debuffName)
813 for k, name in Detox.db.profile.debuffsInSkipList do
814 if name == debuffName then return end
815 end
816 table.insert(Detox.db.profile.debuffsInSkipList, debuffName)
817 self:PopulateSkipList()
818 end
819  
820 -- XXX Please note that we don't remove the class ignores for this debuff.
821 -- XXX Should we?
822 function Detox:RemoveSkippedDebuff(debuffName)
823 for k, name in Detox.db.profile.debuffsInSkipList do
824 if name == debuffName then
825 table.remove(Detox.db.profile.debuffsInSkipList, k)
826 end
827 end
828 self:PopulateSkipList()
829 end
830  
831  
832 function Detox:PopulateSkipList()
833 local classes = { "Warrior", "Priest", "Druid", "Shaman", "Paladin", "Mage", "Warlock", "Hunter", "Rogue" }
834  
835 if options.args.filter.args.debuff.args["add"] then
836 compost:Reclaim(options.args.filter.args.debuff.args["add"])
837 options.args.filter.args.debuff.args["add"] = nil
838  
839 for k, debuff in pairs(Detox.db.profile.debuffsInSkipList) do
840 local d = string.gsub(debuff, " ", "")
841 compost:Reclaim(options.args.filter.args.debuff.args["remove"][d])
842 options.args.filter.args.debuff.args["remove"][d] = nil
843 end
844 compost:Reclaim(options.args.filter.args.debuff.args["remove"])
845 options.args.filter.args.debuff.args["remove"] = nil
846 end
847  
848 -- |class| is also the spacer some time during the loop
849 for class in pairs(options.args.filter.args.debuff.args) do
850 for debuff in pairs(options.args.filter.args.debuff.args[class]) do
851 local d = string.gsub(debuff, " ", "")
852 -- If we just added a debuff, it will not be in the menu yet, so
853 -- check in case we try to reclaim it.
854 if options.args.filter.args.debuff.args[class][d] then
855 compost:Reclaim(options.args.filter.args.debuff.args[class][d])
856 options.args.filter.args.debuff.args[class][d] = nil
857 end
858 end
859 compost:Reclaim(options.args.filter.args.debuff.args[class])
860 options.args.filter.args.debuff.args[class] = nil
861 end
862  
863 -- create add option in dewdrop
864 options.args.filter.args.debuff.args["add"] = compost:Acquire()
865 options.args.filter.args.debuff.args["add"].type = 'text'
866 options.args.filter.args.debuff.args["add"].name = L["Add"]
867 options.args.filter.args.debuff.args["add"].desc = L["Adds a new debuff to the class submenus."]
868 options.args.filter.args.debuff.args["add"].get = false
869 options.args.filter.args.debuff.args["add"].set = function(v) self:AddSkippedDebuff(v) end
870 options.args.filter.args.debuff.args["add"].usage = L["<debuff name>"]
871 options.args.filter.args.debuff.args["add"].order = 101
872  
873 -- create remove option in dewdrop
874 options.args.filter.args.debuff.args["remove"] = compost:Acquire()
875 options.args.filter.args.debuff.args["remove"].type = 'group'
876 options.args.filter.args.debuff.args["remove"].name = L["Remove"]
877 options.args.filter.args.debuff.args["remove"].desc = L["Removes a debuff from the class submenus."]
878 options.args.filter.args.debuff.args["remove"].args = compost:Acquire()
879 options.args.filter.args.debuff.args["remove"].order = 102
880 -- create list of debuffs in subfolder
881 for k, debuff in pairs(Detox.db.profile.debuffsInSkipList) do
882 local debuffName = debuff
883 local d = string.gsub(debuffName, " ", "")
884 options.args.filter.args.debuff.args["remove"].args[d] = compost:Acquire()
885 options.args.filter.args.debuff.args["remove"].args[d].type = 'execute'
886 options.args.filter.args.debuff.args["remove"].args[d].name = debuffName
887 options.args.filter.args.debuff.args["remove"].args[d].desc = string.format(L["Remove %s from the class submenus."], debuffName)
888 options.args.filter.args.debuff.args["remove"].args[d].func = function() return self:RemoveSkippedDebuff(debuffName) end
889 end
890  
891 -- create spacer
892 options.args.filter.args.debuff.args.spacer = { type = "header", order = 103 }
893  
894 -- create list of debuffs
895 for k, debuff in pairs(Detox.db.profile.debuffsInSkipList) do
896 local debuffName = debuff
897 local d = string.gsub(debuffName, " ", "")
898 options.args.filter.args.debuff.args[d] = compost:Acquire()
899 options.args.filter.args.debuff.args[d].type = 'group'
900 options.args.filter.args.debuff.args[d].name = debuffName
901 options.args.filter.args.debuff.args[d].desc = string.format(L["Classes to filter for: %s."], debuffName)
902 options.args.filter.args.debuff.args[d].args = compost:Acquire()
903 options.args.filter.args.debuff.args[d].order = 200
904 -- create submenu for classes
905 for k, class in pairs(classes) do
906 local className = class
907 options.args.filter.args.debuff.args[d].args[className] = compost:Acquire()
908 options.args.filter.args.debuff.args[d].args[className].type = 'toggle'
909 options.args.filter.args.debuff.args[d].args[className].name = BC[className]
910 options.args.filter.args.debuff.args[d].args[className].desc = string.format(L["Toggle filtering %s on %s."], debuffName, BC[className])
911 options.args.filter.args.debuff.args[d].args[className].get =
912 function() return Detox.db.profile.skipByClass[string.upper(className)][debuffName] end
913 options.args.filter.args.debuff.args[d].args[className].set =
914 function()
915 Detox.db.profile.skipByClass[string.upper(className)][debuffName] = not Detox.db.profile.skipByClass[string.upper(className)][debuffName]
916 end
917 end
918 end
919  
920  
921  
922 end
923  
924  
925 --[[---------------------------------------------------------------------------------
926 Priority
927 ------------------------------------------------------------------------------------]]
928  
929 function Detox:PriorityPrint(target, priority)
930 if priority then
931 self:Print(string.format(L["%s was added to the priority list."], target))
932 else
933 self:Print(string.format(L["%s has been removed from the priority list."], target))
934 end
935 end
936  
937 function Detox:PriorityToggle(targetType)
938 if not UnitExists("target") then
939 self:Print(L["Can't add/remove current target to priority list, it doesn't exist."])
940 return
941 end
942 local target = UnitName("target")
943 if not targetType then
944 if not RL.roster[target] then
945 self:Print(L["Can't add/remove current target to priority list, it's not in your raid."])
946 return
947 end
948 Detox.db.profile.priorityNames[target] = not Detox.db.profile.priorityNames[target]
949 self:PriorityPrint(target, Detox.db.profile.priorityNames[target])
950 else
951 local targetGroup = nil
952 local targetClass = nil
953  
954 for n, u in pairs(RL.roster) do
955 if u and u.name and u.class ~= "PET" then
956 if not targetGroup and u.name == target then
957 targetGroup = u.subgroup
958 targetClass = u.class
959 break
960 end
961 end
962 end
963  
964 if targetType == "class" and targetClass then
965 Detox.db.profile.priorityClasses[targetClass] = not Detox.db.profile.priorityClasses[targetClass]
966 self:PriorityPrint(string.format(L["Class %s"], targetClass), Detox.db.profile.priorityClasses[targetClass])
967 elseif targetType == "group" and targetGroup then
968 Detox.db.profile.priorityGroups[targetGroup] = not Detox.db.profile.priorityGroups[targetGroup]
969 self:PriorityPrint(string.format(L["Group %s"], targetGroup), Detox.db.profile.priorityGroups[targetGroup])
970 else
971 self:Print(L["Can't add/remove current target to priority list, it's not in your raid."])
972 return
973 end
974 end
975 self:UpdatePriority()
976 end
977  
978 local function sortPri(a,b)
979 return a.pri > b.pri
980 end
981  
982 function Detox:UpdatePriority()
983 self:CreateOptionsTable()
984 -- update and sort all units. this task has a few steps:
985 local datasubtable, index
986 for name, u in pairs(RL.roster) do
987 -- see if that unit is in our priorityIndex table
988 index = priorityIndex[name]
989 -- either use the existing subtable or create a new one
990 datasubtable = index and priority[index] or compost:Acquire()
991 datasubtable.pri = self:GetPriority(name)
992 datasubtable.unitid = u.unitid
993 if not index then
994 -- our subtable still needs a name
995 datasubtable.name = name
996 -- add that subtable to the priority table
997 table.insert(priority, datasubtable)
998 priorityIndex[name] = table.getn(priority) -- maybe not needed, but for safety reasons it can't hurt.
999 end
1000 end
1001 -- now re-sort the priority table
1002 table.sort(priority, sortPri)
1003 -- save the current index for all names
1004 for k,v in ipairs(priority) do
1005 priorityIndex[v.name] = k
1006 end
1007 end
1008  
1009  
1010 function Detox:CreateOptionsTable()
1011 -- we need to update the dewdrop menu, so lets clear the old one and inject new stuff:
1012 for class in options.args.priority.args do
1013 for unit in options.args.priority.args[class].args do
1014 compost:Reclaim(options.args.priority.args[class].args[unit])
1015 options.args.priority.args[class].args[unit] = nil
1016 end
1017 compost:Reclaim(options.args.priority.args[class])
1018 options.args.priority.args[class] = nil
1019 end
1020  
1021 compost:Reclaim(options.args.priority.args["group"])
1022  
1023 options.args.priority.args["group"] = compost:Acquire()
1024 options.args.priority.args["group"].type = 'group'
1025 options.args.priority.args["group"].name = L["Groups"]
1026 options.args.priority.args["group"].desc = L["Prioritize by group."]
1027 options.args.priority.args["group"].order = 100
1028 options.args.priority.args["group"].args = compost:Acquire()
1029 for i = 1, 8 do
1030 local x = i
1031 options.args.priority.args["group"].args["group"..i] = compost:Acquire()
1032 options.args.priority.args["group"].args["group"..i].type = 'toggle'
1033 options.args.priority.args["group"].args["group"..i].name = string.format(L["Group %s"], i)
1034 options.args.priority.args["group"].args["group"..i].desc = string.format(L["Prioritize group %s."], i)
1035 options.args.priority.args["group"].args["group"..i].order = 100 + i
1036 options.args.priority.args["group"].args["group"..i].get =
1037 function() return Detox.db.profile.priorityGroups[x] end
1038 options.args.priority.args["group"].args["group"..i].set =
1039 function()
1040 Detox.db.profile.priorityGroups[x] = not Detox.db.profile.priorityGroups[x]
1041 Detox:UpdatePriority()
1042 end
1043 end
1044  
1045 -- create subgroups
1046 for name, unit in pairs(RL.roster) do
1047 if unit and unit.class ~= "PET" then
1048 local n = name
1049 local u = unit
1050 if not options.args.priority.args[u.class] then
1051 options.args.priority.args[u.class] = compost:Acquire()
1052 options.args.priority.args[u.class].type = 'group'
1053 -- convert class name. gah.
1054 -- we need this as Babble-Class doesnt know capitalized class names, nor does it know "PET"
1055 local c = strupper(strsub(u.class, 1, 1)) .. strlower(strsub(u.class, 2))
1056 if c ~= "Pet" then c = BC[c] end
1057 options.args.priority.args[u.class].name = c
1058 options.args.priority.args[u.class].desc = c
1059 options.args.priority.args[u.class].order = 101
1060 options.args.priority.args[u.class].args = compost:Acquire()
1061  
1062 -- Create "all <class>" item
1063 if not options.args.priority.args[u.class].args[c] then
1064 options.args.priority.args[u.class].args[c] = compost:Acquire()
1065 options.args.priority.args[u.class].args[c].type = 'toggle'
1066 options.args.priority.args[u.class].args[c].name = string.format(L["Every %s"], c)
1067 options.args.priority.args[u.class].args[c].desc = string.format(L["Prioritize every %s."], c)
1068 options.args.priority.args[u.class].args[c].order = 100
1069 options.args.priority.args[u.class].args[c].get =
1070 function() return Detox.db.profile.priorityClasses[u.class] end
1071 options.args.priority.args[u.class].args[c].set =
1072 function()
1073 Detox.db.profile.priorityClasses[u.class] = not Detox.db.profile.priorityClasses[u.class]
1074 Detox:UpdatePriority()
1075 end
1076 end
1077 end
1078 options.args.priority.args[u.class].args[n] = compost:Acquire()
1079 options.args.priority.args[u.class].args[n].type = 'toggle'
1080 options.args.priority.args[u.class].args[n].name = n
1081 options.args.priority.args[u.class].args[n].desc = string.format(L["Prioritize %s."], n)
1082 options.args.priority.args[u.class].args[n].order = 101
1083 options.args.priority.args[u.class].args[n].get =
1084 function() return Detox.db.profile.priorityNames[n] end
1085 options.args.priority.args[u.class].args[n].set =
1086 function()
1087 Detox.db.profile.priorityNames[n] = not Detox.db.profile.priorityNames[n]
1088 Detox:UpdatePriority()
1089 end
1090 end
1091 end
1092 end
1093  
1094  
1095 function Detox:GetPriority(name)
1096 local playergroup = (RL.roster[UnitName("player")] and RL.roster[UnitName("player")].subgroup) or 1
1097 local pri
1098 local grp = RL.roster[name].subgroup
1099 local cls = RL.roster[name].class
1100 -- priority range 1000 - 0.
1101 if name == UnitName("player") then pri = 800
1102 elseif playergroup == grp then pri = 700
1103 elseif playergroup < grp then pri = 700 - (grp - playergroup)*10
1104 else pri = 700 - (grp + 8 - playergroup)*10
1105 end
1106 if cls == "PET" then
1107 if Detox.db.profile.ignorePets then pri = 0 else pri = pri - 200 end
1108 end
1109 if Detox.db.profile.priorityNames[name] then pri = pri + 200 end -- unit in individual priority list
1110 if Detox.db.profile.priorityClasses[cls] then pri = pri + 200 end -- unit in prioritized class
1111 if Detox.db.profile.priorityGroups[grp] then pri = pri + 200 end -- unit in prioritized group
1112 return pri
1113 end
1114  
1115  
1116 --[[---------------------------------------------------------------------------------
1117 Live display and other FuBar stuff
1118 ------------------------------------------------------------------------------------]]
1119  
1120  
1121 function Detox:OnTooltipUpdate()
1122 local cat = nil
1123 local lines = 0
1124 for k,v in ipairs(priority) do
1125 if v.pri == 0 then break end
1126 if lines > Detox.db.profile.liveDisplay then break end
1127 local unit = v.unitid
1128 if UnitIsVisible(unit) and not blacklist[unit] and self:UnitCurable(unit) then
1129 for debuffname, count, type, texture in aura:DebuffIter(unit) do
1130 if ( not Detox.db.profile.filter and ( type == "Magic" or type == "Poison" or type == "Disease" or type == "Curse" ) )
1131 or ( type == "Magic" and spells["magic"] )
1132 or ( type == "Poison" and ( spells["poison1"] or spells["poison2"] ) )
1133 or ( type == "Disease" and ( spells["disease1"] or spells["disease2"] ) )
1134 or ( type == "Curse" and spells["curse"] )
1135 then
1136 local r,g,b = self:GetRaidColors(unit)
1137 if count == 0 then count = 1 end
1138 lines = lines + 1
1139 if not cat then
1140 cat = tablet:AddCategory("columns", 2, "text", "", "text2", "", "showWithoutChildren", false, "hideBlankLine", false)
1141 end
1142 cat:AddLine(
1143 "text", string.format("%s (%dx)", debuffname, count),
1144 "textR", DebuffTypeColor[type].r,
1145 "textG", DebuffTypeColor[type].g,
1146 "textB", DebuffTypeColor[type].b,
1147 "text2", UnitName(unit),
1148 "text2R", r,
1149 "text2G", g,
1150 "text2B", b,
1151 "hasCheck", true,
1152 "checked", true,
1153 "checkIcon", texture,
1154 "func", function(unit) Detox:CureUnit(unit) end,
1155 "arg1", unit,
1156 "justify", "LEFT",
1157 "justify2", "LEFT"
1158 )
1159 -- update fubar display (icon + text) to display the first
1160 -- debuff we find.
1161 if lines == 1 then
1162 self:SetIcon(texture)
1163 self:SetText(string.format("|cff%02x%02x%02x%s|r", DebuffTypeColor[type].r*255, DebuffTypeColor[type].g*255, DebuffTypeColor[type].b*255, UnitName(unit)))
1164 end
1165 end
1166 end
1167 end
1168 end
1169 -- play sound
1170 if Detox.db.profile.sound then
1171 if lines == 0 then
1172 soundPlayed = false
1173 elseif not soundPlayed then
1174 PlaySoundFile("Sound\\interface\\AuctionWindowOpen.wav")
1175 soundPlayed = true
1176 end
1177 end
1178 -- Reset FuBar text and display
1179 if lines == 0 then
1180 self:SetIcon(true)
1181 self:SetText("Detox")
1182 end
1183 -- Show the prioritized units
1184 if Detox.db.profile.showPriorities then
1185 local priCat = nil
1186 local text = nil
1187 for k,v in ipairs(priority) do
1188 if v.pri > 700 then
1189 if not priCat then priCat = tablet:AddCategory("columns", 1, "text", L["Priorities"]) end
1190 local name = v.name
1191 if not text then
1192 text = name
1193 else
1194 text = text..", "..name
1195 end
1196 end
1197 end
1198 if priCat then priCat:AddLine("text", text..".", "wrap", true) end
1199 end
1200 end
1201  
1202  
1203 function Detox:OnClick()
1204 self:Clean()
1205 end
1206  
1207  
1208 function Detox:GetRaidColors(unit)
1209 local _,class = UnitClass(unit)
1210 if RAID_CLASS_COLORS[class] then
1211 return RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b
1212 else
1213 return 0.5, 0.5, 0.5
1214 end
1215 end