You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1581 lines
58 KiB

local Units = {headerFrames = {}, unitFrames = {}, frameList = {}, unitEvents = {}, remappedUnits = {}, canCure = {}}
Units.childUnits = {["partytarget"] = "party", ["partytargettarget"] = "party", ["partypet"] = "party", ["maintanktarget"] = "maintank", ["mainassisttarget"] = "mainassist", ["bosstarget"] = "boss", ["arenatarget"] = "arena", ["arenatargettarget"] = "arena", ["arenapet"] = "arena", ["battlegroundpet"] = "battleground", ["battlegroundtarget"] = "battleground", ["battlegroundtargettarget"] = "battleground", ["maintanktargettarget"] = "maintank", ["mainassisttargettarget"] = "mainassist", ["bosstargettarget"] = "boss"}
Units.zoneUnits = {["arena"] = "arena", ["arenapet"] = "arena", ["arenatarget"] = "arena", ["arenatargettarget"] = "arena", ["boss"] = {"party", "raid"}, ["bosstarget"] = {"party", "raid"}, ["battleground"] = "pvp", ["battlegroundtarget"] = "pvp", ["battlegroundtargettarget"] = "pvp", ["battlegroundpet"] = "pvp", ["bosstargettarget"] = {"party", "raid"}}
Units.remappedUnits = {["battleground"] = "arena", ["battlegroundpet"] = "arenapet", ["battlegroundtarget"] = "arenatarget", ["battlegroundtargettarget"] = "arenatargettarget"}
Units.headerUnits = {["raid"] = true, ["party"] = true, ["maintank"] = true, ["mainassist"] = true, ["raidpet"] = true, ["partypet"] = true}
local stateMonitor = CreateFrame("Frame", nil, nil, "SecureHandlerBaseTemplate")
stateMonitor.raids = {}
local playerClass = select(2, UnitClass("player"))
local unitFrames, headerFrames, frameList, unitEvents, childUnits, headerUnits, queuedCombat, zoneUnits = Units.unitFrames, Units.headerFrames, Units.frameList, Units.unitEvents, Units.childUnits, Units.headerUnits, {}, Units.zoneUnits
local remappedUnits = Units.remappedUnits
local _G = getfenv(0)
ShadowUF.Units = Units
ShadowUF:RegisterModule(Units, "units")
-- This is the wrapper frame that everything parents to so we can just hide it when we need to deal with pet battles
local petBattleFrame = CreateFrame("Frame", "SUFWrapperFrame", UIParent, "SecureHandlerBaseTemplate")
petBattleFrame:SetFrameStrata("BACKGROUND")
petBattleFrame:SetAllPoints(UIParent)
petBattleFrame:WrapScript(petBattleFrame, "OnAttributeChanged", [[
if( name ~= "state-petbattle" ) then return end
if( value == "active" ) then
self:Hide()
else
self:Show()
end
]])
RegisterStateDriver(petBattleFrame, "petbattle", "[petbattle] active; none")
-- Frame shown, do a full update
local function FullUpdate(self)
for i=1, #(self.fullUpdates), 2 do
local handler = self.fullUpdates[i]
handler[self.fullUpdates[i + 1]](handler, self)
end
end
-- Re-registers events when unit changes
local function ReregisterUnitEvents(self)
-- Not an unit event
if( ShadowUF.fakeUnits[self.unitRealType] or not headerUnits[self.unitType] ) then return end
for event, list in pairs(self.registeredEvents) do
if( unitEvents[event] ) then
local hasHandler
for handler in pairs(list) do
hasHandler = true
break
end
if( hasHandler ) then
self:UnregisterEvent(event)
self:BlizzRegisterUnitEvent(event, self.unitOwner, self.vehicleUnit)
end
end
end
end
-- Register an event that should always call the frame
local function RegisterNormalEvent(self, event, handler, func, unitOverride)
-- Make sure the handler/func exists
if( not handler[func] ) then
error(string.format("Invalid handler/function passed for %s on event %s, the function %s does not exist.", self:GetName() or tostring(self), tostring(event), tostring(func)), 3)
return
end
-- XXX: replace once 9.0 goes live, and we can cleanly remove events from tags and all modules
if event == "UNIT_HEALTH_FREQUENT" then
event = "UNIT_HEALTH"
end
if( unitEvents[event] and not ShadowUF.fakeUnits[self.unitRealType] ) then
self:BlizzRegisterUnitEvent(event, unitOverride or self.unitOwner, self.vehicleUnit)
if unitOverride then
self.unitEventOverrides = self.unitEventOverrides or {}
self.unitEventOverrides[event] = unitOverride
end
else
self:RegisterEvent(event)
end
self.registeredEvents[event] = self.registeredEvents[event] or {}
-- Each handler can only register an event once per a frame.
if( self.registeredEvents[event][handler] ) then
return
end
self.registeredEvents[event][handler] = func
end
-- Unregister an event
local function UnregisterEvent(self, event, handler)
if( self.registeredEvents[event] and self.registeredEvents[event][handler] ) then
self.registeredEvents[event][handler] = nil
local hasHandler
for _handler in pairs(self.registeredEvents[event]) do
hasHandler = true
break
end
if( not hasHandler ) then
self:UnregisterEvent(event)
end
end
end
-- Register an event thats only called if it's for the actual unit
local function RegisterUnitEvent(self, event, handler, func)
unitEvents[event] = true
RegisterNormalEvent(self, event, handler, func)
end
-- Register a function to be called in an OnUpdate if it's an invalid unit (targettarget/etc)
local function RegisterUpdateFunc(self, handler, func)
if( not handler[func] ) then
error(string.format("Invalid handler/function passed to RegisterUpdateFunc for %s, the function %s does not exist.", self:GetName() or tostring(self), func), 3)
return
end
for i=1, #(self.fullUpdates), 2 do
local data = self.fullUpdates[i]
if( data == handler and self.fullUpdates[i + 1] == func ) then
return
end
end
table.insert(self.fullUpdates, handler)
table.insert(self.fullUpdates, func)
end
local function UnregisterUpdateFunc(self, handler, func)
for i=#(self.fullUpdates), 1, -1 do
if( self.fullUpdates[i] == handler and self.fullUpdates[i + 1] == func ) then
table.remove(self.fullUpdates, i + 1)
table.remove(self.fullUpdates, i)
end
end
end
-- Used when something is disabled, removes all callbacks etc to it
local function UnregisterAll(self, handler)
for i=#(self.fullUpdates), 1, -1 do
if( self.fullUpdates[i] == handler ) then
table.remove(self.fullUpdates, i + 1)
table.remove(self.fullUpdates, i)
end
end
for event, list in pairs(self.registeredEvents) do
if( list[handler] ) then
list[handler] = nil
local hasRegister
for _handler in pairs(list) do
hasRegister = true
break
end
if( not hasRegister ) then
self:UnregisterEvent(event)
end
end
end
end
-- Handles setting alphas in a way so combat fader and range checker don't override each other
local function DisableRangeAlpha(self, toggle)
self.disableRangeAlpha = toggle
if( not toggle and self.rangeAlpha ) then
self:SetAlpha(self.rangeAlpha)
end
end
local function SetRangeAlpha(self, alpha)
if( not self.disableRangeAlpha ) then
self:SetAlpha(alpha)
else
self.rangeAlpha = alpha
end
end
local function SetBarColor(self, key, r, g, b)
self:SetBlockColor(self[key], key, r, g, b)
end
local function SetBlockColor(self, bar, key, r, g, b)
local bgColor = bar.background.overrideColor or bar.background.backgroundColor
if( not ShadowUF.db.profile.units[self.unitType][key].invert ) then
bar:SetStatusBarColor(r, g, b, ShadowUF.db.profile.bars.alpha)
if( not bgColor ) then
bar.background:SetVertexColor(r, g, b, ShadowUF.db.profile.bars.backgroundAlpha)
else
bar.background:SetVertexColor(bgColor.r, bgColor.g, bgColor.b, ShadowUF.db.profile.bars.backgroundAlpha)
end
else
bar.background:SetVertexColor(r, g, b, ShadowUF.db.profile.bars.alpha)
if( not bgColor ) then
bar:SetStatusBarColor(0, 0, 0, 1 - ShadowUF.db.profile.bars.backgroundAlpha)
else
bar:SetStatusBarColor(bgColor.r, bgColor.g, bgColor.b, 1 - ShadowUF.db.profile.bars.backgroundAlpha)
end
end
end
-- Event handling
local function OnEvent(self, event, unit, ...)
if( not unitEvents[event] or self.unit == unit or (self.unitEventOverrides and self.unitEventOverrides[event] == unit)) then
for handler, func in pairs(self.registeredEvents[event]) do
handler[func](handler, self, event, unit, ...)
end
end
end
Units.OnEvent = OnEvent
-- Do a full update OnShow, and stop watching for events when it's not visible
local function OnShowForced(self)
-- Reset the event handler
self:SetScript("OnEvent", OnEvent)
self:FullUpdate()
end
local function OnShow(self)
-- Reset the event handler
self:SetScript("OnEvent", OnEvent)
Units:CheckUnitStatus(self)
end
local function OnHide(self)
self:SetScript("OnEvent", nil)
-- If it's a volatile such as target or focus, next time it's shown it has to do an update
-- OR if the unit is still shown, but it's been hidden because our parent (Basically UIParent)
-- we want to flag it as having changed so it can be updated
if( self.isUnitVolatile or self:IsShown() ) then
self.unitGUID = nil
end
end
-- Deal with enabling modules inside a zone
local function SetVisibility(self)
local layoutUpdate
local instanceType = select(2, IsInInstance()) or "none"
local playerSpec = GetSpecialization()
if( instanceType == "scenario" ) then instanceType = "party" end
-- Selectively disable modules
for _, module in pairs(ShadowUF.moduleOrder) do
if( module.OnEnable and module.OnDisable and ShadowUF.db.profile.units[self.unitType][module.moduleKey] ) then
local key = module.moduleKey
local enabled = ShadowUF.db.profile.units[self.unitType][key].enabled
-- These modules have mini-modules, the entire module should be enabled if at least one is enabled, and disabled if all are disabled
if( key == "auras" or key == "indicators" or key == "highlight" ) then
enabled = nil
for _, option in pairs(ShadowUF.db.profile.units[self.unitType][key]) do
if( type(option) == "table" and option.enabled or option == true ) then
enabled = true
break
end
end
end
-- In an actual zone, check to see if we have an override for the zone
if( instanceType ~= "none" ) then
if( ShadowUF.db.profile.visibility[instanceType][self.unitType .. key] == false ) then
enabled = nil
elseif( ShadowUF.db.profile.visibility[instanceType][self.unitType .. key] == true ) then
enabled = true
end
end
-- Force disable modules for people who aren't the appropriate class
if( module.moduleClass and module.moduleClass ~= playerClass ) then
enabled = nil
-- Force disable if they aren't the appropriate spec
elseif( module.moduleSpec and module.moduleSpec[playerSpec] ~= true ) then
enabled = nil
end
-- Restrict by level
if( module.moduleLevel and enabled and self.unitType == "player" ) then
if( UnitLevel("player") < module.moduleLevel ) then
enabled = nil
end
end
-- Module isn't enabled all the time, only in this zone so we need to force it to be enabled
if( not self.visibility[key] and enabled ) then
module:OnEnable(self)
layoutUpdate = true
elseif( self.visibility[key] and not enabled ) then
module:OnDisable(self)
layoutUpdate = true
end
self.visibility[key] = enabled or nil
end
end
-- We had a module update, force a full layout update of this frame
if( layoutUpdate ) then
ShadowUF.Layout:Load(self)
end
end
-- Vehicles do not always return their data right away, a pure OnUpdate check seems to be the most accurate unfortunately
local function checkVehicleData(self, elapsed)
self.timeElapsed = self.timeElapsed + elapsed
if( self.timeElapsed >= 0.50 ) then
self.timeElapsed = 0
self.dataAttempts = self.dataAttempts + 1
-- Took too long to get vehicle data, or they are no longer in a vehicle
if( self.dataAttempts >= 6 or not UnitHasVehicleUI(self.unitOwner) or not UnitHasVehiclePlayerFrameUI(self.unitOwner) ) then
self.timeElapsed = nil
self.dataAttempts = nil
self:SetScript("OnUpdate", nil)
self.inVehicle = false
self.unit = self.unitOwner
self:FullUpdate()
-- Got data, stop checking and do a full frame update
elseif( UnitIsConnected(self.unit) or UnitHealthMax(self.unit) > 0 ) then
self.timeElapsed = nil
self.dataAttempts = nil
self:SetScript("OnUpdate", nil)
self.unitGUID = UnitGUID(self.unit)
self:FullUpdate()
end
end
end
-- Check if a unit entered a vehicle
function Units:CheckVehicleStatus(frame, event, unit)
if( event and frame.unitOwner ~= unit ) then return end
-- Not in a vehicle yet, and they entered one that has a UI or they were in a vehicle but the GUID changed (vehicle -> vehicle)
if( ( not frame.inVehicle or frame.unitGUID ~= UnitGUID(frame.vehicleUnit) ) and UnitHasVehicleUI(frame.unitOwner) and UnitHasVehiclePlayerFrameUI(frame.unitOwner) and not ShadowUF.db.profile.units[frame.unitType].disableVehicle ) then
frame.inVehicle = true
frame.unit = frame.vehicleUnit
if( not UnitIsConnected(frame.unit) or UnitHealthMax(frame.unit) == 0 ) then
frame.timeElapsed = 0
frame.dataAttempts = 0
frame:SetScript("OnUpdate", checkVehicleData)
else
frame.unitGUID = UnitGUID(frame.unit)
frame:FullUpdate()
end
-- Was in a vehicle, no longer has a UI
elseif( frame.inVehicle and ( not UnitHasVehicleUI(frame.unitOwner) or not UnitHasVehiclePlayerFrameUI(frame.unitOwner) or ShadowUF.db.profile.units[frame.unitType].disableVehicle ) ) then
frame.inVehicle = false
frame.unit = frame.unitOwner
frame.unitGUID = UnitGUID(frame.unit)
frame:FullUpdate()
end
end
-- Handles checking for GUID changes for doing a full update, this fixes frames sometimes showing the wrong unit when they change
function Units:CheckUnitStatus(frame)
local guid = frame.unit and UnitGUID(frame.unit)
if( guid ~= frame.unitGUID ) then
frame.unitGUID = guid
if( guid ) then
frame:FullUpdate()
end
end
end
-- The argument from UNIT_PET is the pets owner, so the player summoning a new pet gets "player", party1 summoning a new pet gets "party1" and so on
function Units:CheckPetUnitUpdated(frame, event, unit)
if( unit == frame.unitRealOwner and UnitExists(frame.unit) ) then
frame.unitGUID = UnitGUID(frame.unit)
frame:FullUpdate()
end
end
-- When raid1, raid2, raid3 are in a group with each other and raid1 or raid2 are in a vehicle and get kicked
-- OnAttributeChanged won't do anything because the frame is already setup, however, the active unit is non-existant
-- while the primary unit is. So if we see they're in a vehicle with this case, we force the full update to get the vehicle change
function Units:CheckGroupedUnitStatus(frame)
if( frame.inVehicle and not UnitExists(frame.unit) and UnitExists(frame.unitOwner) ) then
frame.inVehicle = false
frame.unit = frame.unitOwner
frame.unitGUID = UnitGUID(frame.unit)
frame:FullUpdate()
else
frame.unitGUID = UnitGUID(frame.unit)
frame:FullUpdate()
end
end
-- More fun with sorting, due to sorting magic we have to check if we want to create stuff when the frame changes of partys too
local function createChildUnits(self)
if( not self.unitID ) then return end
for child, parentUnit in pairs(childUnits) do
if( parentUnit == self.unitType and ShadowUF.db.profile.units[child].enabled ) then
Units:LoadChildUnit(self, child, self.unitID)
end
end
end
local OnAttributeChanged
local function updateChildUnits(...)
if( not ShadowUF.db.profile.locked ) then return end
for i=1, select("#", ...) do
local child = select(i, ...)
if( child.parent and child.unitType ) then
OnAttributeChanged(child, "unit", SecureButton_GetModifiedUnit(child))
end
end
end
local function createFakeUnitUpdateTimer(frame)
if( not frame.updateTimer ) then
frame.updateTimer = C_Timer.NewTicker(0.5, function() if( UnitExists(frame.unit) ) then frame:FullUpdate() end end)
end
end
-- Attribute set, something changed
-- unit = Active unitid
-- unitID = Just the number from the unitid
-- unitType = Unitid minus numbers in it, used for configuration
-- unitRealType = The actual unit type, if party is shown in raid this will be "party" while unitType is still "raid"
-- unitOwner = Always the units owner even when unit changes due to vehicles
-- vehicleUnit = Unit to use when the unitOwner is in a vehicle
OnAttributeChanged = function(self, name, unit)
if( name ~= "unit" or not unit or unit == self.unitOwner ) then return end
-- Nullify the previous entry if it had one
local configUnit = self.unitUnmapped or unit
if( self.configUnit and unitFrames[self.configUnit] == self ) then unitFrames[self.configUnit] = nil end
-- Setup identification data
self.unit = unit
self.unitID = tonumber(string.match(unit, "([0-9]+)"))
self.unitRealType = string.gsub(unit, "([0-9]+)", "")
self.unitType = self.unitUnmapped and string.gsub(self.unitUnmapped, "([0-9]+)", "") or self.unitType or self.unitRealType
self.unitOwner = unit
self.vehicleUnit = self.unitOwner == "player" and "vehicle" or self.unitRealType == "party" and "partypet" .. self.unitID or self.unitRealType == "raid" and "raidpet" .. self.unitID or nil
self.inVehicle = nil
-- Split everything into two maps, this is the simple parentUnit -> frame map
-- This is for things like finding a party parent for party target/pet, the main map for doing full updates is
-- an indexed frame that is updated once and won't have unit conflicts.
if( self.unitRealType == self.unitType ) then
unitFrames[configUnit] = self
end
frameList[self] = true
if( self.hasChildren ) then
updateChildUnits(self:GetChildren())
end
-- Create child frames
createChildUnits(self)
-- Unit already exists but unitid changed, update the info we got on them
-- Don't need to recheck the unitType and force a full update, because a raid frame can never become
-- a party frame, or a player frame and so on
if( self.unitInitialized ) then
self:ReregisterUnitEvents()
self:FullUpdate()
return
end
self.unitInitialized = true
-- Add to Clique
if( not self:GetAttribute("isHeaderDriven") ) then
ClickCastFrames = ClickCastFrames or {}
ClickCastFrames[self] = true
end
-- Handles switching the internal unit variable to that of their vehicle
if( self.unit == "player" or self.unitRealType == "party" or self.unitRealType == "raid" ) then
self:RegisterNormalEvent("UNIT_ENTERED_VEHICLE", Units, "CheckVehicleStatus")
self:RegisterNormalEvent("UNIT_EXITED_VEHICLE", Units, "CheckVehicleStatus")
self:RegisterUpdateFunc(Units, "CheckVehicleStatus")
end
-- Phase change, do a full update on it
self:RegisterUnitEvent("UNIT_PHASE", self, "FullUpdate")
-- Pet changed, going from pet -> vehicle for one
if( self.unit == "pet" or self.unitType == "partypet" ) then
self.unitRealOwner = self.unit == "pet" and "player" or ShadowUF.partyUnits[self.unitID]
self:SetAttribute("unitRealOwner", self.unitRealOwner)
self:RegisterNormalEvent("UNIT_PET", Units, "CheckPetUnitUpdated")
if( self.unit == "pet" ) then
self:SetAttribute("disableVehicleSwap", ShadowUF.db.profile.units.player.disableVehicle)
else
self:SetAttribute("disableVehicleSwap", ShadowUF.db.profile.units.party.disableVehicle)
end
-- Logged out in a vehicle
if( UnitHasVehicleUI(self.unitRealOwner) and UnitHasVehiclePlayerFrameUI(self.unitRealOwner) ) then
self:SetAttribute("unitIsVehicle", true)
end
-- Hide any pet that became a vehicle, we detect this by the owner being untargetable but they have a pet out
stateMonitor:WrapScript(self, "OnAttributeChanged", [[
if( name == "state-vehicleupdated" ) then
self:SetAttribute("unitIsVehicle", UnitHasVehicleUI(self:GetAttribute("unitRealOwner")) and value == "vehicle" and true or false)
elseif( name == "disablevehicleswap" or name == "state-unitexists" or name == "unitisvehicle" ) then
-- Unit does not exist, OR unit is a vehicle and vehicle swap is not disabled, hide frame
if( not self:GetAttribute("state-unitexists") or ( self:GetAttribute("unitIsVehicle") and not self:GetAttribute("disableVehicleSwap") ) ) then
self:Hide()
-- Unit exists, show it
else
self:Show()
end
end
]])
RegisterStateDriver(self, "vehicleupdated", string.format("[target=%s, nohelp, noharm] vehicle; pet", self.unitRealOwner, self.unit))
-- Automatically do a full update on target change
elseif( self.unit == "target" ) then
self.isUnitVolatile = true
self:RegisterNormalEvent("PLAYER_TARGET_CHANGED", Units, "CheckUnitStatus")
self:RegisterUnitEvent("UNIT_TARGETABLE_CHANGED", self, "FullUpdate")
-- Automatically do a full update on focus change
elseif( self.unit == "focus" ) then
self.isUnitVolatile = true
self:RegisterNormalEvent("PLAYER_FOCUS_CHANGED", Units, "CheckUnitStatus")
self:RegisterUnitEvent("UNIT_TARGETABLE_CHANGED", self, "FullUpdate")
elseif( self.unit == "player" ) then
-- this should not get called in combat, but just in case make sure we are not actually in combat
if not InCombatLockdown() then
self:SetAttribute("toggleForVehicle", true)
end
-- Force a full update when the player is alive to prevent freezes when releasing in a zone that forces a ressurect (naxx/tk/etc)
self:RegisterNormalEvent("PLAYER_ALIVE", self, "FullUpdate")
-- full update when the player targetable changes, ie. during cutscenes or transports
self:RegisterUnitEvent("UNIT_TARGETABLE_CHANGED", self, "FullUpdate")
-- Update boss
elseif( self.unitType == "boss" ) then
self:RegisterNormalEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT", self, "FullUpdate")
self:RegisterUnitEvent("UNIT_TARGETABLE_CHANGED", self, "FullUpdate")
self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus")
-- Update arena
elseif( self.unitType == "arena" ) then
self:RegisterUnitEvent("UNIT_NAME_UPDATE", self, "FullUpdate")
self:RegisterUnitEvent("UNIT_CONNECTION", self, "FullUpdate")
-- Update battleground
elseif( self.unitType == "battleground" ) then
self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus")
-- Check for a unit guid to do a full update
elseif( self.unitRealType == "raid" ) then
self:RegisterNormalEvent("GROUP_ROSTER_UPDATE", Units, "CheckGroupedUnitStatus")
self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus")
self:RegisterUnitEvent("UNIT_CONNECTION", self, "FullUpdate")
-- Party members need to watch for changes
elseif( self.unitRealType == "party" ) then
self:RegisterNormalEvent("GROUP_ROSTER_UPDATE", Units, "CheckGroupedUnitStatus")
self:RegisterNormalEvent("PARTY_MEMBER_ENABLE", Units, "CheckGroupedUnitStatus")
self:RegisterNormalEvent("PARTY_MEMBER_DISABLE", Units, "CheckGroupedUnitStatus")
self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus")
self:RegisterUnitEvent("UNIT_OTHER_PARTY_CHANGED", self, "FullUpdate")
self:RegisterUnitEvent("UNIT_CONNECTION", self, "FullUpdate")
-- *target units are not real units, thus they do not receive events and must be polled for data
elseif( ShadowUF.fakeUnits[self.unitRealType] ) then
createFakeUnitUpdateTimer(self)
-- Speeds up updating units when their owner changes target, if party1 changes target then party1target is force updated, if target changes target
-- then targettarget and targettargettarget are also force updated
if( self.unitRealType == "partytarget" ) then
self.unitRealOwner = ShadowUF.partyUnits[self.unitID]
elseif( self.unitRealType == "partytargettarget" ) then
self.unitRealOwner = ShadowUF.partyUnits[self.unitID] .. "target"
elseif( self.unitRealType == "raid" ) then
self.unitRealOwner = ShadowUF.raidUnits[self.unitID]
elseif( self.unitRealType == "arenatarget" ) then
self.unitRealOwner = ShadowUF.arenaUnits[self.unitID]
elseif( self.unitRealType == "arenatargettarget" ) then
self.unitRealOwner = ShadowUF.arenaUnits[self.unitID] .. "target"
elseif( self.unit == "focustarget" ) then
self.unitRealOwner = "focus"
self:RegisterNormalEvent("PLAYER_FOCUS_CHANGED", Units, "CheckUnitStatus")
elseif( self.unit == "targettarget" or self.unit == "targettargettarget" ) then
self.unitRealOwner = "target"
self:RegisterNormalEvent("PLAYER_TARGET_CHANGED", Units, "CheckUnitStatus")
end
self:RegisterNormalEvent("UNIT_TARGET", Units, "CheckPetUnitUpdated")
end
self:SetVisibility()
Units:CheckUnitStatus(self)
end
Units.OnAttributeChanged = OnAttributeChanged
local secureInitializeUnit = [[
local header = self:GetParent()
self:SetHeight(header:GetAttribute("style-height"))
self:SetWidth(header:GetAttribute("style-width"))
self:SetScale(header:GetAttribute("style-scale"))
self:SetAttribute("toggleForVehicle", true)
self:SetAttribute("*type1", "target")
self:SetAttribute("*type2", "togglemenu")
self:SetAttribute("type2", "togglemenu")
self:SetAttribute("isHeaderDriven", true)
-- initialize frame
header:CallMethod("initialConfigFunction", self:GetName())
-- Clique integration
local clickHeader = header:GetFrameRef("clickcast_header")
if( clickHeader ) then
clickHeader:SetAttribute("clickcast_button", self)
clickHeader:RunAttribute("clickcast_register")
end
]]
local unitButtonTemplate = ClickCastHeader and (BackdropTemplateMixin and "ClickCastUnitTemplate,SUF_SecureUnitTemplate,BackdropTemplate" or "ClickCastUnitTemplate,SUF_SecureUnitTemplate") or (BackdropTemplateMixin and "SUF_SecureUnitTemplate,BackdropTemplate" or "SUF_SecureUnitTemplate")
-- Header unit initialized
local function initializeUnit(header, frameName)
local frame = _G[frameName]
frame.ignoreAnchor = true
frame.unitType = header.unitType
Units:CreateUnit(frame)
end
-- Show tooltip
local function OnEnter(self)
if( self.OnEnter ) then
self:OnEnter()
end
end
local function OnLeave(self)
if( self.OnLeave ) then
self:OnLeave()
end
end
local function SUF_OnEnter(self)
if( not ShadowUF.db.profile.tooltipCombat or not InCombatLockdown() ) then
if not GameTooltip:IsForbidden() then
UnitFrame_OnEnter(self)
end
end
end
local function SUF_OnLeave(self)
if not GameTooltip:IsForbidden() then
UnitFrame_OnLeave(self)
end
end
-- Create the generic things that we want in every secure frame regardless if it's a button or a header
local function ClassToken(self)
return (select(2, UnitClass(self.unit)))
end
local function ArenaClassToken(self)
local specID = GetArenaOpponentSpec(self.unitID)
return specID and select(6, GetSpecializationInfoByID(specID))
end
function Units:CreateUnit(...)
local frame = select("#", ...) > 1 and CreateFrame(...) or select(1, ...)
frame.fullUpdates = {}
frame.registeredEvents = {}
frame.visibility = {}
frame.BlizzRegisterUnitEvent = frame.RegisterUnitEvent
frame.RegisterNormalEvent = RegisterNormalEvent
frame.RegisterUnitEvent = RegisterUnitEvent
frame.RegisterUpdateFunc = RegisterUpdateFunc
frame.UnregisterAll = UnregisterAll
frame.UnregisterSingleEvent = UnregisterEvent
frame.SetRangeAlpha = SetRangeAlpha
frame.DisableRangeAlpha = DisableRangeAlpha
frame.UnregisterUpdateFunc = UnregisterUpdateFunc
frame.ReregisterUnitEvents = ReregisterUnitEvents
frame.SetBarColor = SetBarColor
frame.SetBlockColor = SetBlockColor
frame.FullUpdate = FullUpdate
frame.SetVisibility = SetVisibility
frame.UnitClassToken = ClassToken
frame.topFrameLevel = 5
-- Ensures that text is the absolute highest thing there is
frame.highFrame = CreateFrame("Frame", nil, frame)
frame.highFrame:SetFrameLevel(frame.topFrameLevel + 2)
frame.highFrame:SetAllPoints(frame)
frame:HookScript("OnAttributeChanged", OnAttributeChanged)
frame:SetScript("OnEvent", OnEvent)
frame:HookScript("OnEnter", OnEnter)
frame:HookScript("OnLeave", OnLeave)
frame:SetScript("OnShow", OnShow)
frame:SetScript("OnHide", OnHide)
frame.OnEnter = SUF_OnEnter
frame.OnLeave = SUF_OnLeave
frame:RegisterForClicks("AnyUp")
-- non-header frames don't set those, so we need to do it
if( not InCombatLockdown() and not frame:GetAttribute("isHeaderDriven") ) then
frame:SetAttribute("*type1", "target")
frame:SetAttribute("*type2", "togglemenu")
end
return frame
end
-- Reload a header completely
function Units:ReloadHeader(type)
if( ShadowUF.db.profile.units[type].frameSplit ) then
if( headerFrames.raid ) then
self:InitializeFrame("raid")
else
self:SetHeaderAttributes(headerFrames.raidParent, type)
ShadowUF.Layout:AnchorFrame(UIParent, headerFrames.raidParent, ShadowUF.db.profile.positions[type])
ShadowUF:FireModuleEvent("OnLayoutReload", type)
end
elseif( type == "raid" and not ShadowUF.db.profile.units[type].frameSplit and headerFrames.raidParent ) then
self:InitializeFrame("raid")
elseif( headerFrames[type] ) then
self:SetHeaderAttributes(headerFrames[type], type)
ShadowUF.Layout:AnchorFrame(UIParent, headerFrames[type], ShadowUF.db.profile.positions[type])
ShadowUF:FireModuleEvent("OnLayoutReload", type)
end
end
function Units:PositionHeaderChildren(frame)
local point = frame:GetAttribute("point") or "TOP"
local relativePoint = ShadowUF.Layout:GetRelativeAnchor(point)
if( #(frame.children) == 0 ) then return end
local xMod, yMod = math.abs(frame:GetAttribute("xMod")), math.abs(frame:GetAttribute("yMod"))
local x = frame:GetAttribute("xOffset") or 0
local y = frame:GetAttribute("yOffset") or 0
for id, child in pairs(frame.children) do
if( id > 1 ) then
frame.children[id]:ClearAllPoints()
frame.children[id]:SetPoint(point, frame.children[id - 1], relativePoint, xMod * x, yMod * y)
else
frame.children[id]:ClearAllPoints()
frame.children[id]:SetPoint(point, frame, point, 0, 0)
end
end
end
function Units:CheckGroupVisibility()
if( not ShadowUF.db.profile.locked ) then return end
local raid = headerFrames.raid and not ShadowUF.db.profile.units.raid.frameSplit and headerFrames.raid or headerFrames.raidParent
local party = headerFrames.party
if( party ) then
party:SetAttribute("showParty", ( not ShadowUF.db.profile.units.raid.showParty or not ShadowUF.enabledUnits.raid ) and true or false)
party:SetAttribute("showPlayer", ShadowUF.db.profile.units.party.showPlayer)
end
if( raid and party ) then
raid:SetAttribute("showParty", not party:GetAttribute("showParty"))
raid:SetAttribute("showPlayer", party:GetAttribute("showPlayer"))
end
end
function Units:SetHeaderAttributes(frame, type)
local config = ShadowUF.db.profile.units[type]
local xMod = config.attribPoint == "LEFT" and 1 or config.attribPoint == "RIGHT" and -1 or 0
local yMod = config.attribPoint == "TOP" and -1 or config.attribPoint == "BOTTOM" and 1 or 0
local widthMod = (config.attribPoint == "LEFT" or config.attribPoint == "RIGHT") and MEMBERS_PER_RAID_GROUP or 1
local heightMod = (config.attribPoint == "TOP" or config.attribPoint == "BOTTOM") and MEMBERS_PER_RAID_GROUP or 1
frame:SetAttribute("point", config.attribPoint)
frame:SetAttribute("sortMethod", config.sortMethod)
frame:SetAttribute("sortDir", config.sortOrder)
frame:SetAttribute("xOffset", config.offset * xMod)
frame:SetAttribute("yOffset", config.offset * yMod)
frame:SetAttribute("xMod", xMod)
frame:SetAttribute("yMod", yMod)
-- Split up raid frame groups
if( config.frameSplit and type == "raid" ) then
local anchorPoint, relativePoint, xModRow, yModRow = ShadowUF.Layout:GetSplitRelativeAnchor(config.attribPoint, config.attribAnchorPoint)
local columnPoint, xColMod, yColMod = ShadowUF.Layout:GetRelativeAnchor(config.attribPoint)
local lastHeader = frame
for id=1, 8 do
local childHeader = headerFrames["raid" .. id]
if( childHeader ) then
childHeader:SetAttribute("showRaid", ShadowUF.db.profile.locked and true)
childHeader:SetAttribute("minWidth", config.width * widthMod)
childHeader:SetAttribute("minHeight", config.height * heightMod)
if( childHeader ~= frame ) then
childHeader:SetAttribute("point", config.attribPoint)
childHeader:SetAttribute("sortMethod", config.sortMethod)
childHeader:SetAttribute("sortDir", config.sortOrder)
childHeader:SetAttribute("showPlayer", nil)
childHeader:SetAttribute("showParty", nil)
childHeader:SetAttribute("xOffset", frame:GetAttribute("xOffset"))
childHeader:SetAttribute("yOffset", frame:GetAttribute("yOffset"))
childHeader:ClearAllPoints()
if( (id - 1) % config.groupsPerRow == 0 ) then
local x = config.groupSpacing * xColMod
local y = config.groupSpacing * yColMod
-- When we're anchoring a new column to the bottom of naother one, the height will mess it up
-- if what we anchored to isn't full, by anchoring it to the top instead will get a consistent result
local point = columnPoint
if( point == "BOTTOM" ) then
point = config.attribPoint
x = x + (config.height * 5) * xColMod
y = y + (config.height * 5) * yColMod
end
childHeader:SetPoint(config.attribPoint, headerFrames["raid" .. id - config.groupsPerRow], point, x, y)
else
childHeader:SetPoint(anchorPoint, lastHeader, relativePoint, config.columnSpacing * xModRow, config.columnSpacing * yModRow)
end
lastHeader = childHeader
end
-- There appears to be a bug where if you reloadui with a split raid frames the positions get messed up
-- if we force a repositioning through startingIndex it's fixed thought.
childHeader:SetAttribute("startingIndex", 10000)
childHeader:SetAttribute("startingIndex", 1)
end
end
-- Normal raid, ma or mt
elseif( type == "raidpet" or type == "raid" or type == "mainassist" or type == "maintank" ) then
local filter
if( config.filters ) then
for id, enabled in pairs(config.filters) do
if( enabled ) then
if( filter ) then
filter = filter .. "," .. id
else
filter = id
end
end
end
else
filter = config.groupFilter
end
frame:SetAttribute("showRaid", ShadowUF.db.profile.locked and true)
frame:SetAttribute("maxColumns", config.maxColumns)
frame:SetAttribute("unitsPerColumn", config.unitsPerColumn)
frame:SetAttribute("columnSpacing", config.columnSpacing)
frame:SetAttribute("columnAnchorPoint", config.attribAnchorPoint)
frame:SetAttribute("groupFilter", filter or "1,2,3,4,5,6,7,8")
frame:SetAttribute("roleFilter", config.roleFilter)
if( config.groupBy == "CLASS" ) then
frame:SetAttribute("groupingOrder", "DEATHKNIGHT,DEMONHUNTER,DRUID,HUNTER,MAGE,PALADIN,PRIEST,ROGUE,SHAMAN,WARLOCK,WARRIOR,MONK,EVOKER")
frame:SetAttribute("groupBy", "CLASS")
elseif( config.groupBy == "ASSIGNEDROLE" ) then
frame:SetAttribute("groupingOrder", "TANK,HEALER,DAMAGER,NONE")
frame:SetAttribute("groupBy", "ASSIGNEDROLE")
else
frame:SetAttribute("groupingOrder", "1,2,3,4,5,6,7,8")
frame:SetAttribute("groupBy", "GROUP")
end
-- Need to position the fake units
elseif( type == "boss" or type == "arena" or type == "battleground" ) then
frame:SetAttribute("attribPoint", config.attribPoint)
frame:SetAttribute("baseOffset", config.offset)
frame:SetAttribute("childChanged", 1)
self:PositionHeaderChildren(frame)
-- Update party frames to not show anyone if they should be in raids
elseif( type == "party" ) then
frame:SetAttribute("maxColumns", math.ceil((config.showPlayer and 5 or 4) / config.unitsPerColumn))
frame:SetAttribute("unitsPerColumn", config.unitsPerColumn)
frame:SetAttribute("columnSpacing", config.columnSpacing)
frame:SetAttribute("columnAnchorPoint", config.attribAnchorPoint)
self:CheckGroupVisibility()
if( stateMonitor.party ) then
stateMonitor.party:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.party.hideSemiRaid)
stateMonitor.party:SetAttribute("hideAnyRaid", ShadowUF.db.profile.units.party.hideAnyRaid)
end
end
if( type == "raid" ) then
self:CheckGroupVisibility()
for id, monitor in pairs(stateMonitor.raids) do
monitor:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.raid.hideSemiRaid)
end
end
if( not InCombatLockdown() and headerUnits[type] and frame.shouldReset ) then
-- Children no longer have ClearAllPoints() called on them before they are repositioned
-- this tries to stop it from bugging out by clearing it then forcing it to reposition everything
local name = frame:GetName() .. "UnitButton"
local index = 1
local child = _G[name .. index]
while( child ) do
child:ClearAllPoints()
index = index + 1
child = _G[name .. index]
end
-- Hiding and reshowing the header forces an update
if( frame:IsShown() ) then
frame:Hide()
frame:Show()
end
end
frame.shouldReset = true
end
-- Load a single unit such as player, target, pet, etc
function Units:LoadUnit(unit)
-- Already be loaded, just enable
if( unitFrames[unit] ) then
RegisterUnitWatch(unitFrames[unit], unitFrames[unit].hasStateWatch)
return
end
local frame = self:CreateUnit("Button", "SUFUnit" .. unit, petBattleFrame, BackdropTemplateMixin and "SecureUnitButtonTemplate,BackdropTemplate" or "SecureUnitButtonTemplate")
frame:SetAttribute("unit", unit)
frame.hasStateWatch = unit == "pet"
-- Annd lets get this going
RegisterUnitWatch(frame, frame.hasStateWatch)
end
local function setupRaidStateMonitor(id, headerFrame)
if( stateMonitor.raids[id] ) then return end
stateMonitor.raids[id] = CreateFrame("Frame", nil, nil, "SecureHandlerBaseTemplate")
stateMonitor.raids[id]:SetAttribute("raidDisabled", nil)
stateMonitor.raids[id]:SetFrameRef("raidHeader", headerFrame)
stateMonitor.raids[id]:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.raid.hideSemiRaid)
stateMonitor.raids[id]:WrapScript(stateMonitor.raids[id], "OnAttributeChanged", [[
if( name ~= "state-raidmonitor" and name ~= "raiddisabled" and name ~= "hidesemiraid" ) then
return
end
local header = self:GetFrameRef("raidHeader")
if( self:GetAttribute("raidDisabled") ) then
if( header:IsVisible() ) then header:Hide() end
return
end
if( self:GetAttribute("hideSemiRaid") and self:GetAttribute("state-raidmonitor") ~= "raid6" ) then
header:Hide()
else
header:Show()
end
]])
RegisterStateDriver(stateMonitor.raids[id], "raidmonitor", "[target=raid6, exists] raid6; none")
end
function Units:LoadSplitGroupHeader(type)
if( headerFrames.raid ) then headerFrames.raid:Hide() end
headerFrames.raidParent = nil
for id, monitor in pairs(stateMonitor.raids) do
monitor:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.raid.hideSemiRaid)
monitor:SetAttribute("raidDisabled", id == -1 and true or nil)
monitor:SetAttribute("recheck", time())
end
local config = ShadowUF.db.profile.units[type]
for id, enabled in pairs(ShadowUF.db.profile.units[type].filters) do
local frame = headerFrames["raid" .. id]
if( enabled ) then
if( not frame ) then
frame = CreateFrame("Frame", "SUFHeader" .. type .. id, petBattleFrame, "SecureGroupHeaderTemplate")
frame:SetAttribute("template", unitButtonTemplate)
frame:SetAttribute("initial-unitWatch", true)
frame:SetAttribute("showRaid", true)
frame:SetAttribute("groupFilter", id)
frame:SetAttribute("initialConfigFunction", secureInitializeUnit)
frame.initialConfigFunction = initializeUnit
frame.isHeaderFrame = true
frame.unitType = type
frame.unitMappedType = type
frame.splitParent = type
frame.groupID = id
--frame:SetBackdrop({bgFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1})
--frame:SetBackdropBorderColor(1, 0, 0, 1)
--frame:SetBackdropColor(0, 0, 0, 0)
frame:SetAttribute("style-height", config.height)
frame:SetAttribute("style-width", config.width)
frame:SetAttribute("style-scale", config.scale)
if( ClickCastHeader ) then
-- the OnLoad adds the functions like SetFrameRef to the header
SecureHandler_OnLoad(frame)
frame:SetFrameRef("clickcast_header", ClickCastHeader)
end
headerFrames["raid" .. id] = frame
end
frame:Show()
if( not headerFrames.raidParent or headerFrames.raidParent.groupID > id ) then
headerFrames.raidParent = frame
end
setupRaidStateMonitor(id, frame)
elseif( frame ) then
frame:Hide()
end
end
if( headerFrames.raidParent ) then
self:SetHeaderAttributes(headerFrames.raidParent, type)
ShadowUF.Layout:AnchorFrame(UIParent, headerFrames.raidParent, ShadowUF.db.profile.positions.raid)
end
end
-- Load a header unit, party or raid
function Units:LoadGroupHeader(type)
-- Already created, so just reshow and we out
if( headerFrames[type] ) then
headerFrames[type]:Show()
if( type == "party" and stateMonitor.party ) then
stateMonitor.party:SetAttribute("partyDisabled", nil)
end
if( type == "raid" ) then
for id, monitor in pairs(stateMonitor.raids) do
monitor:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.raid.hideSemiRaid)
monitor:SetAttribute("raidDisabled", id >= 0 and true or nil)
end
end
if( type == "party" or type == "raid" ) then
self:CheckGroupVisibility()
end
return
end
local headerFrame = CreateFrame("Frame", "SUFHeader" .. type, petBattleFrame, type == "raidpet" and "SecureGroupPetHeaderTemplate" or "SecureGroupHeaderTemplate")
headerFrames[type] = headerFrame
self:SetHeaderAttributes(headerFrame, type)
headerFrame:SetAttribute("template", unitButtonTemplate)
headerFrame:SetAttribute("initial-unitWatch", true)
headerFrame:SetAttribute("initialConfigFunction", secureInitializeUnit)
headerFrame.initialConfigFunction = initializeUnit
headerFrame.isHeaderFrame = true
headerFrame.unitType = type
headerFrame.unitMappedType = type
-- For securely managely the display
local config = ShadowUF.db.profile.units[type]
headerFrame:SetAttribute("style-height", config.height)
headerFrame:SetAttribute("style-width", config.width)
headerFrame:SetAttribute("style-scale", config.scale)
if( type == "raidpet" ) then
headerFrame:SetAttribute("filterOnPet", true)
end
if( ClickCastHeader ) then
-- the OnLoad adds the functions like SetFrameRef to the header
SecureHandler_OnLoad(headerFrame)
headerFrame:SetFrameRef("clickcast_header", ClickCastHeader)
end
ShadowUF.Layout:AnchorFrame(UIParent, headerFrame, ShadowUF.db.profile.positions[type])
-- We have to do party hiding based off raid as a state driver so that we can smoothly hide the party frames based off of combat and such
-- technically this isn't the cleanest solution because party frames will still have unit watches active
-- but this isn't as big of a deal, because SUF automatically will unregister the OnEvent for party frames while hidden
if( type == "party" ) then
stateMonitor.party = CreateFrame("Frame", nil, nil, "SecureHandlerBaseTemplate")
stateMonitor.party:SetAttribute("partyDisabled", nil)
stateMonitor.party:SetFrameRef("partyHeader", headerFrame)
stateMonitor.party:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.party.hideSemiRaid)
stateMonitor.party:SetAttribute("hideAnyRaid", ShadowUF.db.profile.units.party.hideAnyRaid)
stateMonitor.party:WrapScript(stateMonitor.party, "OnAttributeChanged", [[
if( name ~= "state-raidmonitor" and name ~= "partydisabled" and name ~= "hideanyraid" and name ~= "hidesemiraid" ) then return end
if( self:GetAttribute("partyDisabled") ) then return end
if( self:GetAttribute("hideAnyRaid") and ( self:GetAttribute("state-raidmonitor") == "raid1" or self:GetAttribute("state-raidmonitor") == "raid6" ) ) then
self:GetFrameRef("partyHeader"):Hide()
elseif( self:GetAttribute("hideSemiRaid") and self:GetAttribute("state-raidmonitor") == "raid6" ) then
self:GetFrameRef("partyHeader"):Hide()
else
self:GetFrameRef("partyHeader"):Show()
end
]])
RegisterStateDriver(stateMonitor.party, "raidmonitor", "[target=raid6, exists] raid6; [target=raid1, exists] raid1; none")
elseif( type == "raid" ) then
setupRaidStateMonitor(-1, headerFrame)
else
headerFrame:Show()
end
-- Any frames that were split out in this group need to be hidden
if( headerFrames.raidParent ) then
for _, f in pairs(headerFrames) do
if( f.splitParent == type ) then
f:Hide()
end
end
end
end
-- Fake headers that are supposed to act like headers to the users, but are really not
function Units:LoadZoneHeader(type)
if( headerFrames[type] ) then
headerFrames[type]:Show()
for _, child in pairs(headerFrames[type].children) do
RegisterUnitWatch(child, child.hasStateWatch)
end
if( type == "arena" ) then
self:InitializeArena()
end
return
end
local headerFrame = CreateFrame("Frame", "SUFHeader" .. type, petBattleFrame, "SecureHandlerBaseTemplate")
headerFrame.isHeaderFrame = true
headerFrame.unitType = type
headerFrame.unitMappedType = remappedUnits[type] or type
headerFrame:SetClampedToScreen(true)
headerFrame:SetMovable(true)
headerFrame:SetHeight(0.1)
headerFrame:SetAttribute("totalChildren", #(ShadowUF[type .. "Units"]))
headerFrame.children = {}
headerFrames[type] = headerFrame
if( type == "arena" ) then
headerFrame:SetScript("OnAttributeChanged", function(frame, key, value)
if( key == "childChanged" and value and frame.children[value] and frame:IsVisible() ) then
frame.children[value]:FullUpdate()
end
end)
end
for id, unit in pairs(ShadowUF[type .. "Units"]) do
local frame = self:CreateUnit("Button", "SUFHeader" .. type .. "UnitButton" .. id, headerFrame, BackdropTemplateMixin and "SecureUnitButtonTemplate,BackdropTemplate" or "SecureUnitButtonTemplate")
frame.ignoreAnchor = true
frame.hasStateWatch = true
frame.unitUnmapped = type .. id
frame:SetAttribute("unit", unit)
frame:SetAttribute("unitID", id)
frame:Hide()
-- Override with our arena specific concerns
frame.UnitClassToken = ArenaClassToken
frame:SetScript("OnShow", OnShowForced)
headerFrame.children[id] = frame
headerFrame:SetFrameRef("child" .. id, frame)
-- Arena frames are only allowed to be shown not hidden from the unit existing, or else when a Rogue
-- stealths the frame will hide which looks bad. Instead force it to stay open and it has to be manually hidden when the player leaves an arena.
if( type == "arena" ) then
stateMonitor:WrapScript(frame, "OnAttributeChanged", [[
if( name == "state-unitexists" ) then
local parent = self:GetParent()
if( value and self:GetAttribute("unitDisappeared") ) then
parent:SetAttribute("childChanged", self:GetAttribute("unitID"))
self:SetAttribute("unitDisappeared", nil)
elseif( not value and not self:GetAttribute("unitDisappeared") ) then
self:SetAttribute("unitDisappeared", true)
end
if( value ) then
self:Show()
end
end
]])
else
stateMonitor:WrapScript(frame, "OnAttributeChanged", [[
if( name == "state-unitexists" ) then
if( value ) then
self:Show()
else
self:Hide()
end
local parent = self:GetParent()
parent:SetAttribute("childChanged", self:GetAttribute("unitID"))
end
]])
end
RegisterUnitWatch(frame, frame.hasStateWatch)
end
-- Dynamic height/width adjustment
stateMonitor:WrapScript(headerFrame, "OnAttributeChanged", [[
if( name ~= "childchanged" ) then return end
local visible = 0
for i=1, self:GetAttribute("totalChildren") do
if( self:GetFrameRef("child" .. i):IsShown() ) then
visible = visible + 1
end
end
if( visible == 0 ) then
self:Hide()
return
end
local child = self:GetFrameRef("child1")
local xMod = math.abs(self:GetAttribute("xMod"))
local yMod = math.abs(self:GetAttribute("yMod"))
local offset = self:GetAttribute("baseOffset")
self:SetWidth(xMod * ((child:GetWidth() * (visible - 1)) + (offset * (visible - 1))) + child:GetWidth())
self:SetHeight(yMod * ((child:GetHeight() * (visible - 1)) + (offset * (visible - 1))) + child:GetHeight())
self:Show()
]])
self:SetHeaderAttributes(headerFrame, type)
ShadowUF.Layout:AnchorFrame(UIParent, headerFrame, ShadowUF.db.profile.positions[type])
if( type == "arena" ) then
self:InitializeArena()
end
end
-- Load a unit that is a child of another unit (party pet/party target)
function Units:LoadChildUnit(parent, type, id)
if( InCombatLockdown() ) then
if( not queuedCombat[parent:GetName() .. type] ) then
queuedCombat[parent:GetName() .. type] = {parent = parent, type = type, id = id}
end
return
else
-- This is a bit confusing to write down, but just in case I forget:
-- It's possible theres a bug where you have a frame skip creating it's child because it thinks one was already created, but the one that was created is actually associated to another parent. What would need to be changed is it checks if the frame has the parent set to it and it's the same unit type before returning, not that the units match.
for frame in pairs(frameList) do
if( frame.unitType == type and frame.parent == parent ) then
RegisterUnitWatch(frame, frame.hasStateWatch)
return
end
end
end
parent.hasChildren = true
local suffix
if( string.match(type, "pet$") ) then
suffix = "pet"
elseif( string.match(type, "targettarget$") ) then
suffix = "targettarget"
else
suffix = "target"
end
-- Now we can create the actual frame
local frame = self:CreateUnit("Button", "SUFChild" .. type .. string.match(parent:GetName(), "(%d+)"), parent, BackdropTemplateMixin and "SecureUnitButtonTemplate,BackdropTemplate" or "SecureUnitButtonTemplate")
frame.unitType = type
frame.parent = parent
frame.isChildUnit = true
frame.hasStateWatch = type == "partypet"
frame:SetFrameStrata("LOW")
frame:SetAttribute("useparent-unit", true)
frame:SetAttribute("unitsuffix", suffix)
OnAttributeChanged(frame, "unit", SecureButton_GetModifiedUnit(frame))
frameList[frame] = true
RegisterUnitWatch(frame, frame.hasStateWatch)
ShadowUF.Layout:AnchorFrame(parent, frame, ShadowUF.db.profile.positions[type])
end
-- Initialize units
function Units:InitializeFrame(type)
if( type == "raid" and ShadowUF.db.profile.units[type].frameSplit ) then
self:LoadSplitGroupHeader(type)
elseif( type == "party" or type == "raid" or type == "maintank" or type == "mainassist" or type == "raidpet" ) then
self:LoadGroupHeader(type)
elseif( self.childUnits[type] ) then
for frame in pairs(frameList) do
if( frame.unitType == self.childUnits[type] and ShadowUF.db.profile.units[frame.unitType] and frame.unitID ) then
self:LoadChildUnit(frame, type, frame.unitID)
end
end
elseif( self.zoneUnits[type] ) then
self:LoadZoneHeader(type)
else
self:LoadUnit(type)
end
end
-- Uninitialize units
function Units:UninitializeFrame(type)
if( type == "party" or type == "raid" ) then
self:CheckGroupVisibility()
end
-- Disables showing party in raid automatically if raid frames are disabled
if( type == "party" and stateMonitor.party ) then
stateMonitor.party:SetAttribute("partyDisabled", true)
end
if( type == "raid" ) then
for _, monitor in pairs(stateMonitor.raids) do
monitor:SetAttribute("raidDisabled", true)
end
end
-- Disable the parent and the children will follow
if( ShadowUF.db.profile.units[type].frameSplit ) then
for _, headerFrame in pairs(headerFrames) do
if( headerFrame.splitParent == type ) then
headerFrame:Hide()
end
end
elseif( headerFrames[type] ) then
headerFrames[type]:Hide()
if( headerFrames[type].children ) then
for _, frame in pairs(headerFrames[type].children) do
if( self.zoneUnits[type] ) then
UnregisterUnitWatch(frame)
frame:SetAttribute("state-unitexists", false)
end
frame:Hide()
end
end
else
-- Disable all frames of this type
for frame in pairs(frameList) do
if( frame.unitType == type ) then
UnregisterUnitWatch(frame)
frame:SetAttribute("state-unitexits", false)
frame:Hide()
end
end
end
end
-- Profile changed, reload units
function Units:ProfileChanged()
-- Reset the anchors for all frames to prevent X is dependant on Y
for frame in pairs(frameList) do
if( frame.unit ) then
frame:ClearAllPoints()
end
end
for frame in pairs(frameList) do
if( frame.unit and ShadowUF.db.profile.units[frame.unitType].enabled ) then
-- Force all enabled modules to disable
for key, module in pairs(ShadowUF.modules) do
if( frame[key] and frame.visibility[key] ) then
frame.visibility[key] = nil
module:OnDisable(frame)
end
end
-- Now enable whatever we need to
frame:SetVisibility()
ShadowUF.Layout:Load(frame)
frame:FullUpdate()
end
end
for _, frame in pairs(headerFrames) do
if( ShadowUF.db.profile.units[frame.unitType].enabled ) then
self:ReloadHeader(frame.unitType)
end
end
end
-- Small helper function for creating bars with
function Units:CreateBar(parent)
local bar = CreateFrame("StatusBar", nil, parent)
bar:SetFrameLevel(parent.topFrameLevel or 5)
bar.parent = parent
bar.background = bar:CreateTexture(nil, "BORDER")
bar.background:SetHeight(1)
bar.background:SetWidth(1)
bar.background:SetAllPoints(bar)
bar.background:SetHorizTile(false)
return bar
end
-- Handle showing for the arena prep frames
function Units:InitializeArena()
if( not headerFrames.arena or InCombatLockdown() ) then return end
local specs = GetNumArenaOpponentSpecs()
if( not specs or specs == 0 ) then return end
for i=1, specs do
local frame = headerFrames.arena.children[i]
frame:SetAttribute("state-unitexists", true)
frame:Show()
frame:FullUpdate()
end
end
-- Deal with zone changes for enabling modules
local instanceType, queueZoneCheck
function Units:CheckPlayerZone(force)
if( InCombatLockdown() ) then
queueZoneCheck = force and 2 or 1
return
end
-- CanHearthAndResurrectFromArea() returns true for world pvp areas, according to BattlefieldFrame.lua
local instance = CanHearthAndResurrectFromArea() and "pvp" or select(2, IsInInstance()) or "none"
if( instance == "scenario" ) then instance = "party" end
if( instance == instanceType and not force ) then return end
instanceType = instance
ShadowUF:LoadUnits()
for frame in pairs(frameList) do
if( frame.unit and ShadowUF.db.profile.units[frame.unitType].enabled ) then
frame:SetVisibility()
-- Auras are enabled so will need to check if the filter has to change
if( frame.visibility.auras ) then
ShadowUF.modules.auras:UpdateFilter(frame)
end
if( UnitExists(frame.unit) ) then
frame:FullUpdate()
end
end
end
end
-- Handle figuring out what auras players can cure
local curableSpells = {
["DRUID"] = {[88423] = {"Magic", "Curse", "Poison"}, [2782] = {"Curse", "Poison"}},
["PRIEST"] = {[527] = {"Magic", "Disease"}, [32375] = {"Magic"}, [213634] = {"Disease"}},
["PALADIN"] = {[4987] = {"Poison", "Disease", "Magic"}, [213644] = {"Poison", "Disease"}},
["SHAMAN"] = {[77130] = {"Curse", "Magic"}, [51886] = {"Curse"}},
["MONK"] = {[115450] = {"Poison", "Disease", "Magic"}, [218164] = {"Poison", "Disease"}},
["MAGE"] = {[475] = {"Curse"}},
["WARLOCK"] = {[89808] = {"Magic"}},
}
curableSpells = curableSpells[playerClass]
local function checkCurableSpells()
if( not curableSpells ) then return end
table.wipe(Units.canCure)
for spellID, cures in pairs(curableSpells) do
if( IsPlayerSpell(spellID) or IsSpellKnown(spellID, true) ) then
for _, auraType in pairs(cures) do
Units.canCure[auraType] = true
end
end
end
end
local centralFrame = CreateFrame("Frame")
centralFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
centralFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
centralFrame:RegisterEvent("PLAYER_LOGIN")
centralFrame:RegisterEvent("PLAYER_LEVEL_UP")
centralFrame:RegisterEvent("CINEMATIC_STOP")
centralFrame:RegisterEvent("ARENA_PREP_OPPONENT_SPECIALIZATIONS")
centralFrame:RegisterEvent("ARENA_OPPONENT_UPDATE")
centralFrame:SetScript("OnEvent", function(self, event, unit)
-- Check if the player changed zone types and we need to change module status, while they are dead
-- we won't change their zone type as releasing from an instance will change the zone type without them
-- really having left the zone
if( event == "ZONE_CHANGED_NEW_AREA" ) then
if( UnitIsDeadOrGhost("player") ) then
self:RegisterEvent("PLAYER_UNGHOST")
else
self:UnregisterEvent("PLAYER_UNGHOST")
Units:CheckPlayerZone()
end
-- Force update frames
elseif( event == "ARENA_PREP_OPPONENT_SPECIALIZATIONS" or event == "ARENA_OPPONENT_UPDATE" ) then
Units:InitializeArena()
-- They're alive again so they "officially" changed zone types now
elseif( event == "PLAYER_UNGHOST" ) then
Units:CheckPlayerZone()
-- Monitor level up
elseif( event == "PLAYER_LEVEL_UP" or event == "CINEMATIC_STOP" ) then
if( unitFrames.player ) then
unitFrames.player:SetVisibility()
unitFrames.player:FullUpdate()
end
-- Monitor talent changes for curable changes
elseif( event == "PLAYER_SPECIALIZATION_CHANGED" or event == "UNIT_PET" ) then
checkCurableSpells()
for frame in pairs(ShadowUF.Units.frameList) do
if( frame.unit ) then
frame:SetVisibility()
if( frame:IsVisible() ) then
frame:FullUpdate()
end
end
end
elseif( event == "PLAYER_LOGIN" ) then
checkCurableSpells()
self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
if( playerClass == "WARLOCK" ) then
self:RegisterUnitEvent("UNIT_PET", "player", nil)
end
-- This is slightly hackish, but it suits the purpose just fine for somthing thats rarely called.
elseif( event == "PLAYER_REGEN_ENABLED" ) then
-- Now do all of the creation for child wrapping
for _, queue in pairs(queuedCombat) do
Units:LoadChildUnit(queue.parent, queue.type, queue.id)
end
table.wipe(queuedCombat)
if( queueZoneCheck ) then
Units:CheckPlayerZone(queueZoneCheck == 2 and true)
queueZoneCheck = nil
end
end
end)