vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
--[[---------------------------------------------------------------------------------
 Detox
 written by Maia/Proudmoore, 
 code based on the Decursive 1.9.7 core code by Archarodim and Quu.
 The name 'Detox' was provided by Tem.
------------------------------------------------------------------------------------]]

local vmajor,vminor = "0.4", tonumber(string.sub("$Revision: 12266 $", 12, -3))

--[[---------------------------------------------------------------------------------
 locals
------------------------------------------------------------------------------------]]

local compost           = AceLibrary("Compost-2.0")
local L                 = AceLibrary("AceLocale-2.0"):new("Detox")
local BS                = AceLibrary("Babble-Spell-2.0")
local BC                = AceLibrary("Babble-Class-2.0")
local tablet            = AceLibrary("Tablet-2.0")
local aura              = nil -- AceLibrary("SpecialEvents-Aura-2.0")
local RL                = nil -- AceLibrary("RosterLib-2.0")

local last_DemonType    = nil
local curr_DemonType    = nil
local castTarget        = nil
local soundPlayed       = false
local hasSpells         = false
local blacklist         = {}
local outofrange        = {}
local spells            = {} -- will be filled with available spells
local priorityIndex     = {} -- this table references name to index of the priority table
local priority          = {} -- this integer keyed table will hold subtables containing name, unitid and priority for each unit in our roster

local DebuffTypeColor      = {}
DebuffTypeColor["none"]    = { r = 0.80, g = 0, b = 0 }
DebuffTypeColor["Magic"]   = { r = 0.20, g = 0.60, b = 1.00 }
DebuffTypeColor["Curse"]   = { r = 0.60, g = 0.00, b = 1.00 }
DebuffTypeColor["Disease"] = { r = 0.60, g = 0.40, b = 0 }
DebuffTypeColor["Poison"]  = { r = 0.00, g = 0.60, b = 0 }

local invisible = {
        [BS["Prowl"]]                 = true,
        [BS["Stealth"]]               = true,
        [BS["Shadowmeld"]]            = true,
}

local ignore = {
        [BS["Banish"]]                = true,
        [BS["Phase Shift"]]           = true,
        [L["Frost Trap Aura"]]        = true,
}

local skip = {
        [L["Dreamless Sleep"]]        = true,
        [L["Greater Dreamless Sleep"]] = true,
}

local cleaningSpells = {
        [BS["Cure Disease"]]          = true,
        [BS["Abolish Disease"]]       = true,
        [BS["Purify"]]                = true,
        [BS["Cleanse"]]               = true,
        [BS["Dispel Magic"]]          = true,
        [BS["Cure Poison"]]           = true,
        [BS["Abolish Poison"]]        = true,
        [BS["Remove Lesser Curse"]]   = true,
        [BS["Remove Curse"]]          = true,
        [BS["Purge"]]                 = true,
        [BS["Devour Magic"]]          = true,
        [BS["Stoneform"]]             = true,
}

BINDING_HEADER_DETOX              = "Detox"
BINDING_NAME_CLEAN                = L["Clean group"]
BINDING_NAME_PRIORITYTOGGLE       = L["Toggle target priority"]
BINDING_NAME_PRIORITYTOGGLEGROUP  = L["Toggle target group priority"]
BINDING_NAME_PRIORITYTOGGLECLASS  = L["Toggle target class priority"]

--[[---------------------------------------------------------------------------------
 defaults and AceOptions table
------------------------------------------------------------------------------------]]

local defaults = {
        showPriorities = true,
        ignoreStealthed = true,
        blacklistTime = 5,
        checkAbolish = true,
        ignorePets = false,
        liveDisplay = 5,
        filter = true,
        range = true,
        sound = true,
        feedback = true,
        updateSpeed = 0.5,
        priorityNames = {},
        priorityGroups = {},
        priorityClasses = {},
        debuffsInSkipList = {
                BS["Silence"],
                L["Ancient Hysteria"],
                L["Ignite Mana"],
                L["Tainted Mind"],
                L["Magma Shackles"],
                L["Cripple"],
                L["Dust Cloud"],
                L["Widow's Embrace"],
                L["Curse of Tongues"],
                L["Sonic Burst"],
                L["Thunderclap"],
                L["Delusions of Jin'do"]
        },
        skipByClass = {
                ["WARRIOR"] = {
                        [L["Ancient Hysteria"]]   = true,
                        [L["Ignite Mana"]]        = true,
                        [L["Tainted Mind"]]       = true,
                        [L["Widow's Embrace"]]    = true,
                        [L["Curse of Tongues"]]   = true,
                        [L["Delusions of Jin'do"]]= true,
                },
                ["ROGUE"] = {
                        [BS["Silence"]]           = true,
                        [L["Ancient Hysteria"]]   = true,
                        [L["Ignite Mana"]]        = true,
                        [L["Tainted Mind"]]       = true,
                        [L["Widow's Embrace"]]    = true,
                        [L["Curse of Tongues"]]   = true,
                        [L["Sonic Burst"]]        = true,
                        [L["Delusions of Jin'do"]]= true,
                },
                ["HUNTER"] = {
                        [L["Magma Shackles"]]     = true,
                        [L["Delusions of Jin'do"]]= true,
                },
                ["MAGE"] = {
                        [L["Magma Shackles"]]     = true,
                        [L["Cripple"]]            = true,
                        [L["Dust Cloud"]]         = true,
                        [L["Thunderclap"]]        = true,
                        [L["Delusions of Jin'do"]]= true,
                },
                ["WARLOCK"] = {
                        [L["Cripple"]]            = true,
                        [L["Dust Cloud"]]         = true,
                        [L["Thunderclap"]]        = true,
                        [L["Delusions of Jin'do"]]= true,
                },
                ["DRUID"] = {
                        [L["Cripple"]]            = true,
                        [L["Dust Cloud"]]         = true,
                        [L["Thunderclap"]]        = true,
                        [L["Delusions of Jin'do"]]= true,
                },
                ["PALADIN"] = {
                        [L["Cripple"]]            = true,
                        [L["Dust Cloud"]]         = true,
                        [L["Delusions of Jin'do"]]= true,
                },
                ["PRIEST"] = {
                        [L["Cripple"]]            = true,
                        [L["Dust Cloud"]]         = true,
                        [L["Thunderclap"]]        = true,
                        [L["Delusions of Jin'do"]]= true,
                },
                ["SHAMAN"] = {
                        [L["Cripple"]]            = true,
                        [L["Dust Cloud"]]         = true,
                        [L["Delusions of Jin'do"]]= true,
                }
        }
}

