Quantcast
--[[
##############################################################################
_____/\\\\\\\\\\\____/\\\________/\\\__/\\\________/\\\__/\\\\\\\\\\\_       #
 ___/\\\/////////\\\_\/\\\_______\/\\\_\/\\\_______\/\\\_\/////\\\///__      #
  __\//\\\______\///__\//\\\______/\\\__\/\\\_______\/\\\_____\/\\\_____     #
   ___\////\\\__________\//\\\____/\\\___\/\\\_______\/\\\_____\/\\\_____    #
    ______\////\\\________\//\\\__/\\\____\/\\\_______\/\\\_____\/\\\_____   #
     _________\////\\\______\//\\\/\\\_____\/\\\_______\/\\\_____\/\\\_____  #
      __/\\\______\//\\\______\//\\\\\______\//\\\______/\\\______\/\\\_____ #
       _\///\\\\\\\\\\\/________\//\\\________\///\\\\\\\\\/____/\\\\\\\\\\\_#
        ___\///////////___________\///___________\/////////_____\///////////_#
##############################################################################
S U P E R - V I L L A I N - U I   By: Munglunch                              #
##############################################################################
--]]
--[[ GLOBALS ]]--
local _G        = _G;
local unpack    = _G.unpack;
local select    = _G.select;
local pairs     = _G.pairs;
local ipairs    = _G.ipairs;
local type      = _G.type;
local tostring  = _G.tostring;
local tinsert   = _G.tinsert;
local string    = _G.string;
--[[ STRING METHODS ]]--
local find, format, upper = string.find, string.format, string.upper;
local match, gsub = string.match, string.gsub;

