WoWInterface SVN Critline

[/] [trunk/] [filters.lua] - Rev 25

Compare with Previous | Blame | View Log

local addonName, addon = ...

local L = LibStub("AceLocale-3.0"):GetLocale(addonName)
local templates = addon.templates

local band = bit.band
local CombatLog_Object_IsA = CombatLog_Object_IsA
local IsSpellKnown = IsSpellKnown
local UnitAura = UnitAura

local COMBATLOG_FILTER_ME = COMBATLOG_FILTER_ME

-- mobs whose received hits won't be tracked due to various vulnerabilities
local specialMobs = {
        [12460] = true, -- Death Talon Wyrmguard
        [12461] = true, -- Death Talon Overseer
        [14020] = true, -- Chromaggus
        [15339] = true, -- Ossirian the Unscarred
        [15928] = true, -- Thaddius
        [16803] = true, -- Death Knight Understudy
        [22841] = true, -- Shade of Akama
        [33329] = true, -- Heart of the Deconstructor
        [33670] = true, -- Aerial Command Unit
        [34496] = true, -- Eydis Darkbane
        [34497] = true, -- Fjola Lightbane
        [38567] = true, -- Phantom Hallucination
        [42347] = true, -- Exposed Head of Magmaw ?
        [42803] = true, -- Drakeadon Mongrel
        [46083] = true, -- Drakeadon Mongrel
        [46273] = true, -- Debilitated Apexar
        [48270] = true, -- Exposed Head of Magmaw
}