local options = {
        type = 'group',
        args = {
                clean = {
                        type = 'execute',
                        name = L["Clean group"],
                        desc = L["Will attempt to clean a player in your raid/party."],
                        func = function()
                                Detox:Clean()
                        end,
                        order = 101,
                },
                spacer = { type = "header", order = 102 },
                livelistoptions = {
                        type = 'group',
                        name = L["Live list"],
                        desc = L["Options for the live list."],
                        order = 110,
                        args = {
                                show = {
                                        type = 'toggle',
                                        name = L["Show live list"],
                                        desc = L["Detaches the live list from the Detox icon."],
                                        get = function() return Detox:IsTooltipDetached() end,
                                        set = function() Detox:ToggleTooltipDetached() end,
                                        order = 100
                                },
                                sound = {
                                        type = 'toggle',
                                        name = L["Play sound if unit needs decursing"],
                                        desc = L["Play sound if unit needs decursing"]..".",
                                        get = function() return Detox.db.profile.sound end,
                                        set = function()
                                                Detox.db.profile.sound = not Detox.db.profile.sound
                                        end,
                                        order = 101
                                },
                                showpriorizations = {
                                        type = 'toggle',
                                        name = L["Show priorities"],
                                        desc = L["Displays who is prioritized in the live list."],
                                        get = function() return Detox.db.profile.showPriorities end,
                                        set = function() Detox.db.profile.showPriorities = not Detox.db.profile.showPriorities end,
                                        order = 102,
                                },
                                live = {
                                        type = 'range',
                                        name = L["Max debuffs shown"],
                                        desc = L["Defines the max number of debuffs to display in the live list."],
                                        get = function() return Detox.db.profile.liveDisplay end,
                                        set = function(v) 
                                                Detox.db.profile.liveDisplay = v
                                        end,
                                        min = 0,
                                        max = 20,
                                        step = 1,
                                        isPercent = false,
                                        order = 103,
                                },
                                speed = {
                                        type = 'range',
                                        name = L["Update speed"],
                                        desc = L["Defines the speed the live list is updated, in seconds."],
                                        get = function() return Detox.db.profile.updateSpeed end,
                                        set = function(v) 
                                                Detox.db.profile.updateSpeed = v
                                                Detox:CancelScheduledEvent("liveList")
                                                Detox:ScheduleRepeatingEvent("liveList", Detox.Update, v, Detox)
                                        end,
                                        min = 0.2,
                                        max = 2,
                                        step = 0.1,
                                        isPercent = false,
                                        order = 104,
                                },
                        },
                },
                priority = {
                        type = 'group',
                        name = L["Priority"],
                        desc = L["These units will be priorized when curing."],
                        order = 120,
                        args = {}
                },
                filter = {
                        type = 'group',
                        name = L["Filter"],
                        desc = L["Options for filtering various debuffs and conditions."],
                        order = 130,
                        args = {
                                filter = {
                                        type = 'toggle',
                                        name = L["Filter by type"],
                                        desc = L["Only show debuffs you can cure."],
                                        get = function() return Detox.db.profile.filter end,
                                        set = function()
                                                Detox.db.profile.filter = not Detox.db.profile.filter
                                        end,
                                        order = 103,
                                },
                                range = {
                                        type = 'toggle',
                                        name = L["Filter by range"],
                                        desc = L["Only show units in range."],
                                        get = function() return Detox.db.profile.range end,
                                        set = function()
                                                Detox.db.profile.range = not Detox.db.profile.range
                                        end,
                                        order = 104
                                },
                                stealth = {
                                        type = 'toggle',
                                        name = L["Filter stealthed units"],
                                        desc = L["It is recommended not to cure stealthed units."],
                                        get = function() return Detox.db.profile.ignoreStealthed end,
                                        set = function()
                                                Detox.db.profile.ignoreStealthed = not Detox.db.profile.ignoreStealthed
                                        end,
                                        order = 105
                                },
                                abolish = {
                                        type = 'toggle',
                                        name = L["Filter Abolished units"],
                                        desc = L["Skip units that have an active Abolish buff."],
                                        get = function() return Detox.db.profile.checkAbolish end,
                                        set = function()
                                                Detox.db.profile.checkAbolish = not Detox.db.profile.checkAbolish
                                        end,
                                        order = 106
                                },
                                pets = {
                                        type = 'toggle',
                                        name = L["Filter pets"],
                                        desc = L["Pets are also your friends."],
                                        get = function() return Detox.db.profile.ignorePets end,
                                        set = function()
                                                Detox.db.profile.ignorePets = not Detox.db.profile.ignorePets
                                                Detox:UpdatePriority()
                                        end,
                                        order = 107
                                },
                                debuff = {
                                        type = 'group',
                                        name = L["Debuff"],
                                        desc = L["Filter by debuff and class."],
                                        order = 400,
                                        args = {}
                                },
                        },
                },
                blacklist = {
                        type = 'range',
                        name = L["Seconds to blacklist"],
                        desc = L["Units that are out of Line of Sight will be blacklisted for the set duration."],
                        get = function() return Detox.db.profile.blacklistTime end,
                        set = function(v) 
                                Detox.db.profile.blacklistTime = v
                        end,
                        min = 1,
                        max = 20,
                        step = 1,
                        isPercent = false,
                        order = 140
                },
                feedback = {
                        type = 'toggle',
                        name = L["Show detoxing in scrolling combat frame"],
                        desc = L["This will use SCT5 when available, otherwise Blizzards Floating Combat Text."],
                        get = function() return Detox.db.profile.feedback end,
                        set = function()
                                Detox.db.profile.feedback = not Detox.db.profile.feedback
                        end,
                        order = 150
                },
        },
}