local SuperVillain, L = unpack(select(2, ...));
local MOD = SuperVillain.Registry:Expose('SVUnit')
if(not MOD) then return end;
local _, ns = ...
local oUF_SuperVillain = ns.oUF
--[[ MUNGLUNCH's FASTER ASSERT FUNCTION ]]--
local assert = enforce;
assert(oUF_SuperVillain, "SVUI was unable to locate oUF.")
local ceil,tinsert = math.ceil,table.insert
--[[
##########################################################
LOCAL DATA
##########################################################
]]--
local CONSTRUCTORS, UPDATERS = {}, {}
local _POINTMAP = {
    ["DOWN_RIGHT"] = {[1]="TOP",[2]="TOPLEFT",[3]="LEFT",[4]="RIGHT",[5]="LEFT",[6]=1,[7]=-1,[8]=false},
    ["DOWN_LEFT"] = {[1]="TOP",[2]="TOPRIGHT",[3]="RIGHT",[4]="LEFT",[5]="RIGHT",[6]=1,[7]=-1,[8]=false},
    ["UP_RIGHT"] = {[1]="BOTTOM",[2]="BOTTOMLEFT",[3]="LEFT",[4]="RIGHT",[5]="LEFT",[6]=1,[7]=1,[8]=false},
    ["UP_LEFT"] = {[1]="BOTTOM",[2]="BOTTOMRIGHT",[3]="RIGHT",[4]="LEFT",[5]="RIGHT",[6]=-1,[7]=1,[8]=false},
    ["RIGHT_DOWN"] = {[1]="LEFT",[2]="TOPLEFT",[3]="TOP",[4]="BOTTOM",[5]="TOP",[6]=1,[7]=-1,[8]=true},
    ["RIGHT_UP"] = {[1]="LEFT",[2]="BOTTOMLEFT",[3]="BOTTOM",[4]="TOP",[5]="BOTTOM",[6]=1,[7]=1,[8]=true},
    ["LEFT_DOWN"] = {[1]="RIGHT",[2]="TOPRIGHT",[3]="TOP",[4]="BOTTOM",[5]="TOP",[6]=-1,[7]=-1,[8]=true},
    ["LEFT_UP"] = {[1]="RIGHT",[2]="BOTTOMRIGHT",[3]="BOTTOM",[4]="TOP",[5]="BOTTOM",[6]=-1,[7]=1,[8]=true},
    ["UP"] = {[1]="BOTTOM",[2]="BOTTOM",[3]="BOTTOM",[4]="TOP",[5]="TOP",[6]=1,[7]=1,[8]=false},
    ["DOWN"] = {[1]="TOP",[2]="TOP",[3]="TOP",[4]="BOTTOM",[5]="BOTTOM",[6]=1,[7]=1,[8]=false},
}

local _GSORT = {
    ['CLASS']=function(self)
        self:SetAttribute("groupingOrder","DEATHKNIGHT,DRUID,HUNTER,MAGE,PALADIN,PRIEST,SHAMAN,WARLOCK,WARRIOR,MONK")
        self:SetAttribute('sortMethod','NAME')
        self:SetAttribute("sortMethod",'CLASS')
    end,
    ['MTMA']=function(self)
        self:SetAttribute("groupingOrder","MAINTANK,MAINASSIST,NONE")
        self:SetAttribute('sortMethod','NAME')
        self:SetAttribute("sortMethod",'ROLE')
    end,
    ['ROLE']=function(self)
        self:SetAttribute("groupingOrder","TANK,HEALER,DAMAGER,NONE")
        self:SetAttribute('sortMethod','NAME')
        self:SetAttribute("sortMethod",'ASSIGNEDROLE')
    end,
    ['ROLE_TDH']=function(self)
        self:SetAttribute("groupingOrder","TANK,DAMAGER,HEALER,NONE")
        self:SetAttribute('sortMethod','NAME')
        self:SetAttribute("sortMethod",'ASSIGNEDROLE')
    end,
    ['ROLE_HTD']=function(self)
        self:SetAttribute("groupingOrder","HEALER,TANK,DAMAGER,NONE")
        self:SetAttribute('sortMethod','NAME')
        self:SetAttribute("sortMethod",'ASSIGNEDROLE')
    end,
    ['ROLE_HDT']=function(self)
        self:SetAttribute("groupingOrder","HEALER,DAMAGER,TANK,NONE")
        self:SetAttribute('sortMethod','NAME')
        self:SetAttribute("sortMethod",'ASSIGNEDROLE')
    end,
    ['NAME']=function(self)
        self:SetAttribute("groupingOrder","1,2,3,4,5,6,7,8")
        self:SetAttribute('sortMethod','NAME')
        self:SetAttribute("sortMethod",nil)
    end,
    ['GROUP']=function(self)
        self:SetAttribute("groupingOrder","1,2,3,4,5,6,7,8")
        self:SetAttribute('sortMethod','INDEX')
        self:SetAttribute("sortMethod",'GROUP')
    end,
    ['PETNAME']=function(self)
        self:SetAttribute("groupingOrder","1,2,3,4,5,6,7,8")
        self:SetAttribute('sortMethod','NAME')
        self:SetAttribute("sortMethod",nil)
        self:SetAttribute("filterOnPet",true)
    end
}
--[[
##########################################################
ALL UNIT HELPERS
##########################################################
]]--
local GroupMediaUpdate = function(self, updateElements)
    local key = self.___key
    local index = 1;
    local childFrame = self:GetAttribute("child"..index)
    while childFrame do
        MOD.RefreshUnitMedia(childFrame, key, updateElements)
        if(_G[childFrame:GetName().."Pet"]) then
            MOD.RefreshUnitMedia(_G[childFrame:GetName().."Pet"], key, updateElements)
        end
        if(_G[childFrame:GetName().."Target"]) then
            MOD.RefreshUnitMedia(_G[childFrame:GetName().."Target"], key, updateElements)
        end
        index = index + 1;
        childFrame = self:GetAttribute("child"..index)
    end
end

local UpdateTargetGlow = function(self)
    if not self.unit then return end
    local unit = self.unit;
    if(UnitIsUnit(unit, "target")) then
        self.TargetGlow:Show()
        local reaction = UnitReaction(unit, "player")
        if(UnitIsPlayer(unit)) then
            local _, class = UnitClass(unit)
            if class then
                local colors = SVUI_CLASS_COLORS[class]
                self.TargetGlow:SetBackdropBorderColor(colors.r, colors.g, colors.b)
            else
                self.TargetGlow:SetBackdropBorderColor(1, 1, 1)
            end
        elseif(reaction) then
            local colors = FACTION_BAR_COLORS[reaction]
            self.TargetGlow:SetBackdropBorderColor(colors.r, colors.g, colors.b)
        else
            self.TargetGlow:SetBackdropBorderColor(1, 1, 1)
        end
    else
        self.TargetGlow:Hide()
    end
end

local DetachSubFrames = function(...)
    for i = 1, select("#", ...) do
        local frame = select(i,...)
        frame:ClearAllPoints()
    end
end
--[[
##########################################################
RAID 10, 25, 40
##########################################################
]]--
local Raid10Visibility = function(self, event)
    local db = MOD.db["raid10"]
    if (not db or (db and not db.enable) or (MOD.db and not MOD.db.smartRaidFilter) or self.isForced) then return end

    local instance, instanceType = IsInInstance()
    local _, _, _, _, maxPlayers, _, _ = GetInstanceInfo()
    if event == "PLAYER_REGEN_ENABLED"then
        self:UnregisterEvent("PLAYER_REGEN_ENABLED")
    end
    if not InCombatLockdown()then
        if(instance and (instanceType == "raid") and (maxPlayers == 10)) then
            UnregisterStateDriver(self, "visibility")
            self:Show()
        elseif(instance and (instanceType == "raid")) then
            UnregisterStateDriver(self, "visibility")
            self:Hide()
        elseif db.visibility then
            RegisterStateDriver(self, "visibility", db.visibility)
        end
    else
        self:RegisterEvent("PLAYER_REGEN_ENABLED")
        return
    end
end

local Raid25Visibility = function(self, event)
    local db = MOD.db["raid25"]
    if (not db or (db and not db.enable) or (MOD.db and not MOD.db.smartRaidFilter) or self.isForced) then return end

    local instance, instanceType = IsInInstance()
    local _, _, _, _, maxPlayers, _, _ = GetInstanceInfo()
    if event == "PLAYER_REGEN_ENABLED"then
        self:UnregisterEvent("PLAYER_REGEN_ENABLED")
    end
    if not InCombatLockdown()then
        if(instance and (instanceType == "raid") and (maxPlayers == 25)) then
            UnregisterStateDriver(self, "visibility")
            self:Show()
        elseif(instance and (instanceType == "raid")) then
            UnregisterStateDriver(self, "visibility")
            self:Hide()
        elseif db.visibility then
            RegisterStateDriver(self, "visibility", db.visibility)
        end
    else
        self:RegisterEvent("PLAYER_REGEN_ENABLED")
        return
    end
end

local Raid40Visibility = function(self, event)
    local db = MOD.db["raid40"]
    if (not db or (db and not db.enable) or (MOD.db and not MOD.db.smartRaidFilter) or self.isForced) then return end

    local instance, instanceType = IsInInstance()
    local _, _, _, _, maxPlayers, _, _ = GetInstanceInfo()
    if event == "PLAYER_REGEN_ENABLED"then
        self:UnregisterEvent("PLAYER_REGEN_ENABLED")
    end
    if not InCombatLockdown()then
        if(instance and (instanceType == "raid") and (maxPlayers == 40)) then
            UnregisterStateDriver(self, "visibility")
            self:Show()
        elseif(instance and (instanceType == "raid")) then
            UnregisterStateDriver(self, "visibility")
            self:Hide()
        elseif db.visibility then
            RegisterStateDriver(self, "visibility", db.visibility)
        end
    else
        self:RegisterEvent("PLAYER_REGEN_ENABLED")
        return
    end
end

local UpdateRaidSubUnit = function(self, key, db)
    self.colors = oUF_SuperVillain.colors;
    self:RegisterForClicks(MOD.db.fastClickTarget and "AnyDown" or "AnyUp")
    if not InCombatLockdown() then self:Size(db.width, db.height) end
    do
        local rdBuffs = self.RaidDebuffs;
        if db.rdebuffs.enable then
            self:EnableElement("RaidDebuffs")
            rdBuffs:Size(db.rdebuffs.size)
            rdBuffs:Point("CENTER", self, "CENTER", db.rdebuffs.xOffset, db.rdebuffs.yOffset)
        else
            self:DisableElement("RaidDebuffs")
            rdBuffs:Hide()
        end
    end
    MOD.RefreshUnitMedia(self, key)
    MOD:UpdateAuraWatch(self, key)
    MOD:RefreshUnitLayout(self, key)
    if(key ~= "raidpet") then
        self:EnableElement("ReadyCheck")
    end
    self:UpdateAllElements()
end

local Raid10Update = function(self)
    local frame = self:GetParent()
    if not frame.positioned then
        frame:ClearAllPoints()
        frame:Point("LEFT", SuperVillain.UIParent, "LEFT", 4, 0)
        SuperVillain:SetSVMovable(frame, frame:GetName().."_MOVE", L["Raid 10 Frames"], nil, nil, nil, "ALL, RAID"..10)
        frame:RegisterEvent("PLAYER_ENTERING_WORLD")
        frame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
        frame:SetScript("OnEvent", Raid10Visibility)
        frame.positioned = true
    end
    Raid10Visibility(frame)
    local key = "raid10"
    local db = MOD.db[key]
    local index = 1;
    local childFrame = self:GetAttribute("child"..index)
    while childFrame do
        UpdateRaidSubUnit(childFrame, key, db)
        if(_G[childFrame:GetName().."Pet"]) then
            UpdateRaidSubUnit(_G[childFrame:GetName().."Pet"], key, db)
        end
        if(_G[childFrame:GetName().."Target"]) then
            UpdateRaidSubUnit(_G[childFrame:GetName().."Target"], key, db)
        end
        index = index + 1;
        childFrame = self:GetAttribute("child"..index)
    end
end
UPDATERS["raid10"] = Raid10Update

local Raid25Update = function(self)
    local frame = self:GetParent()
    if not frame.positioned then
        frame:ClearAllPoints()
        frame:Point("LEFT", SuperVillain.UIParent, "LEFT", 4, 0)
        SuperVillain:SetSVMovable(frame, frame:GetName().."_MOVE", L["Raid 25 Frames"], nil, nil, nil, "ALL, RAID"..25)
        frame:RegisterEvent("PLAYER_ENTERING_WORLD")
        frame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
        frame:SetScript("OnEvent", Raid25Visibility)
        frame.positioned = true
    end
    Raid25Visibility(frame)
    local key = "raid25"
    local db = MOD.db[key]
    local index = 1;
    local childFrame = self:GetAttribute("child"..index)
    while childFrame do
        UpdateRaidSubUnit(childFrame, key, db)
        if(_G[childFrame:GetName().."Pet"]) then
            UpdateRaidSubUnit(_G[childFrame:GetName().."Pet"], key, db)
        end
        if(_G[childFrame:GetName().."Target"]) then
            UpdateRaidSubUnit(_G[childFrame:GetName().."Target"], key, db)
        end
        index = index + 1;
        childFrame = self:GetAttribute("child"..index)
    end
end
UPDATERS["raid25"] = Raid25Update

local Raid40Update = function(self)
    local frame = self:GetParent()
    if not frame.positioned then
        frame:ClearAllPoints()
        frame:Point("LEFT", SuperVillain.UIParent, "LEFT", 4, 0)
        SuperVillain:SetSVMovable(frame, frame:GetName().."_MOVE", L["Raid 40 Frames"], nil, nil, nil, "ALL, RAID"..40)
        frame:RegisterEvent("PLAYER_ENTERING_WORLD")
        frame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
        frame:SetScript("OnEvent", Raid40Visibility)
        frame.positioned = true
    end
    Raid40Visibility(frame)
    local key = "raid40"
    local db = MOD.db[key]
    local index = 1;
    local childFrame = self:GetAttribute("child"..index)
    while childFrame do
        UpdateRaidSubUnit(childFrame, key, db)
        if(_G[childFrame:GetName().."Pet"]) then
            UpdateRaidSubUnit(_G[childFrame:GetName().."Pet"], key, db)
        end
        if(_G[childFrame:GetName().."Target"]) then
            UpdateRaidSubUnit(_G[childFrame:GetName().."Target"], key, db)
        end
        index = index + 1;
        childFrame = self:GetAttribute("child"..index)
    end
end
UPDATERS["raid40"] = Raid40Update

local function SetRaidFrame(frame)
    frame:SetScript("OnEnter", UnitFrame_OnEnter)
    frame:SetScript("OnLeave", UnitFrame_OnLeave)

    frame.RaidDebuffs = MOD:CreateRaidDebuffs(frame)
    frame.Afflicted = MOD:CreateAfflicted(frame)
    frame.ResurrectIcon = MOD:CreateResurectionIcon(frame)
    frame.LFDRole = MOD:CreateRoleIcon(frame)
    frame.RaidRoleFramesAnchor = MOD:CreateRaidRoleFrames(frame)
    frame.RaidIcon = MOD:CreateRaidIcon(frame)
    frame.ReadyCheck = MOD:CreateReadyCheckIcon(frame)
    frame.HealPrediction = MOD:CreateHealPrediction(frame)
    frame.Range = { insideAlpha = 1, outsideAlpha = 1 }

    local shadow = CreateFrame("Frame", nil, frame)
    shadow:SetFrameLevel(1)
    shadow:SetFrameStrata(frame:GetFrameStrata())
    shadow:WrapOuter(frame, 3, 3)
    shadow:SetBackdrop({
        edgeFile = [[Interface\AddOns\SVUI\assets\artwork\Template\GLOW]],
        edgeSize = SuperVillain:Scale(3),
        insets = {
            left = SuperVillain:Scale(5),
            right = SuperVillain:Scale(5),
            top = SuperVillain:Scale(5),
            bottom = SuperVillain:Scale(5)
        }
    })
    shadow:SetBackdropColor(0, 0, 0, 0)
    shadow:SetBackdropBorderColor(0, 0, 0, 0.9)
    shadow:Hide()
    frame.TargetGlow = shadow
    tinsert(frame.__elements, UpdateTargetGlow)
    frame:RegisterEvent("PLAYER_TARGET_CHANGED", UpdateTargetGlow)
    frame:RegisterEvent("PLAYER_ENTERING_WORLD", UpdateTargetGlow)

    return frame
end

CONSTRUCTORS["raid10"] = function(self, unit)
    local key = "raid10"
    self.unit = unit
    self.___key = key
    MOD:SetActionPanel(self, key)
    self.Health = MOD:CreateHealthBar(self, true)
    self.Power = MOD:CreatePowerBar(self, true)
    self.Power.frequentUpdates = false
    self.Buffs = MOD:CreateBuffs(self, key)
    self.Debuffs = MOD:CreateDebuffs(self, key)
    self.AuraWatch = MOD:CreateAuraWatch(self, key)
    return SetRaidFrame(self)
end

CONSTRUCTORS["raid25"] = function(self, unit)
    local key = "raid25"
    self.unit = unit
    self.___key = key
    MOD:SetActionPanel(self, key)
    self.Health = MOD:CreateHealthBar(self, true)
    self.Power = MOD:CreatePowerBar(self, true)
    self.Power.frequentUpdates = false
    self.Buffs = MOD:CreateBuffs(self, key)
    self.Debuffs = MOD:CreateDebuffs(self, key)
    self.AuraWatch = MOD:CreateAuraWatch(self, key)
    return SetRaidFrame(self)
end

CONSTRUCTORS["raid40"] = function(self, unit)
    local key = "raid40"
    self.unit = unit
    self.___key = key
    MOD:SetActionPanel(self, key)
    self.Health = MOD:CreateHealthBar(self, true)
    self.Power = MOD:CreatePowerBar(self, true)
    self.Power.frequentUpdates = false
    self.Buffs = MOD:CreateBuffs(self, key)
    self.Debuffs = MOD:CreateDebuffs(self, key)
    self.AuraWatch = MOD:CreateAuraWatch(self, key)
    return SetRaidFrame(self)
end
--[[
##########################################################
RAID PETS
##########################################################
]]--
local RaidPetVisibility = function(self, event)
    local db = MOD.db["raidpet"]
    if (not db or (db and not db.enable) or (MOD.db and not MOD.db.smartRaidFilter) or self.isForced) then return end
    local inInstance, instanceType = IsInInstance()
    if event == "PLAYER_REGEN_ENABLED" then self:UnregisterEvent("PLAYER_REGEN_ENABLED") end

    if not InCombatLockdown() then
        if inInstance and instanceType == "raid" then
            UnregisterStateDriver(self, "visibility")
            self:Show()
        elseif db.visibility then
            RegisterStateDriver(self, "visibility", db.visibility)
        end
    else
        self:RegisterEvent("PLAYER_REGEN_ENABLED")
        return
    end
end

local UpdateRaidPetFrame = function(self)
    local raidPets = self:GetParent()
    if not raidPets.positioned then
        raidPets:ClearAllPoints()
        raidPets:Point("BOTTOMLEFT", SuperVillain.UIParent, "BOTTOMLEFT", 4, 433)
        SuperVillain:SetSVMovable(raidPets, raidPets:GetName().."_MOVE", L["Raid Pet Frames"], nil, nil, nil, "ALL, RAID10, RAID25, RAID40")
        raidPets.positioned = true;
        raidPets:RegisterEvent("PLAYER_ENTERING_WORLD")
        raidPets:RegisterEvent("ZONE_CHANGED_NEW_AREA")
        raidPets:SetScript("OnEvent", RaidPetVisibility)
    end
    RaidPetVisibility(raidPets)
    local key = "raidpet"
    local db = MOD.db[key]
    local index = 1;
    local childFrame = self:GetAttribute("child"..index)
    while childFrame do
        UpdateRaidSubUnit(childFrame, key, db)
        if(_G[childFrame:GetName().."Pet"]) then
            UpdateRaidSubUnit(_G[childFrame:GetName().."Pet"], key, db)
        end
        if(_G[childFrame:GetName().."Target"]) then
            UpdateRaidSubUnit(_G[childFrame:GetName().."Target"], key, db)
        end
        index = index + 1;
        childFrame = self:GetAttribute("child"..index)
    end
end
UPDATERS["raidpet"] = UpdateRaidPetFrame

CONSTRUCTORS["raidpet"] = function(self, unit)
    local key = "raidpet"
    self.unit = unit
    self.___key = key
    self:SetScript("OnEnter", UnitFrame_OnEnter)
    self:SetScript("OnLeave", UnitFrame_OnLeave)
    MOD:SetActionPanel(self, key)
    self.Health = MOD:CreateHealthBar(self, true)
    self.Debuffs = MOD:CreateDebuffs(self, key)
    self.AuraWatch = MOD:CreateAuraWatch(self, key)
    self.RaidDebuffs = MOD:CreateRaidDebuffs(self)
    self.Afflicted = MOD:CreateAfflicted(self)
    self.RaidIcon = MOD:CreateRaidIcon(self)
    self.Range = { insideAlpha = 1, outsideAlpha = 1 }

    local shadow = CreateFrame("Frame", nil, self)
    shadow:SetFrameLevel(1)
    shadow:SetFrameStrata(self:GetFrameStrata())
    shadow:WrapOuter(self, 3, 3)
    shadow:SetBackdrop({
        edgeFile = [[Interface\AddOns\SVUI\assets\artwork\Template\GLOW]],
        edgeSize = SuperVillain:Scale(3),
        insets = {
            left = SuperVillain:Scale(5),
            right = SuperVillain:Scale(5),
            top = SuperVillain:Scale(5),
            bottom = SuperVillain:Scale(5)
        }
    })
    shadow:SetBackdropColor(0, 0, 0, 0)
    shadow:SetBackdropBorderColor(0, 0, 0, 0.9)
    shadow:Hide()
    self.TargetGlow = shadow
    tinsert(self.__elements, UpdateTargetGlow)
    self:RegisterEvent("PLAYER_TARGET_CHANGED", UpdateTargetGlow)
    self:RegisterEvent("PLAYER_ENTERING_WORLD", UpdateTargetGlow)
    return self
end
--[[
##########################################################
PARTY
##########################################################
]]--
local PartyVisibility = function(self, event)
    local db = MOD.db["party"]
    if (not db or (db and not db.enable) or (MOD.db and not MOD.db.smartRaidFilter) or self.isForced) then return end
    local instance, instanceType = IsInInstance()
    if(event == "PLAYER_REGEN_ENABLED") then
        self:UnregisterEvent("PLAYER_REGEN_ENABLED")
    end
    if(not InCombatLockdown()) then
        if(instance and instanceType == "raid") then
            UnregisterStateDriver(self,"visibility")
            self:Hide()
        elseif db.visibility then
            RegisterStateDriver(self, "visibility", db.visibility)
        end
    else
        self:RegisterEvent("PLAYER_REGEN_ENABLED")
    end
end

local UpdatePartySubUnit = function(self, key, db)
    self.colors = oUF_SuperVillain.colors;
    self:RegisterForClicks(MOD.db.fastClickTarget and 'AnyDown' or 'AnyUp')
    MOD.RefreshUnitMedia(self, key)
    if self.isChild then
        local altDB = db.petsGroup;
        if self == _G[self.originalParent:GetName()..'Target'] then
            altDB = db.targetsGroup
        end
        if not self.originalParent.childList then
            self.originalParent.childList = {}
        end
        self.originalParent.childList[self] = true;
        if not InCombatLockdown()then
            if altDB.enable then
                self:SetParent(self.originalParent)
                self:Size(altDB.width,altDB.height)
                self:ClearAllPoints()
                SuperVillain:ReversePoint(self, altDB.anchorPoint, self.originalParent, altDB.xOffset, altDB.yOffset)
            else
                self:SetParent(SuperVillain.Cloaked)
            end
        end
        do
            local health = self.Health;
            health.Smooth = nil;
            health.frequentUpdates = nil;
            health.colorSmooth = nil;
            health.colorHealth = nil;
            health.colorClass = true;
            health.colorReaction = true;
            health:ClearAllPoints()
            health:Point("TOPRIGHT", self, "TOPRIGHT", -1, -1)
            health:Point("BOTTOMLEFT", self, "BOTTOMLEFT", 1, 1)
        end
        do
            local nametext = self.InfoPanel.Name
            self:Tag(nametext, altDB.tags)
        end
    else
        if not InCombatLockdown() then
            self:Size(db.width,db.height)
        end
        MOD:RefreshUnitLayout(self, key)
        MOD:UpdateAuraWatch(self, key)
    end
    self:EnableElement('ReadyCheck')
    self:UpdateAllElements()
end

local UpdatePartyFrame = function(self)
    local group = self:GetParent()
    if not group.positioned then
        group:ClearAllPoints()
        group:Point("LEFT",SuperVillain.UIParent,"LEFT",40,0)
        SuperVillain:SetSVMovable(group, group:GetName()..'_MOVE', L['Party Frames'], nil, nil, nil, 'ALL,PARTY,ARENA');
        group.positioned = true;
        group:RegisterEvent("PLAYER_ENTERING_WORLD")
        group:RegisterEvent("ZONE_CHANGED_NEW_AREA")
        group:SetScript("OnEvent", PartyVisibility)
    end
    PartyVisibility(group)
    local key = "party"
    local db = MOD.db[key]
    local index = 1;
    local childFrame = self:GetAttribute("child"..index)
    while childFrame do
        UpdatePartySubUnit(childFrame, key, db)
        if(_G[childFrame:GetName().."Pet"]) then
            UpdatePartySubUnit(_G[childFrame:GetName().."Pet"], key, db)
        end
        if(_G[childFrame:GetName().."Target"]) then
            UpdatePartySubUnit(_G[childFrame:GetName().."Target"], key, db)
        end
        index = index + 1;
        childFrame = self:GetAttribute("child"..index)
    end
end
UPDATERS["party"] = UpdatePartyFrame

CONSTRUCTORS["party"] = function(self, unit)
    local key = "party"
    self.unit = unit
    self.___key = key
    self:SetScript("OnEnter", UnitFrame_OnEnter)
    self:SetScript("OnLeave", UnitFrame_OnLeave)

    MOD:SetActionPanel(self, key)
    self.Health = MOD:CreateHealthBar(self, true)

    if self.isChild then
        self.originalParent = self:GetParent()
    else
        self.Power = MOD:CreatePowerBar(self, true)
        self.Power.frequentUpdates = false
        MOD:CreatePortrait(self, true)
        self.Buffs = MOD:CreateBuffs(self, key)
        self.Debuffs = MOD:CreateDebuffs(self, key)
        self.AuraWatch = MOD:CreateAuraWatch(self, key)
        self.Afflicted = MOD:CreateAfflicted(self)
        self.ResurrectIcon = MOD:CreateResurectionIcon(self)
        self.LFDRole = MOD:CreateRoleIcon(self)
        self.RaidRoleFramesAnchor = MOD:CreateRaidRoleFrames(self)
        self.RaidIcon = MOD:CreateRaidIcon(self)
        self.ReadyCheck = MOD:CreateReadyCheckIcon(self)
        self.HealPrediction = MOD:CreateHealPrediction(self)

        local shadow = CreateFrame("Frame", nil, self)
        shadow:SetFrameLevel(1)
        shadow:SetFrameStrata(self:GetFrameStrata())
        shadow:WrapOuter(self, 3, 3)
        shadow:SetBackdrop({
            edgeFile = [[Interface\AddOns\SVUI\assets\artwork\Template\GLOW]],
            edgeSize = SuperVillain:Scale(3),
            insets = {
                left = SuperVillain:Scale(5),
                right = SuperVillain:Scale(5),
                top = SuperVillain:Scale(5),
                bottom = SuperVillain:Scale(5)
            }
        })
        shadow:SetBackdropColor(0, 0, 0, 0)
        shadow:SetBackdropBorderColor(0, 0, 0, 0.9)
        shadow:Hide()
        self.TargetGlow = shadow
        tinsert(self.__elements, UpdateTargetGlow)
        self:RegisterEvent("PLAYER_TARGET_CHANGED", UpdateTargetGlow)
        self:RegisterEvent("PLAYER_ENTERING_WORLD", UpdateTargetGlow)
        self:RegisterEvent("GROUP_ROSTER_UPDATE", UpdateTargetGlow)
    end

    self.Range = { insideAlpha = 1, outsideAlpha = 1 }
    return self
end
--[[
##########################################################
TANK
##########################################################
]]--
local UpdateTankSubUnit = function(self, key, db)
    self.colors = oUF_SuperVillain.colors;
    self:RegisterForClicks(MOD.db.fastClickTarget and "AnyDown" or "AnyUp")
    MOD.RefreshUnitMedia(self, key)
    if self.isChild and self.originalParent then
        local targets = db.targetsGroup;
        if not self.originalParent.childList then
            self.originalParent.childList = {}
        end
        self.originalParent.childList[self] = true;
        if not InCombatLockdown()then
            if targets.enable then
                self:SetParent(self.originalParent)
                self:Size(targets.width, targets.height)
                self:ClearAllPoints()
                SuperVillain:ReversePoint(self, targets.anchorPoint, self.originalParent, targets.xOffset, targets.yOffset)
            else
                self:SetParent(SuperVillain.Cloaked)
            end
        end
    elseif not InCombatLockdown()then
        self:Size(db.width, db.height)
    end
    MOD:RefreshUnitLayout(self, key)
    do
        local nametext = self.InfoPanel.Name;
        if oUF_SuperVillain.colors.healthclass then
            self:Tag(nametext, "[name:10]")
        else
            self:Tag(nametext, "[name:color][name:10]")
        end
    end
    self:UpdateAllElements()
end

local UpdateTankFrame = function(self)
    local key = "tank"
    local db = MOD.db[key]
    if db.enable ~= true then
        UnregisterAttributeDriver(self, "state-visibility")
        self:Hide()
        return
    end
    self:Hide()
    DetachSubFrames(self:GetChildren())
    self:SetAttribute("startingIndex", -1)
    RegisterAttributeDriver(self, "state-visibility", "show")
    self.dirtyWidth, self.dirtyHeight = self:GetSize()
    RegisterAttributeDriver(self, "state-visibility", "[@raid1, exists] show;hide")
    self:SetAttribute("startingIndex", 1)
    self:SetAttribute("point", "BOTTOM")
    self:SetAttribute("columnAnchorPoint", "LEFT")
    DetachSubFrames(self:GetChildren())
    self:SetAttribute("yOffset", 7)
    if not self.positioned then
        self:ClearAllPoints()
        self:Point("TOPLEFT", SuperVillain.UIParent, "TOPLEFT", 4, -40)
        SuperVillain:SetSVMovable(self, self:GetName().."_MOVE", L["Tank Frames"], nil, nil, nil, "ALL, RAID10, RAID25, RAID40")
        self.Avatar.positionOverride = "TOPLEFT"
        self:SetAttribute("minHeight", self.dirtyHeight)
        self:SetAttribute("minWidth", self.dirtyWidth)
        self.positioned = true
    end
    for i = 1, self:GetNumChildren() do
        local childFrame = select(i, self:GetChildren())
        UpdateTankSubUnit(childFrame, key, db)
        if(_G[childFrame:GetName().."Pet"]) then
            UpdateTankSubUnit(_G[childFrame:GetName().."Pet"], key, db)
        end
        if(_G[childFrame:GetName().."Target"]) then
            UpdateTankSubUnit(_G[childFrame:GetName().."Target"], key, db)
        end
    end
end
UPDATERS["tank"] = UpdateTankFrame

CONSTRUCTORS["tank"] = function(self, unit)
    local key = "tank"
    local db = MOD.db[key]
    self.unit = unit
    self.___key = key
    self:SetScript("OnEnter", UnitFrame_OnEnter)
    self:SetScript("OnLeave", UnitFrame_OnLeave)
    MOD:SetActionPanel(self, key)
    self.Health = MOD:CreateHealthBar(self, true)
    self.RaidIcon = MOD:CreateRaidIcon(self)
    self.RaidIcon:SetPoint("BOTTOMRIGHT")
    self.Range = { insideAlpha = 1, outsideAlpha = 1 }
    UpdateTankSubUnit(self, key, db)
    self.originalParent = self:GetParent()
    return self
end
--[[
##########################################################
ASSIST
##########################################################
]]--
local UpdateAssistSubUnit = function(self, key, db)
    self.colors = oUF_SuperVillain.colors;
    self:RegisterForClicks(MOD.db.fastClickTarget and "AnyDown" or "AnyUp")
    MOD.RefreshUnitMedia(self, key)
    if self.isChild and self.originalParent then
        local targets = db.targetsGroup;
        if not self.originalParent.childList then
            self.originalParent.childList = {}
        end
        self.originalParent.childList[self] = true;
        if not InCombatLockdown()then
            if targets.enable then
                self:SetParent(self.originalParent)
                self:Size(targets.width, targets.height)
                self:ClearAllPoints()
                SuperVillain:ReversePoint(self, targets.anchorPoint, self.originalParent, targets.xOffset, targets.yOffset)
            else
                self:SetParent(SuperVillain.Cloaked)
            end
        end
    elseif not InCombatLockdown()then
        self:Size(db.width, db.height)
    end

    MOD:RefreshUnitLayout(self, key)

    do
        local nametext = self.InfoPanel.Name;
        if oUF_SuperVillain.colors.healthclass then
            self:Tag(nametext, "[name:10]")
        else
            self:Tag(nametext, "[name:color][name:10]")
        end
    end
    self:UpdateAllElements()
end

local UpdateAssistFrame = function(self)
    local key = "assist"
    local db = MOD.db[key]
    self:Hide()
    DetachSubFrames(self:GetChildren())
    self:SetAttribute("startingIndex", -1)
    RegisterAttributeDriver(self, "state-visibility", "show")
    self.dirtyWidth, self.dirtyHeight = self:GetSize()
    RegisterAttributeDriver(self, "state-visibility", "[@raid1, exists] show;hide")
    self:SetAttribute("startingIndex", 1)
    self:SetAttribute("point", "BOTTOM")
    self:SetAttribute("columnAnchorPoint", "LEFT")
    DetachSubFrames(self:GetChildren())
    self:SetAttribute("yOffset", 7)
    if not self.positioned then
        self:ClearAllPoints()
        self:Point("TOPLEFT", SuperVillain.UIParent, "TOPLEFT", 4, -140)
        SuperVillain:SetSVMovable(self, self:GetName().."_MOVE", L["Assist Frames"], nil, nil, nil, "ALL, RAID10, RAID25, RAID40")
        self.Avatar.positionOverride = "TOPLEFT"
        self:SetAttribute("minHeight", self.dirtyHeight)
        self:SetAttribute("minWidth", self.dirtyWidth)
        self.positioned = true
    end
    for i = 1, self:GetNumChildren() do
        local childFrame = select(i, self:GetChildren())
        UpdateAssistSubUnit(childFrame, key, db)
        if(_G[childFrame:GetName().."Pet"]) then
            UpdateAssistSubUnit(_G[childFrame:GetName().."Pet"], key, db)
        end
        if(_G[childFrame:GetName().."Target"]) then
            UpdateAssistSubUnit(_G[childFrame:GetName().."Target"], key, db)
        end
    end
end
UPDATERS["assist"] = UpdateAssistFrame

CONSTRUCTORS["assist"] = function(self, unit)
    local key = "assist"
    local db = MOD.db[key]
    self.unit = unit
    self.___key = key
    self:SetScript("OnEnter", UnitFrame_OnEnter)
    self:SetScript("OnLeave", UnitFrame_OnLeave)
    MOD:SetActionPanel(self, key)
    self.Health = MOD:CreateHealthBar(self, true)
    self.RaidIcon = MOD:CreateRaidIcon(self)
    self.RaidIcon:SetPoint("BOTTOMRIGHT")
    self.Range = { insideAlpha = 1, outsideAlpha = 1 }
    UpdateAssistSubUnit(self, key, db)
    self.originalParent = self:GetParent()
    return self
end
--[[
##########################################################
SUBUNIT CONSTRUCTORS
##########################################################
]]--
local SecureHeaderClear = function(self)
    self:Hide()
    self:SetAttribute("showPlayer", true)
    self:SetAttribute("showSolo", true)
    self:SetAttribute("showParty", true)
    self:SetAttribute("showRaid", true)
    self:SetAttribute("columnSpacing", nil)
    self:SetAttribute("columnAnchorPoint", nil)
    self:SetAttribute("sortMethod", nil)
    self:SetAttribute("groupFilter", nil)
    self:SetAttribute("groupingOrder", nil)
    self:SetAttribute("maxColumns", nil)
    self:SetAttribute("nameList", nil)
    self:SetAttribute("point", nil)
    self:SetAttribute("sortDir", nil)
    self:SetAttribute("sortMethod", "NAME")
    self:SetAttribute("startingIndex", nil)
    self:SetAttribute("strictFiltering", nil)
    self:SetAttribute("unitsPerColumn", nil)
    self:SetAttribute("xOffset", nil)
    self:SetAttribute("yOffset", nil)
end

local function ConstructGroupHeader(parentFrame, filter, styleName, headerName, template1, groupName, template2)
    local db = MOD.db[groupName]
    oUF_SuperVillain:SetActiveStyle(styleName)
    local groupHeader = oUF_SuperVillain:SpawnHeader(headerName, template2, nil,
        "oUF-initialConfigFunction", ("self:SetWidth(%d); self:SetHeight(%d); self:SetFrameLevel(5)"):format(db.width, db.height),
        "groupFilter", filter,
        "showParty", true,
        "showRaid", true,
        "showSolo", true,
        template1 and "template", template1
    )
    groupHeader.___groupkey = groupName
    groupHeader:SetParent(parentFrame)
    groupHeader:Show()

    groupHeader.Update = UPDATERS[groupName]
    groupHeader.MediaUpdate = GroupMediaUpdate
    groupHeader.ClearAllAttributes = SecureHeaderClear

    return groupHeader
end
--[[
##########################################################
GROUP HEADER METHODS
##########################################################
]]--
local GroupSetConfigEnvironment = function(self)
    local key = self.___groupkey
    local db = MOD.db[key]
    local anchorPoint;
    local widthCalc, heightCalc, xCalc, yCalc = 0, 0, 0, 0;
    local sorting = db.showBy;
    local pointMap = _POINTMAP[sorting]
    local point1, point2, point3, point4, point5, horizontal, vertical, isHorizontal = pointMap[1], pointMap[2], pointMap[3], pointMap[4], pointMap[5], pointMap[6], pointMap[7], pointMap[8];
    for i = 1, db.groupCount do
        local frame = self.groups[i]
        if frame then
            if(db.showBy == "UP") then
                db.showBy = "UP_RIGHT"
            end
            if(db.showBy == "DOWN") then
                db.showBy = "DOWN_RIGHT"
            end
            if isHorizontal then
                frame:SetAttribute("xOffset", db.wrapXOffset * horizontal)
                frame:SetAttribute("yOffset", 0)
                frame:SetAttribute("columnSpacing", db.wrapYOffset)
            else
                frame:SetAttribute("xOffset", 0)
                frame:SetAttribute("yOffset", db.wrapYOffset * vertical)
                frame:SetAttribute("columnSpacing", db.wrapXOffset)
            end
            if not frame.isForced then
                if not frame.initialized then
                    frame:SetAttribute("startingIndex", db.customSorting and (-min(db.groupCount * db.gRowCol * 5, MAX_RAID_MEMBERS) + 1) or -4)
                    frame:Show()
                    frame.initialized = true
                end
                frame:SetAttribute("startingIndex", 1)
            end
            frame:ClearAllPoints()
            if db.customSorting and db.invertGroupingOrder then
                frame:SetAttribute("columnAnchorPoint", point4)
            else
                frame:SetAttribute("columnAnchorPoint", point3)
            end
            DetachSubFrames(frame:GetChildren())
            frame:SetAttribute("point", point1)
            if not frame.isForced then
                frame:SetAttribute("maxColumns", db.customSorting and db.groupCount or 1)
                frame:SetAttribute("unitsPerColumn", db.customSorting and (db.gRowCol * 5) or 5)
                _GSORT[db.sortMethod](frame)
                frame:SetAttribute("sortDir", db.sortDir)
                frame:SetAttribute("showPlayer", db.showPlayer)
            end
            if i == 1 and db.customSorting then
                frame:SetAttribute("groupFilter", "1, 2, 3, 4, 5, 6, 7, 8")
            else
                frame:SetAttribute("groupFilter", tostring(i))
            end
        end
        local anchorPoint = point2
        if db.customSorting and db.startFromCenter then
            anchorPoint = point5
        end
        if (i - 1) % db.gRowCol == 0 then
            if isHorizontal then
                if frame then
                    frame:SetPoint(anchorPoint, self, anchorPoint, 0, heightCalc * vertical)
                end
                heightCalc = heightCalc + db.height + db.wrapYOffset;
                yCalc = yCalc + 1
            else
                if frame then
                    frame:SetPoint(anchorPoint, self, anchorPoint, widthCalc * horizontal, 0)
                end
                widthCalc = widthCalc + db.width + db.wrapXOffset;
                xCalc = xCalc + 1
            end
        else
            if isHorizontal then
                if yCalc == 1 then
                    if frame then
                        frame:SetPoint(anchorPoint, self, anchorPoint, widthCalc * horizontal, 0)
                    end
                    widthCalc = widthCalc + (db.width + db.wrapXOffset) * 5;
                    xCalc = xCalc + 1
                elseif frame then
                    frame:SetPoint(anchorPoint, self, anchorPoint, (((db.width + db.wrapXOffset) * 5) * ((i - 1) % db.gRowCol)) * horizontal, ((db.height + db.wrapYOffset) * (yCalc - 1)) * vertical)
                end
            else
                if xCalc == 1 then
                    if frame then
                        frame:SetPoint(anchorPoint, self, anchorPoint, 0, heightCalc * vertical)
                    end
                    heightCalc = heightCalc + (db.height + db.wrapYOffset) * 5;
                    yCalc = yCalc + 1
                elseif frame then
                    frame:SetPoint(anchorPoint, self, anchorPoint, ((db.width + db.wrapXOffset) * (xCalc - 1)) * horizontal, (((db.height + db.wrapYOffset) * 5) * ((i - 1) % db.gRowCol)) * vertical)
                end
            end
        end
        if heightCalc == 0 then
            heightCalc = heightCalc + (db.height + db.wrapYOffset) * 5
        elseif widthCalc == 0 then
            widthCalc = widthCalc + (db.width + db.wrapXOffset) * 5
        end
    end
    self:SetSize(widthCalc - db.wrapXOffset, heightCalc - db.wrapYOffset)
end

local GroupHeaderUpdate = function(self)
    local key = self.___groupkey
    if MOD.db[key].enable ~= true then
        UnregisterAttributeDriver(self, "state-visibility")
        self:Hide()
        return
    end
    for i=1,#self.groups do
        self.groups[i]:Update()
    end
end

local GroupSetActiveState = function(self)
    if not self.isForced then
        local key = self.___groupkey
        local db = MOD.db[key]
        if(db) then
            for i=1,#self.groups do
                local frame = self.groups[i]
                if(i <= db.groupCount and ((db.customSorting and i <= 1) or not db.customSorting)) then

                    frame:Show()
                else
                    if frame.forceShow then
                        frame:Hide()
                        MOD:RestrictChildren(frame, frame:GetChildren())
                        frame:SetAttribute('startingIndex',1)
                    else
                        frame:ClearAllAttributes()
                    end
                end
            end
        end
    end
end
--[[
##########################################################
LOAD/UPDATE METHOD
##########################################################
]]--
function MOD:SetGroupFrame(key, filter, template1, forceUpdate, template2)
    if not self.db[key] then return end
    local db = self.db[key]
    local realName = key:gsub("(.)", upper, 1)
    local styleName = "SVUI_"..realName
    local frame, groupName
    if(not self.Headers[key]) then
        oUF_SuperVillain:RegisterStyle(styleName, CONSTRUCTORS[key])
        oUF_SuperVillain:SetActiveStyle(styleName)

        if(key == "tank" or key == "assist") then
            frame = ConstructGroupHeader(SVUI_UnitFrameParent, filter, styleName, styleName, template1, key, template2)
        else
            frame = CreateFrame("Frame", styleName, SVUI_UnitFrameParent, "SecureHandlerStateTemplate")
            frame.groups = {}
            frame.___groupkey = key;
            frame.Update = GroupHeaderUpdate
            frame.MediaUpdate = GroupMediaUpdate
            frame.SetActiveState = GroupSetActiveState
            frame.SetConfigEnvironment = GroupSetConfigEnvironment
        end
        frame:Show()
        self.Headers[key] = frame
    else
        frame = self.Headers[key]
    end

    if(key == "tank" or key == "assist") then
        frame:Update()
    else
        if(db.enable ~= true and key ~= "raidpet") then
            UnregisterStateDriver(frame, "visibility")
            frame:Hide()
            return
        end

        if(db.customSorting) then
            if(not frame.groups[1]) then
                groupName = styleName .. "Group1"
                frame.groups[1] = ConstructGroupHeader(frame, 1, styleName, groupName, template1, key, template2)
            end
        else
            for i = 1, db.groupCount do
                if(not frame.groups[i]) then
                    groupName = styleName .. "Group" .. i
                    frame.groups[i] = ConstructGroupHeader(frame, i, styleName, groupName, template1, key, template2)
                end
            end
        end

        frame:SetActiveState()

        if(forceUpdate or not frame.Avatar) then
            frame:SetConfigEnvironment()
            if(not frame.isForced) then
                RegisterStateDriver(frame, "visibility", db.visibility)
            end
        else
            frame:SetConfigEnvironment()
            frame:Update()
        end

        if(db.enable ~= true and key == "raidpet") then
            UnregisterStateDriver(frame, "visibility")
            frame:Hide()
            return
        end
    end
end