-- auras that when gained will suppress record tracking
local specialAuras = {
        [18173] = true, -- Burning Adrenaline (Vaelastrasz the Corrupt)
        [24378] = true, -- Berserking (battlegrounds)
        [41337] = true, -- Aura of Anger (Reliquary of Souls)
        [41350] = true, -- Aura of Desire (Reliquary of Souls)
        [44335] = true, -- Energy Feedback (Vexallus)
        [44406] = true, -- Energy Infusion (Vexallus)
        [53642] = true, -- Might of Mograine (Light's Hope Chapel)
        [55849] = true, -- Power Spark (Malygos)
        [56330] = true, -- Iron's Bane (Storm Peaks quest)
        [56648] = true, -- Potent Fungus (Amanitar)
        [57524] = true, -- Metanoia (Valkyrion Aspirant)
        [58026] = true, -- Blessing of the Crusade (Icecrown quest)
        [58361] = true, -- Might of Mograine (Patchwerk)
        [58549] = true, -- Tenacity (Lake Wintergrasp)
        [59641] = true, -- Warchief's Blessing (The Battle For The Undercity)
        [60964] = true, -- Strength of Wrynn (The Battle For The Undercity)
        [61888] = true, -- Overwhelming Power (Assembly of Iron 25)
        [62243] = true, -- Unstable Sun Beam (Elder Brightleaf)
        [62650] = true, -- Fortitude of Frost (Yogg-Saron)
        [62670] = true, -- Resilience of Nature (Yogg-Saron)
        [62671] = true, -- Speed of Invention (Yogg-Saron)
        [62702] = true, -- Fury of the Storm (Yogg-Saron)
        [63277] = true, -- Shadow Crash (General Vezax)
        [63711] = true, -- Storm Power (Hodir 10)
        [64320] = true, -- Rune of Power (Assembly of Iron)
        [64321] = true, -- Potent Pheromones (Freya)
        [64637] = true, -- Overwhelming Power (Assembly of Iron 10)
        [65134] = true, -- Storm Power (Hodir 25)
        [70867] = true, -- Essence of the Blood Queen (Blood Queen Lana'thel)
        [70879] = true, -- Essence of the Blood Queen (Blood Queen Lana'thel, bitten by a player)
        [72219] = true, -- Gastric Bloat (Festergut)
        [73822] = true, -- Hellscream's Warsong (Icecrown Citadel)
        [73828] = true, -- Strength of Wrynn (Icecrown Citadel) 
        [76133] = true, -- Tidal Surge (Neptulon)
        [76155] = true, -- Tidal Surge (Neptulon)
        [76159] = true, -- Pyrogenics (Sun-Touched Spriteling)
        [76355] = true, -- Blessing of the Sun (Rajh)
        [76693] = true, -- Empowering Twilight (Crimsonborne Warlord)
        [79624] = true, -- Power Generator (Arcanotron) ?
        [79629] = true, -- Power Generator (Arcanotron 10)
        [81096] = true, -- Red Mist (Red Mist)
        [86622] = true, -- Engulfing Magic (Theralion) ?
        [86872] = true, -- Frothing Rage (Thundermar Ale)
        [89879] = true, -- Blessing of the Sun (Rajh heroic)
        [90932] = true, -- Ragezone (Defias Blood Wizard)
        [90933] = true, -- Ragezone (Defias Blood Wizard heroic)
        [91871] = true, -- Lightning Charge (Siamat)
        [91555] = true, -- Power Generator (Arcanotron 25)
        [93777] = true, -- Invocation of Flame (Skullcrusher the Mountain)
        [95639] = true, -- Engulfing Magic (Theralion) ?
        [95640] = true, -- Engulfing Magic (Theralion) ?
        [95641] = true, -- Engulfing Magic (Theralion) ?
        [96493] = true, -- Spirit's Vengeance (Bloodlord Mandokir)
}

-- these are auras that increases the target's damage or healing received
local targetAuras = {
        [64436] = true, -- Magnetic Core (Aerial Command Unit) ?
        [65280] = true, -- Singed (Hodir)
        [66758] = true, -- Staggered Daze (Icehowl) ?
        [75664] = true, -- Shadow Gale (Erudax) ?
        [75846] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ?
        [76015] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ?
        [76232] = true, -- Storm's Fury (Ragnaros - Mount Hyjal) ?
        [77615] = true, -- Debilitating Slime (Maloriak)
        [77717] = true, -- Vertigo (Atramedes 10)
        [80164] = true, -- Chemical Cloud (Toxitron)
        [87683] = true, -- Dragon's Vengeance (Halfus Wyrmbreaker)
        [87904] = true, -- Feedback (Al'Akir)
        [91086] = true, -- Shadow Gale (Erudax heroic)
        [91478] = true, -- Chemical Cloud (Toxitron 25) ?
        [92389] = true, -- Vertigo (Atramedes 25) ?
        [92390] = true, -- Vertigo (Atramedes) ?
        [92910] = true, -- Debilitating Slime (Maloriak) ?
        [93567] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ?
        [95723] = true, -- Storm's Fury (Ragnaros - Mount Hyjal) ?
        [97320] = true, -- Sunder Rift (Jin'do the Godbreaker)
}

-- these heals are treated as periodic, but has no aura associated with them, or is associated to an aura with a different name, need to add exceptions for them to filter properly
local directHoTs = {
        [54172] = true, -- Divine Storm
        -- [63106] = "Corruption", -- Siphon Life
}

local activeAuras = {
        player = {},
        pet = {},
}
local corruptSpells = {
        player = {},
        pet = {},
}
local corruptTargets = {}

-- amount of buttons in the spell, mob and aura filter scroll lists
local NUMSPELLBUTTONS = 8
local SPELLBUTTONHEIGHT = 22
local NUMFILTERBUTTONS = 10
local FILTERBUTTONHEIGHT = 16


local filters = templates:CreateConfigFrame(FILTERS, addonName, true)
filters:SetScript("OnEvent", function(self, event, ...)
        return self[event] and self[event](self, ...)
end)
addon.filters = filters


local function filterButtonOnClick(self)
        local module = self.module
        local scrollFrame = module.scrollFrame
        local offset = FauxScrollFrame_GetOffset(scrollFrame)
        local id = self:GetID()
        
        local selection = scrollFrame.selected
        if selection then
                if selection - offset == id then
                        -- clicking the selected button, clear selection
                        self:UnlockHighlight()
                        selection = nil
                else
                        -- clear selection if visible, and set new selection
                        local prevHilite = scrollFrame.buttons[selection - offset]
                        if prevHilite then
                                prevHilite:UnlockHighlight()
                        end
                        self:LockHighlight()
                        selection = id + offset
                end
        else
                -- no previous selection, just set new and lock highlight
                self:LockHighlight()
                selection = id + offset
        end
        
        -- enable/disable "Delete" button depending on if selection exists
        if selection then
                module.delete:Enable()
        else
                module.delete:Disable()
        end
        scrollFrame.selected = selection
end

-- template function for mob filter buttons
local function createFilterButton(parent)
        local btn = CreateFrame("Button", nil, parent)
        btn:SetHeight(FILTERBUTTONHEIGHT)
        btn:SetPoint("LEFT")
        btn:SetPoint("RIGHT")
        btn:SetNormalFontObject("GameFontNormal")
        btn:SetHighlightFontObject("GameFontHighlight")
        btn:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
        btn:SetPushedTextOffset(0, 0)
        btn:SetScript("OnClick", filterButtonOnClick)
        return btn
end

local function createFilterButtons(parent, onEnter)
        local buttons = {}
        for i = 1, NUMFILTERBUTTONS do
                local btn = createFilterButton(parent)
                if i == 1 then
                        btn:SetPoint("TOP")
                else
                        btn:SetPoint("TOP", buttons[i - 1], "BOTTOM")
                end
                btn:SetID(i)
                if onEnter then
                        btn:SetScript("OnEnter", onEnter)
                        btn:SetScript("OnLeave", GameTooltip_Hide)
                end
                btn.module = parent
                buttons[i] = btn
        end
        parent.scrollFrame.buttons = buttons
end

local function resetScroll(self)
        FauxScrollFrame_SetOffset(self, 0)
        self.scrollBar:SetValue(0)
        self:Update()
end

local function onVerticalScroll(self, offset)
        FauxScrollFrame_OnVerticalScroll(self, offset, self.buttonHeight, self.Update)
end

local function filterFrameOnShow(self)
        local scrollFrame = self.scrollFrame
        if scrollFrame.selected then
                local prevHilite = scrollFrame.buttons[scrollFrame.selected - FauxScrollFrame_GetOffset(scrollFrame)]
                if prevHilite then
                        prevHilite:UnlockHighlight()
                end
                scrollFrame.selected = nil
                self.delete:Disable()
        end
end

local function addButtonOnClick(self)
        StaticPopup_Show(self.popup)
end

local function deleteButtonOnClick(self)
        local scrollFrame = self.scrollFrame
        local filterName = scrollFrame.filter
        local selection = scrollFrame.selected
        if selection then
                local filter = filters.db.global[filterName]
                local selectedEntry = filter[selection]
                tremove(filter, selection)
                local prevHighlight = scrollFrame.buttons[selection - FauxScrollFrame_GetOffset(scrollFrame)]
                if prevHighlight then
                        prevHighlight:UnlockHighlight()
                end
                scrollFrame.selected = nil
                scrollFrame:Update()
                self:Disable()
                addon:Message(self.msg:format(GetSpellInfo(selectedEntry) or selectedEntry))
                if self.func then
                        self.func(selectedEntry)
                end
        end
end

local function createFilterFrame(name, parent, numButtons, buttonHeight)
        local frame = CreateFrame("Frame", nil, parent)
        frame:SetHeight(numButtons * buttonHeight)
        parent[name] = frame

        local scrollName = "CritlineFilters"..name.."ScrollFrame"
        local scrollFrame = CreateFrame("ScrollFrame", scrollName, frame, "FauxScrollFrameTemplate")
        scrollFrame:SetAllPoints()
        scrollFrame:SetScript("OnShow", resetScroll)
        scrollFrame:SetScript("OnVerticalScroll", onVerticalScroll)
        scrollFrame.scrollBar = _G[scrollName.."ScrollBar"]
        scrollFrame.buttons = frame.buttons
        scrollFrame.numButtons = numButtons
        scrollFrame.buttonHeight = buttonHeight
        scrollFrame.filter = name
        frame.scrollFrame = scrollFrame
        
        if name ~= "spell" then
                frame:SetScript("OnShow", filterFrameOnShow)

                local add = templates:CreateButton(frame)
                add:SetScript("OnClick", addButtonOnClick)
                frame.add = add

                local delete = templates:CreateButton(frame)
                delete:Disable()
                delete:SetScript("OnClick", deleteButtonOnClick)
                delete.scrollFrame = scrollFrame
                frame.delete = delete
        end
        
        return frame
end


do
        local options = {}
        filters.options = options

        local checkButtons = {
                {
                        text = L["Filter new spells"],
                        tooltipText = L["Enable to filter out new spell entries by default."],
                        setting = "filterNew",
                },
                {
                        text = L["Ignore mob filter"],
                        tooltipText = L["Enable to ignore integrated mob filter."],
                        setting = "ignoreMobFilter",
                },
                {
                        text = L["Ignore aura filter"],
                        tooltipText = L["Enable to ignore integrated aura filter."],
                        setting = "ignoreAuraFilter",
                },
                {
                        text = L["Only known spells"],
                        tooltipText = L["Enable to ignore spells that are not in your (or your pet's) spell book."],
                        setting = "onlyKnown",
                },
                {
                        text = L["Suppress mind control"],
                        tooltipText = L["Suppress all records while mind controlled."],
                        setting = "suppressMC",
                        newColumn = true,
                },
                {
                        text = L["Don't filter magic"],
                        tooltipText = L["Enable to let magical damage ignore the level filter."],
                        setting = "dontFilterMagic",
                },
        }

        options.checkButtons = checkButtons
        
        local columnEnd = #checkButtons

        for i, v in ipairs(checkButtons) do
                local btn = templates:CreateCheckButton(filters, v)
                if i == 1 then
                        btn:SetPoint("TOPLEFT", filters.title, "BOTTOMLEFT", -2, -16)
                elseif btn.newColumn then
                        btn:SetPoint("TOPLEFT", filters.title, "BOTTOM", 0, -16)
                        columnEnd = i - 1
                else
                        btn:SetPoint("TOP", checkButtons[i - 1], "BOTTOM", 0, -8)
                end
                btn.module = filters
                checkButtons[i] = btn
        end
        
        local slider = templates:CreateSlider(filters, {
                text = L["Level filter"],
                tooltipText = L["If level difference between you and the target is greater than this setting, records will not be registered."],
                minValue = -1,
                maxValue = 10,
                valueStep = 1,
                minText = OFF,
                maxText = 10,
                func = function(self)
                        local value = self:GetValue()
                        self.value:SetText(value == -1 and OFF or value)
                        filters.profile.levelFilter = value
                end,
        })
        slider:SetPoint("TOPLEFT", checkButtons[#checkButtons], "BOTTOMLEFT", 4, -24)
        options.slider = slider
        
        local filterTypes = {}

        -- spell filter frame
        local spellFilter = createFilterFrame("spell", filters, NUMSPELLBUTTONS, SPELLBUTTONHEIGHT)
        spellFilter:SetPoint("TOP", checkButtons[columnEnd], "BOTTOM", 0, -48)
        spellFilter:SetPoint("LEFT", 48, 0)
        spellFilter:SetPoint("RIGHT", -48, 0)
        filterTypes.spell = spellFilter
        
        do      -- spell filter buttons
                local function spellButtonOnClick(self)
                        local checked = self:GetChecked() == 1
                        PlaySound(checked and "igMainMenuOptionCheckBoxOn" or "igMainMenuOptionCheckBoxOff")
                        filters:FilterSpell(not checked, filters.spell.tree:GetSelectedValue(), self.data)
                end
                
                local function spellButtonOnEnter(self)
                        -- prevent records being added twice
                        GameTooltip.Critline = true
                        GameTooltip:SetOwner(self, "ANCHOR_LEFT")
                        GameTooltip:SetSpellByID(self.data.spellID)
                        GameTooltip:AddLine(" ")
                        addon:AddTooltipLine(self.data)
                        GameTooltip:Show()
                end
                
                local buttons = {}
                for i = 1, NUMSPELLBUTTONS do
                        local btn = templates:CreateCheckButton(spellFilter)
                        if i == 1 then
                                btn:SetPoint("TOPLEFT")
                        else
                                btn:SetPoint("TOP", buttons[i - 1], "BOTTOM", 0, 4)
                        end
                        btn:SetScript("OnClick", spellButtonOnClick)
                        btn:SetScript("OnEnter", spellButtonOnEnter)
                        buttons[i] = btn
                end
                spellFilter.scrollFrame.buttons = buttons
        end
        
        -- spell filter scroll frame
        local spellScrollFrame = spellFilter.scrollFrame

        -- spell filter tree dropdown
        local menu = {
                {text = L["Damage"],    value = "dmg"},
                {text = L["Healing"],   value = "heal"},
                {text = L["Pet"],               value = "pet"},
        }

        local spellFilterTree = templates:CreateDropDownMenu("CritlineSpellFilterTree", spellFilter, menu)
        spellFilterTree:SetFrameWidth(120)
        spellFilterTree:SetPoint("BOTTOMRIGHT", spellFilter, "TOPRIGHT", 16, 0)
        spellFilterTree:SetSelectedValue("dmg")
        spellFilterTree.onClick = function(self)
                self.owner:SetSelectedValue(self.value)
                FauxScrollFrame_SetOffset(spellScrollFrame, 0)
                spellScrollFrame.scrollBar:SetValue(0)
                spellScrollFrame:Update()
        end
        spellFilter.tree = spellFilterTree
        spellScrollFrame.tree = spellFilter.tree
        
        do      -- mob filter frame
                local mobFilter = createFilterFrame("mobs", filters, NUMFILTERBUTTONS, FILTERBUTTONHEIGHT)
                mobFilter:SetPoint("TOP", spellFilter)
                mobFilter:SetPoint("LEFT", spellFilter)
                mobFilter:SetPoint("RIGHT", spellFilter)
                mobFilter:Hide()
                filterTypes.mobs = mobFilter
                
                createFilterButtons(mobFilter)
                
                local addTarget = templates:CreateButton(mobFilter)
                addTarget:SetSize(96, 22)
                addTarget:SetPoint("TOPLEFT", mobFilter, "BOTTOMLEFT", 0, -8)
                addTarget:SetText(L["Add target"])
                addTarget:SetScript("OnClick", function()
                        local targetName = UnitName("target")
                        if targetName then
                                -- we don't want to add PCs to the filter
                                if UnitIsPlayer("target") then
                                        addon:Message(L["Cannot add players to mob filter."])
                                else
                                        filters:AddMob(targetName)
                                end
                        else
                                addon:Message(L["No target selected."])
                        end
                end)
                
                local add = mobFilter.add
                add:SetSize(96, 22)
                add:SetPoint("TOP", mobFilter, "BOTTOM", 0, -8)
                add:SetText(L["Add by name"])
                add.popup = "CRITLINE_ADD_MOB_BY_NAME"
                
                local delete = mobFilter.delete
                delete:SetSize(96, 22)
                delete:SetPoint("TOPRIGHT", mobFilter, "BOTTOMRIGHT", 0, -8)
                delete:SetText(L["Delete mob"])
                delete.msg = L["%s removed from mob filter."]
        end
        
        do      -- aura filter frame
                local auraFilter = createFilterFrame("auras", filters, NUMFILTERBUTTONS, FILTERBUTTONHEIGHT)
                auraFilter:SetPoint("TOP", spellFilter)
                auraFilter:SetPoint("LEFT", spellFilter)
                auraFilter:SetPoint("RIGHT", spellFilter)
                auraFilter:Hide()
                filterTypes.auras = auraFilter

                createFilterButtons(auraFilter, function(self)
                        GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT")
                        GameTooltip:SetHyperlink("spell:"..self.spellID)
                end)
                
                local add = auraFilter.add
                add:SetSize(128, 22)
                add:SetPoint("TOPLEFT", auraFilter, "BOTTOMLEFT", 0, -8)
                add:SetText(L["Add by spell ID"])
                add.popup = "CRITLINE_ADD_AURA_BY_ID"
                
                -- local addAura = templates:CreateButton(auraFilter)
                -- addAura:SetSize(48, 22)
                -- addAura:SetPoint("TOP", auraFilter, "BOTTOM")
                -- addAura:SetText("Add")
                -- addAura:SetScript("OnClick", function() if auraList:IsShown() then auraList:Hide() else auraList:Show() end end)

                local delete = auraFilter.delete
                delete:SetSize(128, 22)
                delete:SetPoint("TOPRIGHT", auraFilter, "BOTTOMRIGHT", 0, -8)
                delete:SetText(L["Delete aura"])
                delete.msg = L["%s removed from aura filter."]
                delete.func = function(spellID)
                        activeAuras.player[spellID] = nil
                        activeAuras.pet[spellID] = nil
                        if not filters:IsEmpowered("player") then
                                addon:Debug("No filtered aura detected on player. Resuming record tracking.")
                        end
                        if not filters:IsEmpowered("pet") then
                                addon:Debug("No filtered aura detected on pet. Resuming record tracking.")
                        end
                end
        end
        
        do      -- filter tree dropdown
                local menu = {
                        {
                                text = L["Spell filter"],
                                value = "spell",
                        },
                        {
                                text = L["Mob filter"],
                                value = "mobs",
                        },
                        {
                                text = L["Aura filter"],
                                value = "auras",
                        },
                }
                
                local filterType = templates:CreateDropDownMenu("CritlineFilterType", filters, menu)
                filterType:SetPoint("BOTTOMLEFT", spellFilter, "TOPLEFT", -16, 0)
                filterType:SetFrameWidth(120)
                filterType:SetSelectedValue("spell")
                filterType.onClick = function(self)
                        self.owner:SetSelectedValue(self.value)
                        for k, v in pairs(filterTypes) do
                                if k == self.value then
                                        v:Show()
                                else
                                        v:Hide()
                                end
                        end
                end
                filters.type = filterType
        end
end


StaticPopupDialogs["CRITLINE_ADD_MOB_BY_NAME"] = {
        text = L["Enter mob name:"],
        button1 = OKAY,
        button2 = CANCEL,
        hasEditBox = true,
        OnAccept = function(self)
                local name = self.editBox:GetText():trim()
                if not name:match("%S+") then
                        addon:Message(L["Invalid mob name."])
                        return
                end
                filters:AddMob(name)
        end,
        EditBoxOnEnterPressed = function(self)
                local name = self:GetText():trim()
                if not name:match("%S+") then
                        addon:Message(L["Invalid mob name."])
                        return
                end
                filters:AddMob(name)
                self:GetParent():Hide()
        end,
        EditBoxOnEscapePressed = function(self)
                self:GetParent():Hide()
        end,
        OnShow = function(self)
                self.editBox:SetFocus()
        end,
        whileDead = true,
        timeout = 0,
}

StaticPopupDialogs["CRITLINE_ADD_AURA_BY_ID"] = {
        text = L["Enter spell ID:"],
        button1 = OKAY,
        button2 = CANCEL,
        hasEditBox = true,
        OnAccept = function(self)
                local id = tonumber(self.editBox:GetText())
                if not id then
                        addon:Message(L["Invalid input. Please enter a spell ID."])
                        return
                elseif not GetSpellInfo(id) then
                        addon:Message(L["Invalid spell ID. No such spell."])
                        return
                end
                filters:AddAura(id)
        end,
        EditBoxOnEnterPressed = function(self)
                local id = tonumber(self:GetText())
                if not id then
                        addon:Message(L["Invalid input. Please enter a spell ID."])
                        return
                elseif not GetSpellInfo(id) then
                        addon:Message(L["Invalid spell ID. No such spell exists."])
                        return
                end
                filters:AddAura(id)
                self:GetParent():Hide()
        end,
        EditBoxOnEscapePressed = function(self)
                self:GetParent():Hide()
        end,
        OnShow = function(self)
                self.editBox:SetFocus()
        end,
        whileDead = true,
        timeout = 0,
}


local function updateSpellFilter(self)
        local selectedTree = self.tree:GetSelectedValue()
        local spells = addon:GetSpellArray(selectedTree)
        local size = #spells
        
        FauxScrollFrame_Update(self, size, self.numButtons, self.buttonHeight)
        
        local offset = FauxScrollFrame_GetOffset(self)
        local buttons = self.buttons
        for line = 1, NUMSPELLBUTTONS do
                local button = buttons[line]
                local lineplusoffset = line + offset
                if lineplusoffset <= size then
                        local data = spells[lineplusoffset]
                        button.data = data
                        button:SetText(addon:GetFullSpellName(data.spellID, data.periodic))
                        button:SetChecked(not data.filtered)
                        button:Show()
                else
                        button:Hide()
                end
        end
end

local function updateFilter(self)
        local filter = filters.db.global[self.filter]
        local size = #filter
        
        FauxScrollFrame_Update(self, size, self.numButtons, self.buttonHeight)
        
        local offset = FauxScrollFrame_GetOffset(self)
        local buttons = self.buttons
        for line = 1, self.numButtons do
                local button = buttons[line]
                local lineplusoffset = line + offset
                if lineplusoffset <= size then
                        if self.selected then
                                if self.selected - offset == line then
                                        button:LockHighlight()
                                else
                                        button:UnlockHighlight()
                                end
                        end
                        local entry = filter[lineplusoffset]
                        button.spellID = entry
                        button:SetText(type(entry) == "number" and GetSpellInfo(entry) or entry)
                        button:Show()
                else
                        button:Hide()
                end
        end
end


local defaults = {
        profile = {
                filterNew = false,
                onlyKnown = false,
                ignoreMobFilter = false,
                ignoreAuraFilter = false,
                suppressMC = true,
                dontFilterMagic = false,
                levelFilter = -1,
        },
        global = {
                mobs = {},
                auras = {},
        },
}

function filters:AddonLoaded()
        self.db = addon.db:RegisterNamespace("filters", defaults)
        addon.RegisterCallback(self, "SettingsLoaded", "LoadSettings")
        addon.RegisterCallback(self, "PerCharSettingsLoaded", "UpdateSpellFilter")
        addon.RegisterCallback(self, "SpellsChanged", "UpdateSpellFilter")
        
        self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
        self:RegisterEvent("PLAYER_LOGIN")
        self:RegisterEvent("UNIT_NAME_UPDATE")
        self:RegisterEvent("PLAYER_CONTROL_LOST")
        self:RegisterEvent("PLAYER_CONTROL_GAINED")
        
        -- mix in scroll frame update functions
        self.spell.scrollFrame.Update = updateSpellFilter
        self.mobs.scrollFrame.Update = updateFilter
        self.auras.scrollFrame.Update = updateFilter
end

addon.RegisterCallback(filters, "AddonLoaded")


function filters:COMBAT_LOG_EVENT_UNFILTERED(timestamp, eventType, hideCaster, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags, spellID, spellName)
        if eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" then
                -- if this is one of the damage-taken-increased auras, we flag this target - along with the aura in question - as rotten
                if targetAuras[spellID] then
                        corruptTargets[destGUID] = corruptTargets[destGUID] or {}
                        corruptTargets[destGUID][spellID] = true
                        addon:Debug(format("Target (%s) gained filtered aura. (%s) Ignore received damage.", destName, spellName))
                end
                
                local destUnit = self:GetUnit(destFlags, destGUID)
                if destUnit and self:IsFilteredAura(spellID) then
                        -- if we gain any aura in the filter we can just stop tracking records
                        if not (self:IsEmpowered(destUnit) or self.profile.ignoreAuraFilter) then
                                addon:Debug(format("%s gained filtered aura. (%s) Disabling combat log tracking.", destUnit:gsub(".", string.upper, 1), spellName))
                        end
                        activeAuras[destUnit][spellID] = true
                end
                
                local sourceUnit = self:GetUnit(sourceFlags, sourceGUID)
                if sourceUnit then
                        local corruptSpell = corruptSpells[sourceUnit][spellID] or {}
                        corruptSpell[destGUID] = self:IsEmpowered(sourceUnit) or self:IsVulnerableTarget(destGUID)
                        corruptSpells[sourceUnit][spellID] = corruptSpell
                end
        elseif (eventType == "SPELL_AURA_REMOVED" or eventType == "SPELL_AURA_BROKEN" or eventType == "SPELL_AURA_BROKEN_SPELL" or eventType == "SPELL_AURA_STOLEN") then
                if targetAuras[spellID] then
                        corruptTargets[destGUID] = corruptTargets[destGUID] or {}
                        corruptTargets[destGUID][spellID] = nil
                        addon:Debug(format("Filtered aura (%s) faded from %s.", spellName, destName))
                end
                
                if self:IsFilteredAura(spellID) then
                        local unit = self:GetUnit(destFlags, destGUID)
                        if unit then
                                addon:Debug(format("Filtered aura (%s) faded from %s.", spellName, unit))
                                -- if we lost a special aura we have to check if any other filtered auras remain
                                activeAuras[unit][spellID] = nil
                                if not filters:IsEmpowered(unit) then
                                        addon:Debug(format("No filtered aura detected on %s. Resuming record tracking.", unit))
                                end
                        end
                end
        end
end


function filters:PLAYER_LOGIN()
        self:ScanAuras()
end


function filters:UNIT_NAME_UPDATE()
        self:ScanAuras()
        self:UnregisterEvent("UNIT_NAME_UPDATE")
end


function filters:PLAYER_CONTROL_LOST()
        self.inControl = false
        addon:Debug("Lost control. Disabling combat log tracking.")
end


function filters:PLAYER_CONTROL_GAINED()
        self.inControl = true
        addon:Debug("Regained control. Resuming combat log tracking.")
end


function filters:LoadSettings()
        self.profile = self.db.profile
        
        for i, v in ipairs(self.options.checkButtons) do
                v:LoadSetting()
        end
        
        self.options.slider:SetValue(self.profile.levelFilter)
end


local auraTypes = {
        BUFF = "HELPFUL",
        DEBUFF = "HARMFUL",
}

function filters:ScanAuras()
        local auras = {}
        for auraType, filter in pairs(auraTypes) do
                for i = 1, 40 do
                        local spellName, _, _, _, _, _, _, source, _, _, spellID = UnitAura("player", i, filter)
                        if not spellID then break end
                        auras[spellID] = true
                        if specialAuras[spellID] then
                                activeAuras[spellID] = true
                        end
                end
        end
        if next(auras) then
                self:UnregisterEvent("UNIT_NAME_UPDATE")
        end
        for i, v in ipairs(self.db.global.auras) do
                activeAuras[v] = auras[v]
        end
        if next(activeAuras) then
                addon:Debug("Filtered aura detected. Disabling combat log tracking.")
        end
        self.inControl = HasFullControl()
        if not self.inControl then
                addon:Debug("Lost control. Disabling combat log tracking.")
        end
end


function filters:UpdateSpellFilter()
        self.spell.scrollFrame:Update()
end


function filters:UpdateFilter()
        self[self.type:GetSelectedValue()].scrollFrame:Update()
end


function filters:FilterSpell(filter, tree, data)
        data.filtered = filter
        addon:GetSpellInfo(tree, data.spellID, data.periodic).filtered = filter
        addon:UpdateTopRecords(tree)
        addon:UpdateRecords(tree)
end


-- adds a mob to the mob filter
function filters:AddMob(name)
        if self:IsFilteredTarget(name) then
                addon:Message(L["%s is already in mob filter."]:format(name))
        else
                tinsert(self.db.global.mobs, name)
                self:UpdateFilter()
                addon:Message(L["%s added to mob filter."]:format(name))
        end
end


-- adds an aura to the aura filter
function filters:AddAura(spellID)
        local spellName = GetSpellInfo(spellID)
        if self:IsFilteredAura(spellID) then
                addon:Message(L["%s is already in aura filter."]:format(spellName))
        else
                tinsert(self.db.global.auras, spellID)
                -- after we add an aura to the filter; check if we have it
                for i = 1, 40 do
                        local buffID = select(11, UnitBuff("player", i))
                        local debuffID = select(11, UnitDebuff("player", i))
                        if not (buffID or debuffID) then
                                break
                        else
                                for _, v in ipairs(self.db.global.auras) do
                                        if v == buffID then
                                                activeAuras[buffID] = true
                                                break
                                        elseif v == debuffID then
                                                activeAuras[debuffID] = true
                                                break
                                        end
                                end
                        end
                end
                self:UpdateFilter()
                addon:Message(L["%s added to aura filter."]:format(spellName))
        end
end


-- check if a spell passes the filter settings
function filters:SpellPassesFilters(tree, spellName, spellID, isPeriodic, destGUID, destName, school, targetLevel)
        local isPet = tree == "pet"
        if spellID and not IsSpellKnown(spellID, isPet) and self.profile.onlyKnown then
                addon:Debug(format("%s is not in your%s spell book. Return.", spellName, isPet and " pet's" or ""))
                return
        end
        
        local unit = isPet and "pet" or "player"
        if ((corruptSpells[unit][spellID] and corruptSpells[unit][spellID][destGUID]) or (self:IsEmpowered(unit) and (not isPeriodic or directHoTs[spellID]))) and not self.profile.ignoreAuraFilter then
                addon:Debug(format("Spell (%s) was cast under the influence of a filtered aura. Return.", spellName))
                return
        end
        
        if self:IsVulnerableTarget(destGUID) and not isPeriodic and not self.profile.ignoreAuraFilter then
                addon:Debug("Target is vulnerable. Return.")
                return
        end
        
        local levelDiff = 0
        if (targetLevel > 0) and (targetLevel < UnitLevel("player")) then
                levelDiff = (UnitLevel("player") - targetLevel)
        end
        
        -- ignore level adjustment if magic damage and the setting is enabled
        if not isHeal and (self.profile.levelFilter >= 0) and (self.profile.levelFilter < levelDiff) and (school == 1 or not self.profile.dontFilterMagic) then
                -- target level is too low to pass level filter
                addon:Debug(format("Target (%s) level too low (%d) and damage school is filtered. Return.", destName, targetLevel))
                return
        end
        
        local filteredTarget = self:IsFilteredTarget(destName, destGUID)
        if filteredTarget then
                addon:Debug(format("Target (%s) is in %s target filter.", destName, filteredTarget))
                return
        end
        
        return true, self:IsFilteredSpell(tree, spellID, isPeriodic and 2 or 1), targetLevel
end


-- check if a spell will be filtered out
function filters:IsFilteredSpell(tree, spellID, periodic)
        local spell = addon:GetSpellInfo(tree, spellID, periodic)
        return (not spell and self.db.profile.filterNew) or (spell and spell.filtered)
end


-- scan for filtered auras from the specialAuras table
function filters:IsEmpowered(unit)
        if next(activeAuras[unit]) or not self.inControl then
                return true
        end
end


-- checks if a target is affected by any vulnerability auras
function filters:IsVulnerableTarget(guid)
        local corruptTarget = corruptTargets[guid]
        if corruptTarget and next(corruptTarget) then
                return true
        end
end


function filters:IsFilteredTarget(targetName, guid)
        -- GUID is provided if the function was called from the combat event handler
        if guid and not self.profile.ignoreMobFilter and specialMobs[tonumber(guid:sub(7, 10), 16)] then
                return "default"
        end
        for _, v in ipairs(self.db.global.mobs) do
                if v:lower() == targetName:lower() then
                        return "custom"
                end
        end
end


function filters:IsFilteredAura(spellID)
        if specialAuras[spellID] then
                return true
        end
        for _, v in ipairs(self.db.global.auras) do
                if v == spellID then
                        return true
                end
        end
end


function filters:GetUnit(unitFlags, unitGUID)
        if CombatLog_Object_IsA(unitFlags, COMBATLOG_FILTER_ME) then
                return "player"
        elseif addon:IsMyPet(unitFlags, unitGUID) then
                return "pet"
        end
end

Compare with Previous | Blame