--[[---------------------------------------------------------------------------------
 Initialization
------------------------------------------------------------------------------------]]

Detox = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceDB-2.0", "AceConsole-2.0", "AceDebug-2.0", "FuBarPlugin-2.0")

-- stuff for FuBar:
Detox.hasIcon = true
Detox.defaultPosition = "LEFT"
Detox.defaultMinimapPosition = 250
Detox.cannotDetachTooltip = false
Detox.tooltipHiddenWhenEmpty = true
Detox.hideWithoutStandby = true
Detox.clickableTooltip = true
Detox.independentProfile = true

function Detox:OnInitialize()
        self:RegisterDB("DetoxDB")
        self:RegisterDefaults('profile', defaults )
        self:RegisterChatCommand({'/detox'}, options )
        self.OnMenuRequest = options
        self.OnMenuRequest.args.lockTooltip.hidden = true
        self.OnMenuRequest.args.detachTooltip.hidden = true
        if not FuBar then
                self.OnMenuRequest.args.hide.guiName = "Hide minimap icon"
                self.OnMenuRequest.args.hide.desc = "Hide minimap icon"
        end
end


function Detox:OnEnable()
        if not aura then aura = AceLibrary("SpecialEvents-Aura-2.0") end
        if not RL then RL = AceLibrary("RosterLib-2.0") end

        self:RegisterEvent("SPELLCAST_STOP")
        self:RegisterEvent("UNIT_PET",1)
        self:RegisterEvent("SPELLS_CHANGED","RescanSpellbook",1)
        self:RegisterEvent("LEARNED_SPELL_IN_TAB","RescanSpellbook",1)
        self:RegisterEvent("UI_ERROR_MESSAGE")
        self:RegisterEvent("RosterLib_RosterChanged")
        self:ParseSpellbook()
        self:ScheduleRepeatingEvent(self.OnUpdate, 1, self)
        self:ScheduleRepeatingEvent("liveList", self.Update, Detox.db.profile.updateSpeed or 0.5, self) -- this is the FuBar live list update!
        self.debugging = self:IsDebugging()
        self:PopulateSkipList()
end


function Detox:OnDisable()
        local detached = self:IsTooltipDetached()
        if detached then
                self:ReattachTooltip()
                self.db.profile.detachedTooltip.detached = true
        end
end


function Detox:OnClick()  -- FuBarPlugin
        self:Clean()
end

function Detox:IsDebugging() return self.db.profile.debugging end
function Detox:SetDebugging(debugging) self.db.profile.debugging = debugging; self.debugging = debugging; end

--[[---------------------------------------------------------------------------------
 Events
------------------------------------------------------------------------------------]]

function Detox:OnUpdate()
        self:UpdateBlacklist()
end


function Detox:UpdateBlacklist()
        for unit in pairs(blacklist) do
                blacklist[unit] = blacklist[unit] - 1
                if blacklist[unit] < 0 then
                        blacklist[unit] = nil
                end
        end
end


function Detox:RosterLib_RosterChanged(tbl)
        for name in pairs(tbl) do
                local u = tbl[name]
                if not u.name then -- someone left the raid
                        for i = table.getn(priority), 1, -1 do
                                if not RL.roster[priority[i].name] then
                                        priorityIndex[priority[i].name] = nil
                                        compost:Reclaim(priority[i])
                                        priority[i] = nil
                                        table.remove(priority, i)
                                end
                        end
                end
        end
        self:UpdatePriority()
end


function Detox:SPELLCAST_STOP()
        castTarget = nil
end


function Detox:UNIT_PET(unit)
        if unit == "player" then
                self:ScheduleEvent(self.CheckPet, 2, self)
        end
end


function Detox:UI_ERROR_MESSAGE(arg1)
        if arg1 == SPELL_FAILED_LINE_OF_SIGHT then
                if castTarget and not UnitIsUnit(castTarget, "player") then
                        blacklist[castTarget] = Detox.db.profile.blacklistTime
                end
        end
end


--[[---------------------------------------------------------------------------------
 Spellbook
------------------------------------------------------------------------------------]]


