-- I am undecided if this is a brilliant idea or an insane one local L = ShadowUF.L local Movers = {} local originalEnvs = {} local unitConfig = {} local attributeBlacklist = {["showplayer"] = true, ["showraid"] = true, ["showparty"] = true, ["showsolo"] = true, ["initial-unitwatch"] = true} local playerClass = select(2, UnitClass("player")) local noop = function() end local OnDragStop, OnDragStart, configEnv ShadowUF:RegisterModule(Movers, "movers") -- This is the fun part, the env to fake units and make them show up as examples local function getValue(func, unit, value) unit = string.gsub(unit, "(%d+)", "") if( unitConfig[func .. unit] == nil ) then unitConfig[func .. unit] = value end return unitConfig[func .. unit] end local function createConfigEnv() if( configEnv ) then return end configEnv = setmetatable({ GetRaidTargetIndex = function(unit) return getValue("GetRaidTargetIndex", unit, math.random(1, 8)) end, GetLootMethod = function(unit) return "master", 0, 0 end, GetComboPoints = function() return MAX_COMBO_POINTS end, UnitInRaid = function() return true end, UnitInParty = function() return true end, UnitIsUnit = function(unitA, unitB) return unitB == "player" and true or false end, UnitIsDeadOrGhost = function(unit) return false end, UnitIsConnected = function(unit) return true end, UnitLevel = function(unit) return MAX_PLAYER_LEVEL end, UnitIsPlayer = function(unit) return unit ~= "boss" and unit ~= "pet" and not string.match(unit, "(%w+)pet") end, UnitHealth = function(unit) return getValue("UnitHealth", unit, math.random(20000, 50000)) end, UnitIsQuestBoss = function(unit) return unit == "target" or unit == "focus" end, UnitIsWildBattlePet = function(unit) return unit == "target" or unit == "focus" end, UnitBattlePetType = function(unit) if( unit == "target" or unit == "focus" ) then return getValue("UnitBattlePetType", unit, math.random(#(PET_TYPE_SUFFIX))) end end, GetArenaOpponentSpec = function(unitID) return getValue("GetArenaOpponentSpec", unitID, math.random(250, 270)) end, UnitHealthMax = function(unit) return 50000 end, UnitPower = function(unit, powerType) if( powerType == Enum.PowerType.HolyPower or powerType == Enum.PowerType.SoulShards or powerType == Enum.PowerType.Essence ) then return 3 elseif( powerType == Enum.PowerType.Chi) then return 4 end return getValue("UnitPower", unit, math.random(20000, 50000)) end, UnitGetTotalHealAbsorbs = function(unit) return getValue("UnitGetTotalHealAbsorbs", unit, math.random(5000, 10000)) end, UnitGetIncomingHeals = function(unit) return getValue("UnitGetIncomingHeals", unit, math.random(10000, 15000)) end, UnitGetTotalAbsorbs = function(unit) return getValue("UnitGetTotalAbsorbs", unit, math.random(2500, 5000)) end, UnitPowerMax = function(unit, powerType) if( powerType == Enum.PowerType.Rage or powerType == Enum.PowerType.Energy or powerType == Enum.PowerType.RunicPower or powerType == Enum.PowerType.LunarPower or powerType == Enum.PowerType.Maelstrom or powerType == Enum.PowerType.Insanity or powerType == Enum.PowerType.Fury or powerType == Enum.PowerType.Pain ) then return 100 elseif( powerType == Enum.PowerType.Focus ) then return 120 elseif( powerType == Enum.PowerType.ComboPoints or powerType == Enum.PowerType.SoulShards or powerType == Enum.PowerType.HolyPower or powerType == Enum.PowerType.Chi or powerType == Enum.PowerType.Essence ) then return 5 elseif( powerType == Enum.PowerType.Runes ) then return 6 elseif( powerType == Enum.PowerType.ArcaneCharges ) then return 4 end return 50000 end, UnitHasIncomingResurrection = function(unit) return true end, UnitInOtherParty = function(unit) return getValue("UnitInOtherParty", unit, math.random(0, 1) == 1) end, UnitPhaseReason = function(unit) return nil end, UnitExists = function(unit) return true end, UnitIsGroupLeader = function() return true end, UnitIsPVP = function(unit) return true end, UnitIsDND = function(unit) return false end, UnitIsAFK = function(unit) return false end, UnitFactionGroup = function(unit) return _G.UnitFactionGroup("player") end, UnitAffectingCombat = function() return true end, UnitThreatSituation = function() return 0 end, UnitDetailedThreatSituation = function() return nil end, UnitCastingInfo = function(unit) -- 1 -> 10: spell, displayName, icon, startTime, endTime, isTradeSkill, castID, notInterruptible, spellID local data = unitConfig["UnitCastingInfo" .. unit] or {} if( not data[5] or GetTime() < data[5] ) then data[1] = L["Test spell"] data[2] = L["Test spell"] data[3] = "Interface\\Icons\\Spell_Nature_Rejuvenation" data[4] = GetTime() * 1000 data[5] = data[4] + 60000 data[6] = false data[7] = math.floor(GetTime()) data[8] = math.random(0, 100) < 25 data[9] = 1000 unitConfig["UnitCastingInfo" .. unit] = data end return unpack(data) end, UnitIsFriend = function(unit) return unit ~= "target" and unit ~= ShadowUF.fakeUnits[unit] and unit ~= "arena" end, GetReadyCheckStatus = function(unit) local status = getValue("GetReadyCheckStatus", unit, math.random(1, 3)) return status == 1 and "ready" or status == 2 and "notready" or "waiting" end, GetPartyAssignment = function(type, unit) local assignment = getValue("GetPartyAssignment", unit, math.random(1, 2) == 1 and "MAINTANK" or "MAINASSIST") return assignment == type end, UnitGroupRolesAssigned = function(unit) local role = getValue("UnitGroupRolesAssigned", unit, math.random(1, 3)) return role == 1 and "TANK" or (role == 2 and "HEALER" or (role == 3 and "DAMAGER")) end, UnitPowerType = function(unit) local powerType = math.random(0, 4) powerType = getValue("UnitPowerType", unit, powerType == 4 and 6 or powerType) return powerType, powerType == 0 and "MANA" or powerType == 1 and "RAGE" or powerType == 2 and "FOCUS" or powerType == 3 and "ENERGY" or powerType == 6 and "RUNIC_POWER" end, UnitStagger = function(unit) if( unit ~= "player" ) then return nil end return getValue("UnitStagger", math.random(2000, 10000)) end, UnitAura = function(unit, id, filter) if( type(id) ~= "number" or id > 40 ) then return end local texture = filter == "HELPFUL" and "Interface\\Icons\\Spell_Nature_Rejuvenation" or "Interface\\Icons\\Ability_DualWield" local mod = id % 5 local auraType = mod == 0 and "Magic" or mod == 1 and "Curse" or mod == 2 and "Poison" or mod == 3 and "Disease" or "none" return L["Test Aura"], texture, id, auraType, 0, 0, "player", id % 6 == 0 end, UnitName = function(unit) local unitID = string.match(unit, "(%d+)") if( unitID ) then return string.format("%s #%d", L.units[string.gsub(unit, "(%d+)", "")] or unit, unitID) end return L.units[unit] end, UnitClass = function(unit) local classToken = getValue("UnitClass", unit, CLASS_SORT_ORDER[math.random(1, #(CLASS_SORT_ORDER))]) return LOCALIZED_CLASS_NAMES_MALE[classToken], classToken end, }, { __index = _G, __newindex = function(tbl, key, value) _G[key] = value end, }) end -- Child units have to manually be added to the list to make sure they function properly local function prepareChildUnits(header, ...) for i=1, select("#", ...) do local frame = select(i, ...) if( frame.unitType and not frame.configUnitID ) then ShadowUF.Units.frameList[frame] = true frame.configUnitID = header.groupID and (header.groupID * 5) - 5 + i or i frame:SetAttribute("unit", ShadowUF[header.unitMappedType .. "Units"][frame.configUnitID]) end end end local function OnEnter(self) local tooltip = self.tooltipText or self.unitID and string.format("%s #%d", L.units[self.unitType], self.unitID) or L.units[self.unit] or self.unit local additionalText = ShadowUF.Units.childUnits[self.unitType] and L["Child units cannot be dragged, you will have to reposition them through /shadowuf."] GameTooltip:SetOwner(self, "ANCHOR_BOTTOMLEFT") GameTooltip:SetText(tooltip, 1, 0.81, 0, 1, true) if( additionalText ) then GameTooltip:AddLine(additionalText, 0.90, 0.90, 0.90, 1) end GameTooltip:Show() end local function OnLeave(self) GameTooltip:Hide() end local function setupUnits(childrenOnly) for frame in pairs(ShadowUF.Units.frameList) do if( frame.configMode ) then -- Units visible, but it's not supposed to be if( frame:IsVisible() and not ShadowUF.db.profile.units[frame.unitType].enabled ) then RegisterUnitWatch(frame, frame.hasStateWatch) if( not UnitExists(frame.unit) ) then frame:Hide() end -- Unit's not visible and it's enabled so it should elseif( not frame:IsVisible() and ShadowUF.db.profile.units[frame.unitType].enabled ) then UnregisterUnitWatch(frame) frame:SetAttribute("state-unitexists", true) frame:FullUpdate() frame:Show() end elseif( not frame.configMode and ShadowUF.db.profile.units[frame.unitType].enabled ) then frame.originalUnit = frame:GetAttribute("unit") frame.originalOnEnter = frame.OnEnter frame.originalOnLeave = frame.OnLeave frame.originalOnUpdate = frame:GetScript("OnUpdate") frame:SetMovable(not ShadowUF.Units.childUnits[frame.unitType]) frame:SetScript("OnDragStop", OnDragStop) frame:SetScript("OnDragStart", OnDragStart) frame.OnEnter = OnEnter frame.OnLeave = OnLeave frame:SetScript("OnEvent", nil) frame:SetScript("OnUpdate", nil) frame:RegisterForDrag("LeftButton") frame.configMode = true frame.unitOwner = nil frame.originalMenu = frame.menu frame.menu = nil local unit if( frame.isChildUnit ) then local unitFormat = string.gsub(string.gsub(frame.unitType, "target$", "%%dtarget"), "pet$", "pet%%d") unit = string.format(unitFormat, frame.parent.configUnitID or "") else unit = frame.unitType .. (frame.configUnitID or "") end ShadowUF.Units.OnAttributeChanged(frame, "unit", unit) if( frame.healthBar ) then frame.healthBar:SetScript("OnUpdate", nil) end if( frame.powerBar ) then frame.powerBar:SetScript("OnUpdate", nil) end if( frame.indicators ) then frame.indicators:SetScript("OnUpdate", nil) end UnregisterUnitWatch(frame) frame:FullUpdate() frame:Show() end end end function Movers:Enable() createConfigEnv() -- Force create zone headers for type, zone in pairs(ShadowUF.Units.zoneUnits) do if( ShadowUF.db.profile.units[type].enabled ) then ShadowUF.Units:InitializeFrame(type) end end -- Setup the headers for _, header in pairs(ShadowUF.Units.headerFrames) do for key in pairs(attributeBlacklist) do header:SetAttribute(key, nil) end local config = ShadowUF.db.profile.units[header.unitType] if( config.frameSplit ) then header:SetAttribute("startingIndex", -4) elseif( config.maxColumns ) then local maxUnits = MAX_RAID_MEMBERS if( config.filters ) then for _, enabled in pairs(config.filters) do if( not enabled ) then maxUnits = maxUnits - 5 end end end header:SetAttribute("startingIndex", -math.min(config.maxColumns * config.unitsPerColumn, maxUnits) + 1) elseif( ShadowUF[header.unitType .. "Units"] ) then header:SetAttribute("startingIndex", -#(ShadowUF[header.unitType .. "Units"]) + 1) end header.startingIndex = header:GetAttribute("startingIndex") header:SetMovable(true) prepareChildUnits(header, header:GetChildren()) end -- Setup the test env if( not self.isEnabled ) then for _, func in pairs(ShadowUF.tagFunc) do if( type(func) == "function" ) then originalEnvs[func] = getfenv(func) setfenv(func, configEnv) end end for _, module in pairs(ShadowUF.modules) do if( module.moduleName ) then for key, func in pairs(module) do if( type(func) == "function" ) then originalEnvs[module[key]] = getfenv(module[key]) setfenv(module[key], configEnv) end end end end end -- Why is this called twice you ask? Child units are created on the OnAttributeChanged call -- so the first call gets all the parent units, the second call gets the child units setupUnits() setupUnits(true) for unitType in pairs(ShadowUF.Units.zoneUnits) do local header = ShadowUF.Units.headerFrames[unitType] if( ShadowUF.db.profile.units[unitType].enabled and header ) then header:SetAttribute("childChanged", 1) end end -- Don't show the dialog if the configuration is opened through the configmode spec if( not self.isConfigModeSpec ) then self:CreateInfoFrame() self.infoFrame:Show() elseif( self.infoFrame ) then self.infoFrame:Hide() end self.isEnabled = true end function Movers:Disable() if( not self.isEnabled ) then return nil end for func, env in pairs(originalEnvs) do setfenv(func, env) originalEnvs[func] = nil end for frame in pairs(ShadowUF.Units.frameList) do if( frame.configMode ) then if( frame.isMoving ) then frame:GetScript("OnDragStop")(frame) end frame.configMode = nil frame.unitOwner = nil frame.unit = nil frame.configUnitID = nil frame.menu = frame.originalMenu frame.originalMenu = nil frame.Hide = frame.originalHide frame:SetAttribute("unit", frame.originalUnit) frame:SetScript("OnDragStop", nil) frame:SetScript("OnDragStart", nil) frame:SetScript("OnEvent", frame:IsVisible() and ShadowUF.Units.OnEvent or nil) frame:SetScript("OnUpdate", frame.originalOnUpdate) frame.OnEnter = frame.originalOnEnter frame.OnLeave = frame.originalOnLeave frame:SetMovable(false) frame:RegisterForDrag() if( frame.isChildUnit ) then ShadowUF.Units.OnAttributeChanged(frame, "unit", SecureButton_GetModifiedUnit(frame)) end RegisterUnitWatch(frame, frame.hasStateWatch) if( not UnitExists(frame.unit) ) then frame:Hide() end end end for type, header in pairs(ShadowUF.Units.headerFrames) do header:SetMovable(false) header:SetAttribute("startingIndex", 1) header:SetAttribute("initial-unitWatch", true) if( header.unitType == type or type == "raidParent" ) then ShadowUF.Units:ReloadHeader(header.unitType) end end ShadowUF.Units:CheckPlayerZone(true) ShadowUF.Layout:Reload() -- Don't store these so everything can be GCed unitConfig = {} if( self.infoFrame ) then self.infoFrame:Hide() end self.isConfigModeSpec = nil self.isEnabled = nil end OnDragStart = function(self) if( not self:IsMovable() ) then return end if( self.unitType == "raid" and ShadowUF.Units.headerFrames.raidParent and ShadowUF.Units.headerFrames.raidParent:IsVisible() ) then self = ShadowUF.Units.headerFrames.raidParent else self = ShadowUF.Units.headerFrames[self.unitType] or ShadowUF.Units.unitFrames[self.unitType] end self.isMoving = true self:StartMoving() end OnDragStop = function(self) if( not self:IsMovable() ) then return end if( self.unitType == "raid" and ShadowUF.Units.headerFrames.raidParent and ShadowUF.Units.headerFrames.raidParent:IsVisible() ) then self = ShadowUF.Units.headerFrames.raidParent else self = ShadowUF.Units.headerFrames[self.unitType] or ShadowUF.Units.unitFrames[self.unitType] end self.isMoving = nil self:StopMovingOrSizing() -- When dragging the frame around, Blizzard changes the anchoring based on the closet portion of the screen -- When a widget is near the top left it uses top left, near the left it uses left and so on, which messes up positioning for header frames local scale = (self:GetScale() * UIParent:GetScale()) or 1 local position = ShadowUF.db.profile.positions[self.unitType] local point, _, relativePoint, x, y = self:GetPoint() -- Figure out the horizontal anchor if( self.isHeaderFrame ) then if( ShadowUF.db.profile.units[self.unitType].attribAnchorPoint == "RIGHT" ) then x = self:GetRight() point = "RIGHT" else x = self:GetLeft() point = "LEFT" end if( ShadowUF.db.profile.units[self.unitType].attribPoint == "BOTTOM" ) then y = self:GetBottom() point = "BOTTOM" .. point else y = self:GetTop() point = "TOP" .. point end relativePoint = "BOTTOMLEFT" position.bottom = self:GetBottom() * scale position.top = self:GetTop() * scale end position.anchorTo = "UIParent" position.movedAnchor = nil position.anchorPoint = "" position.point = point position.relativePoint = relativePoint position.x = x * scale position.y = y * scale ShadowUF.Layout:AnchorFrame(UIParent, self, ShadowUF.db.profile.positions[self.unitType]) -- Unlock the parent frame from the mover now too if( self.parent ) then ShadowUF.Layout:AnchorFrame(UIParent, self.parent, ShadowUF.db.profile.positions[self.parent.unitType]) end -- Notify the configuration it can update itself now local ACR = LibStub("AceConfigRegistry-3.0", true) if( ACR ) then ACR:NotifyChange("ShadowedUF") end end function Movers:Update() if( not ShadowUF.db.profile.locked ) then self:Enable() elseif( ShadowUF.db.profile.locked ) then self:Disable() end end function Movers:CreateInfoFrame() if( self.infoFrame ) then return end -- Show an info frame that users can lock the frames through local frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate" or nil) frame:SetClampedToScreen(true) frame:SetWidth(300) frame:SetHeight(115) frame:RegisterForDrag("LeftButton") frame:EnableMouse(true) frame:SetMovable(true) frame:RegisterEvent("PLAYER_REGEN_DISABLED") frame:SetScript("OnEvent", function(f) if( not ShadowUF.db.profile.locked and f:IsVisible() ) then ShadowUF.db.profile.locked = true Movers:Disable() DEFAULT_CHAT_FRAME:AddMessage(L["You have entered combat, unit frames have been locked. Once you leave combat you will need to unlock them again through /shadowuf."]) end end) frame:SetScript("OnDragStart", function(f) f:StartMoving() end) frame:SetScript("OnDragStop", function(f) f:StopMovingOrSizing() end) frame:SetBackdrop({ bgFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", edgeSize = 26, insets = {left = 9, right = 9, top = 9, bottom = 9}, }) frame:SetBackdropColor(0, 0, 0, 0.85) frame:SetPoint("CENTER", UIParent, "CENTER", 0, 225) frame.titleBar = frame:CreateTexture(nil, "ARTWORK") frame.titleBar:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header") frame.titleBar:SetPoint("TOP", 0, 8) frame.titleBar:SetWidth(350) frame.titleBar:SetHeight(45) frame.title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormal") frame.title:SetPoint("TOP", 0, 0) frame.title:SetText("Shadowed Unit Frames") frame.text = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") frame.text:SetText(L["The unit frames you see are examples, they are not perfect and do not show all the data they normally would.|n|nYou can hide them by locking them through /shadowuf or clicking the button below."]) frame.text:SetPoint("TOPLEFT", 12, -22) frame.text:SetWidth(frame:GetWidth() - 20) frame.text:SetJustifyH("LEFT") frame.lock = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") frame.lock:SetText(L["Lock frames"]) frame.lock:SetHeight(20) frame.lock:SetWidth(100) frame.lock:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 6, 8) frame.lock:SetScript("OnEnter", OnEnter) frame.lock:SetScript("OnLeave", OnLeave) frame.lock.tooltipText = L["Locks the unit frame positionings hiding the mover boxes."] frame.lock:SetScript("OnClick", function() ShadowUF.db.profile.locked = true Movers:Update() end) frame.unlink = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") frame.unlink:SetText(L["Unlink frames"]) frame.unlink:SetHeight(20) frame.unlink:SetWidth(100) frame.unlink:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -6, 8) frame.unlink:SetScript("OnEnter", OnEnter) frame.unlink:SetScript("OnLeave", OnLeave) frame.unlink.tooltipText = L["WARNING: This will unlink all frames from each other so you can move them without another frame moving with it."] frame.unlink:SetScript("OnClick", function() for f in pairs(ShadowUF.Units.frameList) do if( not ShadowUF.Units.childUnits[f.unitType] and f:GetScript("OnDragStart") and f:GetScript("OnDragStop") ) then f:GetScript("OnDragStart")(f) f:GetScript("OnDragStop")(f) end end Movers:Update() end) self.infoFrame = frame end