function Detox:ParseSpellbook()
        compost:Reclaim(spells,1)
        spells = compost:Acquire()
        hasSpells = false
        local BookType    = BOOKTYPE_SPELL
        local break_flag  = false
        local i = 1
        local maxRankMagic      = 0
        local maxRankEnemyMagic = 0
        while not break_flag do
                while (true) do --  HUH?
                        local name, rank = GetSpellName(i, BookType)
                        if not name then
                                if BookType == BOOKTYPE_PET then
                                        break_flag = true
                                        break
                                end
                                BookType = BOOKTYPE_PET
                                i = 1
                                break
                        end
                        if cleaningSpells[name] then
                                hasSpells = true
                                spells["cooldown"] = compost:Acquire(i, BookType)
                                if name == BS["Cure Disease"] or name == BS["Purify"] then
                                        spells["disease1"] = compost:Acquire(i, BookType, name)
                                end
                                if name == BS["Abolish Disease"] or name == BS["Cleanse"] then
                                        spells["disease2"] = compost:Acquire(i, BookType, name)
                                end
                                if name == BS["Cure Poison"] or name == BS["Purify"] then
                                        spells["poison1"] = compost:Acquire(i, BookType, name)
                                end
                                if name == BS["Abolish Poison"] or name == BS["Cleanse"] then
                                        spells["poison2"] = compost:Acquire(i, BookType, name)
                                end
                                if name == BS["Remove Curse"] or name == BS["Remove Lesser Curse"] then
                                        spells["curse"] = compost:Acquire(i, BookType, name)
                                end
                                if name == BS["Stoneform"] then
                                        spells["poison3"] = compost:Acquire(i, BookType, name)
                                end
                                if name == BS["Cleanse"] or name == BS["Dispel Magic"] or name == BS["Devour Magic"] then
                                        if rank and string.find(rank, L["Rank (%d+)"]) then
                                                local ranknum = string.gsub(rank, L["Rank (%d+)"], "%1")
                                                if tonumber(ranknum) > maxRankMagic then
                                                        spells["magic"] = compost:Acquire(i, BookType, name)
                                                        maxRankMagic = tonumber(ranknum)
                                                end
                                        else
                                                spells["magic"] = compost:Acquire(i, BookType, name)
                                        end
                                end
                                if name == BS["Dispel Magic"] or name == BS["Purge"] or name == BS["Devour Magic"] then
                                        if rank and string.find(rank, L["Rank (%d+)"]) then
                                                local ranknum = string.gsub(rank, L["Rank (%d+)"], "%1")
                                                if tonumber(ranknum) > maxRankEnemyMagic then
                                                        spells["enemymagic"] = compost:Acquire(i, BookType, name)
                                                        maxRankEnemyMagic = tonumber(ranknum)
                                                end
                                        else
                                                spells["enemymagic"] = compost:Acquire(i, BookType, name)
                                        end
                                end
                        end
                        i = i + 1
                end
        end
end


function Detox:RescanSpellbook()
        if not hasSpells then return end
        local valid = true
        for spell in pairs(spells) do
                if GetSpellName(spells[spell][1], spells[spell][2]) ~= spells[spell][3] then 
                        valid = false
                end
        end
        if not valid then
                self:ParseSpellbook()
        end
end


function Detox:CheckPet()
        curr_DemonType = UnitCreatureFamily("pet")
        if last_DemonType ~= curr_DemonType then
                last_DemonType = curr_DemonType
                self:ParseSpellbook()
        end
end


--[[---------------------------------------------------------------------------------
 Cleaning
------------------------------------------------------------------------------------]]


function Detox:Clean()
        if not hasSpells then
                self:ParseSpellbook()
                if not hasSpells then
                        return false
                end
        end
        -- check for cooldown
        local _, cooldown = GetSpellCooldown(spells["cooldown"][1], spells["cooldown"][2])
        if cooldown ~= 0 then
                return false
        end
        -- temporarily disable selfcasting
        local selfCast = GetCVar("autoSelfCast")
        SetCVar("autoSelfCast", "0")
        local cleanedType = nil
        local cleanedName = nil
        castTarget = nil
        -- save original target
        local targetIsEnemy = false
        local targetFriendlyName = nil
        if UnitExists("target") then
                if UnitIsFriend("target","player") then 
                        targetFriendlyName = UnitName("target")
                else 
                        targetIsEnemy = true
                end
        end     
        if SpellIsTargeting() then SpellStopTargeting() end
        -- try to clean target first
        if UnitExists("target") then
                cleanedType = self:CureUnit("target")
                if cleanedType then
                        cleanedName = UnitName("target")
                end
        end
        -- couldn't clean target, check for MCd units
        if not cleanedType and spells["enemymagic"] then
                for k,v in ipairs(priority) do
                        if v.pri == 0 then break end
                        local unit = v.unitid
                        if      UnitIsCharmed(unit) 
                        and not blacklist[unit] then
                                cleanedType = self:CureUnit(unit)
                                if cleanedType then
                                        cleanedName = UnitName(unit)
                                        break
                                end
                        end
                end
        end
        -- now do the rest (this is duplicated code, gah!)
        if not cleanedType then
                for k,v in ipairs(priority) do
                        if v.pri == 0 then break end
                        local unit = v.unitid
                        if  not blacklist[unit] 
                        and not UnitIsCharmed(unit) then
                                cleanedType = self:CureUnit(unit)
                                if cleanedType then
                                        cleanedName = UnitName(unit)
                                        break
                                end
                        end
                end     
        end     
        -- now try blacklist
        if not cleanedType then
                for unit in pairs(blacklist) do
                        cleanedType = self:CureUnit(unit)
                        if cleanedType then
                                cleanedName = UnitName(unit)
                                blacklist[unit] = nil
                                break
                        end
                end
        end
        -- now try enemies
        if not cleanedType and spells["enemymagic"] then
                for n, u in pairs(RL.roster) do
                        if UnitExists(u.unitid.."target") then
                                cleanedType = self:CureUnit(u.unitid.."target")
                                if cleanedType then
                                        cleanedName = UnitName(u.unitid.."target")
                                        self:Debug("dispelled enemy",cleanedName)
                                        break
                                end
                        end
                end
        end
        --restore original target
        if targetIsEnemy then
                if not UnitIsEnemy("target","player") then 
                        TargetLastEnemy() 
                end
        elseif targetFriendlyName then
                if targetFriendlyName ~= UnitName("target") then 
                        TargetByName(targetFriendlyName) 
                end
        elseif UnitExists("target") then 
                ClearTarget() 
        end
        -- restore self-cast
        SetCVar("autoSelfCast", selfCast)  -- maybe not do this here but on a delayed timer?
        -- scrolling combat text notification
        if cleanedType and cleanedName and self.db.profile.feedback then
                local text = string.format(L["Cleaned %s"], cleanedName)
                local color = DebuffTypeColor[cleanedType]
                if SCT and SCT_MSG_FRAME then -- SCT 5.x
                        SCT_MSG_FRAME:AddMessage( text, color.r, color.g, color.b, 1 )
                elseif MikSBT then -- Mik's Scrolling Battle Text(MSBT)
                        MikSBT.DisplayMessage(text, MikSBT.DISPLAYTYPE_NOTIFICATION, true, color.r * 255, color.g * 255, color.b * 255)
                elseif CombatText_AddMessage then
                        -- CHECK FOR COMBAT TEXT
                        CombatText_AddMessage(text, COMBAT_TEXT_SCROLL_FUNCTION, color.r, color.g, color.b, nil, nil)
                end
        end
        return cleanedType and true or false
end


function Detox:UnitCurable(unit)
        if not UnitExists(unit) then return false end
        if not UnitIsVisible(unit) then return false end
        if Detox.db.profile.range and not CheckInteractDistance(unit, 4) and not curr_DemonType then return false end
        -- check if unit is stealthed
        if Detox.db.profile.ignoreStealthed then
                for buff in pairs(invisible) do
                        if aura:UnitHasBuff(unit, buff) then return false end
                end
        end
        -- check if we need to ignore unit
        for debuff in pairs(ignore) do
                if aura:UnitHasDebuff(unit, debuff) then return false end
        end
        -- check for debuffs that are buffs
        for debuff in pairs(skip) do
                if aura:UnitHasDebuff(unit, debuff) then return false end
        end
        -- check for debuffs we want to skip for specific classes when in combat
        if UnitAffectingCombat("player") then
                local _, class = UnitClass(unit)
                if Detox.db.profile.skipByClass[class] then
                        for debuff in Detox.db.profile.skipByClass[class] do
                                if Detox.db.profile.skipByClass[class][debuff] then
                                        if aura:UnitHasDebuff(unit, debuff) then return false end
                                end
                        end
                end
        end
        if Detox.db.profile.checkAbolish and aura:UnitHasBuff(unit, BS["Abolish Poison"]) then return false end
        if Detox.db.profile.checkAbolish and aura:UnitHasBuff(unit, BS["Abolish Disease"]) then return false end
        return true
end


function Detox:CureUnit(unit)
        if not self:UnitCurable(unit) then return false end
        local cleaned = false
        local skip
        local type
        -- check if we can clean a magic debuff
        if (spells["magic"] or spells["enemymagic"]) and aura:UnitHasDebuffType(unit, "Magic") then
                type = "Magic"
                -- check for MC
                if spells["enemymagic"] and UnitIsCharmed(unit) and UnitCanAttack("player", unit) then
                                cleaned = self:CastCuringSpell(spells["enemymagic"], unit)
                elseif spells["magic"] and not UnitCanAttack("player", unit) then
                                cleaned = self:CastCuringSpell(spells["magic"], unit)
                end
        end
        -- check if we can clean a curse
        if not cleaned and spells["curse"] and not UnitIsCharmed(unit) and aura:UnitHasDebuffType(unit, "Curse") then
                type = "Curse"
                cleaned = self:CastCuringSpell(spells["curse"], unit)
        end
        -- check if we can clean poison
        skip = ( Detox.db.profile.checkAbolish and aura:UnitHasBuff(unit, BS["Abolish Poison"]) ) or false
        if not cleaned and ( spells["poison1"] or spells["poison2"] ) and not UnitIsCharmed(unit) and not skip and aura:UnitHasDebuffType(unit, "Poison") then
                type = "Poison"
                if spells["poison2"] and not aura:UnitHasBuff(unit, BS["Abolish Poison"]) then
                        cleaned = self:CastCuringSpell(spells["poison2"], unit)
                else
                        cleaned = self:CastCuringSpell(spells["poison1"], unit)
                end
        end
        -- check if we can use stoneform to clean our own poison
        if not cleaned and unit == "player" and spells["poison3"] and aura:UnitHasDebuffType(unit, "Poison") then
                type = "Poison"
                cleaned = self:CastCuringSpell(spells["poison3"], unit)
        end
        -- check if we can clean disease
        skip = ( Detox.db.profile.checkAbolish and aura:UnitHasBuff(unit, BS["Abolish Disease"]) ) or false
        if not cleaned and ( spells["disease1"] or spells["disease2"] ) and not UnitIsCharmed(unit) and not skip and aura:UnitHasDebuffType(unit, "Disease") then
                type = "Disease"
                if spells["disease2"] and not aura:UnitHasBuff(unit, BS["Abolish Disease"]) then
                        cleaned = self:CastCuringSpell(spells["disease2"], unit)
                else
                        cleaned = self:CastCuringSpell(spells["disease1"], unit)
                end
        end
        if cleaned then return type else return nil end
end


function Detox:CastCuringSpell(spellID, unit)
        if UnitIsDeadOrGhost("player") then return false end
        if spellID[1] == 0 then return false end
        if spellID[2] ~= BOOKTYPE_PET and not CheckInteractDistance(unit, 4) then return false end
        local spellCasted = false
        local castingOnTarget = false
        -- check if we plan to clean our current target
        if unit ~= "target" then 
                ClearTarget()
        end
        -- cast spell
        castTarget = unit
        CastSpell(spellID[1], spellID[2])
        if SpellIsTargeting() then -- check if spell is usable
                SpellTargetUnit(unit)
        end
        spellCasted = true
        -- if spell is still targetting, we had a problem with casting on the unit
        if SpellIsTargeting() then 
                SpellStopTargeting()
                spellCasted = false
        end
        -- we're done
        return spellCasted
end


--[[---------------------------------------------------------------------------------
 Debuff skipping
------------------------------------------------------------------------------------]]


function Detox:AddSkippedDebuff(debuffName)
        for k, name in Detox.db.profile.debuffsInSkipList do
                if name == debuffName then return end
        end
        table.insert(Detox.db.profile.debuffsInSkipList, debuffName)
        self:PopulateSkipList()
end

-- XXX Please note that we don't remove the class ignores for this debuff.
-- XXX Should we?
function Detox:RemoveSkippedDebuff(debuffName)
        for k, name in Detox.db.profile.debuffsInSkipList do
                if name == debuffName then
                        table.remove(Detox.db.profile.debuffsInSkipList, k)
                end
        end
        self:PopulateSkipList()
end


function Detox:PopulateSkipList()
        local classes = { "Warrior", "Priest", "Druid", "Shaman", "Paladin", "Mage", "Warlock", "Hunter", "Rogue" }

        if options.args.filter.args.debuff.args["add"] then
                compost:Reclaim(options.args.filter.args.debuff.args["add"])
                options.args.filter.args.debuff.args["add"] = nil

                for k, debuff in pairs(Detox.db.profile.debuffsInSkipList) do
                        local d = string.gsub(debuff, " ", "")
                        compost:Reclaim(options.args.filter.args.debuff.args["remove"][d])
                        options.args.filter.args.debuff.args["remove"][d] = nil
                end
                compost:Reclaim(options.args.filter.args.debuff.args["remove"])
                options.args.filter.args.debuff.args["remove"] = nil
        end

        -- |class| is also the spacer some time during the loop
        for class in pairs(options.args.filter.args.debuff.args) do
                for debuff in pairs(options.args.filter.args.debuff.args[class]) do
                        local d = string.gsub(debuff, " ", "")
                        -- If we just added a debuff, it will not be in the menu yet, so
                        -- check in case we try to reclaim it.
                        if options.args.filter.args.debuff.args[class][d] then
                                compost:Reclaim(options.args.filter.args.debuff.args[class][d])
                                options.args.filter.args.debuff.args[class][d] = nil
                        end
                end
                compost:Reclaim(options.args.filter.args.debuff.args[class])
                options.args.filter.args.debuff.args[class] = nil
        end

        -- create add option in dewdrop
        options.args.filter.args.debuff.args["add"] = compost:Acquire()
        options.args.filter.args.debuff.args["add"].type = 'text'
        options.args.filter.args.debuff.args["add"].name = L["Add"]
        options.args.filter.args.debuff.args["add"].desc = L["Adds a new debuff to the class submenus."]
        options.args.filter.args.debuff.args["add"].get = false
        options.args.filter.args.debuff.args["add"].set = function(v) self:AddSkippedDebuff(v) end
        options.args.filter.args.debuff.args["add"].usage = L["<debuff name>"]
        options.args.filter.args.debuff.args["add"].order = 101

        -- create remove option in dewdrop
        options.args.filter.args.debuff.args["remove"] = compost:Acquire()
        options.args.filter.args.debuff.args["remove"].type = 'group'
        options.args.filter.args.debuff.args["remove"].name = L["Remove"]
        options.args.filter.args.debuff.args["remove"].desc = L["Removes a debuff from the class submenus."]
        options.args.filter.args.debuff.args["remove"].args = compost:Acquire()
        options.args.filter.args.debuff.args["remove"].order = 102
        -- create list of debuffs in subfolder
        for k, debuff in pairs(Detox.db.profile.debuffsInSkipList) do
                local debuffName = debuff
                local d = string.gsub(debuffName, " ", "")
                options.args.filter.args.debuff.args["remove"].args[d] = compost:Acquire()
                options.args.filter.args.debuff.args["remove"].args[d].type = 'execute'
                options.args.filter.args.debuff.args["remove"].args[d].name = debuffName
                options.args.filter.args.debuff.args["remove"].args[d].desc = string.format(L["Remove %s from the class submenus."], debuffName)
                options.args.filter.args.debuff.args["remove"].args[d].func = function() return self:RemoveSkippedDebuff(debuffName) end
        end
        
        -- create spacer
        options.args.filter.args.debuff.args.spacer = { type = "header", order = 103 }

        -- create list of debuffs
        for k, debuff in pairs(Detox.db.profile.debuffsInSkipList) do
                local debuffName = debuff
                local d = string.gsub(debuffName, " ", "")
                options.args.filter.args.debuff.args[d] = compost:Acquire()
                options.args.filter.args.debuff.args[d].type = 'group'
                options.args.filter.args.debuff.args[d].name = debuffName
                options.args.filter.args.debuff.args[d].desc = string.format(L["Classes to filter for: %s."], debuffName)
                options.args.filter.args.debuff.args[d].args = compost:Acquire()
                options.args.filter.args.debuff.args[d].order = 200
                -- create submenu for classes
                for k, class in pairs(classes) do
                        local className = class
                        options.args.filter.args.debuff.args[d].args[className] = compost:Acquire()
                        options.args.filter.args.debuff.args[d].args[className].type = 'toggle'
                        options.args.filter.args.debuff.args[d].args[className].name = BC[className]
                        options.args.filter.args.debuff.args[d].args[className].desc = string.format(L["Toggle filtering %s on %s."], debuffName, BC[className])
                        options.args.filter.args.debuff.args[d].args[className].get =
                                function() return Detox.db.profile.skipByClass[string.upper(className)][debuffName] end
                        options.args.filter.args.debuff.args[d].args[className].set =
                                function()
                                        Detox.db.profile.skipByClass[string.upper(className)][debuffName] = not Detox.db.profile.skipByClass[string.upper(className)][debuffName]
                                end
                end
        end
        
        
        
end


--[[---------------------------------------------------------------------------------
 Priority
------------------------------------------------------------------------------------]]

function Detox:PriorityPrint(target, priority)
        if priority then
                self:Print(string.format(L["%s was added to the priority list."], target))
        else
                self:Print(string.format(L["%s has been removed from the priority list."], target))
        end
end

function Detox:PriorityToggle(targetType)
        if not UnitExists("target") then
                self:Print(L["Can't add/remove current target to priority list, it doesn't exist."])
                return
        end
        local target = UnitName("target")
        if not targetType then
                if not RL.roster[target] then
                        self:Print(L["Can't add/remove current target to priority list, it's not in your raid."])
                        return
                end
                Detox.db.profile.priorityNames[target] = not Detox.db.profile.priorityNames[target]
                self:PriorityPrint(target, Detox.db.profile.priorityNames[target])
        else
                local targetGroup = nil
                local targetClass = nil

                for n, u in pairs(RL.roster) do
                        if u and u.name and u.class ~= "PET" then
                                if not targetGroup and u.name == target then
                                        targetGroup = u.subgroup
                                        targetClass = u.class
                                        break
                                end
                        end
                end

                if targetType == "class" and targetClass then
                        Detox.db.profile.priorityClasses[targetClass] = not Detox.db.profile.priorityClasses[targetClass]
                        self:PriorityPrint(string.format(L["Class %s"], targetClass), Detox.db.profile.priorityClasses[targetClass])
                elseif targetType == "group" and targetGroup then
                        Detox.db.profile.priorityGroups[targetGroup] = not Detox.db.profile.priorityGroups[targetGroup]
                        self:PriorityPrint(string.format(L["Group %s"], targetGroup), Detox.db.profile.priorityGroups[targetGroup])
                else
                        self:Print(L["Can't add/remove current target to priority list, it's not in your raid."])
                        return
                end
        end
        self:UpdatePriority()
end

local function sortPri(a,b) 
        return a.pri > b.pri 
end

function Detox:UpdatePriority()
        self:CreateOptionsTable()
        -- update and sort all units. this task has a few steps:
        local datasubtable, index
        for name, u in pairs(RL.roster) do
                -- see if that unit is in our priorityIndex table
                index = priorityIndex[name]
                -- either use the existing subtable or create a new one
                datasubtable = index and priority[index] or compost:Acquire()
                datasubtable.pri = self:GetPriority(name)
                datasubtable.unitid = u.unitid
                if not index then
                        -- our subtable still needs a name
                        datasubtable.name = name
                        -- add that subtable to the priority table
                        table.insert(priority, datasubtable)
                        priorityIndex[name] = table.getn(priority)  -- maybe not needed, but for safety reasons it can't hurt.
                end
        end
        -- now re-sort the priority table
        table.sort(priority, sortPri)
        -- save the current index for all names
        for k,v in ipairs(priority) do
                priorityIndex[v.name] = k
        end
end


function Detox:CreateOptionsTable()
        -- we need to update the dewdrop menu, so lets clear the old one and inject new stuff:
        for class in options.args.priority.args do
                for unit in options.args.priority.args[class].args do
                        compost:Reclaim(options.args.priority.args[class].args[unit])
                        options.args.priority.args[class].args[unit] = nil
                end
                compost:Reclaim(options.args.priority.args[class])
                options.args.priority.args[class] = nil
        end

        compost:Reclaim(options.args.priority.args["group"])

        options.args.priority.args["group"] = compost:Acquire()
        options.args.priority.args["group"].type = 'group'
        options.args.priority.args["group"].name = L["Groups"]
        options.args.priority.args["group"].desc = L["Prioritize by group."]
        options.args.priority.args["group"].order = 100
        options.args.priority.args["group"].args = compost:Acquire()
        for i = 1, 8 do
                local x = i
                options.args.priority.args["group"].args["group"..i] = compost:Acquire()
                options.args.priority.args["group"].args["group"..i].type = 'toggle'
                options.args.priority.args["group"].args["group"..i].name = string.format(L["Group %s"], i)
                options.args.priority.args["group"].args["group"..i].desc = string.format(L["Prioritize group %s."], i)
                options.args.priority.args["group"].args["group"..i].order = 100 + i
                options.args.priority.args["group"].args["group"..i].get =
                        function() return Detox.db.profile.priorityGroups[x] end
                options.args.priority.args["group"].args["group"..i].set =
                        function()
                                Detox.db.profile.priorityGroups[x] = not Detox.db.profile.priorityGroups[x]
                                Detox:UpdatePriority()
                        end
        end

        -- create subgroups
        for name, unit in pairs(RL.roster) do
                if unit and unit.class ~= "PET" then
                        local n = name
                        local u = unit
                        if not options.args.priority.args[u.class] then
                                options.args.priority.args[u.class] = compost:Acquire()
                                options.args.priority.args[u.class].type = 'group'
                                -- convert class name. gah.
                                -- we need this as Babble-Class doesnt know capitalized class names, nor does it know "PET"
                                local c = strupper(strsub(u.class, 1, 1)) .. strlower(strsub(u.class, 2))
                                if c ~= "Pet" then c = BC[c] end
                                options.args.priority.args[u.class].name = c
                                options.args.priority.args[u.class].desc = c
                                options.args.priority.args[u.class].order = 101
                                options.args.priority.args[u.class].args = compost:Acquire()

                                -- Create "all <class>" item
                                if not options.args.priority.args[u.class].args[c] then
                                        options.args.priority.args[u.class].args[c] = compost:Acquire()
                                        options.args.priority.args[u.class].args[c].type = 'toggle'
                                        options.args.priority.args[u.class].args[c].name = string.format(L["Every %s"], c)
                                        options.args.priority.args[u.class].args[c].desc = string.format(L["Prioritize every %s."], c)
                                        options.args.priority.args[u.class].args[c].order = 100
                                        options.args.priority.args[u.class].args[c].get = 
                                                function() return Detox.db.profile.priorityClasses[u.class] end
                                        options.args.priority.args[u.class].args[c].set =
                                                function()
                                                        Detox.db.profile.priorityClasses[u.class] = not Detox.db.profile.priorityClasses[u.class]
                                                        Detox:UpdatePriority()
                                                end
                                end
                        end
                        options.args.priority.args[u.class].args[n] = compost:Acquire()
                        options.args.priority.args[u.class].args[n].type = 'toggle'
                        options.args.priority.args[u.class].args[n].name = n
                        options.args.priority.args[u.class].args[n].desc = string.format(L["Prioritize %s."], n)
                        options.args.priority.args[u.class].args[n].order = 101
                        options.args.priority.args[u.class].args[n].get = 
                                function() return Detox.db.profile.priorityNames[n] end
                        options.args.priority.args[u.class].args[n].set = 
                                function() 
                                        Detox.db.profile.priorityNames[n] = not Detox.db.profile.priorityNames[n]
                                        Detox:UpdatePriority()
                                end
                end
        end
end


function Detox:GetPriority(name)
        local playergroup = (RL.roster[UnitName("player")] and RL.roster[UnitName("player")].subgroup) or 1
        local pri
        local grp = RL.roster[name].subgroup
        local cls = RL.roster[name].class
        -- priority range 1000 - 0.
        if     name == UnitName("player") then  pri = 800
        elseif playergroup == grp         then  pri = 700
        elseif playergroup < grp          then  pri = 700 - (grp - playergroup)*10
        else                                    pri = 700 - (grp + 8 - playergroup)*10
        end
        if cls == "PET" then
                if Detox.db.profile.ignorePets then pri = 0 else pri = pri - 200 end
        end
        if Detox.db.profile.priorityNames[name] then pri = pri + 200 end  -- unit in individual priority list
        if Detox.db.profile.priorityClasses[cls] then pri = pri + 200 end  -- unit in prioritized class
        if Detox.db.profile.priorityGroups[grp] then pri = pri + 200 end  -- unit in prioritized group
        return pri
end


--[[---------------------------------------------------------------------------------
 Live display and other FuBar stuff
------------------------------------------------------------------------------------]]


function Detox:OnTooltipUpdate()
        local cat = nil
        local lines = 0
        for k,v in ipairs(priority) do
                if v.pri == 0 then break end
                if lines > Detox.db.profile.liveDisplay then break end
                local unit = v.unitid
                if UnitIsVisible(unit) and not blacklist[unit] and self:UnitCurable(unit) then
                        for debuffname, count, type, texture in aura:DebuffIter(unit) do
                                if ( not Detox.db.profile.filter and ( type == "Magic" or type == "Poison" or type == "Disease" or type == "Curse" ) )
                                or ( type == "Magic" and spells["magic"] )
                                or ( type == "Poison" and ( spells["poison1"] or spells["poison2"] ) )
                                or ( type == "Disease" and ( spells["disease1"] or spells["disease2"] ) )
                                or ( type == "Curse" and spells["curse"] )
                                then
                                        local r,g,b = self:GetRaidColors(unit)
                                        if count == 0 then count = 1 end
                                        lines = lines + 1
                                        if not cat then
                                                cat = tablet:AddCategory("columns", 2, "text", "", "text2", "", "showWithoutChildren", false, "hideBlankLine", false)
                                        end
                                        cat:AddLine(
                                                "text", string.format("%s (%dx)", debuffname, count), 
                                                "textR", DebuffTypeColor[type].r,
                                                "textG", DebuffTypeColor[type].g,
                                                "textB", DebuffTypeColor[type].b,
                                                "text2", UnitName(unit),
                                                "text2R", r,
                                                "text2G", g,
                                                "text2B", b,
                                                "hasCheck", true, 
                                                "checked", true, 
                                                "checkIcon", texture,
                                                "func", function(unit) Detox:CureUnit(unit) end,
                                                "arg1", unit,
                                                "justify", "LEFT",
                                                "justify2", "LEFT"
                                        )
                                        -- update fubar display (icon + text) to display the first
                                        -- debuff we find.
                                        if lines == 1 then
                                                self:SetIcon(texture)
                                                self:SetText(string.format("|cff%02x%02x%02x%s|r", DebuffTypeColor[type].r*255, DebuffTypeColor[type].g*255, DebuffTypeColor[type].b*255, UnitName(unit)))
                                        end
                                end
                        end
                end
        end
        -- play sound
        if Detox.db.profile.sound then
                if lines == 0 then
                        soundPlayed = false
                elseif not soundPlayed then
                        PlaySoundFile("Sound\\interface\\AuctionWindowOpen.wav")
                        soundPlayed = true
                end
        end
        -- Reset FuBar text and display
        if lines == 0 then
                self:SetIcon(true)
                self:SetText("Detox")
        end
        -- Show the prioritized units
        if Detox.db.profile.showPriorities then
                local priCat = nil
                local text = nil
                for k,v in ipairs(priority) do
                        if v.pri > 700 then
                                if not priCat then priCat = tablet:AddCategory("columns", 1, "text", L["Priorities"]) end
                                local name = v.name
                                if not text then
                                        text = name
                                else
                                        text = text..", "..name
                                end
                        end
                end
                if priCat then priCat:AddLine("text", text..".", "wrap", true) end
        end
end


function Detox:OnClick()
        self:Clean()
end


function Detox:GetRaidColors(unit)
        local _,class = UnitClass(unit)
        if RAID_CLASS_COLORS[class] then
                return RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b
        else
                return 0.5, 0.5, 0.5
        end
end