From 3dada108b96865289b06cfa6d1acbf42a7c2108e Mon Sep 17 00:00:00 2001 From: andrew6180 <16847730+andrew6180@users.noreply.github.com> Date: Mon, 23 Dec 2024 06:56:21 -0700 Subject: [PATCH] missing files --- ShadowedUnitFrames/ShadowedUnitFrames.lua | 570 ++++++++ ShadowedUnitFrames/ShadowedUnitFrames.toc | 53 + ShadowedUnitFrames/modules/defaultlayout.lua | 648 +++++++++ ShadowedUnitFrames/modules/druid.lua | 46 + ShadowedUnitFrames/modules/empty.lua | 59 + ShadowedUnitFrames/modules/fader.lua | 140 ++ ShadowedUnitFrames/modules/health.lua | 181 +++ ShadowedUnitFrames/modules/highlight.lua | 155 +++ ShadowedUnitFrames/modules/incheal.lua | 195 +++ ShadowedUnitFrames/modules/indicators.lua | 394 ++++++ ShadowedUnitFrames/modules/layout.lua | 540 ++++++++ ShadowedUnitFrames/modules/movers.lua | 496 +++++++ ShadowedUnitFrames/modules/portrait.lua | 98 ++ ShadowedUnitFrames/modules/power.lua | 75 + ShadowedUnitFrames/modules/range.lua | 87 ++ ShadowedUnitFrames/modules/runes.lua | 115 ++ ShadowedUnitFrames/modules/tags.lua | 1279 ++++++++++++++++++ ShadowedUnitFrames/modules/totems.lua | 111 ++ ShadowedUnitFrames/modules/units.lua | 1173 ++++++++++++++++ ShadowedUnitFrames/modules/xp.lua | 161 +++ 20 files changed, 6576 insertions(+) create mode 100644 ShadowedUnitFrames/ShadowedUnitFrames.lua create mode 100644 ShadowedUnitFrames/ShadowedUnitFrames.toc create mode 100644 ShadowedUnitFrames/modules/defaultlayout.lua create mode 100644 ShadowedUnitFrames/modules/druid.lua create mode 100644 ShadowedUnitFrames/modules/empty.lua create mode 100644 ShadowedUnitFrames/modules/fader.lua create mode 100644 ShadowedUnitFrames/modules/health.lua create mode 100644 ShadowedUnitFrames/modules/highlight.lua create mode 100644 ShadowedUnitFrames/modules/incheal.lua create mode 100644 ShadowedUnitFrames/modules/indicators.lua create mode 100644 ShadowedUnitFrames/modules/layout.lua create mode 100644 ShadowedUnitFrames/modules/movers.lua create mode 100644 ShadowedUnitFrames/modules/portrait.lua create mode 100644 ShadowedUnitFrames/modules/power.lua create mode 100644 ShadowedUnitFrames/modules/range.lua create mode 100644 ShadowedUnitFrames/modules/runes.lua create mode 100644 ShadowedUnitFrames/modules/tags.lua create mode 100644 ShadowedUnitFrames/modules/totems.lua create mode 100644 ShadowedUnitFrames/modules/units.lua create mode 100644 ShadowedUnitFrames/modules/xp.lua diff --git a/ShadowedUnitFrames/ShadowedUnitFrames.lua b/ShadowedUnitFrames/ShadowedUnitFrames.lua new file mode 100644 index 0000000..d0db7dc --- /dev/null +++ b/ShadowedUnitFrames/ShadowedUnitFrames.lua @@ -0,0 +1,570 @@ +--[[ + Shadowed Unit Frames, Shadow of Mal'Ganis (US) PvP +]] + +ShadowUF = select(2, ...) +local L = ShadowUF.L +ShadowUF.dbRevision = 1 +ShadowUF.playerUnit = "player" +ShadowUF.enabledUnits = {} +ShadowUF.modules = {} +ShadowUF.moduleOrder = {} +ShadowUF.unitList = {"player", "pet", "pettarget", "target", "targettarget", "targettargettarget", "focus", "focustarget", "party", "partypet", "partytarget", "raid", "raidpet", "boss", "bosstarget", "maintank", "maintanktarget", "mainassist", "mainassisttarget", "arena", "arenatarget", "arenapet"} +ShadowUF.fakeUnits = {["targettarget"] = true, ["targettargettarget"] = true, ["pettarget"] = true, ["arenatarget"] = true, ["focustarget"] = true, ["focustargettarget"] = true, ["partytarget"] = true, ["raidtarget"] = true, ["bosstarget"] = true, ["maintanktarget"] = true, ["mainassisttarget"] = true} +L.units = {["raidpet"] = L["Raid pet"], ["PET"] = L["Pet"], ["VEHICLE"] = L["Vehicle"], ["arena"] = L["Arena"], ["arenapet"] = L["Arena Pet"], ["arenatarget"] = L["Arena Target"], ["boss"] = L["Boss"], ["bosstarget"] = L["Boss Target"], ["focus"] = L["Focus"], ["focustarget"] = L["Focus Target"], ["mainassist"] = L["Main Assist"], ["mainassisttarget"] = L["Main Assist Target"], ["maintank"] = L["Main Tank"], ["maintanktarget"] = L["Main Tank Target"], ["party"] = L["Party"], ["partypet"] = L["Party Pet"], ["partytarget"] = L["Party Target"], ["pet"] = L["Pet"], ["pettarget"] = L["Pet Target"], ["player"] = L["Player"],["raid"] = L["Raid"], ["target"] = L["Target"], ["targettarget"] = L["Target of Target"], ["targettargettarget"] = L["Target of Target of Target"]} + + +-- Cache the units so we don't have to concat every time it updates +ShadowUF.unitTarget = setmetatable({}, {__index = function(tbl, unit) rawset(tbl, unit, unit .. "target"); return unit .. "target" end}) +ShadowUF.partyUnits, ShadowUF.raidUnits, ShadowUF.raidPetUnits, ShadowUF.bossUnits, ShadowUF.arenaUnits = {}, {}, {}, {}, {} +ShadowUF.maintankUnits, ShadowUF.mainassistUnits, ShadowUF.raidpetUnits = ShadowUF.raidUnits, ShadowUF.raidUnits, ShadowUF.raidPetUnits +for i=1, MAX_PARTY_MEMBERS do ShadowUF.partyUnits[i] = "party" .. i end +for i=1, MAX_RAID_MEMBERS do ShadowUF.raidUnits[i] = "raid" .. i end +for i=1, MAX_RAID_MEMBERS do ShadowUF.raidPetUnits[i] = "raidpet" .. i end +for i=1, MAX_BOSS_FRAMES do ShadowUF.bossUnits[i] = "boss" .. i end +for i=1, 5 do ShadowUF.arenaUnits[i] = "arena" .. i end + +function ShadowUF:OnInitialize() + self.defaults = { + profile = { + locked = false, + advanced = false, + tooltipCombat = false, + omnicc = false, + tags = {}, + units = {}, + positions = {}, + range = {}, + filters = {zonewhite = {}, zoneblack = {}, whitelists = {}, blacklists = {}}, + visibility = {arena = {}, pvp = {}, party = {}, raid = {}}, + hidden = {cast = false, runes = true, buffs = true, party = true, player = true, pet = true, target = true, focus = true, boss = true, arena = true}, + }, + } + + self:LoadUnitDefaults() + + -- Initialize DB + self.db = LibStub:GetLibrary("AceDB-3.0"):New("ShadowedUFDB", self.defaults, true) + self.db.RegisterCallback(self, "OnProfileChanged", "ProfilesChanged") + self.db.RegisterCallback(self, "OnProfileCopied", "ProfilesChanged") + self.db.RegisterCallback(self, "OnProfileReset", "ProfileReset") + + -- Setup tag cache + self.tagFunc = setmetatable({}, { + __index = function(tbl, index) + if( not ShadowUF.Tags.defaultTags[index] and not ShadowUF.db.profile.tags[index] ) then + tbl[index] = false + return false + end + + local func, msg = loadstring("return " .. (ShadowUF.Tags.defaultTags[index] or ShadowUF.db.profile.tags[index].func or "")) + if( func ) then + func = func() + elseif( msg ) then + error(msg, 3) + end + + tbl[index] = func + return tbl[index] + end}) + + if( not self.db.profile.loadedLayout ) then + self:LoadDefaultLayout() + else + self:CheckUpgrade() + end + + self.db.profile.revision = self.dbRevision + self:FireModuleEvent("OnInitialize") + self:HideBlizzardFrames() + self.Layout:LoadSML() + self:LoadUnits() + self.modules.movers:Update() +end + +function ShadowUF:CheckUpgrade() + -- June 19th + if( not ShadowUF.db.profile.font.color ) then + ShadowUF.db.profile.font.color = {r = 1, g = 1, b = 1, a = 1} + for unit, config in pairs(self.db.profile.units) do + local indicators = ShadowUF.db.profile.units[unit].indicators + if( indicators and indicators.class ) then + indicators.class.anchorTo = "$parent" + indicators.class.anchorPoint = "BL" + indicators.class.x = 0 + indicators.class.y = 0 + end + end + end + + -- April 29th + if( self.db.profile.filters.zones ) then + for unit, filter in pairs(self.db.profile.filters.zones) do + if( self.db.profile.filters.whitelists[filter] ) then + self.db.profile.filters.zonewhite[unit] = filter + else + self.db.profile.filters.zoneblack[unit] = filter + end + end + end + + -- February 16th + if( not self.db.profile.units.raidpet.enabled and self.db.profile.units.raidpet.height == 0 and self.db.profile.units.raidpet.width == 0 and self.db.profile.positions.raidpet.anchorPoint == "" and self.db.profile.positions.raidpet.point == "" ) then + self:LoadDefaultLayout(true) + end + + self.db.profile.units.party.unitsPerColumn = self.db.profile.units.party.unitsPerColumn or 5 + self.db.profile.units.raid.groupsPerRow = self.db.profile.units.raid.groupsPerRow or 8 + + local castName = {enabled = true, size = 0, anchorTo = "$parent", rank = true, anchorPoint = "CLI", x = 1, y = 0} + local castTime = {enabled = true, size = 0, anchorTo = "$parent", anchorPoint = "CRI", x = -1, y = 0} + + for unit, config in pairs(self.db.profile.units) do + config.portrait = config.portrait or {} + config.portrait.type = config.portrait.type or "3D" + config.portrait.fullBefore = config.portrait.fullBefore or 0 + config.portrait.fullAfter = config.portrait.fullAfter or 100 + config.portrait.order = config.portrait.order or 40 + config.portrait.height = config.portrait.height or 0.50 + + config.highlight.size = config.highlight.size or 10 + + config.castBar = config.castBar or {} + config.castBar.icon = config.castBar.icon or "HIDE" + config.castBar.height = config.castBar.height or 0.60 + config.castBar.order = config.castBar.order or 40 + + config.castBar.name = config.castBar.name or {} + config.castBar.time = config.castBar.time or {} + + for key, value in pairs(castName) do + if( config.castBar.name[key] == nil ) then + config.castBar.name[key] = value + end + end + + for key, value in pairs(castTime) do + if( config.castBar.time[key] == nil ) then + config.castBar.time[key] = value + end + end + end +end + +function ShadowUF:LoadUnits() + -- CanHearthAndResurrectFromArea() returns true for world pvp areas, according to BattlefieldFrame.lua + local instanceType = CanHearthAndResurrectFromArea() and "pvp" or select(2, IsInInstance()) + + for _, type in pairs(self.unitList) do + local enabled = self.db.profile.units[type].enabled + if( ShadowUF.Units.zoneUnits[type] and enabled ) then + enabled = ShadowUF.Units.zoneUnits[type] == instanceType + elseif( instanceType ~= "none" ) then + if( self.db.profile.visibility[instanceType][type] == false ) then + enabled = false + elseif( self.db.profile.visibility[instanceType][type] == true ) then + enabled = true + end + end + + self.enabledUnits[type] = enabled + + if( enabled ) then + self.Units:InitializeFrame(type) + else + self.Units:UninitializeFrame(type) + end + end +end + +function ShadowUF:LoadUnitDefaults() + for _, unit in pairs(self.unitList) do + self.defaults.profile.positions[unit] = {point = "", relativePoint = "", anchorPoint = "", anchorTo = "UIParent", x = 0, y = 0} + + -- The reason why the defaults are so sparse, is because the layout needs to specify most of this. The reason I set tables here is basically + -- as an indication that hey, the unit wants this, if it doesn't that it won't want it. + self.defaults.profile.units[unit] = { + enabled = false, height = 0, width = 0, scale = 1.0, + healthBar = {enabled = true}, + powerBar = {enabled = true}, + emptyBar = {enabled = false}, + portrait = {enabled = false}, + castBar = {enabled = false, name = {}, time = {}}, + text = { + {enabled = true, name = L["Left text"], text = "[name]", anchorPoint = "C", anchorTo = "$healthBar", size = 0}, + {enabled = true, name = L["Right text"], text = "[curmaxhp]", anchorPoint = "C", anchorTo = "$healthBar", size = 0}, + {enabled = true, name = L["Left text"], text = "[level] [race]", anchorPoint = "C", anchorTo = "$powerBar", size = 0}, + {enabled = true, name = L["Right text"], text = "[curmaxpp]", anchorPoint = "C", anchorTo = "$powerBar", size = 0}, + {enabled = true, name = L["Text"], text = "", anchorTo = "$emptyBar", anchorPoint = "C", size = 0, x = 0, y = 0}, + }, + indicators = {raidTarget = {enabled = true, size = 0}}, + highlight = {}, + auras = { + buffs = {enabled = false, perRow = 10, maxRows = 4, selfScale = 1.30, prioritize = true, enlargeSelf = false}, + debuffs = {enabled = false, perRow = 10, maxRows = 4, selfScale = 1.30, enlargeSelf = true}, + }, + } + + if( not self.fakeUnits[unit] ) then + self.defaults.profile.units[unit].combatText = {enabled = true, anchorTo = "$parent", anchorPoint = "C", x = 0, y = 0} + + if( unit ~= "arena" and unit ~= "arenapet" ) then + self.defaults.profile.units[unit].incHeal = {enabled = false, cap = 1.30} + end + end + + if( unit ~= "player" ) then + self.defaults.profile.units[unit].range = {enabled = false, oorAlpha = 0.80, inAlpha = 1.0} + + if( not string.match(unit, "pet") ) then + self.defaults.profile.units[unit].indicators.class = {enabled = false, size = 19} + end + end + + -- Want pvp/leader/ML enabled for these units + if( unit == "player" or unit == "party" or unit == "target" or unit == "raid" or unit == "focus" ) then + self.defaults.profile.units[unit].indicators.leader = {enabled = true, size = 0} + self.defaults.profile.units[unit].indicators.masterLoot = {enabled = true, size = 0} + self.defaults.profile.units[unit].indicators.pvp = {enabled = true, size = 0} + self.defaults.profile.units[unit].indicators.role = {enabled = true, size = 0} + self.defaults.profile.units[unit].indicators.status = {enabled = false, size = 19} + + if( unit ~= "focus" and unit ~= "target" ) then + self.defaults.profile.units[unit].indicators.ready = {enabled = true, size = 0} + end + end + end + + -- PLAYER + self.defaults.profile.units.player.enabled = true + self.defaults.profile.units.player.healthBar.predicted = true + self.defaults.profile.units.player.powerBar.predicted = true + self.defaults.profile.units.player.indicators.status.enabled = true + self.defaults.profile.units.player.runeBar = {enabled = false} + self.defaults.profile.units.player.totemBar = {enabled = false} + self.defaults.profile.units.player.druidBar = {enabled = false} + self.defaults.profile.units.player.xpBar = {enabled = false} + self.defaults.profile.units.player.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + self.defaults.profile.units.player.indicators.lfdRole = {enabled = true, size = 0, x = 0, y = 0} + -- PET + self.defaults.profile.units.pet.enabled = true + self.defaults.profile.units.pet.indicators.happiness = {enabled = true, size = 16, anchorPoint = "BR", anchorTo = "$parent", x = 2, y = -2} + self.defaults.profile.units.pet.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + self.defaults.profile.units.pet.xpBar = {enabled = false} + -- FOCUS + self.defaults.profile.units.focus.enabled = true + self.defaults.profile.units.focus.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + self.defaults.profile.units.focus.indicators.lfdRole = {enabled = false, size = 0, x = 0, y = 0} + -- FOCUSTARGET + self.defaults.profile.units.focustarget.enabled = true + self.defaults.profile.units.focustarget.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + -- TARGET + self.defaults.profile.units.target.enabled = true + self.defaults.profile.units.target.comboPoints = {enabled = true, isBar = false, height = 0.40, order = 30, anchorTo = "$parent", anchorPoint = "BR", x = 0, y = 0} + self.defaults.profile.units.target.indicators.lfdRole = {enabled = false, size = 0, x = 0, y = 0} + -- TARGETTARGET/TARGETTARGETTARGET + self.defaults.profile.units.targettarget.enabled = true + self.defaults.profile.units.targettargettarget.enabled = true + -- PARTY + self.defaults.profile.units.party.enabled = true + self.defaults.profile.units.party.auras.debuffs.maxRows = 1 + self.defaults.profile.units.party.auras.buffs.maxRows = 1 + self.defaults.profile.units.party.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + self.defaults.profile.units.party.combatText.enabled = false + self.defaults.profile.units.party.indicators.lfdRole = {enabled = true, size = 0, x = 0, y = 0} + -- ARENA + self.defaults.profile.units.arena.enabled = false + self.defaults.profile.units.arena.attribPoint = "TOP" + self.defaults.profile.units.arena.attribAnchorPoint = "LEFT" + self.defaults.profile.units.arena.auras.debuffs.maxRows = 1 + self.defaults.profile.units.arena.auras.buffs.maxRows = 1 + self.defaults.profile.units.arena.offset = 0 + -- BOSS + self.defaults.profile.units.boss.enabled = false + self.defaults.profile.units.boss.attribPoint = "TOP" + self.defaults.profile.units.boss.attribAnchorPoint = "LEFT" + self.defaults.profile.units.boss.auras.debuffs.maxRows = 1 + self.defaults.profile.units.boss.auras.buffs.maxRows = 1 + self.defaults.profile.units.boss.offset = 0 + -- RAID + self.defaults.profile.units.raid.groupBy = "GROUP" + self.defaults.profile.units.raid.sortOrder = "ASC" + self.defaults.profile.units.raid.sortMethod = "INDEX" + self.defaults.profile.units.raid.attribPoint = "TOP" + self.defaults.profile.units.raid.attribAnchorPoint = "RIGHT" + self.defaults.profile.units.raid.offset = 0 + self.defaults.profile.units.raid.filters = {[1] = true, [2] = true, [3] = true, [4] = true, [5] = true, [6] = true, [7] = true, [8] = true} + self.defaults.profile.units.raid.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + self.defaults.profile.units.raid.combatText.enabled = false + -- RAID PET + self.defaults.profile.units.raidpet.groupBy = "GROUP" + self.defaults.profile.units.raidpet.sortOrder = "ASC" + self.defaults.profile.units.raidpet.sortMethod = "INDEX" + self.defaults.profile.units.raidpet.attribPoint = "TOP" + self.defaults.profile.units.raidpet.attribAnchorPoint = "RIGHT" + self.defaults.profile.units.raidpet.offset = 0 + self.defaults.profile.units.raidpet.filters = {[1] = true, [2] = true, [3] = true, [4] = true, [5] = true, [6] = true, [7] = true, [8] = true} + self.defaults.profile.units.raidpet.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + self.defaults.profile.units.raidpet.combatText.enabled = false + -- MAINTANK + self.defaults.profile.units.maintank.groupFilter = "MAINTANK" + self.defaults.profile.units.maintank.groupBy = "GROUP" + self.defaults.profile.units.maintank.sortOrder = "ASC" + self.defaults.profile.units.maintank.sortMethod = "INDEX" + self.defaults.profile.units.maintank.attribPoint = "TOP" + self.defaults.profile.units.maintank.attribAnchorPoint = "RIGHT" + self.defaults.profile.units.maintank.offset = 0 + self.defaults.profile.units.maintank.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + -- MAINASSIST + self.defaults.profile.units.mainassist.groupFilter = "MAINASSIST" + self.defaults.profile.units.mainassist.groupBy = "GROUP" + self.defaults.profile.units.mainassist.sortOrder = "ASC" + self.defaults.profile.units.mainassist.sortMethod = "INDEX" + self.defaults.profile.units.mainassist.attribPoint = "TOP" + self.defaults.profile.units.mainassist.attribAnchorPoint = "RIGHT" + self.defaults.profile.units.mainassist.offset = 0 + self.defaults.profile.units.mainassist.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + -- PARTYPET + self.defaults.profile.positions.partypet.anchorTo = "$parent" + self.defaults.profile.positions.partypet.anchorPoint = "RB" + self.defaults.profile.units.partypet.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} + -- PARTYTARGET + self.defaults.profile.positions.partytarget.anchorTo = "$parent" + self.defaults.profile.positions.partytarget.anchorPoint = "RT" + self.defaults.profile.units.partytarget.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} +end + +-- Module APIs +function ShadowUF:RegisterModule(module, key, name, isBar, class) + -- December 16th + if( module.OnDefaultsSet ) then + DEFAULT_CHAT_FRAME:AddMessage(string.format("[WARNING!] You are running an outdated version of %s, you need to update it to the latest available for it to work with SUF.", name or key or "unknown")) + return + end + + self.modules[key] = module + + module.moduleKey = key + module.moduleHasBar = isBar + module.moduleName = name + module.moduleClass = class + + table.insert(self.moduleOrder, module) +end + +function ShadowUF:FireModuleEvent(event, frame, unit) + for _, module in pairs(self.moduleOrder) do + if( module[event] ) then + module[event](module, frame, unit) + end + end +end + +-- Profiles changed +-- I really dislike this solution, but if we don't do it then there is setting issues +-- because when copying a profile, AceDB-3.0 fires OnProfileReset -> OnProfileCopied +-- SUF then sees that on the new reset profile has no profile, tries to load one in +-- ... followed by the profile copying happen and it doesn't copy everything correctly +-- due to variables being reset already. +local resetTimer +function ShadowUF:ProfileReset() + if( not resetTimer ) then + resetTimer = CreateFrame("Frame") + resetTimer:SetScript("OnUpdate", function(self) + ShadowUF:ProfilesChanged() + self:Hide() + end) + end + + resetTimer:Show() +end + +function ShadowUF:ProfilesChanged() + if( self.layoutImporting ) then return end + if( resetTimer ) then resetTimer:Hide() end + + self.db:RegisterDefaults(self.defaults) + + -- No active layout, register the default one + if( not self.db.profile.loadedLayout ) then + self:LoadDefaultLayout() + else + self:CheckUpgrade() + end + + self:FireModuleEvent("OnProfileChange") + self:LoadUnits() + self:HideBlizzardFrames() + self.Layout:CheckMedia() + self.Units:ProfileChanged() + self.modules.movers:Update() +end + +-- Stolen from haste +ShadowUF.noop = function() end +function ShadowUF:HideBlizzardFrames() + if( ShadowUF.db.profile.hidden.runes ) then + RuneFrame.Show = self.noop + RuneFrame:Hide() + end + + if( ShadowUF.db.profile.hidden.cast ) then + CastingBarFrame:UnregisterAllEvents() + PetCastingBarFrame:UnregisterAllEvents() + end + + if( ShadowUF.db.profile.hidden.party ) then + for i=1, MAX_PARTY_MEMBERS do + local name = "PartyMemberFrame" .. i + local frame = _G[name] + + frame:UnregisterAllEvents() + frame.Show = self.noop + frame:Hide() + + _G[name .. "HealthBar"]:UnregisterAllEvents() + _G[name .. "ManaBar"]:UnregisterAllEvents() + end + end + + if( ShadowUF.db.profile.hidden.buffs ) then + BuffFrame:UnregisterAllEvents() + BuffFrame.Show = self.noop + BuffFrame:Hide() + ConsolidatedBuffs.Show = self.noop + ConsolidatedBuffs:Hide() + TemporaryEnchantFrame.Show = self.noop + TemporaryEnchantFrame:Hide() + end + + if( ShadowUF.db.profile.hidden.player ) then + PlayerFrame:UnregisterAllEvents() + PlayerFrame.Show = self.noop + PlayerFrame:Hide() + + PlayerFrame:RegisterEvent('UNIT_ENTERING_VEHICLE') + PlayerFrame:RegisterEvent('UNIT_ENTERED_VEHICLE') + PlayerFrame:RegisterEvent('UNIT_EXITING_VEHICLE') + PlayerFrame:RegisterEvent('UNIT_EXITED_VEHICLE') + + PlayerFrameHealthBar:UnregisterAllEvents() + PlayerFrameManaBar:UnregisterAllEvents() + end + + if( ShadowUF.db.profile.hidden.pet ) then + PetFrame:UnregisterAllEvents() + PetFrame.Show = self.noop + PetFrame:Hide() + + PetFrameHealthBar:UnregisterAllEvents() + PetFrameManaBar:UnregisterAllEvents() + end + + if( ShadowUF.db.profile.hidden.target ) then + TargetFrame:UnregisterAllEvents() + TargetFrame.Show = self.noop + TargetFrame:Hide() + + TargetFrameHealthBar:UnregisterAllEvents() + TargetFrameManaBar:UnregisterAllEvents() + TargetFrameSpellBar:UnregisterAllEvents() + + ComboFrame:UnregisterAllEvents() + ComboFrame.Show = self.noop + ComboFrame:Hide() + end + + if( ShadowUF.db.profile.hidden.focus ) then + FocusFrame:UnregisterAllEvents() + FocusFrame.Show = self.noop + FocusFrame:Hide() + + FocusFrameHealthBar:UnregisterAllEvents() + FocusFrameManaBar:UnregisterAllEvents() + FocusFrameSpellBar:UnregisterAllEvents() + end + + if( ShadowUF.db.profile.hidden.boss ) then + for i=1, MAX_BOSS_FRAMES do + local name = "Boss" .. i .. "TargetFrame" + local frame = _G[name] + + frame:UnregisterAllEvents() + frame.Show = self.noop + frame:Hide() + + _G[name .. "HealthBar"]:UnregisterAllEvents() + _G[name .. "ManaBar"]:UnregisterAllEvents() + end + end + + if( ShadowUF.db.profile.hidden.arena ) then + Arena_LoadUI = self.noop + end + + -- Don't modify the raid menu because that will taint the MA/MT stuff and it'll break and that's bad + for key, list in pairs(UnitPopupMenus) do + if( key ~= "RAID" ) then + for i=#(list), 1, -1 do + if( list[i] == "SET_FOCUS" or list[i] == "CLEAR_FOCUS" or list[i] == "LOCK_FOCUS_FRAME" or list[i] == "UNLOCK_FOCUS_FRAME" ) then + table.remove(list, i) + end + end + end + end +end + +function ShadowUF:Print(msg) + DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99Shadow UF|r: " .. msg) +end + +CONFIGMODE_CALLBACKS = CONFIGMODE_CALLBACKS or {} +CONFIGMODE_CALLBACKS["Shadowed Unit Frames"] = function(mode) + if( mode == "ON" ) then + ShadowUF.db.profile.locked = false + ShadowUF.modules.movers.isConfigModeSpec = true + elseif( mode == "OFF" ) then + ShadowUF.db.profile.locked = true + end + + ShadowUF.modules.movers:Update() +end + +SLASH_SHADOWEDUF1 = "/suf" +SLASH_SHADOWEDUF2 = "/shadowuf" +SLASH_SHADOWEDUF3 = "/shadoweduf" +SLASH_SHADOWEDUF4 = "/shadowedunitframes" +SlashCmdList["SHADOWEDUF"] = function(msg) + msg = msg and string.lower(msg) + if( msg and string.match(msg, "^profile (.+)") ) then + local profile = string.match(msg, "^profile (.+)") + + for id, name in pairs(ShadowUF.db:GetProfiles()) do + if( string.lower(name) == profile ) then + ShadowUF.db:SetProfile(name) + ShadowUF:Print(string.format(L["Changed profile to %s."], name)) + return + end + end + + ShadowUF:Print(string.format(L["Cannot find any profiles named \"%s\"."], profile)) + return + end + + local loaded, reason = LoadAddOn("ShadowedUF_Options") + if( not ShadowUF.Config ) then + DEFAULT_CHAT_FRAME:AddMessage(string.format(L["Failed to load ShadowedUF_Options, cannot open configuration. Error returned: %s"], reason and _G["ADDON_" .. reason] or "")) + return + end + + ShadowUF.Config:Open() +end + +local frame = CreateFrame("Frame") +frame:RegisterEvent("PLAYER_LOGIN") +frame:SetScript("OnEvent", function(self, event, addon) + if( event == "PLAYER_LOGIN" ) then + ShadowUF:OnInitialize() + self:UnregisterEvent("PLAYER_LOGIN") + end +end) \ No newline at end of file diff --git a/ShadowedUnitFrames/ShadowedUnitFrames.toc b/ShadowedUnitFrames/ShadowedUnitFrames.toc new file mode 100644 index 0000000..fd62da5 --- /dev/null +++ b/ShadowedUnitFrames/ShadowedUnitFrames.toc @@ -0,0 +1,53 @@ +## Interface: 30300 +## Title: Shadowed Unit Frames +## Notes: An apple a day keeps the raptor away, or so they say +## Author: Shadowed +## Version: v3.2.12 +## SavedVariables: ShadowedUFDB +## OptionalDeps: Ace3, LibSharedMedia-3.0, LibHealComm-4.0, AceGUI-3.0-SharedMediaWidgets +## X-Curse-Packaged-Version: v3.2.12 +## X-Curse-Project-Name: Shadowed Unit Frames +## X-Curse-Project-ID: shadowed-unit-frames +## X-Curse-Repository-ID: wow/shadowed-unit-frames/mainline + +#@no-lib-strip@ +libs\LibStub\LibStub.lua +libs\CallbackHandler-1.0\CallbackHandler-1.0\CallbackHandler-1.0.xml +libs\LibSharedMedia-3.0\lib.xml +libs\LibHealComm-4.0\LibHealComm-4.0.xml +libs\AceDB-3.0\AceDB-3.0.xml +#@end-no-lib-strip@ + +localization\enUS.lua +localization\deDE.lua +localization\esES.lua +localization\esMX.lua +localization\frFR.lua +localization\koKR.lua +localization\ruRU.lua +localization\zhCN.lua +localization\zhTW.lua + +ShadowedUnitFrames.lua +modules\units.lua +modules\layout.lua +modules\movers.lua +modules\defaultlayout.lua +modules\highlight.lua +modules\tags.lua +modules\health.lua +modules\power.lua +modules\portrait.lua +modules\indicators.lua +modules\xp.lua +modules\cast.lua +modules\auras.lua +modules\combattext.lua +modules\totems.lua +modules\runes.lua +modules\fader.lua +modules\combopoints.lua +modules\incheal.lua +modules\range.lua +modules\empty.lua +modules\druid.lua diff --git a/ShadowedUnitFrames/modules/defaultlayout.lua b/ShadowedUnitFrames/modules/defaultlayout.lua new file mode 100644 index 0000000..64a8985 --- /dev/null +++ b/ShadowedUnitFrames/modules/defaultlayout.lua @@ -0,0 +1,648 @@ +local L = ShadowUF.L +local playerClass = select(2, UnitClass("player")) + +local function finalizeData(config, useMerge) + local self = ShadowUF + -- Merges all of the parentUnit options into the child if they weren't set. + -- if it's a table, it recurses inside the table and copies any nil values in too + local function mergeToChild(parent, child, forceMerge) + for key, value in pairs(parent) do + if( type(child[key]) == "table" ) then + mergeToChild(value, child[key], forceMerge) + elseif( type(value) == "table" ) then + child[key] = CopyTable(value) + elseif( forceMerge or ( value ~= nil and child[key] == nil ) ) then + child[key] = value + end + end + end + + -- This makes sure that the unit has no values it shouldn't, for example if the defaults do not set incHeal for targettarget + -- and I try to set incHeal table here, then it'll remove it since it can't do that. + local function verifyTable(tbl, checkTable) + for key, value in pairs(tbl) do + if( type(value) == "table" ) then + if( not checkTable[key] ) then + tbl[key] = nil + else + for subKey, subValue in pairs(value) do + if( type(subValue) == "table" ) then + verifyTable(value, checkTable[key]) + end + end + end + end + end + end + + -- Set everything + for unit, child in pairs(config.units) do + if( self.defaults.profile.units[unit] ) then + if( not useMerge or ( useMerge and not self.db.profile.units[unit].enabled and self.db.profile.units[unit].height == 0 and self.db.profile.units[unit].width == 0 and self.db.profile.positions[unit].anchorPoint == "" and self.db.profile.positions[unit].point == "" ) ) then + -- Merge the primary parent table + mergeToChild(config.parentUnit, child) + -- Strip any invalid tables + verifyTable(child, self.defaults.profile.units[unit]) + -- Merge it in + mergeToChild(child, self.db.profile.units[unit], true) + + -- Merge position in too + if( useMerge and self.db.profile.positions[unit].point == "" and self.db.profile.positions[unit].relativePoint == "" and self.db.profile.positions[unit].anchorPoint == "" and self.db.profile.positions[unit].x == 0 and self.db.profile.positions[unit].y == 0 ) then + self.db.profile.positions[unit] = config.positions[unit] + end + end + end + end + + self.db.profile.loadedLayout = true + + if( not useMerge ) then + config.parentUnit = nil + config.units = nil + + for key, data in pairs(config) do + self.db.profile[key] = data + end + end +end + +function ShadowUF:LoadDefaultLayout(useMerge) + local config = {} + config.bars = { + texture = "Minimalist", + spacing = -1.25, + alpha = 1.0, + backgroundAlpha = 0.20, + } + config.auras = { + borderType = "light", + } + config.backdrop = { + tileSize = 1, + edgeSize = 5, + clip = 1, + inset = 3, + backgroundTexture = "Chat Frame", + backgroundColor = {r = 0, g = 0, b = 0, a = 0.80}, + borderTexture = "None", + borderColor = {r = 0.30, g = 0.30, b = 0.50, a = 1}, + } + config.hidden = { + cast = false, runes = true, buffs = false, party = true, player = true, pet = true, target = true, focus = true, boss = true, arena = true + } + config.font = { + name = "Myriad Condensed Web", + size = 11, + extra = "", + shadowColor = {r = 0, g = 0, b = 0, a = 1}, + color = {r = 1, g = 1, b = 1, a = 1}, + shadowX = 0.80, + shadowY = -0.80, + } + + -- Some localizations do not work with Myriad Condensed Web, need to automatically swap it to a localization that will work for it + local SML = LibStub:GetLibrary("LibSharedMedia-3.0") + if( GetLocale() == "koKR" or GetLocale() == "zhCN" or GetLocale() == "zhTW" or GetLocale() == "ruRU" ) then + config.font.name = SML.DefaultMedia.font + end + + config.classColors = { + HUNTER = {r = 0.67, g = 0.83, b = 0.45}, + WARLOCK = {r = 0.58, g = 0.51, b = 0.79}, + PRIEST = {r = 1.0, g = 1.0, b = 1.0}, + PALADIN = {r = 0.96, g = 0.55, b = 0.73}, + MAGE = {r = 0.41, g = 0.8, b = 0.94}, + ROGUE = {r = 1.0, g = 0.96, b = 0.41}, + DRUID = {r = 1.0, g = 0.49, b = 0.04}, + SHAMAN = {r = 0.14, g = 0.35, b = 1.0}, + WARRIOR = {r = 0.78, g = 0.61, b = 0.43}, + DEATHKNIGHT = {r = 0.77, g = 0.12 , b = 0.23}, + PET = {r = 0.20, g = 0.90, b = 0.20}, + VEHICLE = {r = 0.23, g = 0.41, b = 0.23}, + } + config.powerColors = { + MANA = {r = 0.30, g = 0.50, b = 0.85}, + RAGE = {r = 0.90, g = 0.20, b = 0.30}, + FOCUS = {r = 1.0, g = 0.85, b = 0}, + ENERGY = {r = 1.0, g = 0.85, b = 0.10}, + HAPPINESS = {r = 0.50, g = 0.90, b = 0.70}, + RUNES = {r = 0.50, g = 0.50, b = 0.50}, + RUNIC_POWER = {b = 0.60, g = 0.45, r = 0.35}, + AMMOSLOT = {r = 0.85, g = 0.60, b = 0.55}, + FUEL = {r = 0.85, g = 0.47, b = 0.36}, + } + config.healthColors = { + tapped = {r = 0.5, g = 0.5, b = 0.5}, + red = {r = 0.90, g = 0.0, b = 0.0}, + green = {r = 0.20, g = 0.90, b = 0.20}, + static = {r = 0.70, g = 0.20, b = 0.90}, + yellow = {r = 0.93, g = 0.93, b = 0.0}, + inc = {r = 0, g = 0.35, b = 0.23}, + enemyUnattack = {r = 0.60, g = 0.20, b = 0.20}, + hostile = {r = 0.90, g = 0.0, b = 0.0}, + friendly = {r = 0.20, g = 0.90, b = 0.20}, + neutral = {r = 0.93, g = 0.93, b = 0.0}, + offline = {r = 0.50, g = 0.50, b = 0.50} + } + config.castColors = { + channel = {r = 0.25, g = 0.25, b = 1.0}, + cast = {r = 1.0, g = 0.70, b = 0.30}, + interrupted = {r = 1, g = 0, b = 0}, + uninterruptible = {r = 0.71, g = 0, b = 1}, + finished = {r = 0.10, g = 1.0, b = 0.10}, + } + config.xpColors = { + normal = {r = 0.58, g = 0.0, b = 0.55}, + rested = {r = 0.0, g = 0.39, b = 0.88}, + } + + config.positions = { + targettargettarget = {anchorPoint = "RC", anchorTo = "#SUFUnittargettarget", x = 0, y = 0}, + targettarget = {anchorPoint = "TL", anchorTo = "#SUFUnittarget", x = 0, y = 0}, + focustarget = {anchorPoint = "TL", anchorTo = "#SUFUnitfocus", x = 0, y = 0}, + party = {point = "TOPLEFT", anchorTo = "#SUFUnitplayer", relativePoint = "TOPLEFT", movedAnchor = "TL", x = 0, y = -60}, + focus = {anchorPoint = "RB", anchorTo = "#SUFUnittarget", x = 35, y = -4}, + target = {anchorPoint = "RC", anchorTo = "#SUFUnitplayer", x = 50, y = 0}, + player = {point = "TOPLEFT", anchorTo = "UIParent", relativePoint = "TOPLEFT", y = -25, x = 20}, + pet = {anchorPoint = "TL", anchorTo = "#SUFUnitplayer", x = 0, y = 0}, + pettarget = {anchorPoint = "C", anchorTo = "UIParent", x = 0, y = 0}, + partypet = {anchorPoint = "RB", anchorTo = "$parent", x = 0, y = 0}, + partytarget = {anchorPoint = "RT", anchorTo = "$parent", x = 0, y = 0}, + raid = {anchorPoint = "C", anchorTo = "UIParent", x = 0, y = 0}, + raidpet = {anchorPoint = "C", anchorTo = "UIParent", x = 0, y = 0}, + maintank = {anchorPoint = "C", anchorTo = "UIParent", x = 0, y = 0}, + maintanktarget = {anchorPoint = "RT", anchorTo = "$parent", x = 0, y = 0}, + mainassist = {anchorPoint = "C", anchorTo = "UIParent", x = 0, y = 0}, + mainassisttarget = {anchorPoint = "RT", anchorTo = "$parent", x = 0, y = 0}, + arena = {anchorPoint = "C", anchorTo = "UIParent", point = "", relativePoint = "", x = 0, y = 0}, + arenapet = {anchorPoint = "RB", anchorTo = "$parent", x = 0, y = 0}, + arenatarget = {anchorPoint = "RT", anchorTo = "$parent", x = 0, y = 0}, + boss = {anchorPoint = "C", anchorTo = "UIParent", point = "", relativePoint = "", x = 0, y = 0}, + bosstarget = {anchorPoint = "RB", anchorTo = "$parent", x = 0, y = 0}, + } + + -- Parent unit options that all the children will inherit unless they override it + config.parentUnit = { + portrait = {enabled = false, type = "3D", alignment = "LEFT", width = 0.22, height = 0.50, order = 15, fullBefore = 0, fullAfter = 100}, + auras = { + buffs = {enabled = false, anchorPoint = "BL", size = 16, perRow = 10, x = 0, y = 0}, + debuffs = {enabled = false, anchorPoint = "BL", size = 16, perRow = 10, x = 0, y = 0}, + }, + text = { + {width = 0.50, name = L["Left text"], anchorTo = "$healthBar", anchorPoint = "CLI", x = 3, y = 0, size = 0}, + {width = 0.60, name = L["Right text"], anchorTo = "$healthBar", anchorPoint = "CRI", x = -3, y = 0, size = 0}, + + {width = 0.50, name = L["Left text"], anchorTo = "$powerBar", anchorPoint = "CLI", x = 3, y = 0, size = 0}, + {width = 0.60, name = L["Right text"], anchorTo = "$powerBar", anchorPoint = "CRI", x = -3, y = 0, size = 0}, + + {width = 1, name = L["Text"], anchorTo = "$emptyBar", anchorPoint = "CLI", x = 3, y = 0, size = 0}, + }, + indicators = { + raidTarget = {anchorTo = "$parent", anchorPoint = "C", size = 20, x = 0, y = 0}, + class = {anchorTo = "$parent", anchorPoint = "BL", size = 16, x = 0, y = 0}, + masterLoot = {anchorTo = "$parent", anchorPoint = "TL", size = 12, x = 16, y = -10}, + leader = {anchorTo = "$parent", anchorPoint = "TL", size = 14, x = 2, y = -12}, + pvp = {anchorTo = "$parent", anchorPoint = "TR", size = 22, x = 11, y = -21}, + ready = {anchorTo = "$parent", anchorPoint = "LC", size = 24, x = 35, y = 0}, + role = {anchorTo = "$parent", anchorPoint = "TL", size = 14, x = 30, y = -11}, + status = {anchorTo = "$parent", anchorPoint = "LB", size = 16, x = 12, y = -2}, + lfdRole = {enabled = true, anchorPoint = "BR", size = 14, x = 3, y = 14, anchorTo = "$parent"} + }, + highlight = {size = 10}, + combatText = {anchorTo = "$parent", anchorPoint = "C", x = 0, y = 0}, + emptyBar = {background = true, height = 1, reactionType = "none", order = 0}, + healthBar = {background = true, colorType = "class", reactionType = "npc", height = 1.20, order = 10}, + powerBar = {background = true, height = 1.0, order = 20}, + druidBar = {background = true, height = 0.40, order = 25}, + xpBar = {background = true, height = 0.25, order = 55}, + castBar = {background = true, height = 0.60, order = 40, icon = "HIDE", name = {enabled = true, size = 0, anchorTo = "$parent", rank = true, anchorPoint = "CLI", x = 1, y = 0}, time = {enabled = true, size = 0, anchorTo = "$parent", anchorPoint = "CRI", x = -1, y = 0}}, + runeBar = {background = false, height = 0.40, order = 70}, + totemBar = {background = false, height = 0.40, order = 70}, + } + + -- Units configuration + config.units = { + raid = { + width = 100, + height = 30, + scale = 0.85, + unitsPerColumn = 8, + maxColumns = 8, + columnSpacing = 5, + groupsPerRow = 8, + groupSpacing = 0, + attribPoint = "TOP", + attribAnchorPoint = "LEFT", + healthBar = {reactionType = "none"}, + powerBar = {height = 0.30}, + incHeal = {cap = 1}, + indicators = { + pvp = {anchorTo = "$parent", anchorPoint = "BL", size = 22, x = 0, y = 11}, + masterLoot = {anchorTo = "$parent", anchorPoint = "TR", size = 12, x = -2, y = -10}, + role = {enabled = false, anchorTo = "$parent", anchorPoint = "BR", size = 14, x = 0, y = 14}, + ready = {anchorTo = "$parent", anchorPoint = "LC", size = 24, x = 25, y = 0}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[missinghp]"}, + {text = ""}, + {text = ""}, + {text = "[(()afk() )][name]"}, + }, + }, + raidpet = { + width = 90, + height = 30, + scale = 0.85, + unitsPerColumn = 8, + maxColumns = 8, + columnSpacing = 5, + groupsPerRow = 8, + groupSpacing = 0, + attribPoint = "TOP", + attribAnchorPoint = "LEFT", + healthBar = {reactionType = "none"}, + powerBar = {height = 0.30}, + incHeal = {cap = 1}, + indicators = { + pvp = {anchorTo = "$parent", anchorPoint = "BL", size = 22, x = 0, y = 11}, + masterLoot = {anchorTo = "$parent", anchorPoint = "TR", size = 12, x = -2, y = -10}, + role = {enabled = false, anchorTo = "$parent", anchorPoint = "BR", size = 14, x = 0, y = 14}, + ready = {anchorTo = "$parent", anchorPoint = "LC", size = 24, x = 25, y = 0}, + }, + text = { + {text = "[name]"}, + {text = "[missinghp]"}, + {text = ""}, + {text = ""}, + {text = "[name]"}, + }, + }, + + player = { + width = 190, + height = 45, + scale = 1.0, + portrait = {enabled = true, fullAfter = 50}, + castBar = {order = 60}, + xpBar = {order = 55}, + runeBar = {enabled = true, order = 70}, + totemBar = {enabled = true, order = 70}, + auras = { + buffs = {enabled = false, maxRows = 1, temporary = playerClass == "ROGUE" or playerClass == "SHAMAN"}, + debuffs = {enabled = false, maxRows = 1}, + }, + text = { + {text = "[(()afk() )][name][( ()group())]"}, + {text = "[curmaxhp]"}, + {text = "[perpp]"}, + {text = "[curmaxpp]"}, + {text = "[(()afk() )][name][( ()group())]"}, + }, + }, + party = { + width = 190, + height = 45, + scale = 1.0, + attribPoint = "TOP", + attribAnchorPoint = "LEFT", + unitsPerColumn = 5, + columnSpacing = 30, + portrait = {enabled = true, fullAfter = 50}, + castBar = {order = 60}, + offset = 23, + auras = { + buffs = {enabled = true, maxRows = 1}, + debuffs = {enabled = true, maxRows = 1}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[curmaxhp]"}, + {text = "[level( )][perpp]"}, + {text = "[curmaxpp]"}, + {text = "[(()afk() )][name]"}, + }, + }, + boss = { + enabled = true, + width = 160, + height = 40, + scale = 1.0, + attribPoint = "TOP", + attribAnchorPoint = "LEFT", + offset = 20, + auras = { + buffs = {enabled = true, maxRows = 1, perRow = 8}, + debuffs = {enabled = true, maxRows = 1, perRow = 8}, + }, + text = { + {text = "[name]"}, + {text = "[curmaxhp]"}, + {text = "[perpp]"}, + {text = "[curmaxpp]"}, + {text = "[name]"}, + }, + portrait = {enabled = false}, + }, + bosstarget = { + width = 90, + height = 25, + scale = 1.0, + powerBar = {height = 0.60}, + text = { + {text = "[name]"}, + {text = "[curhp]"}, + {text = ""}, + {text = ""}, + {text = "[name]"}, + }, + }, + arena = { + width = 170, + height = 45, + scale = 1.0, + attribPoint = "TOP", + attribAnchorPoint = "LEFT", + portrait = {enabled = false, fullAfter = 50}, + castBar = {order = 60}, + offset = 25, + auras = { + buffs = {enabled = true, maxRows = 1, perRow = 9}, + debuffs = {enabled = true, maxRows = 1, perRow = 9}, + }, + text = { + {text = "[name]"}, + {text = "[curmaxhp]"}, + {text = "[perpp]"}, + {text = "[curmaxpp]"}, + {text = "[name]"}, + }, + }, + arenapet = { + width = 90, + height = 25, + scale = 1.0, + powerBar = {height = 0.60}, + text = { + {text = "[name]"}, + {text = "[curhp]"}, + {text = ""}, + {text = ""}, + {text = "[name]"}, + }, + }, + arenatarget = { + width = 90, + height = 25, + scale = 1.0, + powerBar = {height = 0.60}, + indicators = { + pvp = {anchorTo = "$parent", anchorPoint = "BL", size = 22, x = 0, y = 11}, + }, + text = { + {text = "[name]"}, + {text = "[curhp]"}, + {text = ""}, + {text = ""}, + {text = "[name]"}, + }, + }, + maintank = { + width = 150, + height = 40, + scale = 1.0, + attribPoint = "TOP", + attribAnchorPoint = "LEFT", + offset = 5, + unitsPerColumn = 5, + maxColumns = 1, + columnSpacing = 5, + incHeal = {cap = 1}, + portrait = {enabled = false, fullAfter = 50}, + castBar = {order = 60}, + auras = { + buffs = {enabled = false}, + debuffs = {enabled = false}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[curmaxhp]"}, + {text = "[perpp]"}, + {text = "[curmaxpp]"}, + {text = "[(()afk() )][name]"}, + }, + }, + maintanktarget = { + width = 150, + height = 40, + scale = 1.0, + auras = { + buffs = {enabled = false}, + debuffs = {enabled = false}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[curmaxhp]"}, + {text = "[classification( )][perpp]", width = 0.50}, + {text = "[curmaxpp]", anchorTo = "$powerBar", width = 0.60}, + {text = "[(()afk() )][name]"}, + }, + }, + mainassist = { + width = 150, + height = 40, + scale = 1.0, + attribPoint = "TOP", + attribAnchorPoint = "LEFT", + offset = 5, + unitsPerColumn = 5, + maxColumns = 1, + columnSpacing = 5, + incHeal = {cap = 1}, + portrait = {enabled = false, fullAfter = 50}, + castBar = {order = 60}, + auras = { + buffs = {enabled = false}, + debuffs = {enabled = false}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[curmaxhp]"}, + {text = "[level( )][perpp]"}, + {text = "[curmaxpp]"}, + {text = "[(()afk() )][name]"}, + }, + }, + mainassisttarget = { + width = 150, + height = 40, + scale = 1.0, + auras = { + buffs = {enabled = false}, + debuffs = {enabled = false}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[curmaxhp]"}, + {text = "[level( )][classification( )][perpp]", width = 0.50}, + {text = "[curmaxpp]", anchorTo = "$powerBar", width = 0.60}, + {text = "[(()afk() )][name]"}, + }, + }, + partypet = { + width = 90, + height = 25, + scale = 1.0, + powerBar = {height = 0.60}, + text = { + {text = "[name]"}, + {text = "[curhp]"}, + {text = ""}, + {text = ""}, + {text = "[name]"}, + }, + }, + partytarget = { + width = 90, + height = 25, + scale = 1.0, + powerBar = {height = 0.60}, + indicators = { + pvp = {anchorTo = "$parent", anchorPoint = "BL", size = 22, x = 0, y = 11}, + }, + text = { + {text = "[name]"}, + {text = "[curhp]"}, + {text = ""}, + {text = ""}, + {text = "[name]"}, + }, + }, + target = { + width = 190, + height = 45, + scale = 1.0, + portrait = {enabled = true, alignment = "RIGHT", fullAfter = 50}, + castBar = {order = 60}, + comboPoints = {anchorTo = "$parent", order = 60, anchorPoint = "BR", x = -3, y = 8, size = 14, spacing = -4, growth = "LEFT", isBar = true}, + indicators = { + lfdRole = {enabled = false} + }, + auras = { + buffs = {enabled = true}, + debuffs = {enabled = true}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[curmaxhp]"}, + {text = "[level( )][classification( )][perpp]", width = 0.50}, + {text = "[curmaxpp]", anchorTo = "$powerBar", width = 0.60}, + {text = "[(()afk() )][name]"}, + }, + }, + pet = { + width = 190, + height = 30, + scale = 1.0, + powerBar = {height = 0.70}, + healthBar = {reactionType = "none"}, + portrait = {enabled = false, fullAfter = 50}, + castBar = {order = 60}, + indicators = { + happiness = {enabled = false, anchorTo = "$parent", anchorPoint = "BR", size = 14, x = 3, y = 13}, + }, + text = { + {text = "[name]"}, + {text = "[curmaxhp]"}, + {text = "[perpp]"}, + {text = "[curmaxpp]"}, + {text = "[name]"}, + }, + }, + pettarget = { + width = 190, + height = 30, + scale = 1.0, + powerBar = {height = 0.70}, + indicators = { + }, + text = { + {text = "[name]"}, + {text = "[curmaxhp]"}, + {text = "[perpp]"}, + {text = "[curmaxpp]"}, + {text = "[name]"}, + }, + }, + focus = { + width = 120, + height = 28, + scale = 1.0, + powerBar = {height = 0.60}, + portrait = {enabled = false, fullAfter = 50}, + castBar = {order = 60}, + indicators = { + lfdRole = {enabled = false}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[curhp]"}, + {text = "[perpp]"}, + {text = "[curpp]"}, + {text = "[(()afk() )][name]"}, + }, + }, + focustarget = { + width = 120, + height = 25, + scale = 1.0, + powerBar = {height = 0.60}, + portrait = {alignment = "RIGHT"}, + indicators = { + pvp = {anchorTo = "$parent", anchorPoint = "BL", size = 22, x = -3, y = 11}, + }, + text = { + {text = "[(()afk() )][name]"}, + {text = "[curhp]"}, + {text = ""}, + {text = ""}, + {text = "[(()afk() )][name]"}, + }, + }, + targettarget = { + width = 110, + height = 30, + scale = 1.0, + powerBar = {height = 0.6}, + portrait = {alignment = "RIGHT"}, + indicators = { + pvp = {anchorTo = "$parent", anchorPoint = "BL", size = 22, x = -3, y = 11}, + }, + text = { + {text = "[name]"}, + {text = "[curhp]"}, + {text = "[perpp]"}, + {text = "[curpp]"}, + }, + }, + targettargettarget = { + width = 80, + height = 30, + scale = 1.0, + powerBar = {height = 0.6}, + portrait = {alignment = "RIGHT"}, + indicators = { + pvp = {anchorTo = "$parent", anchorPoint = "BL", size = 22, x = -3, y = 11}, + }, + text = { + {text = "[name]", width = 1.0}, + {text = ""}, + {text = ""}, + {text = ""}, + }, + }, + } + + finalizeData(config, useMerge) +end + diff --git a/ShadowedUnitFrames/modules/druid.lua b/ShadowedUnitFrames/modules/druid.lua new file mode 100644 index 0000000..ee16c04 --- /dev/null +++ b/ShadowedUnitFrames/modules/druid.lua @@ -0,0 +1,46 @@ +local Druid = {} +ShadowUF:RegisterModule(Druid, "druidBar", ShadowUF.L["Druid mana bar"], true, "DRUID") + +function Druid:OnEnable(frame) + frame.druidBar = frame.druidBar or ShadowUF.Units:CreateBar(frame) + + frame:RegisterUnitEvent("UNIT_MAXMANA", self, "Update") + frame:RegisterUnitEvent("UNIT_MANA", self, "Update") + frame:RegisterUnitEvent("UNIT_DISPLAYPOWER", self, "PowerChanged") + + frame:RegisterUpdateFunc(self, "PowerChanged") + frame:RegisterUpdateFunc(self, "Update") +end + +function Druid:OnDisable(frame) + frame:UnregisterAll(self) +end + +function Druid:OnLayoutApplied(frame) + if( frame.visibility.druidBar ) then + local color = ShadowUF.db.profile.powerColors.MANA + + if( not ShadowUF.db.profile.units[frame.unitType].druidBar.invert ) then + frame.druidBar:SetStatusBarColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) + if( not frame.druidBar.background.overrideColor ) then + frame.druidBar.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + end + else + frame.druidBar.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) + + color = frame.druidBar.background.overrideColor or color + frame.druidBar:SetStatusBarColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + end + end +end + +-- While power type is mana, show the bar once it is mana hide it. +function Druid:PowerChanged(frame) + local powerType = UnitPowerType(frame.unit) + ShadowUF.Layout:SetBarVisibility(frame, "druidBar", powerType == 1 or powerType == 3) +end + +function Druid:Update(frame) + frame.druidBar:SetMinMaxValues(0, UnitPowerMax(frame.unit, 0)) + frame.druidBar:SetValue(UnitIsDeadOrGhost(frame.unit) and 0 or not UnitIsConnected(frame.unit) and 0 or UnitPower(frame.unit, 0)) +end diff --git a/ShadowedUnitFrames/modules/empty.lua b/ShadowedUnitFrames/modules/empty.lua new file mode 100644 index 0000000..b8d07bb --- /dev/null +++ b/ShadowedUnitFrames/modules/empty.lua @@ -0,0 +1,59 @@ +local Empty = {} +local fallbackColor +ShadowUF:RegisterModule(Empty, "emptyBar", ShadowUF.L["Empty bar"], true) + +function Empty:OnEnable(frame) + frame.emptyBar = frame.emptyBar or ShadowUF.Units:CreateBar(frame) + frame.emptyBar:SetMinMaxValues(0, 1) + frame.emptyBar:SetValue(0) + + fallbackColor = fallbackColor or {r = 0, g = 0, b = 0} +end + +function Empty:OnDisable(frame) + frame:UnregisterAll(self) +end + +function Empty:OnLayoutApplied(frame) + if( frame.visibility.emptyBar ) then + local color = frame.emptyBar.background.overrideColor or fallbackColor + frame.emptyBar.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) + + if( ShadowUF.db.profile.units[frame.unitType].emptyBar.reaction or ShadowUF.db.profile.units[frame.unitType].emptyBar.class ) then + frame:RegisterUnitEvent("UNIT_FACTION", self, "UpdateColor") + frame:RegisterUpdateFunc(self, "UpdateColor") + else + self:OnDisable(frame) + end + end +end + +function Empty:UpdateColor(frame) + local color + local reactionType = ShadowUF.db.profile.units[frame.unitType].emptyBar.reactionType + if( not UnitPlayerOrPetInRaid(frame.unit) and not UnitPlayerOrPetInParty(frame.unit) and ( ( ( reactionType == "player" or reactionType == "both" ) and UnitIsPlayer(frame.unit) and not UnitIsFriend(frame.unit, "player") ) or ( ( reactionType == "npc" or reactionType == "both" ) and not UnitIsPlayer(frame.unit) ) ) ) then + if( not UnitIsFriend(frame.unit, "player") and UnitPlayerControlled(frame.unit) ) then + if( UnitCanAttack("player", frame.unit) ) then + color = ShadowUF.db.profile.healthColors.hostile + else + color = ShadowUF.db.profile.healthColors.enemyUnattack + end + elseif( UnitReaction(frame.unit, "player") ) then + local reaction = UnitReaction(frame.unit, "player") + if( reaction > 4 ) then + color = ShadowUF.db.profile.healthColors.friendly + elseif( reaction == 4 ) then + color = ShadowUF.db.profile.healthColors.neutral + elseif( reaction < 4 ) then + color = ShadowUF.db.profile.healthColors.hostile + end + end + elseif( ShadowUF.db.profile.units[frame.unitType].emptyBar.class and ( UnitIsPlayer(frame.unit) or UnitCreatureFamily(frame.unit) ) ) then + local class = UnitCreatureFamily(frame.unit) or select(2, UnitClass(frame.unit)) + color = class and ShadowUF.db.profile.classColors[class] + end + + color = color or frame.emptyBar.background.overrideColor or fallbackColor + frame.emptyBar.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) +end + diff --git a/ShadowedUnitFrames/modules/fader.lua b/ShadowedUnitFrames/modules/fader.lua new file mode 100644 index 0000000..46a8faf --- /dev/null +++ b/ShadowedUnitFrames/modules/fader.lua @@ -0,0 +1,140 @@ +local Fader = {} +ShadowUF:RegisterModule(Fader, "fader", ShadowUF.L["Combat fader"]) + +local function faderUpdate(self, elapsed) + self.timeElapsed = self.timeElapsed + elapsed + if( self.timeElapsed >= self.fadeTime ) then + self.parent:SetAlpha(self.alphaEnd) + self:Hide() + + if( self.fadeType == "in" ) then + self.parent:DisableRangeAlpha(false) + end + return + end + + if( self.fadeType == "in" ) then + self.parent:SetAlpha((self.timeElapsed / self.fadeTime) * (self.alphaEnd - self.alphaStart) + self.alphaStart) + else + self.parent:SetAlpha(((self.fadeTime - self.timeElapsed) / self.fadeTime) * (self.alphaStart - self.alphaEnd) + self.alphaEnd) + end +end + +local function startFading(self, type, alpha, speedyFade) + if( self.fader.fadeType == type ) then return end + if( type == "out" ) then + self:DisableRangeAlpha(true) + end + + self.fader.fadeTime = speedyFade and 0.15 or type == "in" and 0.25 or type == "out" and 0.75 + self.fader.fadeType = type + self.fader.timeElapsed = 0 + self.fader.alphaEnd = alpha + self.fader.alphaStart = self:GetAlpha() + self.fader:Show() +end + +function Fader:OnEnable(frame) + if( not frame.fader ) then + frame.fader = CreateFrame("Frame", nil, frame) + frame.fader.timeElapsed = 0 + frame.fader.parent = frame + frame.fader:SetScript("OnUpdate", faderUpdate) + frame.fader:Hide() + end + + frame:RegisterNormalEvent("PLAYER_REGEN_ENABLED", self, "Update") + frame:RegisterNormalEvent("PLAYER_REGEN_DISABLED", self, "Update") + frame:RegisterUpdateFunc(self, "Update") + + if( InCombatLockdown() ) then + Fader:PLAYER_REGEN_DISABLED(frame, "PLAYER_REGEN_DISABLED") + else + Fader:PLAYER_REGEN_ENABLED(frame, "PLAYER_REGEN_ENABLED") + end +end + +function Fader:OnLayoutApplied(frame) + if( frame.visibility.fader ) then + frame.fader.fadeType = nil + frame:DisableRangeAlpha(false) + end +end + +function Fader:OnDisable(frame) + frame:UnregisterAll(self) + frame:SetAlpha(1.0) + + if( frame.fader ) then + frame.fader.fadeType = nil + frame.fader:Hide() + end +end + +-- While we're in combat, we don't care about the other events so we might as well unregister them +function Fader:PLAYER_REGEN_ENABLED(frame, event) + self:Update(frame, event) + frame:RegisterNormalEvent("PLAYER_TARGET_CHANGED", self, "Update") + frame:RegisterNormalEvent("UNIT_SPELLCAST_CHANNEL_START", self, "CastStart") + frame:RegisterNormalEvent("UNIT_SPELLCAST_CHANNEL_STOP", self, "CastStop") + frame:RegisterNormalEvent("UNIT_SPELLCAST_START", self, "CastStart") + frame:RegisterNormalEvent("UNIT_SPELLCAST_STOP", self, "CastStop") + frame:RegisterUnitEvent("UNIT_HEALTH", self, "Update") + frame:RegisterUnitEvent("UNIT_MANA", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXHEALTH", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXMANA", self, "Update") +end + +function Fader:PLAYER_REGEN_DISABLED(frame, event) + self:Update(frame, event) + frame:UnregisterEvent("PLAYER_TARGET_CHANGED") + frame:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_START") + frame:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_STOP") + frame:UnregisterEvent("UNIT_SPELLCAST_START") + frame:UnregisterEvent("UNIT_SPELLCAST_STOP") + frame:UnregisterEvent("UNIT_HEALTH") + frame:UnregisterEvent("UNIT_MANA") + frame:UnregisterEvent("UNIT_MAXHEALTH") + frame:UnregisterEvent("UNIT_MAXMANA") +end + + +local activeCastID +function Fader:CastStart(frame, event, unit, spellName, spellRank, id) + if( unit ~= "player" or activeCastID == id ) then return end + activeCastID = id + + frame.fader.playerCasting = true + self:Update(frame) +end + +function Fader:CastStop(frame, event, unit, spellName, spellRank, id) + if( unit ~= "player" or activeCastID ~= id ) then return end + activeCastID = nil + + frame.fader.playerCasting = nil + self:Update(frame) +end + + +function Fader:Update(frame, event) + -- In combat, fade back in + if( InCombatLockdown() or event == "PLAYER_REGEN_DISABLED" ) then + startFading(frame, "in", ShadowUF.db.profile.units[frame.unitType].fader.combatAlpha) + -- Player is casting, fade in + elseif( frame.fader.playerCasting ) then + startFading(frame, "in", ShadowUF.db.profile.units[frame.unitType].fader.combatAlpha, true) + -- Ether mana or energy is not at 100%, fade in + elseif( ( UnitPowerType(frame.unit) == 0 or UnitPowerType(frame.unit) == 3 ) and UnitPower(frame.unit) ~= UnitPowerMax(frame.unit) ) then + startFading(frame, "in", ShadowUF.db.profile.units[frame.unitType].fader.combatAlpha) + -- Health is not at max, fade in + elseif( UnitHealth(frame.unit) ~= UnitHealthMax(frame.unit) ) then + startFading(frame, "in", ShadowUF.db.profile.units[frame.unitType].fader.combatAlpha) + -- Targetting somebody, fade in + elseif( frame.unitType == "player" and UnitExists("target") ) then + startFading(frame, "in", ShadowUF.db.profile.units[frame.unitType].fader.combatAlpha) + -- Nothing else? Fade out! + else + startFading(frame, "out", ShadowUF.db.profile.units[frame.unitType].fader.inactiveAlpha) + end +end diff --git a/ShadowedUnitFrames/modules/health.lua b/ShadowedUnitFrames/modules/health.lua new file mode 100644 index 0000000..d701f8f --- /dev/null +++ b/ShadowedUnitFrames/modules/health.lua @@ -0,0 +1,181 @@ +local Health = {} +ShadowUF:RegisterModule(Health, "healthBar", ShadowUF.L["Health bar"], true) + +local function getGradientColor(unit) + local percent = UnitHealth(unit) / UnitHealthMax(unit) + if( percent >= 1 ) then return ShadowUF.db.profile.healthColors.green.r, ShadowUF.db.profile.healthColors.green.g, ShadowUF.db.profile.healthColors.green.b end + if( percent == 0 ) then return ShadowUF.db.profile.healthColors.red.r, ShadowUF.db.profile.healthColors.red.g, ShadowUF.db.profile.healthColors.red.b end + + local sR, sG, sB, eR, eG, eB = 0, 0, 0, 0, 0, 0 + local modifier, inverseModifier = percent * 2, 0 + if( percent > 0.50 ) then + sR, sG, sB = ShadowUF.db.profile.healthColors.green.r, ShadowUF.db.profile.healthColors.green.g, ShadowUF.db.profile.healthColors.green.b + eR, eG, eB = ShadowUF.db.profile.healthColors.yellow.r, ShadowUF.db.profile.healthColors.yellow.g, ShadowUF.db.profile.healthColors.yellow.b + + modifier = modifier - 1 + else + sR, sG, sB = ShadowUF.db.profile.healthColors.yellow.r, ShadowUF.db.profile.healthColors.yellow.g, ShadowUF.db.profile.healthColors.yellow.b + eR, eG, eB = ShadowUF.db.profile.healthColors.red.r, ShadowUF.db.profile.healthColors.red.g, ShadowUF.db.profile.healthColors.red.b + end + + inverseModifier = 1 - modifier + return eR * inverseModifier + sR * modifier, eG * inverseModifier + sG * modifier, eB * inverseModifier + sB * modifier +end + +Health.getGradientColor = getGradientColor + +-- Not doing full health update, because other checks can lag behind without much issue +local function updateTimer(self) + local currentHealth = UnitHealth(self.parent.unit) + if( currentHealth == self.currentHealth ) then return end + self.currentHealth = currentHealth + self:SetValue(currentHealth) + + -- As much as I would rather not have to do this in an OnUpdate, I don't have much choice large health changes in a single update will make them very clearly be lagging behind + for _, fontString in pairs(self.parent.fontStrings) do + if( fontString.fastHealth ) then + fontString:UpdateTags() + end + end + + -- Update incoming heal number + if( self.parent.incHeal and self.parent.incHeal.healed ) then + self.parent.incHeal:SetValue(currentHealth + self.parent.incHeal.healed) + end + + -- The target is not offline, and we have a health percentage so update the gradient + if( not self.parent.healthBar.wasOffline and self.parent.healthBar.hasPercent ) then + Health:SetBarColor(self.parent.healthBar, ShadowUF.db.profile.units[self.parent.unitType].healthBar.invert, getGradientColor(self.parent.unit)) + end +end + +function Health:OnEnable(frame) + if( not frame.healthBar ) then + frame.healthBar = ShadowUF.Units:CreateBar(frame) + end + + frame:RegisterUnitEvent("UNIT_HEALTH", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXHEALTH", self, "Update") + frame:RegisterUnitEvent("UNIT_FACTION", self, "UpdateColor") + frame:RegisterUnitEvent("UNIT_THREAT_SITUATION_UPDATE", self, "UpdateColor") + + if( frame.unit == "pet" ) then + frame:RegisterUnitEvent("UNIT_HAPPINESS", self, "UpdateColor") + end + + frame:RegisterUpdateFunc(self, "UpdateColor") + frame:RegisterUpdateFunc(self, "Update") +end + +function Health:OnLayoutApplied(frame) + if( not frame.visibility.healthBar ) then return end + + if( ShadowUF.db.profile.units[frame.unitType].healthBar.predicted ) then + frame.healthBar:SetScript("OnUpdate", updateTimer) + frame.healthBar.parent = frame + else + frame.healthBar:SetScript("OnUpdate", nil) + end +end + +function Health:OnDisable(frame) + frame:UnregisterAll(self) +end + +function Health:SetBarColor(bar, invert, r, g, b) + if( not invert ) then + bar:SetStatusBarColor(r, g, b, ShadowUF.db.profile.bars.alpha) + if( not bar.background.overrideColor ) then + bar.background:SetVertexColor(r, g, b, ShadowUF.db.profile.bars.backgroundAlpha) + end + else + bar.background:SetVertexColor(r, g, b, ShadowUF.db.profile.bars.alpha) + if( not bar.background.overrideColor ) then + bar:SetStatusBarColor(0, 0, 0, 1 - ShadowUF.db.profile.bars.backgroundAlpha) + else + bar:SetStatusBarColor(bar.background.overrideColor.r, bar.background.overrideColor.g, bar.background.overrideColor.b, 1 - ShadowUF.db.profile.bars.backgroundAlpha) + end + end +end + +function Health:UpdateColor(frame) + frame.healthBar.hasReaction = nil + frame.healthBar.hasPercent = nil + frame.healthBar.wasOffline = nil + + local color + local unit = frame.unit + local reactionType = ShadowUF.db.profile.units[frame.unitType].healthBar.reactionType + if( not UnitIsConnected(unit) ) then + frame.healthBar.wasOffline = true + self:SetBarColor(frame.healthBar, ShadowUF.db.profile.units[frame.unitType].healthBar.invert, ShadowUF.db.profile.healthColors.offline.r, ShadowUF.db.profile.healthColors.offline.g, ShadowUF.db.profile.healthColors.offline.b) + return + elseif( ShadowUF.db.profile.units[frame.unitType].healthBar.colorAggro and UnitThreatSituation(frame.unit) == 3 ) then + self:SetBarColor(frame.healthBar, ShadowUF.db.profile.units[frame.unitType].healthBar.invert, ShadowUF.db.profile.healthColors.hostile.r, ShadowUF.db.profile.healthColors.hostile.g, ShadowUF.db.profile.healthColors.hostile.b) + return + elseif( frame.inVehicle ) then + color = ShadowUF.db.profile.classColors.VEHICLE + elseif( not UnitIsTappedByPlayer(unit) and UnitIsTapped(unit) and UnitCanAttack("player", unit) ) then + color = ShadowUF.db.profile.healthColors.tapped + elseif( unit == "pet" and reactionType == "happiness" and GetPetHappiness() ) then + local happiness = GetPetHappiness() + if( happiness == 3 ) then + color = ShadowUF.db.profile.healthColors.friendly + elseif( happiness == 2 ) then + color = ShadowUF.db.profile.healthColors.neutral + elseif( happiness == 1 ) then + color = ShadowUF.db.profile.healthColors.hostile + end + elseif( not UnitPlayerOrPetInRaid(unit) and not UnitPlayerOrPetInParty(unit) and ( ( ( reactionType == "player" or reactionType == "both" ) and UnitIsPlayer(unit) and not UnitIsFriend(unit, "player") ) or ( ( reactionType == "npc" or reactionType == "both" ) and not UnitIsPlayer(unit) ) ) ) then + if( not UnitIsFriend(unit, "player") and UnitPlayerControlled(unit) ) then + if( UnitCanAttack("player", unit) ) then + color = ShadowUF.db.profile.healthColors.hostile + else + color = ShadowUF.db.profile.healthColors.enemyUnattack + end + elseif( UnitReaction(unit, "player") ) then + local reaction = UnitReaction(unit, "player") + if( reaction > 4 ) then + color = ShadowUF.db.profile.healthColors.friendly + elseif( reaction == 4 ) then + color = ShadowUF.db.profile.healthColors.neutral + elseif( reaction < 4 ) then + color = ShadowUF.db.profile.healthColors.hostile + end + end + elseif( ShadowUF.db.profile.units[frame.unitType].healthBar.colorType == "class" and ( UnitIsPlayer(unit) or UnitCreatureFamily(unit) ) ) then + local class = UnitCreatureFamily(frame.unit) or select(2, UnitClass(frame.unit)) + color = class and ShadowUF.db.profile.classColors[class] + elseif( ShadowUF.db.profile.units[frame.unitType].healthBar.colorType == "static" ) then + color = ShadowUF.db.profile.healthColors.static + end + + if( color ) then + self:SetBarColor(frame.healthBar, ShadowUF.db.profile.units[frame.unitType].healthBar.invert, color.r, color.g, color.b) + else + frame.healthBar.hasPercent = true + self:SetBarColor(frame.healthBar, ShadowUF.db.profile.units[frame.unitType].healthBar.invert, getGradientColor(unit)) + end +end + +function Health:Update(frame) + local isOffline = not UnitIsConnected(frame.unit) + frame.isDead = UnitIsDeadOrGhost(frame.unit) + frame.healthBar.currentHealth = UnitHealth(frame.unit) + frame.healthBar:SetMinMaxValues(0, UnitHealthMax(frame.unit)) + frame.healthBar:SetValue(isOffline and UnitHealthMax(frame.unit) or frame.isDead and 0 or frame.healthBar.currentHealth) + + -- Unit is offline, fill bar up + grey it + if( isOffline ) then + frame.healthBar.wasOffline = true + frame.unitIsOnline = nil + self:SetBarColor(frame.healthBar, ShadowUF.db.profile.units[frame.unitType].healthBar.invert, ShadowUF.db.profile.healthColors.offline.r, ShadowUF.db.profile.healthColors.offline.g, ShadowUF.db.profile.healthColors.offline.b) + -- The unit was offline, but they no longer are so we need to do a forced color update + elseif( frame.healthBar.wasOffline ) then + frame.healthBar.wasOffline = nil + self:UpdateColor(frame) + -- Color health by percentage + elseif( frame.healthBar.hasPercent ) then + self:SetBarColor(frame.healthBar, ShadowUF.db.profile.units[frame.unitType].healthBar.invert, getGradientColor(frame.unit)) + end +end \ No newline at end of file diff --git a/ShadowedUnitFrames/modules/highlight.lua b/ShadowedUnitFrames/modules/highlight.lua new file mode 100644 index 0000000..95bef4a --- /dev/null +++ b/ShadowedUnitFrames/modules/highlight.lua @@ -0,0 +1,155 @@ +local Highlight = {} +local goldColor, mouseColor = {r = 0.75, g = 0.75, b = 0.35}, {r = 0.75, g = 0.75, b = 0.50} +ShadowUF:RegisterModule(Highlight, "highlight", ShadowUF.L["Highlight"]) + +-- Might seem odd to hook my code in the core manually, but HookScript is ~40% slower due to it being a secure hook +local function OnEnter(frame) + if( ShadowUF.db.profile.units[frame.unitType].highlight.mouseover ) then + frame.highlight.hasMouseover = true + Highlight:Update(frame) + end + + frame.highlight.OnEnter(frame) +end + +local function OnLeave(frame) + if( ShadowUF.db.profile.units[frame.unitType].highlight.mouseover ) then + frame.highlight.hasMouseover = nil + Highlight:Update(frame) + end + + frame.highlight.OnLeave(frame) +end + +function Highlight:OnEnable(frame) + if( not frame.highlight ) then + frame.highlight = CreateFrame("Frame", nil, frame) + frame.highlight:SetFrameLevel(frame.topFrameLevel) + frame.highlight:SetAllPoints(frame) + frame.highlight:SetSize(1, 1) + + frame.highlight.top = frame.highlight:CreateTexture(nil, "OVERLAY") + frame.highlight.top:SetBlendMode("ADD") + frame.highlight.top:SetTexture("Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\highlight") + frame.highlight.top:SetPoint("TOPLEFT", frame, ShadowUF.db.profile.backdrop.inset, -ShadowUF.db.profile.backdrop.inset) + frame.highlight.top:SetPoint("TOPRIGHT", frame, -ShadowUF.db.profile.backdrop.inset, ShadowUF.db.profile.backdrop.inset) + frame.highlight.top:SetHeight(30) + frame.highlight.top:SetTexCoord(0.3125, 0.625, 0, 0.3125) + frame.highlight.top:SetHorizTile(false) + + frame.highlight.left = frame.highlight:CreateTexture(nil, "OVERLAY") + frame.highlight.left:SetBlendMode("ADD") + frame.highlight.left:SetTexture("Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\highlight") + frame.highlight.left:SetPoint("TOPLEFT", frame, ShadowUF.db.profile.backdrop.inset, -ShadowUF.db.profile.backdrop.inset) + frame.highlight.left:SetPoint("BOTTOMLEFT", frame, -ShadowUF.db.profile.backdrop.inset, ShadowUF.db.profile.backdrop.inset) + frame.highlight.left:SetWidth(30) + frame.highlight.left:SetTexCoord(0, 0.3125, 0.3125, 0.625) + frame.highlight.left:SetHorizTile(false) + + frame.highlight.right = frame.highlight:CreateTexture(nil, "OVERLAY") + frame.highlight.right:SetBlendMode("ADD") + frame.highlight.right:SetTexture("Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\highlight") + frame.highlight.right:SetPoint("TOPRIGHT", frame, -ShadowUF.db.profile.backdrop.inset, -ShadowUF.db.profile.backdrop.inset) + frame.highlight.right:SetPoint("BOTTOMRIGHT", frame, 0, ShadowUF.db.profile.backdrop.inset) + frame.highlight.right:SetWidth(30) + frame.highlight.right:SetTexCoord(0.625, 0.93, 0.3125, 0.625) + frame.highlight.right:SetHorizTile(false) + + frame.highlight.bottom = frame.highlight:CreateTexture(nil, "OVERLAY") + frame.highlight.bottom:SetBlendMode("ADD") + frame.highlight.bottom:SetTexture("Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\highlight") + frame.highlight.bottom:SetPoint("BOTTOMLEFT", frame, ShadowUF.db.profile.backdrop.inset, ShadowUF.db.profile.backdrop.inset) + frame.highlight.bottom:SetPoint("BOTTOMRIGHT", frame, -ShadowUF.db.profile.backdrop.inset, ShadowUF.db.profile.backdrop.inset) + frame.highlight.bottom:SetHeight(30) + frame.highlight.bottom:SetTexCoord(0.3125, 0.625, 0.625, 0.93) + frame.highlight.bottom:SetHorizTile(false) + frame.highlight:Hide() + end + + frame.highlight.top:SetHeight(ShadowUF.db.profile.units[frame.unitType].highlight.size) + frame.highlight.bottom:SetHeight(ShadowUF.db.profile.units[frame.unitType].highlight.size) + frame.highlight.left:SetWidth(ShadowUF.db.profile.units[frame.unitType].highlight.size) + frame.highlight.right:SetWidth(ShadowUF.db.profile.units[frame.unitType].highlight.size) + + + if( ShadowUF.db.profile.units[frame.unitType].highlight.aggro ) then + frame:RegisterUnitEvent("UNIT_THREAT_SITUATION_UPDATE", self, "UpdateThreat") + frame:RegisterUpdateFunc(self, "UpdateThreat") + end + + if( ShadowUF.db.profile.units[frame.unitType].highlight.attention and frame.unitType ~= "target" and frame.unitType ~= "focus" ) then + frame:RegisterNormalEvent("PLAYER_TARGET_CHANGED", self, "UpdateAttention") + frame:RegisterNormalEvent("PLAYER_FOCUS_CHANGED", self, "UpdateAttention") + frame:RegisterUpdateFunc(self, "UpdateAttention") + end + + if( ShadowUF.db.profile.units[frame.unitType].highlight.debuff ) then + frame:RegisterNormalEvent("UNIT_AURA", self, "UpdateAura") + frame:RegisterUpdateFunc(self, "UpdateAura") + end + + if( ShadowUF.db.profile.units[frame.unitType].highlight.mouseover and not frame.highlight.OnEnter ) then + frame.highlight.OnEnter = frame:GetScript("OnEnter") + frame.highlight.OnLeave = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", OnEnter) + frame:SetScript("OnLeave", OnLeave) + end +end + +function Highlight:OnLayoutApplied(frame) + if( frame.visibility.highlight ) then + self:OnDisable(frame) + self:OnEnable(frame) + end +end + +function Highlight:OnDisable(frame) + frame:UnregisterAll(self) + + frame.highlight.hasDebuff = nil + frame.highlight.hasThreat = nil + frame.highlight.hasAttention = nil + frame.highlight.hasMouseover = nil + + frame.highlight:Hide() +end + +function Highlight:Update(frame) + local color + if( frame.highlight.hasDebuff ) then + color = DebuffTypeColor[frame.highlight.hasDebuff] + elseif( frame.highlight.hasThreat ) then + color = ShadowUF.db.profile.healthColors.hostile + elseif( frame.highlight.hasAttention ) then + color = goldColor + elseif( frame.highlight.hasMouseover ) then + color = mouseColor + end + + if( color ) then + frame.highlight.top:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.units[frame.unitType].highlight.alpha) + frame.highlight.left:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.units[frame.unitType].highlight.alpha) + frame.highlight.bottom:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.units[frame.unitType].highlight.alpha) + frame.highlight.right:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.units[frame.unitType].highlight.alpha) + frame.highlight:Show() + else + frame.highlight:Hide() + end +end + +function Highlight:UpdateThreat(frame) + frame.highlight.hasThreat = UnitThreatSituation(frame.unit) == 3 or nil + self:Update(frame) +end + +function Highlight:UpdateAttention(frame) + frame.highlight.hasAttention = UnitIsUnit(frame.unit, "target") or UnitIsUnit(frame.unit, "focus") or nil + self:Update(frame) +end + +function Highlight:UpdateAura(frame) + -- In theory, we don't need aura scanning because the first debuff returned is always one we can cure... in theory + frame.highlight.hasDebuff = UnitIsFriend(frame.unit, "player") and select(5, UnitDebuff(frame.unit, 1, "RAID")) or nil + self:Update(frame) +end diff --git a/ShadowedUnitFrames/modules/incheal.lua b/ShadowedUnitFrames/modules/incheal.lua new file mode 100644 index 0000000..f0e9404 --- /dev/null +++ b/ShadowedUnitFrames/modules/incheal.lua @@ -0,0 +1,195 @@ +local HealComm = LibStub("LibHealComm-4.0", true) +if( not HealComm ) then return end + +local IncHeal = {} +local frames = {} +local playerEndTime, playerGUID +ShadowUF:RegisterModule(IncHeal, "incHeal", ShadowUF.L["Incoming heals"]) +ShadowUF.Tags.customEvents["HEALCOMM"] = IncHeal + +-- How far ahead to show heals at most +local INCOMING_SECONDS = 3 + +function IncHeal:OnEnable(frame) + frames[frame] = true + frame.incHeal = frame.incHeal or ShadowUF.Units:CreateBar(frame) + + frame:RegisterUnitEvent("UNIT_MAXHEALTH", self, "UpdateFrame") + frame:RegisterUnitEvent("UNIT_HEALTH", self, "UpdateFrame") + frame:RegisterUpdateFunc(self, "UpdateFrame") + + self:Setup() +end + +function IncHeal:OnDisable(frame) + frame:UnregisterAll(self) + frame.incHeal:Hide() + + if( not frame.hasHCTag ) then + frames[frame] = nil + self:Setup() + end +end + +function IncHeal:OnLayoutApplied(frame) + if( frame.visibility.incHeal and frame.visibility.healthBar ) then + frame.incHeal:SetHeight(frame.healthBar:GetHeight()) + frame.incHeal:SetStatusBarTexture(ShadowUF.Layout.mediaPath.statusbar) + frame.incHeal:SetStatusBarColor(ShadowUF.db.profile.healthColors.inc.r, ShadowUF.db.profile.healthColors.inc.g, ShadowUF.db.profile.healthColors.inc.b, ShadowUF.db.profile.bars.alpha) + frame.incHeal:GetStatusBarTexture():SetHorizTile(false) + frame.incHeal:Hide() + + -- When we can cheat and put the incoming bar right behind the health bar, we can efficiently show the incoming heal bar + -- if the main bar has a transparency set, then we need a more complicated method to stop the health bar from being darker with incoming heals up + if( ( ShadowUF.db.profile.units[frame.unitType].healthBar.invert and ShadowUF.db.profile.bars.backgroundAlpha == 0 ) or ( not ShadowUF.db.profile.units[frame.unitType].healthBar.invert and ShadowUF.db.profile.bars.alpha == 1 ) ) then + frame.incHeal.simple = true + frame.incHeal:SetWidth(frame.healthBar:GetWidth() * ShadowUF.db.profile.units[frame.unitType].incHeal.cap) + frame.incHeal:SetFrameLevel(frame.topFrameLevel - 3) + + frame.incHeal:ClearAllPoints() + frame.incHeal:SetPoint("TOPLEFT", frame.healthBar) + frame.incHeal:SetPoint("BOTTOMLEFT", frame.healthBar) + else + frame.incHeal.simple = nil + frame.incHeal:SetFrameLevel(frame.topFrameLevel) + frame.incHeal:SetWidth(1) + frame.incHeal:SetMinMaxValues(0, 1) + frame.incHeal:SetValue(1) + + local x, y = select(4, frame.healthBar:GetPoint()) + frame.incHeal:ClearAllPoints() + frame.incHeal.healthX = x + frame.incHeal.healthY = y + frame.incHeal.healthWidth = frame.healthBar:GetWidth() + frame.incHeal.maxWidth = frame.incHeal.healthWidth * ShadowUF.db.profile.units[frame.unitType].incHeal.cap + frame.incHeal.cappedWidth = frame.incHeal.healthWidth * (ShadowUF.db.profile.units[frame.unitType].incHeal.cap - 1) + end + end +end + +-- Since I don't want a more complicated system where both incheal.lua and tags.lua are watching the same events +-- I'll update the HC tags through here instead +function IncHeal:EnableTag(frame) + frames[frame] = true + frame.hasHCTag = true + + self:Setup() +end + +function IncHeal:DisableTag(frame) + frame.hasHCTag = nil + + if( not frame.visibility.incHeal ) then + frames[frame] = nil + self:Setup() + end +end + +-- Check if we need to register callbacks +function IncHeal:Setup() + playerGUID = UnitGUID("player") + + local enabled + for frame in pairs(frames) do + enabled = true + break + end + + if( not enabled ) then + if( HealComm ) then + HealComm:UnregisterAllCallbacks(IncHeal) + end + return + end + + HealComm.RegisterCallback(self, "HealComm_HealStarted", "HealComm_HealUpdated") + HealComm.RegisterCallback(self, "HealComm_HealStopped") + HealComm.RegisterCallback(self, "HealComm_HealDelayed", "HealComm_HealUpdated") + HealComm.RegisterCallback(self, "HealComm_HealUpdated") + HealComm.RegisterCallback(self, "HealComm_ModifierChanged") + HealComm.RegisterCallback(self, "HealComm_GUIDDisappeared") +end + +-- Update any tags using HC +function IncHeal:UpdateTags(frame, amount) + if( not frame.fontStrings or not frame.hasHCTag ) then return end + + for _, fontString in pairs(frame.fontStrings) do + if( fontString.HEALCOMM ) then + fontString.incoming = amount > 0 and amount or nil + fontString:UpdateTags() + end + end +end + +local function updateHealthBar(frame, interrupted) + -- This makes sure that when a heal like Tranquility is cast, it won't show the entire cast but cap it at 4 seconds into the future + local time = GetTime() + local timeBand = playerEndTime and math.min(playerEndTime - time, INCOMING_SECONDS) or INCOMING_SECONDS + local healed = (HealComm:GetHealAmount(frame.unitGUID, HealComm.ALL_HEALS, time + timeBand) or 0) * HealComm:GetHealModifier(frame.unitGUID) + + -- Update any tags that are using HC data + IncHeal:UpdateTags(frame, healed) + + -- Bar is also supposed to be enabled, lets update that too + if( frame.visibility.incHeal and frame.visibility.healthBar ) then + if( healed > 0 ) then + frame.incHeal.healed = healed + frame.incHeal:Show() + + -- When the primary bar has an alpha of 100%, we can cheat and do incoming heals easily. Otherwise we need to do it a more complex way to keep it looking good + if( frame.incHeal.simple ) then + frame.incHeal.total = UnitHealth(frame.unit) + healed + frame.incHeal:SetMinMaxValues(0, UnitHealthMax(frame.unit) * ShadowUF.db.profile.units[frame.unitType].incHeal.cap) + frame.incHeal:SetValue(frame.incHeal.total) + else + local health, maxHealth = UnitHealth(frame.unit), UnitHealthMax(frame.unit) + local healthWidth = frame.incHeal.healthWidth * (health / maxHealth) + local incWidth = frame.healthBar:GetWidth() * (healed / health) + if( (healthWidth + incWidth) > frame.incHeal.maxWidth ) then + incWidth = frame.incHeal.cappedWidth + end + + frame.incHeal:SetWidth(incWidth) + frame.incHeal:SetPoint("TOPLEFT", SUFUnitplayer, "TOPLEFT", frame.incHeal.healthX + healthWidth, frame.incHeal.healthY) + end + else + frame.incHeal.total = nil + frame.incHeal.healed = nil + frame.incHeal:Hide() + end + end +end + +function IncHeal:UpdateFrame(frame) + updateHealthBar(frame, true) +end + +function IncHeal:UpdateIncoming(interrupted, ...) + for frame in pairs(frames) do + for i=1, select("#", ...) do + if( select(i, ...) == frame.unitGUID ) then + updateHealthBar(frame, interrupted) + end + end + end +end + +-- Handle callbacks from HealComm +function IncHeal:HealComm_HealUpdated(event, casterGUID, spellID, healType, endTime, ...) + if( casterGUID == playerGUID and bit.band(healType, HealComm.CASTED_HEALS) > 0 ) then playerEndTime = endTime end + self:UpdateIncoming(nil, ...) +end + +function IncHeal:HealComm_HealStopped(event, casterGUID, spellID, healType, interrupted, ...) + if( casterGUID == playerGUID and bit.band(healType, HealComm.CASTED_HEALS) > 0 ) then playerEndTime = nil end + self:UpdateIncoming(interrupted, ...) +end + +function IncHeal:HealComm_ModifierChanged(event, guid) + self:UpdateIncoming(nil, guid) +end + +function IncHeal:HealComm_GUIDDisappeared(event, guid) + self:UpdateIncoming(true, guid) +end diff --git a/ShadowedUnitFrames/modules/indicators.lua b/ShadowedUnitFrames/modules/indicators.lua new file mode 100644 index 0000000..dbc054d --- /dev/null +++ b/ShadowedUnitFrames/modules/indicators.lua @@ -0,0 +1,394 @@ +local Indicators = {list = {"status", "pvp", "leader", "masterLoot", "raidTarget", "happiness", "ready", "role", "lfdRole", "class"}} +local leavingWorld + +ShadowUF:RegisterModule(Indicators, "indicators", ShadowUF.L["Indicators"]) + +function Indicators:UpdateClass(frame) + if( not frame.indicators.class or not frame.indicators.class.enabled ) then return end + + local class = select(2, UnitClass(frame.unit)) + if( UnitIsPlayer(frame.unit) and class ) then + local coords = CLASS_BUTTONS[class] + frame.indicators.class:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes") + frame.indicators.class:SetTexCoord(coords[1], coords[2], coords[3], coords[4]) + frame.indicators.class:Show() + else + frame.indicators.class:Hide() + end +end + +function Indicators:UpdateHappiness(frame) + if( not frame.indicators.happiness or not frame.indicators.happiness.enabled ) then return end + + local happiness = GetPetHappiness() + if( not happiness ) then + frame.indicators.happiness:Hide() + elseif( happiness == 3 ) then + frame.indicators.happiness:SetTexCoord(0, 0.1875, 0, 0.359375) + frame.indicators.happiness:Show() + elseif( happiness == 2 ) then + frame.indicators.happiness:SetTexCoord(0.1875, 0.375, 0, 0.359375) + frame.indicators.happiness:Show() + elseif( happiness == 1 ) then + frame.indicators.happiness:SetTexCoord(0.375, 0.5625, 0, 0.359375) + frame.indicators.happiness:Show() + end +end + +function Indicators:UpdateMasterLoot(frame) + if( not frame.indicators.masterLoot or not frame.indicators.masterLoot.enabled ) then return end + + local lootType, partyID, raidID = GetLootMethod() + if( lootType ~= "master" ) then + frame.indicators.masterLoot:Hide() + elseif( ( partyID and partyID == 0 and UnitIsUnit(frame.unit, "player") ) or ( partyID and partyID > 0 and UnitIsUnit(frame.unit, ShadowUF.partyUnits[partyID]) ) or ( raidID and raidID > 0 and UnitIsUnit(frame.unit, ShadowUF.raidUnits[raidID]) ) ) then + frame.indicators.masterLoot:Show() + else + frame.indicators.masterLoot:Hide() + end +end + +function Indicators:UpdateRaidTarget(frame) + if( not frame.indicators.raidTarget or not frame.indicators.raidTarget.enabled ) then return end + + if( UnitExists(frame.unit) and GetRaidTargetIndex(frame.unit) ) then + SetRaidTargetIconTexture(frame.indicators.raidTarget, GetRaidTargetIndex(frame.unit)) + frame.indicators.raidTarget:Show() + else + frame.indicators.raidTarget:Hide() + end +end + +function Indicators:UpdateLFDRole(frame, event) + if( not frame.indicators.lfdRole or not frame.indicators.lfdRole.enabled ) then return end + + local isTank, isHealer, isDamage = UnitGroupRolesAssigned(frame.unitOwner) + if( isTank ) then + frame.indicators.lfdRole:SetTexCoord(0, 19/64, 22/64, 41/64) + frame.indicators.lfdRole:Show() + elseif( isHealer ) then + frame.indicators.lfdRole:SetTexCoord(20/64, 39/64, 1/64, 20/64) + frame.indicators.lfdRole:Show() + elseif( isDamage ) then + frame.indicators.lfdRole:SetTexCoord(20/64, 39/64, 22/64, 41/64) + frame.indicators.lfdRole:Show() + else + frame.indicators.lfdRole:Hide() + end +end + +function Indicators:UpdateRole(frame, event) + if( not frame.indicators.role or not frame.indicators.role.enabled ) then return end + + if( leavingWorld or not UnitInRaid(frame.unit) and not UnitInParty(frame.unit) ) then + frame.indicators.role:Hide() + elseif( GetPartyAssignment("MAINTANK", frame.unit) ) then + frame.indicators.role:SetTexture("Interface\\GroupFrame\\UI-Group-MainTankIcon") + frame.indicators.role:Show() + elseif( GetPartyAssignment("MAINASSIST", frame.unit) ) then + frame.indicators.role:SetTexture("Interface\\GroupFrame\\UI-Group-MainAssistIcon") + frame.indicators.role:Show() + else + frame.indicators.role:Hide() + end +end + +function Indicators:UpdateLeader(frame) + self:UpdateMasterLoot(frame) + self:UpdateRole(frame) + self:UpdateLFDRole(frame) + if( not frame.indicators.leader or not frame.indicators.leader.enabled ) then return end + + if( UnitIsPartyLeader(frame.unit) ) then + frame.indicators.leader:Show() + else + frame.indicators.leader:Hide() + end +end + +function Indicators:UpdatePVPFlag(frame) + if( not frame.indicators.pvp or not frame.indicators.pvp.enabled ) then return end + + if( UnitIsPVP(frame.unit) and UnitFactionGroup(frame.unit) ) then + frame.indicators.pvp:SetTexture(string.format("Interface\\TargetingFrame\\UI-PVP-%s", UnitFactionGroup(frame.unit))) + frame.indicators.pvp:Show() + elseif( UnitIsPVPFreeForAll(frame.unit) ) then + frame.indicators.pvp:SetTexture("Interface\\TargetingFrame\\UI-PVP-FFA") + frame.indicators.pvp:Show() + else + frame.indicators.pvp:Hide() + end +end + +-- Non-player units do not give events when they enter or leave combat, so polling is necessary +local function combatMonitor(self, elapsed) + self.timeElapsed = self.timeElapsed + elapsed + if( self.timeElapsed < 1 ) then return end + self.timeElapsed = self.timeElapsed - 1 + + if( UnitAffectingCombat(self.parent.unit) ) then + self.status:Show() + else + self.status:Hide() + end +end + +-- It looks like the combat check for players is a bit buggy when they are in a vehicle, so swap it to also check polling +function Indicators:CheckVehicle(frame) + frame.indicators.timeElapsed = 0 + frame.indicators:SetScript("OnUpdate", frame.inVehicle and combatMonitor or nil) +end + +function Indicators:UpdateStatus(frame) + if( not frame.indicators.status or not frame.indicators.status.enabled ) then return end + + if( UnitAffectingCombat(frame.unitOwner) ) then + frame.indicators.status:SetTexCoord(0.50, 1.0, 0.0, 0.49) + frame.indicators.status:Show() + elseif( frame.unitRealType == "player" and IsResting() ) then + frame.indicators.status:SetTexCoord(0.0, 0.50, 0.0, 0.421875) + frame.indicators.status:Show() + else + frame.indicators.status:Hide() + end +end + +-- Ready check fading once the check complete +local function fadeReadyStatus(self, elapsed) + self.timeLeft = self.timeLeft - elapsed + self.ready:SetAlpha(self.timeLeft / self.startTime) + + if( self.timeLeft <= 0 ) then + self:SetScript("OnUpdate", nil) + + self.ready.status = nil + self.ready:Hide() + end +end + +local FADEOUT_TIME = 6 +function Indicators:UpdateReadyCheck(frame, event) + if( not frame.indicators.ready or not frame.indicators.ready.enabled ) then return end + + -- We're done, and should fade it out if it's shown + if( event == "READY_CHECK_FINISHED" ) then + if( not frame.indicators.ready:IsShown() ) then return end + + -- Create the central timer frame if ones not already made + if( not self.fadeTimer ) then + self.fadeTimer = CreateFrame("Frame", nil) + self.fadeTimer.fadeList = {} + self.fadeTimer:Hide() + self.fadeTimer:SetScript("OnUpdate", function(self, elapsed) + local hasTimer + for frame, timeLeft in pairs(self.fadeList) do + hasTimer = true + + self.fadeList[frame] = timeLeft - elapsed + frame:SetAlpha(self.fadeList[frame] / FADEOUT_TIME) + + if( self.fadeList[frame] <= 0 ) then + self.fadeList[frame] = nil + frame:Hide() + end + end + + if( not hasTimer ) then self:Hide() end + end) + end + + -- Start the timer + self.fadeTimer.fadeList[frame.indicators.ready] = FADEOUT_TIME + self.fadeTimer:Show() + + -- Player never responded so they are AFK + if( frame.indicators.ready.status == "waiting" ) then + frame.indicators.ready:SetTexture("Interface\\RaidFrame\\ReadyCheck-NotReady") + end + return + end + + -- Have a state change in ready status + local status = GetReadyCheckStatus(frame.unit) + if( not status ) then + frame.indicators.ready.status = nil + frame.indicators.ready:Hide() + return + end + + if( status == "ready" ) then + frame.indicators.ready:SetTexture(READY_CHECK_READY_TEXTURE) + elseif( status == "notready" ) then + frame.indicators.ready:SetTexture(READY_CHECK_NOT_READY_TEXTURE) + elseif( status == "waiting" ) then + frame.indicators.ready:SetTexture(READY_CHECK_WAITING_TEXTURE) + end + + frame.indicators:SetScript("OnUpdate", nil) + frame.indicators.ready.status = status + frame.indicators.ready:SetAlpha(1.0) + frame.indicators.ready:Show() +end + +function Indicators:OnEnable(frame) + -- Forces the indicators to be above the bars/portraits/etc + if( not frame.indicators ) then + frame.indicators = CreateFrame("Frame", nil, frame) + frame.indicators:SetFrameLevel(frame.topFrameLevel + 1) + end + + -- Now lets enable all the indicators + local config = ShadowUF.db.profile.units[frame.unitType] + if( config.indicators.status and config.indicators.status.enabled ) then + frame:RegisterUpdateFunc(self, "UpdateStatus") + frame.indicators.status = frame.indicators.status or frame.indicators:CreateTexture(nil, "OVERLAY") + frame.indicators.status:SetTexture("Interface\\CharacterFrame\\UI-StateIcon") + frame.indicators.timeElapsed = 0 + frame.indicators.parent = frame + + if( frame.unitType == "player" ) then + frame:RegisterUpdateFunc(self, "CheckVehicle") + frame:RegisterNormalEvent("PLAYER_REGEN_ENABLED", self, "UpdateStatus") + frame:RegisterNormalEvent("PLAYER_REGEN_DISABLED", self, "UpdateStatus") + frame:RegisterNormalEvent("PLAYER_UPDATE_RESTING", self, "UpdateStatus") + frame:RegisterNormalEvent("UPDATE_FACTION", self, "UpdateStatus") + else + frame.indicators.status:SetTexCoord(0.50, 1.0, 0.0, 0.49) + frame.indicators:SetScript("OnUpdate", combatMonitor) + end + elseif( frame.indicators.status ) then + frame.indicators:SetScript("OnUpdate", nil) + end + + if( config.indicators.pvp and config.indicators.pvp.enabled ) then + frame:RegisterUnitEvent("PLAYER_FLAGS_CHANGED", self, "UpdatePVPFlag") + frame:RegisterUnitEvent("UNIT_FACTION", self, "UpdatePVPFlag") + frame:RegisterUpdateFunc(self, "UpdatePVPFlag") + + frame.indicators.pvp = frame.indicators.pvp or frame.indicators:CreateTexture(nil, "OVERLAY") + end + + if( config.indicators.class and config.indicators.class.enabled ) then + frame:RegisterUpdateFunc(self, "UpdateClass") + frame.indicators.class = frame.indicators.class or frame.indicators:CreateTexture(nil, "OVERLAY") + end + + if( config.indicators.leader and config.indicators.leader.enabled ) then + frame:RegisterNormalEvent("PARTY_LEADER_CHANGED", self, "UpdateLeader") + frame:RegisterUpdateFunc(self, "UpdateLeader") + + frame.indicators.leader = frame.indicators.leader or frame.indicators:CreateTexture(nil, "OVERLAY") + frame.indicators.leader:SetTexture("Interface\\GroupFrame\\UI-Group-LeaderIcon") + end + + if( config.indicators.masterLoot and config.indicators.masterLoot.enabled ) then + frame:RegisterNormalEvent("PARTY_LOOT_METHOD_CHANGED", self, "UpdateMasterLoot") + frame:RegisterNormalEvent("RAID_ROSTER_UPDATE", self, "UpdateMasterLoot") + frame:RegisterUpdateFunc(self, "UpdateMasterLoot") + + frame.indicators.masterLoot = frame.indicators.masterLoot or frame.indicators:CreateTexture(nil, "OVERLAY") + frame.indicators.masterLoot:SetTexture("Interface\\GroupFrame\\UI-Group-MasterLooter") + end + + if( config.indicators.role and config.indicators.role.enabled ) then + frame:RegisterUpdateFunc(self, "UpdateRole") + + frame.indicators.role = frame.indicators.role or frame.indicators:CreateTexture(nil, "OVERLAY") + frame.indicators.role:SetTexture("Interface\\GroupFrame\\UI-Group-MainAssistIcon") + + -- Silly hack to fix the fact that Blizzard bugged an API and causes " is not in your party" errors + if( not self.leavingFrame ) then + self.leavingFrame = CreateFrame("Frame") + self.leavingFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + self.leavingFrame:RegisterEvent("PLAYER_LEAVING_WORLD") + self.leavingFrame:SetScript("OnEvent", function(self, event) + if( event == "PLAYER_LEAVING_WORLD" ) then + leavingWorld = true + else + leavingWorld = nil + + for frame in pairs(ShadowUF.Units.frameList) do + if( frame:IsVisible() and frame.indicators and frame.indicators.role and frame.indicators.role.enabled ) then + Indicators:UpdateRole(frame) + end + end + end + end) + end + end + + if( config.indicators.raidTarget and config.indicators.raidTarget.enabled ) then + frame:RegisterNormalEvent("RAID_TARGET_UPDATE", self, "UpdateRaidTarget") + frame:RegisterUpdateFunc(self, "UpdateRaidTarget") + + frame.indicators.raidTarget = frame.indicators.raidTarget or frame.indicators:CreateTexture(nil, "OVERLAY") + frame.indicators.raidTarget:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons") + end + + if( config.indicators.ready and config.indicators.ready.enabled ) then + frame:RegisterNormalEvent("READY_CHECK", self, "UpdateReadyCheck") + frame:RegisterNormalEvent("READY_CHECK_CONFIRM", self, "UpdateReadyCheck") + frame:RegisterNormalEvent("READY_CHECK_FINISHED", self, "UpdateReadyCheck") + frame:RegisterUpdateFunc(self, "UpdateReadyCheck") + + frame.indicators.ready = frame.indicators.ready or frame.indicators:CreateTexture(nil, "OVERLAY") + end + + if( config.indicators.happiness and config.indicators.happiness.enabled ) then + frame:RegisterUnitEvent("UNIT_HAPPINESS", self, "UpdateHappiness") + frame:RegisterUpdateFunc(self, "UpdateHappiness") + + frame.indicators.happiness = frame.indicators.happiness or frame.indicators:CreateTexture(nil, "OVERLAY") + frame.indicators.happiness:SetTexture("Interface\\PetPaperDollFrame\\UI-PetHappiness") + end + + if( config.indicators.lfdRole and config.indicators.lfdRole.enabled ) then + if( frame.unit == "player" ) then + frame:RegisterNormalEvent("PLAYER_ROLES_ASSIGNED", self, "UpdateLFDRole") + end + + frame.indicators.lfdRole = frame.indicators.lfdRole or frame.indicators:CreateTexture(nil, "OVERLAY") + frame.indicators.lfdRole:SetTexture("Interface\\LFGFrame\\UI-LFG-ICON-PORTRAITROLES") + end + + -- As they all share the function, register it as long as one is active + if( frame.indicators.leader or frame.indicators.masterLoot or frame.indicators.role or ( frame.unit ~= "player" and frame.indicators.lfdRole ) ) then + frame:RegisterNormalEvent("PARTY_MEMBERS_CHANGED", self, "UpdateLeader") + end +end + +function Indicators:OnDisable(frame) + frame:UnregisterAll(self) + + for _, key in pairs(self.list) do + if( frame.indicators[key] ) then + frame.indicators[key].enabled = nil + frame.indicators[key]:Hide() + end + end +end + +function Indicators:OnLayoutApplied(frame, config) + if( frame.visibility.indicators ) then + self:OnDisable(frame) + self:OnEnable(frame) + + for _, key in pairs(self.list) do + local indicator = frame.indicators[key] + if( indicator and config.indicators[key].enabled and config.indicators[key].size ) then + indicator.enabled = true + indicator:SetHeight(config.indicators[key].size) + indicator:SetWidth(config.indicators[key].size) + ShadowUF.Layout:AnchorFrame(frame, indicator, config.indicators[key]) + elseif( indicator ) then + indicator.enabled = nil + indicator:Hide() + end + end + + -- Disable the polling + if( config.indicators.status and not config.indicators.status.enabled and frame.indicators.status ) then + frame.indicators:SetScript("OnUpdate", nil) + end + end +end \ No newline at end of file diff --git a/ShadowedUnitFrames/modules/layout.lua b/ShadowedUnitFrames/modules/layout.lua new file mode 100644 index 0000000..b08d3ff --- /dev/null +++ b/ShadowedUnitFrames/modules/layout.lua @@ -0,0 +1,540 @@ +local Layout = {mediaPath = {}} +local SML, mediaRequired, anchoringQueued +local mediaPath = Layout.mediaPath +local backdropTbl = {insets = {}} +local _G = getfenv(0) + +ShadowUF.Layout = Layout + +-- Someone is using another mod that is forcing a media type for all mods using SML +function Layout:MediaForced(mediaType) + local oldPath = mediaPath[mediaType] + self:CheckMedia() + + if( mediaPath[mediaType] ~= oldPath ) then + self:Reload() + end +end + +local function loadMedia(type, name, default) + if( name == "" ) then return "" end + + local media = SML:Fetch(type, name, true) + if( not media ) then + mediaRequired = mediaRequired or {} + mediaRequired[type] = name + return default + end + + return media +end + +-- Updates the background table +local function updateBackdrop() + -- Update the backdrop table + local backdrop = ShadowUF.db.profile.backdrop + backdropTbl.bgFile = mediaPath.background + backdropTbl.edgeFile = mediaPath.border + backdropTbl.tile = backdrop.tileSize > 0 and true or false + backdropTbl.edgeSize = backdrop.edgeSize + backdropTbl.tileSize = backdrop.tileSize + backdropTbl.insets.left = backdrop.inset + backdropTbl.insets.right = backdrop.inset + backdropTbl.insets.top = backdrop.inset + backdropTbl.insets.bottom = backdrop.inset +end + +-- Tries to load media, if it fails it will default to whatever I set +function Layout:CheckMedia() + mediaPath[SML.MediaType.STATUSBAR] = loadMedia(SML.MediaType.STATUSBAR, ShadowUF.db.profile.bars.texture, "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\Aluminium") + mediaPath[SML.MediaType.FONT] = loadMedia(SML.MediaType.FONT, ShadowUF.db.profile.font.name, "Interface\\AddOns\\ShadowedUnitFrames\\media\\fonts\\Myriad Condensed Web.ttf") + mediaPath[SML.MediaType.BACKGROUND] = loadMedia(SML.MediaType.BACKGROUND, ShadowUF.db.profile.backdrop.backgroundTexture, "Interface\\ChatFrame\\ChatFrameBackground") + mediaPath[SML.MediaType.BORDER] = loadMedia(SML.MediaType.BORDER, ShadowUF.db.profile.backdrop.borderTexture, "") + + updateBackdrop() +end + +-- We might not have had a media we required at initial load, wait for it to load and then update everything when it does +function Layout:MediaRegistered(event, mediaType, key) + if( mediaRequired and mediaRequired[mediaType] and mediaRequired[mediaType] == key ) then + mediaPath[mediaType] = SML:Fetch(mediaType, key) + mediaRequired[mediaType] = nil + + self:Reload() + end +end + +-- Helper functions +function Layout:ToggleVisibility(frame, visible) + if( frame and visible ) then + frame:Show() + elseif( frame ) then + frame:Hide() + end +end + +function Layout:SetBarVisibility(frame, key, status) + if( status and not frame[key]:IsVisible() ) then + frame[key]:Show() + ShadowUF.Layout:PositionWidgets(frame, ShadowUF.db.profile.units[frame.unitType]) + elseif( not status and frame[key]:IsVisible() ) then + frame[key]:Hide() + ShadowUF.Layout:PositionWidgets(frame, ShadowUF.db.profile.units[frame.unitType]) + end +end + + +-- Frame changed somehow between when we first set it all up and now +function Layout:Reload(unit) + updateBackdrop() + + -- Now update them + for frame in pairs(ShadowUF.Units.frameList) do + if( frame.unit and ( not unit or frame.unitType == unit ) and not frame.isHeaderFrame ) then + frame:SetVisibility() + self:Load(frame) + frame:FullUpdate() + end + end + + ShadowUF:FireModuleEvent("OnLayoutReload", unit) +end + +-- Do a full update +function Layout:Load(frame) + local unitConfig = ShadowUF.db.profile.units[frame.unitType] + + -- About to set layout + ShadowUF:FireModuleEvent("OnPreLayoutApply", frame, unitConfig) + + -- Load all of the layout things + self:SetupFrame(frame, unitConfig) + self:SetupBars(frame, unitConfig) + self:PositionWidgets(frame, unitConfig) + self:SetupText(frame, unitConfig) + + -- Layouts been fully set + ShadowUF:FireModuleEvent("OnLayoutApplied", frame, unitConfig) +end + +-- Register it on file load because authors seem to do a bad job at registering the callbacks +SML = LibStub:GetLibrary("LibSharedMedia-3.0") +SML:Register(SML.MediaType.FONT, "Myriad Condensed Web", "Interface\\AddOns\\ShadowedUnitFrames\\media\\fonts\\Myriad Condensed Web.ttf") +SML:Register(SML.MediaType.BORDER, "Square Clean", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\ABFBorder") +SML:Register(SML.MediaType.BACKGROUND, "Chat Frame", "Interface\\ChatFrame\\ChatFrameBackground") +SML:Register(SML.MediaType.STATUSBAR, "BantoBar", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\banto") +SML:Register(SML.MediaType.STATUSBAR, "Smooth", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\smooth") +SML:Register(SML.MediaType.STATUSBAR, "Perl", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\perl") +SML:Register(SML.MediaType.STATUSBAR, "Glaze", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\glaze") +SML:Register(SML.MediaType.STATUSBAR, "Charcoal", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\Charcoal") +SML:Register(SML.MediaType.STATUSBAR, "Otravi", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\otravi") +SML:Register(SML.MediaType.STATUSBAR, "Striped", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\striped") +SML:Register(SML.MediaType.STATUSBAR, "LiteStep", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\LiteStep") +SML:Register(SML.MediaType.STATUSBAR, "Aluminium", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\Aluminium") +SML:Register(SML.MediaType.STATUSBAR, "Minimalist", "Interface\\AddOns\\ShadowedUnitFrames\\media\\textures\\Minimalist") + +function Layout:LoadSML() + SML.RegisterCallback(self, "LibSharedMedia_Registered", "MediaRegistered") + SML.RegisterCallback(self, "LibSharedMedia_SetGlobal", "MediaForced") + self:CheckMedia() +end + +--[[ + Keep in mind this is relative to where you're parenting it, RT will put the object outside of the frame, on the right side, at the top of it while ITR will put it inside the frame, at the top to the right + + * Positions OUTSIDE the frame + RT = Right Top, RC = Right Center, RB = Right Bottom + LT = Left Top, LC = Left Center, LB = Left Bottom, + BL = Bottom Left, BC = Bottom Center, BR = Bottom Right + TR = Top Right, TC = Top Center, TL = Top Left + + * Positions INSIDE the frame + CLI = Inside Center Left, CRI = Inside Center Right + TRI = Inside Top Right, TLI = Inside Top Left + BRI = Inside Bottom Right, BRI = Inside Bottom left +]] + +local preDefPoint = {C = "CENTER", CLI = "LEFT", RT = "TOPLEFT", BC = "TOP", CRI = "RIGHT", LT = "TOPRIGHT", TR = "BOTTOMRIGHT", BL = "TOPLEFT", LB = "BOTTOMRIGHT", LC = "RIGHT", RB = "BOTTOMLEFT", RC = "LEFT", TC = "BOTTOM", BR = "TOPRIGHT", TL = "BOTTOMLEFT", BRI = "BOTTOMRIGHT", BLI = "BOTTOMLEFT", TRI = "TOPRIGHT", TLI = "TOPLEFT"} +local preDefRelative = {C = "CENTER", CLI = "LEFT", RT = "TOPRIGHT", BC = "BOTTOM", CRI = "RIGHT", LT = "TOPLEFT", TR = "TOPRIGHT", BL = "BOTTOMLEFT", LB = "BOTTOMLEFT", LC = "LEFT", RB = "BOTTOMRIGHT", RC = "RIGHT", TC = "TOP", BR = "BOTTOMRIGHT", TL = "TOPLEFT", BRI = "BOTTOMRIGHT", BLI = "BOTTOMLEFT", TRI = "TOPRIGHT", TLI = "TOPLEFT"} +local columnDirection = {RT = "RIGHT", C = "RIGHT", BC = "BOTTOM", LT = "LEFT", TR = "TOP", BL = "BOTTOM", LB = "LEFT", LC = "LEFT", TRI = "TOP", RB = "RIGHT", RC = "RIGHT", TC = "TOP", CLI = "RIGHT", TL = "TOP", BR = "BOTTOM", IBL = "RIGHT", IBR = "RIGHT", CRI = "RIGHT", TLI = "TOP"} +local auraDirection = {RT = "BOTTOM", C = "LEFT", BC = "LEFT", LT = "BOTTOM", TR = "LEFT", BL = "RIGHT", LB = "TOP", LC = "LEFT", TRI = "LEFT", RB = "TOP", RC = "LEFT", TC = "LEFT", CLI = "RIGHT", TL = "RIGHT", BR = "LEFT", IBL = "TOP", IBR = "TOP", CRI = "LEFT", TLI = "RIGHT"} + +-- Figures out how text should be justified based on where it's anchoring +function Layout:GetJustify(config) + local point = config.anchorPoint and config.anchorPoint ~= "" and preDefPoint[config.anchorPoint] or config.point + if( point and point ~= "" ) then + if( string.match(point, "LEFT$") ) then + return "LEFT" + elseif( string.match(point, "RIGHT$") ) then + return "RIGHT" + end + end + + return "CENTER" +end + +function Layout:GetPoint(key) + return preDefPoint[key] or "CENTER" +end + +function Layout:GetRelative(key) + return preDefRelative[key] or "CENTER" +end + +function Layout:GetColumnGrowth(key) + return columnDirection[key] or "DOWN" +end + +function Layout:GetAuraGrowth(key) + return auraDirection[key] or "LEFT" +end + +function Layout:ReverseDirection(key) + return key == "LEFT" and "RIGHT" or key == "RIGHT" and "LEFT" or key == "TOP" and "BOTTOM" or key == "BOTTOM" and "TOP" +end + +-- Gets the relative anchoring for Blizzards default raid frames, these differ from the split ones +function Layout:GetRelativeAnchor(point) + if( point == "TOP" ) then + return "BOTTOM", 0, -1 + elseif( point == "BOTTOM" ) then + return "TOP", 0, 1 + elseif( point == "LEFT" ) then + return "RIGHT", 1, 0 + elseif( point == "RIGHT" ) then + return "LEFT", -1, 0 + elseif( point == "TOPLEFT" ) then + return "BOTTOMRIGHT", 1, -1 + elseif( point == "TOPRIGHT" ) then + return "BOTTOMLEFT", -1, -1 + elseif( point == "BOTTOMLEFT" ) then + return "TOPRIGHT", 1, 1 + elseif( point == "BOTTOMRIGHT" ) then + return "TOPLEFT", -1, 1 + else + return "CENTER", 0, 0 + end +end + +function Layout:GetSplitRelativeAnchor(point, columnPoint) + -- Column is growing to the RIGHT + if( columnPoint == "LEFT" ) then + return "TOPLEFT", "TOPRIGHT", 1, 0 + -- Column is growing to the LEFT + elseif( columnPoint == "RIGHT" ) then + return "TOPRIGHT", "TOPLEFT", -1, 0 + -- Column is growing DOWN + elseif( columnPoint == "TOP" ) then + return "TOP" .. point, "BOTTOM" .. point, 0, -1 + -- Column is growing UP + elseif( columnPoint == "BOTTOM" ) then + return "BOTTOM" .. point, "TOP" .. point, 0, 1 + end +end + +function Layout:AnchorFrame(parent, frame, config) + if( not config or not config.anchorTo or not config.x or not config.y ) then + return + end + + local anchorTo = config.anchorTo + local prefix = string.sub(config.anchorTo, 0, 1) + if( config.anchorTo == "$parent" ) then + anchorTo = parent + -- $ is used as an indicator of a sub-frame inside a parent, $healthBar -> parent.healthBar and so on + elseif( prefix == "$" ) then + anchorTo = parent[string.sub(config.anchorTo, 2)] + -- # is used as an indicator of an actual frame created by SUF, it lets us know that the frame might not be created yet + -- and if so, to watch for it to be created and fix the anchoring + elseif( prefix == "#" ) then + anchorTo = string.sub(config.anchorTo, 2) + + -- The frame we wanted to anchor to doesn't exist yet, so will queue and wait for it to exist + if( not _G[anchorTo] ) then + frame.queuedParent = parent + frame.queuedConfig = config + frame.queuedName = anchorTo + + anchoringQueued = anchoringQueued or {} + anchoringQueued[frame] = true + + -- For the time being, will take over the frame we wanted to anchor to's position. + local unit = string.match(anchorTo, "SUFUnit(%w+)") or string.match(anchorTo, "SUFHeader(%w+)") + if( unit and ShadowUF.db.profile.positions[unit] ) then + self:AnchorFrame(parent, frame, ShadowUF.db.profile.positions[unit]) + end + return + end + end + + -- Figure out where it's anchored + local point = config.point and config.point ~= "" and config.point or preDefPoint[config.anchorPoint] or "CENTER" + local relativePoint = config.relativePoint and config.relativePoint ~= "" and config.relativePoint or preDefRelative[config.anchorPoint] or "CENTER" + + -- Effective scaling is only used for unit based frames and if they are anchored to UIParent + local scale = 1 + if( config.anchorTo == "UIParent" and frame.unitType ) then + scale = frame:GetScale() * UIParent:GetScale() + end + + frame:ClearAllPoints() + frame:SetPoint(point, anchorTo, relativePoint, config.x / scale, config.y / scale) +end + +-- Setup the main frame +function Layout:SetupFrame(frame, config) + local backdrop = ShadowUF.db.profile.backdrop + frame:SetBackdrop(backdropTbl) + frame:SetBackdropColor(backdrop.backgroundColor.r, backdrop.backgroundColor.g, backdrop.backgroundColor.b, backdrop.backgroundColor.a) + frame:SetBackdropBorderColor(backdrop.borderColor.r, backdrop.borderColor.g, backdrop.borderColor.b, backdrop.borderColor.a) + + -- Prevent these from updating while in combat to prevent tainting + if( not InCombatLockdown() ) then + frame:SetHeight(config.height) + frame:SetWidth(config.width) + frame:SetScale(config.scale) + + -- Let the frame clip closer to the edge, not using inset + clip as that lets you move it too far in + local clamp = backdrop.inset + 0.20 + frame:SetClampRectInsets(clamp, -clamp, -clamp, clamp) + frame:SetClampedToScreen(true) + + -- This is wrong technically, I need to redo the backdrop stuff so it will accept insets and that will fit hitbox issues + -- for the time being, this is a temporary fix to it + local hit = backdrop.borderTexture == "None" and backdrop.inset or 0 + frame:SetHitRectInsets(hit, hit, hit, hit) + + if( not frame.ignoreAnchor ) then + self:AnchorFrame(frame.parent or UIParent, frame, ShadowUF.db.profile.positions[frame.unitType]) + end + end + + -- Check if we had anything parented to us + if( anchoringQueued ) then + for queued in pairs(anchoringQueued) do + if( queued.queuedName == frame:GetName() ) then + self:AnchorFrame(queued.queuedParent, queued, queued.queuedConfig) + + queued.queuedParent = nil + queued.queuedConfig = nil + queued.queuedName = nil + anchoringQueued[queued] = nil + end + end + end + +end + +-- Setup bars +function Layout:SetupBars(frame, config) + for _, module in pairs(ShadowUF.modules) do + local key = module.moduleKey + local widget = frame[key] + if( widget and ( module.moduleHasBar or config[key] and config[key].isBar ) ) then + self:ToggleVisibility(widget, frame.visibility[key]) + + if( widget:IsShown() and widget.SetStatusBarTexture ) then + widget:SetStatusBarTexture(mediaPath.statusbar) + widget:GetStatusBarTexture():SetHorizTile(false) + end + + if( widget.background ) then + if( config[key].background ) then + widget.background:SetTexture(mediaPath.statusbar) + widget.background:SetHorizTile(false) + widget.background:Show() + + widget.background.overrideColor = ShadowUF.db.profile.bars.backgroundColor or config[key].backgroundColor + + if( widget.background.overrideColor ) then + widget.background:SetVertexColor(widget.background.overrideColor.r, widget.background.overrideColor.g, widget.background.overrideColor.b, ShadowUF.db.profile.bars.backgroundAlpha) + end + else + widget.background:Hide() + end + end + end + end +end + +-- Setup text +function Layout:SetupFontString(fontString, extraSize) + local size = ShadowUF.db.profile.font.size + (extraSize or 0) + if( size <= 0 ) then size = 1 end + + fontString:SetFont(mediaPath.font, size, ShadowUF.db.profile.font.extra) + + if( ShadowUF.db.profile.font.shadowColor and ShadowUF.db.profile.font.shadowX and ShadowUF.db.profile.font.shadowY ) then + fontString:SetShadowColor(ShadowUF.db.profile.font.shadowColor.r, ShadowUF.db.profile.font.shadowColor.g, ShadowUF.db.profile.font.shadowColor.b, ShadowUF.db.profile.font.shadowColor.a) + fontString:SetShadowOffset(ShadowUF.db.profile.font.shadowX, ShadowUF.db.profile.font.shadowY) + else + fontString:SetShadowColor(0, 0, 0, 0) + fontString:SetShadowOffset(0, 0) + end +end + +local totalWeight = {} +function Layout:SetupText(frame, config) + -- Update tag text + frame.fontStrings = frame.fontStrings or {} + for _, fontString in pairs(frame.fontStrings) do + ShadowUF.Tags:Unregister(fontString) + fontString:Hide() + end + + for k in pairs(totalWeight) do totalWeight[k] = nil end + + -- Update the actual text, and figure out the weighting information now + for id, row in pairs(config.text) do + local parent = row.anchorTo == "$parent" and frame or frame[string.sub(row.anchorTo, 2)] + if( parent and parent:IsShown() and row.enabled and row.text ~= "" ) then + local fontString = frame.fontStrings[id] or frame.highFrame:CreateFontString(nil, "ARTWORK") + self:SetupFontString(fontString, row.size) + fontString:SetTextColor(ShadowUF.db.profile.font.color.r, ShadowUF.db.profile.font.color.g, ShadowUF.db.profile.font.color.b, ShadowUF.db.profile.font.color.a) + fontString:SetText(row.text) + fontString:SetJustifyH(self:GetJustify(row)) + self:AnchorFrame(frame, fontString, row) + + -- We figure out the anchor point so we can put text in the same area with the same width requirements + local anchorPoint = columnDirection[row.anchorPoint] + if( string.len(row.anchorPoint) == 3 ) then anchorPoint = anchorPoint .. "I" end + + fontString.availableWidth = parent:GetWidth() - row.x + fontString.widthID = row.anchorTo .. anchorPoint .. row.y + totalWeight[fontString.widthID] = (totalWeight[fontString.widthID] or 0) + row.width + + ShadowUF.Tags:Register(frame, fontString, row.text) + fontString:UpdateTags() + fontString:Show() + + frame.fontStrings[id] = fontString + end + end + + -- Now set all of the width using our weightings + for id, fontString in pairs(frame.fontStrings) do + if( fontString:IsShown() ) then + fontString:SetWidth(fontString.availableWidth * (config.text[id].width / totalWeight[fontString.widthID])) + fontString:SetHeight(ShadowUF.db.profile.font.size + 1) + + frame:RegisterUpdateFunc(fontString, "UpdateTags") + else + frame:UnregisterAll(fontString) + end + end +end + +-- Setup the bar barOrder/info +local currentConfig +local function sortOrder(a, b) + return currentConfig[a].order < currentConfig[b].order +end + +local barOrder = {} +function Layout:PositionWidgets(frame, config) + -- Deal with setting all of the bar heights + local totalWeight, totalBars, hasFullSize = 0, -1 + + -- Figure out total weighting as well as what bars are full sized + for i=#(barOrder), 1, -1 do table.remove(barOrder, i) end + for key, module in pairs(ShadowUF.modules) do + if( ( module.moduleHasBar or config[key] and config[key].isBar ) and frame[key] and frame[key]:IsShown() and config[key].height > 0 ) then + totalWeight = totalWeight + config[key].height + totalBars = totalBars + 1 + + table.insert(barOrder, key) + + -- Decide whats full sized + if( not frame.visibility.portrait or config.portrait.isBar or config[key].order < config.portrait.fullBefore or config[key].order > config.portrait.fullAfter ) then + hasFullSize = true + frame[key].fullSize = true + else + frame[key].fullSize = nil + end + end + end + + -- Sort the barOrder so it's all nice and orderly (:>) + currentConfig = config + table.sort(barOrder, sortOrder) + + -- Now deal with setting the heights and figure out how large the portrait should be. + local clip = ShadowUF.db.profile.backdrop.inset + ShadowUF.db.profile.backdrop.clip + local clipDoubled = clip * 2 + + local portraitOffset, portraitAlignment, portraitAnchor, portraitWidth + if( not config.portrait.isBar ) then + self:ToggleVisibility(frame.portrait, frame.visibility.portrait) + + if( frame.visibility.portrait ) then + -- Figure out portrait alignment + portraitAlignment = config.portrait.alignment + + -- Set the portrait width so we can figure out the offset to use on bars, will do height and position later + portraitWidth = math.floor(frame:GetWidth() * config.portrait.width) - ShadowUF.db.profile.backdrop.inset + frame.portrait:SetWidth(portraitWidth - (portraitAlignment == "RIGHT" and 1 or 0.5)) + + -- Disable portrait if there isn't enough room + if( portraitWidth <= 0 ) then + frame.portrait:Hide() + end + + -- As well as how much to offset bars by (if it's using a left alignment) to keep them all fancy looking + portraitOffset = clip + if( portraitAlignment == "LEFT" ) then + portraitOffset = portraitOffset + portraitWidth + end + end + end + + -- Position and size everything + local portraitHeight, xOffset = 0, -clip + local availableHeight = frame:GetHeight() - clipDoubled - (math.abs(ShadowUF.db.profile.bars.spacing) * totalBars) + for id, key in pairs(barOrder) do + local bar = frame[key] + + -- Position the actual bar based on it's type + if( bar.fullSize ) then + bar:SetWidth(frame:GetWidth() - clipDoubled) + bar:SetHeight(availableHeight * (config[key].height / totalWeight)) + + bar:ClearAllPoints() + bar:SetPoint("TOPLEFT", frame, "TOPLEFT", clip, xOffset) + else + bar:SetWidth(frame:GetWidth() - portraitWidth - clipDoubled) + bar:SetHeight(availableHeight * (config[key].height / totalWeight)) + + bar:ClearAllPoints() + bar:SetPoint("TOPLEFT", frame, "TOPLEFT", portraitOffset, xOffset) + + portraitHeight = portraitHeight + bar:GetHeight() + end + + -- Figure out where the portrait is going to be anchored to + if( not portraitAnchor and config[key].order >= config.portrait.fullBefore ) then + portraitAnchor = bar + end + + xOffset = xOffset - bar:GetHeight() + ShadowUF.db.profile.bars.spacing + end + + -- Now position the portrait and set the height + if( frame.portrait and frame.portrait:IsShown() and portraitAnchor and portraitHeight > 0 ) then + if( portraitAlignment == "LEFT" ) then + frame.portrait:ClearAllPoints() + frame.portrait:SetPoint("TOPLEFT", portraitAnchor, "TOPLEFT", -frame.portrait:GetWidth() - 0.5, 0) + elseif( portraitAlignment == "RIGHT" ) then + frame.portrait:ClearAllPoints() + frame.portrait:SetPoint("TOPRIGHT", portraitAnchor, "TOPRIGHT", frame.portrait:GetWidth() + 1, 0) + end + + if( hasFullSize ) then + frame.portrait:SetHeight(portraitHeight) + else + frame.portrait:SetHeight(frame:GetHeight() - clipDoubled) + end + end + + ShadowUF:FireModuleEvent("OnLayoutWidgets", frame, config) +end + diff --git a/ShadowedUnitFrames/modules/movers.lua b/ShadowedUnitFrames/modules/movers.lua new file mode 100644 index 0000000..cc8a939 --- /dev/null +++ b/ShadowedUnitFrames/modules/movers.lua @@ -0,0 +1,496 @@ +-- 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, + GetPetHappiness = function() return getValue("GetPetHappiness", "pet", math.random(1, 3)) 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, + UnitHealthMax = function(unit) return 50000 end, + UnitPower = function(unit) return getValue("UnitPower", unit, math.random(20000, 50000)) end, + UnitExists = function(unit) return true end, + UnitPowerMax = function(unit) return 50000 end, + UnitIsPartyLeader = 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, + UnitThreatSituation = function() return 0 end, + UnitCastingInfo = function(unit) + -- 1 -> 10: spell, rank, displayName, icon, startTime, endTime, isTradeSkill, castID, notInterruptible + local data = unitConfig["UnitCastingInfo" .. unit] or {} + if( not data[6] or GetTime() < data[6] ) then + data[1] = L["Test spell"] + data[2] = L["Rank 1"] + data[3] = L["Test spell"] + data[4] = "Interface\\Icons\\Spell_Nature_Rejuvenation" + data[5] = GetTime() * 1000 + data[6] = data[5] + 60000 + data[7] = false + data[8] = math.floor(GetTime()) + data[9] = math.random(0, 100) < 25 + 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, role == 2, role == 3 + 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, + 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"], L["Rank 1"], 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.unitType .. "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: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:GetScript("OnEnter") + frame.originalOnLeave = frame:GetScript("OnLeave") + frame.originalOnUpdate = frame:GetScript("OnUpdate") + frame:SetMovable(not ShadowUF.Units.childUnits[frame.unitType]) + frame:SetScript("OnDragStop", OnDragStop) + frame:SetScript("OnDragStart", OnDragStart) + frame:SetScript("OnEnter", OnEnter) + frame:SetScript("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) + + -- 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) + frame:SetScript("OnUpdate", frame.originalOnUpdate) + frame:SetScript("OnEnter", frame.originalOnEnter) + frame:SetScript("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() + + 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) + 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(self) + if( not ShadowUF.db.profile.locked and self: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("OnShow", OnShow) + frame:SetScript("OnHide", OnHide) + frame:SetScript("OnDragStart", function(self) + self:StartMoving() + end) + frame:SetScript("OnDragStop", function(self) + self: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 frame in pairs(ShadowUF.Units.frameList) do + if( not ShadowUF.Units.childUnits[frame.unitType] and frame:GetScript("OnDragStart") and frame:GetScript("OnDragStop") ) then + frame:GetScript("OnDragStart")(frame) + frame:GetScript("OnDragStop")(frame) + end + end + + Movers:Update() + end) + + self.infoFrame = frame +end diff --git a/ShadowedUnitFrames/modules/portrait.lua b/ShadowedUnitFrames/modules/portrait.lua new file mode 100644 index 0000000..1e06f07 --- /dev/null +++ b/ShadowedUnitFrames/modules/portrait.lua @@ -0,0 +1,98 @@ +local Portrait = {} +ShadowUF:RegisterModule(Portrait, "portrait", ShadowUF.L["Portrait"]) + +-- If the camera isn't reset OnShow, it'll show the entire character instead of just the head, odd I know +local function resetCamera(self) + self:SetCamera(0) +end + +local function resetGUID(self) + self.guid = nil +end + +function Portrait:OnEnable(frame) + frame:RegisterUnitEvent("UNIT_PORTRAIT_UPDATE", self, "UpdateFunc") + frame:RegisterUnitEvent("UNIT_MODEL_CHANGED", self, "Update") + + if( frame.unitRealType == "party" ) then + -- frame:RegisterNormalEvent("PARTY_MEMBER_ENABLE", self, "Update") + -- frame:RegisterNormalEvent("PARTY_MEMBER_DISABLE", self, "Update") + end + + frame:RegisterUpdateFunc(self, "UpdateFunc") +end + +function Portrait:OnDisable(frame) + frame:UnregisterAll(self) +end + +function Portrait:OnPreLayoutApply(frame, config) + if( not frame.visibility.portrait ) then return end + + if( config.portrait.type == "3D" ) then + if( not frame.portraitModel ) then + frame.portraitModel = CreateFrame("PlayerModel", nil, frame) + frame.portraitModel:SetScript("OnShow", resetCamera) + frame.portraitModel:SetScript("OnHide", resetGUID) + frame.portraitModel.parent = frame + end + + frame.portrait = frame.portraitModel + frame.portrait:Show() + + ShadowUF.Layout:ToggleVisibility(frame.portraitTexture, false) + else + frame.portraitTexture = frame.portraitTexture or frame:CreateTexture(nil, "ARTWORK") + frame.portrait = frame.portraitTexture + frame.portrait:Show() + + ShadowUF.Layout:ToggleVisibility(frame.portraitModel, false) + end +end + +function Portrait:UpdateFunc(frame) + -- Portrait models can't be updated unless the GUID changed or else you have the animation jumping around + if( ShadowUF.db.profile.units[frame.unitType].portrait.type == "3D" ) then + local guid = UnitGUID(frame.unitOwner) + if( frame.portrait.guid ~= guid ) then + self:Update(frame) + end + + frame.portrait.guid = guid + else + self:Update(frame) + end +end + +function Portrait:Update(frame, event) + local type = ShadowUF.db.profile.units[frame.unitType].portrait.type + + -- Use class thingy + if( type == "class" ) then + local classToken = select(2, UnitClass(frame.unitOwner)) + if( classToken ) then + frame.portrait:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes") + frame.portrait:SetTexCoord(CLASS_ICON_TCOORDS[classToken][1], CLASS_ICON_TCOORDS[classToken][2], CLASS_ICON_TCOORDS[classToken][3], CLASS_ICON_TCOORDS[classToken][4]) + else + frame.portrait:SetTexture("") + end + -- Use 2D character image + elseif( type == "2D" ) then + frame.portrait:SetTexCoord(0.10, 0.90, 0.10, 0.90) + SetPortraitTexture(frame.portrait, frame.unitOwner) + -- Using 3D portrait, but the players not in range so swap to 2D + elseif( not UnitIsVisible(frame.unitOwner) or not UnitIsConnected(frame.unitOwner) ) then + frame.portrait:SetModelScale(4.25) + frame.portrait:SetPosition(0, 0, -1.5) + frame.portrait:SetModel("Interface\\Buttons\\talktomequestionmark.mdx") + -- Use animated 3D portrait + else + frame.portrait:SetUnit(frame.unitOwner) + frame.portrait:SetCamera(0) + frame.portrait:Show() + end +end + + + + diff --git a/ShadowedUnitFrames/modules/power.lua b/ShadowedUnitFrames/modules/power.lua new file mode 100644 index 0000000..fcb8d19 --- /dev/null +++ b/ShadowedUnitFrames/modules/power.lua @@ -0,0 +1,75 @@ +local Power = {} +ShadowUF:RegisterModule(Power, "powerBar", ShadowUF.L["Power bar"], true) + +local function updatePower(self, elapsed) + local currentPower = UnitPower(self.parent.unit) + if( currentPower == self.currentPower ) then return end + self.currentPower = currentPower + + self:SetValue(currentPower) + for _, fontString in pairs(self.parent.fontStrings) do + if( fontString.fastPower ) then + fontString:UpdateTags() + end + end +end + +function Power:OnEnable(frame) + frame.powerBar = frame.powerBar or ShadowUF.Units:CreateBar(frame) + + frame:RegisterUnitEvent("UNIT_MANA", self, "Update") + frame:RegisterUnitEvent("UNIT_RAGE", self, "Update") + frame:RegisterUnitEvent("UNIT_ENERGY", self, "Update") + frame:RegisterUnitEvent("UNIT_FOCUS", self, "Update") + frame:RegisterUnitEvent("UNIT_RUNIC_POWER", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXMANA", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXRAGE", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXENERGY", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXFOCUS", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXRUNIC_POWER", self, "Update") + frame:RegisterUnitEvent("UNIT_DISPLAYPOWER", self, "UpdateColor") + + frame:RegisterUpdateFunc(self, "UpdateColor") + frame:RegisterUpdateFunc(self, "Update") +end + +function Power:OnLayoutApplied(frame) + -- Enable predicted updates which requires polling in an OnUpdate to get more up to date values + if( frame.visibility.powerBar ) then + if( ShadowUF.db.profile.units[frame.unitType].powerBar.predicted ) then + frame.powerBar:SetScript("OnUpdate", updatePower) + else + frame.powerBar:SetScript("OnUpdate", nil) + end + end +end + +function Power:OnDisable(frame) + frame:UnregisterAll(self) +end + +function Power:UpdateColor(frame) + local color = ShadowUF.db.profile.powerColors[select(2, UnitPowerType(frame.unit))] or ShadowUF.db.profile.powerColors.MANA + + if( not ShadowUF.db.profile.units[frame.unitType].powerBar.invert ) then + frame.powerBar:SetStatusBarColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) + if( not frame.powerBar.background.overrideColor ) then + frame.powerBar.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + end + else + frame.powerBar.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) + + color = frame.powerBar.background.overrideColor + if( not color ) then + frame.powerBar:SetStatusBarColor(0, 0, 0, 1 - ShadowUF.db.profile.bars.backgroundAlpha) + else + frame.powerBar:SetStatusBarColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + end + end +end + +function Power:Update(frame) + frame.powerBar.currentPower = UnitPower(frame.unit) + frame.powerBar:SetMinMaxValues(0, UnitPowerMax(frame.unit)) + frame.powerBar:SetValue(UnitIsDeadOrGhost(frame.unit) and 0 or not UnitIsConnected(frame.unit) and 0 or frame.powerBar.currentPower) +end \ No newline at end of file diff --git a/ShadowedUnitFrames/modules/range.lua b/ShadowedUnitFrames/modules/range.lua new file mode 100644 index 0000000..2334894 --- /dev/null +++ b/ShadowedUnitFrames/modules/range.lua @@ -0,0 +1,87 @@ +local Range = { + friendly = {["PRIEST"] = GetSpellInfo(2050), ["DRUID"] = GetSpellInfo(48378), ["PALADIN"] = GetSpellInfo(48782), ["SHAMAN"] = GetSpellInfo(49273)}, + hostile = {["PRIEST"] = GetSpellInfo(48127), ["DRUID"] = GetSpellInfo(48461), ["PALADIN"] = GetSpellInfo(62124), ["HUNTER"] = GetSpellInfo(75), ["WARLOCK"] = GetSpellInfo(686), ["SHAMAN"] = GetSpellInfo(529), ["MAGE"] = GetSpellInfo(133), ["DEATHKNIGHT"] = GetSpellInfo(49576)}, + resurrect = {["PALADIN"] = GetSpellInfo(48950), ["PRIEST"] = GetSpellInfo(25435), ["SHAMAN"] = GetSpellInfo(2008), ["DRUID"] = GetSpellInfo(48477)} +} +ShadowUF:RegisterModule(Range, "range", ShadowUF.L["Range indicator"]) + +local playerClass = select(2, UnitClass("player")) +local friendlySpell, hostileSpell +local resurrectSpell = Range.resurrect[playerClass] + +local function checkRange(self, elapsed) + self.timeElapsed = self.timeElapsed + elapsed + if( self.timeElapsed <= 0.50 ) then return end + self.timeElapsed = 0 + + if( self.isFriendly and resurrectSpell and UnitIsDead(self.parent.unit) ) then + self.parent:SetRangeAlpha(IsSpellInRange(resurrectSpell, self.parent.unit) == 1 and ShadowUF.db.profile.units[self.parent.unitType].range.inAlpha or ShadowUF.db.profile.units[self.parent.unitType].range.oorAlpha) + -- We set a spell for them in our flags check, use that + elseif( self.spell ) then + self.parent:SetRangeAlpha(IsSpellInRange(self.spell, self.parent.unit) == 1 and ShadowUF.db.profile.units[self.parent.unitType].range.inAlpha or ShadowUF.db.profile.units[self.parent.unitType].range.oorAlpha) + -- That didn't work, but they are grouped lets try the actual API for this, it's a bit flaky though and not that useful generally + elseif( self.grouped ) then + self.parent:SetRangeAlpha(UnitInRange(self.parent.unit, "player") and ShadowUF.db.profile.units[self.parent.unitType].range.inAlpha or ShadowUF.db.profile.units[self.parent.unitType].range.oorAlpha) + -- Nope, fall back to interaction :( + elseif( self.isFriendly ) then + self.parent:SetRangeAlpha(CheckInteractDistance(self.parent.unit, 4) and ShadowUF.db.profile.units[self.parent.unitType].range.inAlpha or ShadowUF.db.profile.units[self.parent.unitType].range.oorAlpha) + else + self.parent:SetRangeAlpha(ShadowUF.db.profile.units[self.parent.unitType].range.inAlpha) + end +end + +function Range:ForceUpdate(frame) + checkRange(frame.range, 1) +end + +function Range:OnEnable(frame) + if( not frame.range ) then + frame.range = CreateFrame("Frame", nil, frame) + frame.range:SetScript("OnUpdate", checkRange) + frame.range.timeElapsed = 0 + frame.range.parent = frame + frame.range:Hide() + end + + -- I want to say UNIT_FACTION is the function thats called when a unit is MCed, but not 100% sure + frame:RegisterUnitEvent("UNIT_FACTION", self, "UpdateFlags") + frame:RegisterNormalEvent("PARTY_MEMBERS_CHANGED", self, "UpdateFlags") + frame:RegisterNormalEvent("RAID_ROSTER_UPDATE", self, "UpdateFlags") + + frame:RegisterUpdateFunc(self, "UpdateFlags") + frame:RegisterUpdateFunc(self, "ForceUpdate") +end + +function Range:OnLayoutApplied(frame) + if( frame.visibility.range ) then + frame.range.hostileSpell = ShadowUF.db.profile.range["hostile" .. playerClass] or self.hostile[playerClass] + frame.range.friendlySpell = ShadowUF.db.profile.range["friendly" .. playerClass] or self.friendly[playerClass] + end +end + +function Range:OnDisable(frame) + frame:UnregisterAll(self) + + if( frame.range ) then + frame.range:Hide() + frame:SetRangeAlpha(1.0) + end +end + +-- I'd rather store the flags here, they rarely change and we can do that based off events, no sense in doing it eveyr 0.50s +function Range:UpdateFlags(frame) + frame.range.canAttack = UnitCanAttack("player", frame.unit) + frame.range.isFriendly = UnitIsFriend("player", frame.unit) and UnitCanAssist("player", frame.unit) + frame.range.grouped = UnitInRaid(frame.unit) or UnitInParty(frame.unit) + frame.range.spell = frame.range.canAttack and frame.range.hostileSpell or frame.range.isFriendly and frame.range.friendlySpell or nil + + -- No sense in updating range if we have no data + if( UnitIsGhost(frame.unit) or not UnitIsConnected(frame.unit) or ( not frame.range.spell and not frame.range.grouped and not frame.range.isFriendly ) ) then + frame:SetRangeAlpha(ShadowUF.db.profile.units[frame.unitType].range.inAlpha) + frame.range:Hide() + else + frame.range:Show() + end +end + + diff --git a/ShadowedUnitFrames/modules/runes.lua b/ShadowedUnitFrames/modules/runes.lua new file mode 100644 index 0000000..e37e12d --- /dev/null +++ b/ShadowedUnitFrames/modules/runes.lua @@ -0,0 +1,115 @@ +local Runes = {} +local RUNE_MAP = {[1] = 1, [2] = 2, [3] = 5, [4] = 6, [5] = 3, [6] = 4} +local runeColors = {{r = 1, g = 0, b = 0.4}, {r = 0, g = 1, b = 0.4}, {r = 0, g = 0.4, b = 1}, {r = 0.7, g = 0.5, b = 1}} +ShadowUF:RegisterModule(Runes, "runeBar", ShadowUF.L["Rune bar"], true, "DEATHKNIGHT") + +function Runes:OnEnable(frame) + if( not frame.runeBar ) then + frame.runeBar = CreateFrame("StatusBar", nil, frame) + frame.runeBar:SetMinMaxValues(0, 1) + frame.runeBar:SetValue(0) + frame.runeBar.runes = {} + + for id=1, 6 do + local rune = ShadowUF.Units:CreateBar(frame) + rune:SetFrameLevel(1) + + if( id > 1 ) then + rune:SetPoint("TOPLEFT", frame.runeBar.runes[RUNE_MAP[id - 1]], "TOPRIGHT", 1, 0) + else + rune:SetPoint("TOPLEFT", frame.runeBar, "TOPLEFT", 0, 0) + end + + frame.runeBar.runes[RUNE_MAP[id]] = rune + end + end + + frame:RegisterNormalEvent("RUNE_POWER_UPDATE", self, "UpdateUsable") + frame:RegisterNormalEvent("RUNE_TYPE_UPDATE", self, "Update") + frame:RegisterUpdateFunc(self, "Update") + frame:RegisterUpdateFunc(self, "UpdateUsable") +end + +function Runes:OnDisable(frame) + frame:UnregisterAll(self) +end + +function Runes:OnLayoutApplied(frame) + if( frame.visibility.runeBar ) then + local barWidth = (frame.runeBar:GetWidth() - 5) / 6 + + for id, rune in pairs(frame.runeBar.runes) do + if( ShadowUF.db.profile.units[frame.unitType].runeBar.background ) then + rune.background:Show() + else + rune.background:Hide() + end + + rune.background:SetTexture(ShadowUF.Layout.mediaPath.statusbar) + rune.background:SetHorizTile(false) + rune:SetStatusBarTexture(ShadowUF.Layout.mediaPath.statusbar) + rune:GetStatusBarTexture():SetHorizTile(false) + rune:SetHeight(frame.runeBar:GetHeight()) + rune:SetWidth(barWidth) + end + end +end + +local function runeMonitor(self, elapsed) + local time = GetTime() + self:SetValue(time) + + if( time >= self.endTime ) then + self:SetValue(self.endTime) + self:SetAlpha(1.0) + self:SetScript("OnUpdate", nil) + end +end + +-- Updates the timers on runes +function Runes:UpdateUsable(frame, event, id, usable) + if( not id ) then + self:UpdateColors(frame) + return + elseif( not frame.runeBar.runes[id] ) then + return + end + + local rune = frame.runeBar.runes[id] + local startTime, cooldown, cooled = GetRuneCooldown(id) + if( not cooled ) then + rune.endTime = GetTime() + cooldown + rune:SetMinMaxValues(startTime, rune.endTime) + rune:SetValue(GetTime()) + rune:SetAlpha(0.40) + rune:SetScript("OnUpdate", runeMonitor) + else + rune:SetMinMaxValues(0, 1) + rune:SetValue(1) + rune:SetAlpha(1.0) + rune:SetScript("OnUpdate", nil) + end +end + +function Runes:UpdateColors(frame) + for id, rune in pairs(frame.runeBar.runes) do + local color = runeColors[GetRuneType(id)] + if( color ) then + rune:SetStatusBarColor(color.r, color.g, color.b) + + color = ShadowUF.db.profile.bars.backgroundColor or ShadowUF.db.profile.units[frame.unitType].runeBar.backgroundColor or color + rune.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + end + end +end + +-- No rune is passed for full update (Login), a single rune is passed when a single rune type changes, such as Blood Tap +function Runes:Update(frame, event, id) + if( id ) then + local color = runeColors[GetRuneType(id)] + frame.runeBar.runes[id]:SetStatusBarColor(color.r, color.g, color.b) + + color = ShadowUF.db.profile.bars.backgroundColor or ShadowUF.db.profile.units[frame.unitType].runeBar.backgroundColor or color + frame.runeBar.runes[id].background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + end +end diff --git a/ShadowedUnitFrames/modules/tags.lua b/ShadowedUnitFrames/modules/tags.lua new file mode 100644 index 0000000..a27d532 --- /dev/null +++ b/ShadowedUnitFrames/modules/tags.lua @@ -0,0 +1,1279 @@ +-- Thanks to haste for the original tagging code, which I then mostly ripped apart and stole! +local Tags = {afkStatus = {}, offlineStatus = {}, customEvents = {}} +local tagPool, functionPool, temp, regFontStrings, frequentUpdates, frequencyCache = {}, {}, {}, {}, {}, {} +local L = ShadowUF.L + +ShadowUF.Tags = Tags + +-- Register the associated events with all the tags +function Tags:RegisterEvents(parent, fontString, tags) + -- Strip parantheses and anything inside them + for tag in string.gmatch(tags, "%[(.-)%]") do + -- The reason the original %b() match won't work, with [( ()group())] (or any sort of tag with ( or ) + -- was breaking the logic and stripping the entire tag, this is a quick fix to stop that. + local tagKey = select(2, string.match(tag, "(%b())([%w%p]+)(%b())")) + if( not tagKey ) then tagKey = select(2, string.match(tag, "(%b())([%w%p]+)")) end + if( not tagKey ) then tagKey = string.match(tag, "([%w%p]+)(%b())") end + + tag = tagKey or tag + + local tagEvents = Tags.defaultEvents[tag] or ShadowUF.db.profile.tags[tag] and ShadowUF.db.profile.tags[tag].events + if( tagEvents ) then + for event in string.gmatch(tagEvents, "%S+") do + if( self.customEvents[event] ) then + self.customEvents[event]:EnableTag(parent, fontString) + fontString[event] = true + elseif( Tags.eventType[event] ~= "unitless" or ShadowUF.Units.unitEvents[event] ) then + parent:RegisterUnitEvent(event, fontString, "UpdateTags") + else + parent:RegisterNormalEvent(event, fontString, "UpdateTags") + end + + -- The power and health bars will handle updating tags with this flag set + fontString.fastPower = fontString.fastPower or Tags.eventType[event] == "power" + fontString.fastHealth = fontString.fastHealth or Tags.eventType[event] == "health" + end + end + end +end + +-- This pretty much means a tag was updated in some way (or deleted) so we have to do a full update to get the new values shown +function Tags:Reload() + -- Kill cached functions, ugly I know but it ensures its fully updated with the new data + table.wipe(functionPool) + table.wipe(ShadowUF.tagFunc) + table.wipe(tagPool) + + -- Now update frames + for fontString, tags in pairs(regFontStrings) do + self:Register(fontString.parent, fontString, tags) + fontString.parent:RegisterUpdateFunc(fontString, "UpdateTags") + fontString:UpdateTags() + end +end + +-- Frequent updates +local freqFrame = CreateFrame("Frame") +freqFrame:SetScript("OnUpdate", function(self, elapsed) + for fontString, timeLeft in pairs(frequentUpdates) do + if( fontString.parent:IsVisible() ) then + frequentUpdates[fontString] = timeLeft - elapsed + if( frequentUpdates[fontString] <= 0 ) then + frequentUpdates[fontString] = fontString.frequentStart + fontString:UpdateTags() + end + end + end +end) +freqFrame:Hide() + +-- Register a font string with the tag system +function Tags:Register(parent, fontString, tags, resetCache) + -- Unregister the font string first if we did register it already + if( fontString.UpdateTags ) then + self:Unregister(fontString) + end + + fontString.parent = parent + regFontStrings[fontString] = tags + + -- Use the cached polling time if we already saved it + -- as we won't be rececking everything next call + local pollTime = frequencyCache[tags] + if( pollTime ) then + frequentUpdates[fontString] = pollTime + fontString.frequentStart = pollTime + freqFrame:Show() + end + + local updateFunc = not resetCache and tagPool[tags] + if( not updateFunc ) then + -- Using .- prevents supporting tags such as [foo ([)]. Supporting that and having a single pattern + local formattedText = string.gsub(string.gsub(tags, "%%", "%%%%"), "[[].-[]]", "%%s") + local args = {} + + for tag in string.gmatch(tags, "%[(.-)%]") do + -- Tags that use pre or appends "foo(|)" etc need special matching, which is what this will handle + local cachedFunc = not resetCache and functionPool[tag] or ShadowUF.tagFunc[tag] + if( not cachedFunc ) then + local hasPre, hasAp = true, true + local tagKey = select(2, string.match(tag, "(%b())([%w%p]+)(%b())")) + if( not tagKey ) then hasPre, hasAp = true, false tagKey = select(2, string.match(tag, "(%b())([%w%p]+)")) end + if( not tagKey ) then hasPre, hasAp = false, true tagKey = string.match(tag, "([%w%p]+)(%b())") end + + frequencyCache[tag] = tagKey and (self.defaultFrequents[tagKey] or ShadowUF.db.profile.tags[tagKey] and ShadowUF.db.profile.tags[tagKey].frequency) + local tagFunc = tagKey and ShadowUF.tagFunc[tagKey] + if( tagFunc ) then + local startOff, endOff = string.find(tag, tagKey) + local pre = hasPre and string.sub(tag, 2, startOff - 2) + local ap = hasAp and string.sub(tag, endOff + 2, -2) + + if( pre and ap ) then + cachedFunc = function(...) + local str = tagFunc(...) + if( str ) then return pre .. str .. ap end + end + elseif( pre ) then + cachedFunc = function(...) + local str = tagFunc(...) + if( str ) then return pre .. str end + end + elseif( ap ) then + cachedFunc = function(...) + local str = tagFunc(...) + if( str ) then return str .. ap end + end + end + + functionPool[tag] = cachedFunc + end + end + + -- Figure out what the lowest update frequency for this font string and use it + local pollTime = self.defaultFrequents[tag] or frequencyCache[tag] + if( ShadowUF.db.profile.tags[tag] and ShadowUF.db.profile.tags[tag].frequency ) then + pollTime = ShadowUF.db.profile.tags[tag].frequency + end + + if( pollTime and ( not fontString.frequentStart or fontString.frequentStart > pollTime ) ) then + frequencyCache[tags] = pollTime + frequentUpdates[fontString] = pollTime + fontString.frequentStart = pollTime + freqFrame:Show() + end + + -- It's an invalid tag, simply return the tag itself wrapped in brackets + if( not cachedFunc ) then + functionPool[tag] = functionPool[tag] or function() return string.format("[%s-error]", tag) end + cachedFunc = functionPool[tag] + end + + table.insert(args, cachedFunc) + end + + -- Create our update function now + updateFunc = function(fontString) + for id, func in pairs(args) do + temp[id] = func(fontString.parent.unit, fontString.parent.unitOwner, fontString) or "" + end + + fontString:SetFormattedText(formattedText, unpack(temp)) + end + + tagPool[tags] = updateFunc + end + + -- And give other frames an easy way to force an update + fontString.UpdateTags = updateFunc + + -- Register any needed event + self:RegisterEvents(parent, fontString, tags) +end + +function Tags:Unregister(fontString) + regFontStrings[fontString] = nil + frequentUpdates[fontString] = nil + + -- Kill frequent updates if they aren't needed anymore + local hasFrequent + for k in pairs(frequentUpdates) do + hasFrequent = true + break + end + + if( not hasFrequent ) then + freqFrame:Hide() + end + + -- Unregister it as using HC + for key, module in pairs(self.customEvents) do + if( fontString[key] ) then + fontString[key] = nil + module:DisableTag(fontString.parent, fontString) + end + end + + -- Kill any tag data + fontString.parent:UnregisterAll(fontString) + fontString.fastPower = nil + fontString.fastHealth = nil + fontString.frequentStart = nil + fontString.UpdateTags = nil + fontString:SetText("") +end + +-- Helper functions for tags, the reason I store it in ShadowUF is it's easier to type ShadowUF than ShadowUF.modules.Tags, and simpler for users who want to implement it. +function ShadowUF:Hex(r, g, b) + if( type(r) == "table" ) then + if( r.r ) then + r, g, b = r.r, r.g, r.b + else + r, g, b = unpack(r) + end + end + + return string.format("|cff%02x%02x%02x", r * 255, g * 255, b * 255) +end + +function ShadowUF:FormatLargeNumber(number) + if( number < 9999 ) then + return number + elseif( number < 999999 ) then + return string.format("%.1fk", number / 1000) + elseif( number < 99999999 ) then + return string.format("%.2fm", number / 1000000) + end + + return string.format("%dm", number / 1000000) +end + +function ShadowUF:SmartFormatNumber(number) + if( number < 999999 ) then + return number + elseif( number < 99999999 ) then + return string.format("%.2fm", number / 1000000) + end + + return string.format("%dm", number / 1000000) +end + +function ShadowUF:GetClassColor(unit) + if( not UnitIsPlayer(unit) ) then + return nil + end + + local class = select(2, UnitClass(unit)) + return class and ShadowUF:Hex(ShadowUF.db.profile.classColors[class]) +end + +function ShadowUF:FormatShortTime(seconds) + if( seconds >= 3600 ) then + return string.format("%dh", seconds / 3600) + elseif( seconds >= 60 ) then + return string.format("%dm", seconds / 60) + end + + return string.format("%ds", seconds) +end + +-- Name abbreviation +local function abbreviateName(text) + return string.sub(text, 1, 1) .. "." +end + +Tags.abbrevCache = setmetatable({}, { + __index = function(tbl, val) + val = string.gsub(val, "([^%s]+) ", abbreviateName) + rawset(tbl, val, val) + return val +end}) + +-- Going to have to start using an env wrapper for tags I think +local Druid = {} +Druid.CatForm, Druid.Shapeshift = GetSpellInfo(768) +Druid.MoonkinForm = GetSpellInfo(24858) +Druid.TravelForm = GetSpellInfo(783) +Druid.BearForm = GetSpellInfo(5487) +Druid.TreeForm = GetSpellInfo(33891) +Druid.DireBearForm = GetSpellInfo(9634) +Druid.AquaticForm = GetSpellInfo(1066) +Druid.SwiftFlightForm = GetSpellInfo(40120) +Druid.FlightForm = GetSpellInfo(33943) +ShadowUF.Druid = Druid + +Tags.defaultTags = { + ["hp:color"] = [[function(unit, unitOwner) + return ShadowUF:Hex(ShadowUF.modules.healthBar.getGradientColor(unit)) + end]], + ["short:druidform"] = [[function(unit, unitOwner) + if( select(2, UnitClass(unit)) ~= "DRUID" ) then return nil end + + local Druid = ShadowUF.Druid + if( UnitAura(unit, Druid.CatForm, Druid.Shapeshift) ) then + return ShadowUF.L["C"] + elseif( UnitAura(unit, Druid.TreeForm, Druid.Shapeshift) ) then + return ShadowUF.L["T"] + elseif( UnitAura(unit, Druid.MoonkinForm, Druid.Shapeshift) ) then + return ShadowUF.L["M"] + elseif( UnitAura(unit, Druid.DireBearForm, Druid.Shapeshift) or UnitAura(unit, Druid.BearForm, Druid.Shapeshift) ) then + return ShadowUF.L["B"] + elseif( UnitAura(unit, Druid.SwiftFlightForm, Druid.Shapeshift) or UnitAura(unit, Druid.FlightForm, Druid.Shapeshift) ) then + return ShadowUF.L["F"] + elseif( UnitAura(unit, Druid.TravelForm, Druid.Shapeshift) ) then + return ShadowUF.L["T"] + elseif( UnitAura(unit, Druid.AquaticForm, Druid.Shapeshift) ) then + return ShadowUF.L["A"] + end + end]], + ["druidform"] = [[function(unit, unitOwner) + if( select(2, UnitClass(unit)) ~= "DRUID" ) then return nil end + + local Druid = ShadowUF.Druid + if( UnitAura(unit, Druid.CatForm, Druid.Shapeshift) ) then + return ShadowUF.L["Cat"] + elseif( UnitAura(unit, Druid.TreeForm, Druid.Shapeshift) ) then + return ShadowUF.L["Tree"] + elseif( UnitAura(unit, Druid.MoonkinForm, Druid.Shapeshift) ) then + return ShadowUF.L["Moonkin"] + elseif( UnitAura(unit, Druid.DireBearForm, Druid.Shapeshift) or UnitAura(unit, Druid.BearForm, Druid.Shapeshift) ) then + return ShadowUF.L["Bear"] + elseif( UnitAura(unit, Druid.SwiftFlightForm, Druid.Shapeshift) or UnitAura(unit, Druid.FlightForm, Druid.Shapeshift) ) then + return ShadowUF.L["Flight"] + elseif( UnitAura(unit, Druid.TravelForm, Druid.Shapeshift) ) then + return ShadowUF.L["Travel"] + elseif( UnitAura(unit, Druid.AquaticForm, Druid.Shapeshift) ) then + return ShadowUF.L["Aquatic"] + end + end]], + ["guild"] = [[function(unit, unitOwner) + return GetGuildInfo(unitOwner) + end]], + ["abbrev:name"] = [[function(unit, unitOwner) + local name = UnitName(unitOwner) or UNKNOWN + return string.len(name) > 10 and ShadowUF.Tags.abbrevCache[name] or name + end]], + ["unit:situation"] = [[function(unit, unitOwner) + local state = UnitThreatSituation(unit) + if( state == 3 ) then + return ShadowUF.L["Aggro"] + elseif( state == 2 ) then + return ShadowUF.L["High"] + elseif( state == 1 ) then + return ShadowUF.L["Medium"] + end + end]], + ["situation"] = [[function(unit, unitOwner) + local state = UnitThreatSituation("player", "target") + if( state == 3 ) then + return ShadowUF.L["Aggro"] + elseif( state == 2 ) then + return ShadowUF.L["High"] + elseif( state == 1 ) then + return ShadowUF.L["Medium"] + end + end]], + ["unit:color:sit"] = [[function(unit, unitOwner) + local state = UnitThreatSituation(unit) + + return state and state > 0 and ShadowUF:Hex(GetThreatStatusColor(state)) + end]], + ["unit:color:aggro"] = [[function(unit, unitOwner) + local state = UnitThreatSituation(unit) + + return state and state >= 3 and ShadowUF:Hex(GetThreatStatusColor(state)) + end]], + ["color:sit"] = [[function(unit, unitOwner) + local state = UnitThreatSituation("player", "target") + + return state and state > 0 and ShadowUF:Hex(GetThreatStatusColor(state)) + end]], + ["color:aggro"] = [[function(unit, unitOwner) + local state = UnitThreatSituation("player", "target") + + return state and state >= 3 and ShadowUF:Hex(GetThreatStatusColor(state)) + end]], + --["unit:scaled:threat"] = [[function(unit, unitOwner, fontString) + -- local scaled = select(3, UnitDetailedThreatSituation(unit)) + -- return scaled and string.format("%d%%", scaled) + --end]], + ["scaled:threat"] = [[function(unit, unitOwner) + local scaled = select(3, UnitDetailedThreatSituation("player", "target")) + return scaled and string.format("%d%%", scaled) + end]], + ["general:sit"] = [[function(unit, unitOwner) + local state = UnitThreatSituation("player") + if( state == 3 ) then + return ShadowUF.L["Aggro"] + elseif( state == 2 ) then + return ShadowUF.L["High"] + elseif( state == 1 ) then + return ShadowUF.L["Medium"] + end + end]], + ["color:gensit"] = [[function(unit, unitOwner) + local state = UnitThreatSituation("player") + + return state and state > 0 and ShadowUF:Hex(GetThreatStatusColor(state)) + end]], + ["status:time"] = [[function(unit, unitOwner) + local offlineStatus = ShadowUF.Tags.offlineStatus + if( not UnitIsConnected(unitOwner) ) then + offlineStatus[unitOwner] = offlineStatus[unitOwner] or GetTime() + return string.format(ShadowUF.L["Off:%s"], ShadowUF:FormatShortTime(GetTime() - offlineStatus[unitOwner])) + end + + offlineStatus[unitOwner] = nil + end]], + ["afk:time"] = [[function(unit, unitOwner) + if( not UnitIsConnected(unitOwner) ) then return end + + local afkStatus = ShadowUF.Tags.afkStatus + local status = UnitIsAFK(unitOwner) and ShadowUF.L["AFK:%s"] or UnitIsDND(unitOwner) and ShadowUF.L["DND:%s"] + if( status ) then + afkStatus[unitOwner] = afkStatus[unitOwner] or GetTime() + return string.format(status, ShadowUF:FormatShortTime(GetTime() - afkStatus[unitOwner])) + end + + afkStatus[unitOwner] = nil + end]], + ["pvp:time"] = [[function(unit, unitOwner) + if( GetPVPTimer() >= 300000 ) then + return nil + end + + return string.format(ShadowUF.L["PVP:%s"], ShadowUF:FormatShortTime(GetPVPTimer() / 1000)) + end]], + ["afk"] = [[function(unit, unitOwner, fontString) + return UnitIsAFK(unitOwner) and ShadowUF.L["AFK"] or UnitIsDND(unitOwner) and ShadowUF.L["DND"] + end]], + ["close"] = [[function(unit, unitOwner) return "|r" end]], + ["smartrace"] = [[function(unit, unitOwner) + return UnitIsPlayer(unit) and ShadowUF.tagFunc.race(unit) or ShadowUF.tagFunc.creature(unit) + end]], + ["reactcolor"] = [[function(unit, unitOwner) + local color + if( not UnitIsFriend(unit, "player") and UnitPlayerControlled(unit) ) then + if( UnitCanAttack("player", unit) ) then + color = ShadowUF.db.profile.healthColors.hostile + else + color = ShadowUF.db.profile.healthColors.enemyUnattack + end + elseif( UnitReaction(unit, "player") ) then + local reaction = UnitReaction(unit, "player") + if( reaction > 4 ) then + color = ShadowUF.db.profile.healthColors.friendly + elseif( reaction == 4 ) then + color = ShadowUF.db.profile.healthColors.neutral + elseif( reaction < 4 ) then + color = ShadowUF.db.profile.healthColors.hostile + end + end + + return color and ShadowUF:Hex(color) + end]], + ["class"] = [[function(unit, unitOwner) + return UnitIsPlayer(unit) and UnitClass(unit) + end]], + ["classcolor"] = [[function(unit, unitOwner) return ShadowUF:GetClassColor(unit) end]], + ["creature"] = [[function(unit, unitOwner) return UnitCreatureFamily(unit) or UnitCreatureType(unit) end]], + ["curhp"] = [[function(unit, unitOwner) + if( UnitIsDead(unit) ) then + return ShadowUF.L["Dead"] + elseif( UnitIsGhost(unit) ) then + return ShadowUF.L["Ghost"] + elseif( not UnitIsConnected(unit) ) then + return ShadowUF.L["Offline"] + end + + return ShadowUF:FormatLargeNumber(UnitHealth(unit)) + end]], + ["colorname"] = [[function(unit, unitOwner) + local color = ShadowUF:GetClassColor(unitOwner) + local name = UnitName(unitOwner) or UNKNOWN + if( not color ) then + return name + end + + return string.format("%s%s|r", color, name) + end]], + ["curpp"] = [[function(unit, unitOwner) + if( UnitPowerMax(unit) <= 0 ) then + return nil + elseif( UnitIsDeadOrGhost(unit) ) then + return 0 + end + + return ShadowUF:FormatLargeNumber(UnitPower(unit)) + end]], + ["curmaxhp"] = [[function(unit, unitOwner) + if( UnitIsDead(unit) ) then + return ShadowUF.L["Dead"] + elseif( UnitIsGhost(unit) ) then + return ShadowUF.L["Ghost"] + elseif( not UnitIsConnected(unit) ) then + return ShadowUF.L["Offline"] + end + + return string.format("%s/%s", ShadowUF:FormatLargeNumber(UnitHealth(unit)), ShadowUF:FormatLargeNumber(UnitHealthMax(unit))) + end]], + ["smart:curmaxhp"] = [[function(unit, unitOwner) + if( UnitIsDead(unit) ) then + return ShadowUF.L["Dead"] + elseif( UnitIsGhost(unit) ) then + return ShadowUF.L["Ghost"] + elseif( not UnitIsConnected(unit) ) then + return ShadowUF.L["Offline"] + end + + return string.format("%s/%s", ShadowUF:SmartFormatNumber(UnitHealth(unit)), ShadowUF:SmartFormatNumber(UnitHealthMax(unit))) + end]], + ["absolutehp"] = [[function(unit, unitOwner) + if( UnitIsDead(unit) ) then + return ShadowUF.L["Dead"] + elseif( UnitIsGhost(unit) ) then + return ShadowUF.L["Ghost"] + elseif( not UnitIsConnected(unit) ) then + return ShadowUF.L["Offline"] + end + + return string.format("%s/%s", UnitHealth(unit), UnitHealthMax(unit)) + end]], + ["abscurhp"] = [[function(unit, unitOwner) + if( UnitIsDead(unit) ) then + return ShadowUF.L["Dead"] + elseif( UnitIsGhost(unit) ) then + return ShadowUF.L["Ghost"] + elseif( not UnitIsConnected(unit) ) then + return ShadowUF.L["Offline"] + end + + return UnitHealth(unit) + end]], + ["absmaxhp"] = [[function(unit, unitOwner) return UnitHealthMax(unit) end]], + ["abscurpp"] = [[function(unit, unitOwner) + if( UnitPowerMax(unit) <= 0 ) then + return nil + elseif( UnitIsDeadOrGhost(unit) ) then + return 0 + end + + return UnitPower(unit) + end]], + ["absmaxpp"] = [[function(unit, unitOwner) + local power = UnitPowerMax(unit) + return power > 0 and power or nil + end]], + ["absolutepp"] = [[function(unit, unitOwner) + local maxPower = UnitPowerMax(unit) + local power = UnitPower(unit) + if( UnitIsDeadOrGhost(unit) ) then + return string.format("0/%s", maxPower) + elseif( maxPower <= 0 ) then + return nil + end + + return string.format("%s/%s", power, maxPower) + end]], + ["curmaxpp"] = [[function(unit, unitOwner) + local maxPower = UnitPowerMax(unit) + local power = UnitPower(unit) + if( UnitIsDeadOrGhost(unit) ) then + return string.format("0/%s", ShadowUF:FormatLargeNumber(maxPower)) + elseif( maxPower <= 0 ) then + return nil + end + + return string.format("%s/%s", ShadowUF:FormatLargeNumber(power), ShadowUF:FormatLargeNumber(maxPower)) + end]], + ["smart:curmaxpp"] = [[function(unit, unitOwner) + local maxPower = UnitPowerMax(unit) + local power = UnitPower(unit) + if( UnitIsDeadOrGhost(unit) ) then + return string.format("0/%s", maxPower) + elseif( maxPower <= 0 ) then + return nil + end + + return string.format("%s/%s", ShadowUF:SmartFormatNumber(power), ShadowUF:SmartFormatNumber(maxPower)) + end]], + ["levelcolor"] = [[function(unit, unitOwner) + local level = UnitLevel(unit) + if( level < 0 and UnitClassification(unit) == "worldboss" ) then + return nil + end + + if( UnitCanAttack("player", unit) ) then + local color = ShadowUF:Hex(GetQuestDifficultyColor(level > 0 and level or 99)) + if( not color ) then + return level > 0 and level or "??" + end + + return color .. (level > 0 and level or "??") .. "|r" + else + return level + end + end]], + ["faction"] = [[function(unit, unitOwner) return UnitFactionGroup(unitOwner) end]], + ["level"] = [[function(unit, unitOwner) + local level = UnitLevel(unit) + return level > 0 and level or UnitClassification(unit) ~= "worldboss" and "??" or nil + end]], + ["maxhp"] = [[function(unit, unitOwner) return ShadowUF:FormatLargeNumber(UnitHealthMax(unit)) end]], + ["maxpp"] = [[function(unit, unitOwner) + local power = UnitPowerMax(unit) + if( power <= 0 ) then + return nil + elseif( UnitIsDeadOrGhost(unit) ) then + return 0 + end + + return ShadowUF:FormatLargeNumber(power) + end]], + ["missinghp"] = [[function(unit, unitOwner) + if( UnitIsDead(unit) ) then + return ShadowUF.L["Dead"] + elseif( UnitIsGhost(unit) ) then + return ShadowUF.L["Ghost"] + elseif( not UnitIsConnected(unit) ) then + return ShadowUF.L["Offline"] + end + + local missing = UnitHealthMax(unit) - UnitHealth(unit) + if( missing <= 0 ) then return nil end + return "-" .. ShadowUF:FormatLargeNumber(missing) + end]], + ["missingpp"] = [[function(unit, unitOwner) + local power = UnitPowerMax(unit) + if( power <= 0 ) then + return nil + end + + local missing = power - UnitPower(unit) + if( missing <= 0 ) then return nil end + return "-" .. ShadowUF:FormatLargeNumber(missing) + end]], + ["def:name"] = [[function(unit, unitOwner) + local deficit = ShadowUF.tagFunc.missinghp(unit, unitOwner) + if( deficit ) then return deficit end + + return ShadowUF.tagFunc.name(unit, unitOwner) + end]], + ["name"] = [[function(unit, unitOwner) return UnitName(unitOwner) or UNKNOWN end]], + ["server"] = [[function(unit, unitOwner) + local server = select(2, UnitName(unitOwner)) + return server ~= "" and server or nil + end]], + ["perhp"] = [[function(unit, unitOwner) + local max = UnitHealthMax(unit) + if( max <= 0 or UnitIsDead(unit) or UnitIsGhost(unit) or not UnitIsConnected(unit) ) then + return "0%" + end + + return math.floor(UnitHealth(unit) / max * 100 + 0.5) .. "%" + end]], + ["perpp"] = [[function(unit, unitOwner) + local maxPower = UnitPowerMax(unit) + if( maxPower <= 0 ) then + return nil + elseif( UnitIsDeadOrGhost(unit) or not UnitIsConnected(unit) ) then + return "0%" + end + + return string.format("%d%%", math.floor(UnitPower(unit) / maxPower * 100 + 0.5)) + end]], + ["plus"] = [[function(unit, unitOwner) local classif = UnitClassification(unit) return (classif == "elite" or classif == "rareelite") and "+" end]], + ["race"] = [[function(unit, unitOwner) return UnitRace(unit) end]], + ["rare"] = [[function(unit, unitOwner) local classif = UnitClassification(unit) return (classif == "rare" or classif == "rareelite") and ShadowUF.L["Rare"] end]], + ["sex"] = [[function(unit, unitOwner) local sex = UnitSex(unit) return sex == 2 and ShadowUF.L["Male"] or sex == 3 and ShadowUF.L["Female"] end]], + ["smartclass"] = [[function(unit, unitOwner) return UnitIsPlayer(unit) and ShadowUF.tagFunc.class(unit) or ShadowUF.tagFunc.creature(unit) end]], + ["status"] = [[function(unit, unitOwner) + if( UnitIsDead(unit) ) then + return ShadowUF.L["Dead"] + elseif( UnitIsGhost(unit) ) then + return ShadowUF.L["Ghost"] + elseif( not UnitIsConnected(unit) ) then + return ShadowUF.L["Offline"] + end + end]], + ["cpoints"] = [[function(unit, unitOwner) + local points = GetComboPoints(ShadowUF.playerUnit) + if( points == 0 ) then + points = GetComboPoints(ShadowUF.playerUnit, ShadowUF.playerUnit) + end + + return points > 0 and points + end]], + ["smartlevel"] = [[function(unit, unitOwner) + local classif = UnitClassification(unit) + if( classif == "worldboss" ) then + return ShadowUF.L["Boss"] + else + local plus = ShadowUF.tagFunc.plus(unit) + local level = ShadowUF.tagFunc.level(unit) + if( plus ) then + return level .. plus + else + return level + end + end + end]], + ["dechp"] = [[function(unit, unitOwner) return string.format("%.1f%%", (UnitHealth(unit) / UnitHealthMax(unit)) * 100) end]], + ["classification"] = [[function(unit, unitOwner) + local classif = UnitClassification(unit) + if( classif == "rare" ) then + return ShadowUF.L["Rare"] + elseif( classif == "rareelite" ) then + return ShadowUF.L["Rare Elite"] + elseif( classif == "elite" ) then + return ShadowUF.L["Elite"] + elseif( classif == "worldboss" ) then + return ShadowUF.L["Boss"] + end + + return nil + end]], + ["shortclassification"] = [[function(unit, unitOwner) + local classif = UnitClassification(unit) + return classif == "rare" and "R" or classif == "rareelite" and "R+" or classif == "elite" and "+" or classif == "worldboss" and "B" + end]], + ["group"] = [[function(unit, unitOwner) + if( GetNumRaidMembers() == 0 ) then return nil end + local name, server = UnitName(unitOwner) + if( server and server ~= "" ) then + name = string.format("%s-%s", name, server) + end + + for i=1, GetNumRaidMembers() do + local raidName, _, group = GetRaidRosterInfo(i) + if( raidName == name ) then + return group + end + end + + return nil + end]], + ["druid:curpp"] = [[function(unit, unitOwner) + if( select(2, UnitClass(unit)) ~= "DRUID" ) then return nil end + local powerType = UnitPowerType(unit) + if( powerType ~= 1 and powerType ~= 3 ) then return nil end + return ShadowUF:FormatLargeNumber(UnitPower(unit, 0)) + end]], + ["druid:abscurpp"] = [[function(unit, unitOwner) + if( select(2, UnitClass(unit)) ~= "DRUID" ) then return nil end + local powerType = UnitPowerType(unit) + if( powerType ~= 1 and powerType ~= 3 ) then return nil end + return UnitPower(unit, 0) + end]], + ["druid:curmaxpp"] = [[function(unit, unitOwner) + if( select(2, UnitClass(unit)) ~= "DRUID" ) then return nil end + local powerType = UnitPowerType(unit) + if( powerType ~= 1 and powerType ~= 3 ) then return nil end + + local maxPower = UnitPowerMax(unit, 0) + local power = UnitPower(unit, 0) + if( UnitIsDeadOrGhost(unit) ) then + return string.format("0/%s", ShadowUF:FormatLargeNumber(maxPower)) + elseif( maxPower == 0 and power == 0 ) then + return nil + end + + return string.format("%s/%s", ShadowUF:FormatLargeNumber(power), ShadowUF:FormatLargeNumber(maxPower)) + end]], + ["druid:absolutepp"] = [[function(unit, unitOwner) + if( select(2, UnitClass(unit)) ~= "DRUID" ) then return nil end + local powerType = UnitPowerType(unit) + if( powerType ~= 1 and powerType ~= 3 ) then return nil end + return UnitPower(unit, 0) + end]], + ["abs:incheal"] = [[function(unit, unitOwner, fontString) + return fontString.incoming and string.format("%d", fontString.incoming) + end]], + ["incheal"] = [[function(unit, unitOwner, fontString) + return fontString.incoming and ShadowUF:FormatLargeNumber(fontString.incoming) or nil + end]], + ["incheal:name"] = [[function(unit, unitOwner, fontString) + return fontString.incoming and string.format("+%d", fontString.incoming) or ShadowUF.tagFunc.name(unit, unitOwner) + end]], + ["unit:raid:targeting"] = [[function(unit, unitOwner, fontString) + if( GetNumRaidMembers() == 0 ) then return nil end + local guid = UnitGUID(unit) + if( not guid ) then return "0" end + + local total = 0 + for i=1, GetNumRaidMembers() do + local unit = ShadowUF.raidUnits[i] + if( UnitGUID(ShadowUF.unitTarget[unit]) == guid ) then + total = total + 1 + end + end + return total + end]], + ["unit:raid:assist"] = [[function(unit, unitOwner, fontString) + if( GetNumRaidMembers() == 0 ) then return nil end + local guid = UnitGUID(ShadowUF.unitTarget[unit]) + if( not guid ) then return "--" end + + local total = 0 + for i=1, GetNumRaidMembers() do + local unit = ShadowUF.raidUnits[i] + if( UnitGUID(ShadowUF.unitTarget[unit]) == guid ) then + total = total + 1 + end + end + return total + end]], +} + +-- Default tag events +Tags.defaultEvents = { + ["hp:color"] = "UNIT_HEALTH UNIT_MAXHEALTH", + ["short:druidform"] = "UNIT_AURA", + ["druidform"] = "UNIT_AURA", + ["guild"] = "UNIT_NAME_UPDATE", -- Not sure when this data is available, guessing + ["abs:incheal"] = "HEALCOMM", + ["incheal:name"] = "HEALCOMM", + ["incheal"] = "HEALCOMM", + ["afk"] = "PLAYER_FLAGS_CHANGED", -- Yes, I know it's called PLAYER_FLAGS_CHANGED, but arg1 is the unit including non-players. + ["afk:time"] = "PLAYER_FLAGS_CHANGED", + ["status:time"] = "UNIT_MANA", + ["pvp:time"] = "PLAYER_FLAGS_CHANGED", + ["curhp"] = "UNIT_HEALTH", + ["abscurhp"] = "UNIT_HEALTH", + ["curmaxhp"] = "UNIT_HEALTH UNIT_MAXHEALTH", + ["absolutehp"] = "UNIT_HEALTH UNIT_MAXHEALTH", + ["smart:curmaxhp"] = "UNIT_HEALTH UNIT_MAXHEALTH", + ["curpp"] = "UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE UNIT_RUNIC_POWER UNIT_DISPLAYPOWER", + ["abscurpp"] = "UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE UNIT_RUNIC_POWER UNIT_DISPLAYPOWER UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_MAXRUNIC_POWERR", + ["curmaxpp"] = "UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE UNIT_RUNIC_POWER UNIT_DISPLAYPOWER UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_MAXRUNIC_POWER", + ["absolutepp"] = "UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE UNIT_RUNIC_POWER UNIT_DISPLAYPOWER UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_MAXRUNIC_POWER", + ["smart:curmaxpp"] = "UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE UNIT_RUNIC_POWER UNIT_DISPLAYPOWER UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_MAXRUNIC_POWER", + ["druid:curpp"] = "UNIT_MANA UNIT_DISPLAYPOWER", + ["druid:abscurpp"] = "UNIT_MANA UNIT_DISPLAYPOWER", + ["druid:curmaxpp"] = "UNIT_MANA UNIT_MAXMANA UNIT_DISPLAYPOWER", + ["druid:absolutepp"] = "UNIT_MANA UNIT_MAXMANA UNIT_DISPLAYPOWER", + ["level"] = "UNIT_LEVEL PLAYER_LEVEL_UP", + ["levelcolor"] = "UNIT_LEVEL PLAYER_LEVEL_UP", + ["maxhp"] = "UNIT_MAXHEALTH", + ["def:name"] = "UNIT_NAME_UPDATE UNIT_MAXHEALTH UNIT_HEALTH", + ["absmaxhp"] = "UNIT_MAXHEALTH", + ["maxpp"] = "UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_MAXRUNIC_POWER", + ["absmaxpp"] = "UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_MAXRUNIC_POWER", + ["missinghp"] = "UNIT_HEALTH UNIT_MAXHEALTH", + ["missingpp"] = "UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE UNIT_MAXRUNIC_POWER UNIT_RUNIC_POWER", + ["name"] = "UNIT_NAME_UPDATE", + ["abbrev:name"] = "UNIT_NAME_UPDATE", + ["server"] = "UNIT_NAME_UPDATE", + ["colorname"] = "UNIT_NAME_UPDATE", + ["perhp"] = "UNIT_HEALTH UNIT_MAXHEALTH", + ["perpp"] = "UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE UNIT_MAXRUNIC_POWER UNIT_RUNIC_POWER", + ["status"] = "UNIT_HEALTH PLAYER_UPDATE_RESTING", + ["smartlevel"] = "UNIT_LEVEL PLAYER_LEVEL_UP UNIT_CLASSIFICATION_CHANGED", + ["cpoints"] = "UNIT_COMBO_POINTS PLAYER_TARGET_CHANGED", + ["rare"] = "UNIT_CLASSIFICATION_CHANGED", + ["classification"] = "UNIT_CLASSIFICATION_CHANGED", + ["shortclassification"] = "UNIT_CLASSIFICATION_CHANGED", + ["dechp"] = "UNIT_HEALTH UNIT_MAXHEALTH", + ["group"] = "RAID_ROSTER_UPDATE", + ["unit:color:aggro"] = "UNIT_THREAT_SITUATION_UPDATE", + ["color:aggro"] = "UNIT_THREAT_SITUATION_UPDATE", + ["situation"] = "UNIT_THREAT_SITUATION_UPDATE", + ["color:sit"] = "UNIT_THREAT_SITUATION_UPDATE", + ["scaled:threat"] = "UNIT_THREAT_SITUATION_UPDATE", + ["general:sit"] = "UNIT_THREAT_SITUATION_UPDATE", + ["color:gensit"] = "UNIT_THREAT_SITUATION_UPDATE", + ["unit:scaled:threat"] = "UNIT_THREAT_SITUATION_UPDATE", + ["unit:color:sit"] = "UNIT_THREAT_SITUATION_UPDATE", + ["unit:situation"] = "UNIT_THREAT_SITUATION_UPDATE", +} + +-- Default update frequencies for tag updating, used if it's needed to override the update speed +-- or it can't be purely event based +Tags.defaultFrequents = { + ["afk"] = 1, + ["afk:time"] = 1, + ["status:time"] = 1, + ["pvp:time"] = 1, + ["scaled:threat"] = 1, + ["unit:scaled:threat"] = 1, + ["unit:raid:targeting"] = 0.50, + ["unit:raid:assist"] = 0.50, +} + +-- Default tag categories +Tags.defaultCategories = { + ["hp:color"] = "health", + ["abs:incheal"] = "health", + ["incheal"] = "health", + ["incheal:name"] = "health", + ["smart:curmaxhp"] = "health", + ["smart:curmaxpp"] = "health", + ["afk"] = "status", + ["afk:time"] = "status", + ["status:time"] = "status", + ["pvp:time"] = "status", + ["cpoints"] = "misc", + ["smartlevel"] = "classification", + ["classification"] = "classification", + ["shortclassification"] = "classification", + ["rare"] = "classification", + ["plus"] = "classification", + ["sex"] = "misc", + ["smartclass"] = "classification", + ["smartrace"] = "classification", + ["status"] = "status", + ["race"] = "classification", + ["level"] = "classification", + ["maxhp"] = "health", + ["maxpp"] = "power", + ["missinghp"] = "health", + ["missingpp"] = "power", + ["name"] = "misc", + ["abbrev:name"] = "misc", + ["server"] = "misc", + ["perhp"] = "health", + ["perpp"] = "power", + ["class"] = "classification", + ["classcolor"] = "classification", + ["creature"] = "classification", + ["short:druidform"] = "classification", + ["druidform"] = "classification", + ["curhp"] = "health", + ["curpp"] = "power", + ["curmaxhp"] = "health", + ["curmaxpp"] = "power", + ["levelcolor"] = "classification", + ["def:name"] = "health", + ["faction"] = "classification", + ["colorname"] = "misc", + ["guild"] = "misc", + ["absolutepp"] = "power", + ["absolutehp"] = "health", + ["absmaxhp"] = "health", + ["abscurhp"] = "health", + ["absmaxpp"] = "power", + ["abscurpp"] = "power", + ["reactcolor"] = "classification", + ["dechp"] = "health", + ["group"] = "misc", + ["close"] = "misc", + ["druid:curpp"] = "power", + ["druid:abscurpp"] = "power", + ["druid:curmaxpp"] = "power", + ["druid:absolutepp"] = "power", + ["situation"] = "playerthreat", + ["color:sit"] = "playerthreat", + ["scaled:threat"] = "playerthreat", + ["general:sit"] = "playerthreat", + ["color:gensit"] = "playerthreat", + ["color:aggro"] = "playerthreat", + ["unit:scaled:threat"] = "threat", + ["unit:color:sit"] = "threat", + ["unit:situation"] = "threat", + ["unit:color:aggro"] = "threat", + ["unit:raid:assist"] = "raid", + ["unit:raid:targeting"] = "raid", +} + +-- Default tag help +Tags.defaultHelp = { + ["hp:color"] = L["Color code based on percentage of HP left on the unit, this works the same way as the color by health option. But for text instead of the entire bar."], + ["guild"] = L["Show's the units guild name if they are in a guild."], + ["short:druidform"] = L["Short version of [druidform], C = Cat, B = Bear, F = Flight and so on."], + ["druidform"] = L["Returns the units current form if they are a druid, Cat for Cat Form, Moonkin for Moonkin and so on."], + ["abs:incheal"] = L["Absolute incoming heal value, if 10,000 healing is incoming it will show 10,000."], + ["incheal"] = L["Shorten incoming heal value, if 13,000 healing is incoming it will show 13k."], + ["incheal:name"] = L["If the unit has heals incoming, it will show the absolute incoming heal value, otherwise it will show the units name."], + ["smart:curmaxhp"] = L["Smart number formating for [curmaxhp], numbers below 1,000,000 are left as is, numbers above 1,000,000 will use the short version such as 1m."], + ["smart:curmaxpp"] = L["Smart number formating for [curmaxpp], numbers below 1,000,000 are left as is, numbers above 1,000,000 will use the short version such as 1m."], + ["pvp:time"] = L["Shows how long until your PVP flag drops, will not show if the flag is manually on or you are in a hostile zone.|n|nThis will only work for yourself, you cannot use it to see the time left on your party or raid."], + ["afk:time"] = L["Shows how long an unit has been AFK or DND."], + ["status:time"] = L["Shows how long an unit has been offline."], + ["afk"] = L["Shows AFK, DND or nothing depending on the units away status."], + ["cpoints"] = L["Total number of combo points you have on your target."], + ["smartlevel"] = L["Smart level, returns Boss for bosses, +50 for a level 50 elite mob, or just 80 for a level 80."], + ["classification"] = L["Units classification, Rare, Rare Elite, Elite, Boss, nothing is shown if they aren't any of those."], + ["shortclassification"] = L["Short classifications, R for Rare, R+ for Rare Elite, + for Elite, B for boss, nothing is shown if they aren't any of those."], + ["rare"] = L["Returns Rare if the unit is a rare or rare elite mob."], + ["plus"] = L["Returns + if the unit is an elite or rare elite mob."], + ["sex"] = L["Returns the units sex."], + ["smartclass"] = L["If the unit is a player then class is returned, if it's a NPC then the creature type."], + ["smartrace"] = L["If the unit is a player then race is returned, if it's a NPC then the creature type."], + ["status"] = L["Shows Offline, Dead, Ghost or nothing depending on the units current status."], + ["race"] = L["Units race, Blood Elf, Tauren, Troll (unfortunately) and so on."], + ["level"] = L["Level without any coloring."], + ["maxhp"] = L["Max health, uses a short format, 17750 is formatted as 17.7k, values below 10000 are formatted as is."], + ["maxpp"] = L["Max power, uses a short format, 16000 is formatted as 16k, values below 10000 are formatted as is."], + ["missinghp"] = L["Amount of health missing, if none is missing nothing is shown. Uses a short format, -18500 is shown as -18.5k, values below 10000 are formatted as is."], + ["missingpp"] = L["Amount of power missing, if none is missing nothing is shown. Uses a short format, -13850 is shown as 13.8k, values below 10000 are formatted as is."], + ["name"] = L["Unit name"], + ["server"] = L["Unit server, if they are from your server then nothing is shown."], + ["perhp"] = L["Returns current health as a percentage, if the unit is dead or offline than that is shown instead."], + ["perpp"] = L["Returns current power as a percentage."], + ["class"] = L["Class name without coloring, use [classcolor][class][close] if you want the class name to be colored by class."], + ["classcolor"] = L["Color code for the class, use [classcolor][class][close] if you want the class text to be colored by class"], + ["creature"] = L["Creature type, returns Felguard if the unit is a Felguard, Wolf if it's a Wolf and so on."], + ["curhp"] = L["Current health, uses a short format, 11500 is formatted as 11.5k, values below 10000 are formatted as is."], + ["curpp"] = L["Current power, uses a short format, 12750 is formatted as 12.7k, values below 10000 are formatted as is."], + ["curmaxhp"] = L["Current and maximum health, formatted as [curhp]/[maxhp], if the unit is dead or offline then that is shown instead."], + ["curmaxpp"] = L["Current and maximum power, formatted as [curpp]/[maxpp]."], + ["levelcolor"] = L["Returns the color code based off of the units level compared to yours. If you cannot attack them then no color is returned."], + ["def:name"] = L["When the unit is mising health, the [missinghp] tag is shown, when they are at full health then the [name] tag is shown. This lets you see -1000 when they are missing 1000 HP, but their name when they are not missing any."], + ["faction"] = L["Units alignment, Thrall will return Horde, Magni Bronzebeard will return Alliance."], + ["colorname"] = L["Unit name colored by class."], + ["absolutepp"] = L["Shows current and maximum power in absolute form, 18000 power will be showed as 18000 power."], + ["absolutehp"] = L["Shows current and maximum health in absolute form, 17500 health will be showed as 17500 health."], + ["absmaxhp"] = L["Shows maximum health in absolute form, 14000 health is showed as 14000 health."], + ["abscurhp"] = L["Shows current health value in absolute form meaning 15000 health is shown as 15000."], + ["absmaxpp"] = L["Shows maximum power in absolute form, 13000 power is showed as 13000 power."], + ["abscurpp"] = L["Shows current power value in absolute form, 15000 power will be displayed as 1500 still."], + ["reactcolor"] = L["Reaction color code, use [reactcolor][name][close] to color the units name by their reaction."], + ["dechp"] = L["Shows the units health as a percentage rounded to the first decimal, meaning 61 out of 110 health is shown as 55.4%."], + ["abbrev:name"] = L["Abbreviates unit names above 10 characters, \"Dark Rune Champion\" becomes \"D.R.Champion\" and \"Dark Rune Commoner\" becomes \"D.R.Commoner\"."], + ["group"] = L["Shows current group number of the unit."], + ["close"] = L["Closes a color code, prevents colors from showing up on text that you do not want it to."], + ["druid:curpp"] = string.format(L["Works the same as %s, but this is only shown if the unit is in Cat or Bear form."], "currpp"), + ["druid:abscurpp"] = string.format(L["Works the same as %s, but this is only shown if the unit is in Cat or Bear form."], "abscurpp"), + ["druid:curmaxpp"] = string.format(L["Works the same as %s, but this is only shown if the unit is in Cat or Bear form."], "curmaxpp"), + ["druid:absolutepp"] = string.format(L["Works the same as %s, but this is only shown if the unit is in Cat or Bear form."], "absolutepp"), + ["situation"] = L["Returns text based on your threat situation with your target: Aggro for Aggro, High for being close to taking aggro, and Medium as a general warning to be wary."], + ["color:sit"] = L["Returns a color code of the threat situation with your target: Red for Aggro, Orange for High threat and Yellow to be careful."], + ["scaled:threat"] = L["Returns a scaled threat percent of your aggro on your current target, always 0 - 100%."], + ["general:sit"] = L["Returns text based on your general threat situation on all units: Aggro for Aggro, High for being near to pulling aggro and Medium as a general warning."], + ["color:gensit"] = L["Returns a color code of your general threat situation on all units: Red for Aggro, Orange for High threat and Yellow to watch out."], + ["unit:scaled:threat"] = L["Returns the scaled threat percentage for the unit, if you put this on a party member you would see the percentage of how close they are to getting any from any hostile mobs. Always 0 - 100%.|nThis cannot be used on target of target or focus target types of units."], + ["unit:color:sit"] = L["Returns the color code for the units threat situation in general: Red for Aggro, Orange for High threat and Yellow to watch out.|nThis cannot be used on target of target or focus target types of units."], + ["unit:situation"] = L["Returns text based on the units general threat situation: Aggro for Aggro, High for being close to taking aggro, and Medium as a warning to be wary.|nThis cannot be used on target of target or focus target types of units."], + ["unit:color:aggro"] = L["Same as [unit:color:sit] except it only returns red if the unit has aggro, rather than transiting from yellow -> orange -> red."], + ["color:aggro"] = L["Same as [color:sit] except it only returns red if you have aggro, rather than transiting from yellow -> orange -> red."], + ["unit:raid:targeting"] = L["How many people in your raid are targeting the unit, for example if you put this on yourself it will show how many people are targeting you. This includes you in the count!"], + ["unit:raid:assist"] = L["How many people are assisting the unit, for example if you put this on yourself it will show how many people are targeting your target. This includes you in the count!"], +} + +Tags.defaultNames = { + ["incheal:name"] = L["Incoming heal/Name"], + ["unit:scaled:threat"] = L["Unit scaled threat"], + ["unit:color:sit"] = L["Unit colored situation"], + ["unit:situation"] = L["Unit situation name"], + ["hp:color"] = L["Health color"], + ["guild"] = L["Guild name"], + ["druidform"] = L["Druid form"], + ["short:druidform"] = L["Druid form (Short)"], + ["abs:incheal"] = L["Incoming heal (Absolute)"], + ["incheal"] = L["Incoming heal (Short)"], + ["abbrev:name"] = L["Name (Abbreviated)"], + ["smart:curmaxhp"] = L["Cur/Max HP (Smart)"], + ["smart:curmaxpp"] = L["Cur/Max PP (Smart)"], + ["pvp:time"] = L["PVP timer"], + ["afk:time"] = L["AFK timer"], + ["status:time"] = L["Offline timer"], + ["afk"] = L["AFK status"], + ["cpoints"] = L["Combo points"], + ["smartlevel"] = L["Smart level"], + ["classification"] = L["Classificaiton"], + ["shortclassification"] = L["Short classification"], + ["rare"] = L["Rare indicator"], + ["plus"] = L["Short elite indicator"], + ["sex"] = L["Sex"], + ["smartclass"] = L["Class (Smart)"], + ["smartrace"] = L["Race (Smart)"], + ["status"] = L["Status"], + ["race"] = L["Race"], + ["level"] = L["Level"], + ["maxhp"] = L["Max HP (Short)"], + ["maxpp"] = L["Max power (Short)"], + ["missinghp"] = L["Missing HP (Short)"], + ["missingpp"] = L["Missing power (Short)"], + ["name"] = L["Unit name"], + ["server"] = L["Unit server"], + ["perhp"] = L["Percent HP"], + ["perpp"] = L["Percent power"], + ["class"] = L["Class"], + ["classcolor"] = L["Class color tag"], + ["creature"] = L["Creature type"], + ["curhp"] = L["Current HP (Short)"], + ["curpp"] = L["Current Power (Short)"], + ["curmaxhp"] = L["Cur/Max HP (Short)"], + ["curmaxpp"] = L["Cur/Max Power (Short)"], + ["levelcolor"] = L["Level (Colored)"], + ["def:name"] = L["Deficit/Unit Name"], + ["faction"] = L["Unit faction"], + ["colorname"] = L["Unit name (Class colored)"], + ["absolutepp"] = L["Cur/Max power (Absolute)"], + ["absolutehp"] = L["Cur/Max HP (Absolute)"], + ["absmaxhp"] = L["Max HP (Absolute)"], + ["abscurhp"] = L["Current HP (Absolute)"], + ["absmaxpp"] = L["Max power (Absolute)"], + ["abscurpp"] = L["Current power (Absolute)"], + ["reactcolor"] = L["Reaction color tag"], + ["dechp"] = L["Decimal percent HP"], + ["group"] = L["Group number"], + ["close"] = L["Close color"], + ["druid:curpp"] = L["Current power (Druid)"], + ["druid:abscurpp"] = L["Current power (Druid/Absolute)"], + ["druid:curmaxpp"] = L["Cur/Max power (Druid)"], + ["druid:absolutepp"] = L["Current health (Druid/Absolute)"], + ["situation"] = L["Threat situation"], + ["color:sit"] = L["Color code for situation"], + ["scaled:threat"] = L["Scaled threat percent"], + ["general:sit"] = L["General threat situation"], + ["color:gensit"] = L["Color code for general situation"], + ["color:aggro"] = L["Color code on aggro"], + ["unit:color:aggro"] = L["Unit color code on aggro"], + ["unit:raid:targeting"] = L["Raid targeting unit"], + ["unit:raid:assist"] = L["Raid assisting unit"], +} + +-- List of event types +Tags.eventType = { + ["UNIT_ENERGY"] = "power", + ["UNIT_FOCUS"] = "power", + ["UNIT_MANA"] = "power", + ["UNIT_RAGE"] = "power", + ["UNIT_RUNIC_POWER"] = "power", + ["UNIT_HEALTH"] = "health", + ["UNIT_MAXHEALTH"] = "health", + ["RAID_ROSTER_UPDATE"] = "unitless", + ["RAID_TARGET_UPDATE"] = "unitless", + ["PLAYER_TARGET_CHANGED"] = "unitless", + ["PARTY_MEMBERS_CHANGED"] = "unitless", + ["PARTY_LEADER_CHANGED"] = "unitless", + ["PLAYER_ENTERING_WORLD"] = "unitless", + ["PLAYER_XP_UPDATE"] = "unitless", + ["PLAYER_TOTEM_UPDATE"] = "unitless", + ["PLAYER_LEVEL_UP"] = "unitless", + ["UPDATE_EXHAUSTION"] = "unitless", + ["PLAYER_UPDATE_RESTING"] = "unitless", + ["UNIT_COMBO_POINTS"] = "unitless", +} + +-- Tag groups that have a special filter that can't be used on certain units, like the threat API's +Tags.unitBlacklist = { + ["threat"] = "%w+target", +} + +-- Single tags that can only be used on a single unit +Tags.unitRestrictions = { + ["pvp:time"] = "player", +} + +-- Event scanner to automatically figure out what events a tag will need +local function loadAPIEvents() + if( Tags.APIEvents ) then return end + Tags.APIEvents = { + ["InCombatLockdown"] = "PLAYER_REGEN_ENABLED PLAYER_REGEN_DISABLED", + ["UnitLevel"] = "UNIT_LEVEL", + ["UnitName"] = "UNIT_NAME_UPDATE", + ["UnitClassification"] = "UNIT_CLASSIFICATION_CHANGED", + ["UnitFactionGroup"] = "UNIT_FACTION PLAYER_FLAGS_CHANGED", + ["UnitHealth%("] = "UNIT_HEALTH", + ["UnitHealthMax"] = "UNIT_MAXHEALTH", + ["UnitPower%("] = "UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE UNIT_RUNIC_POWER", + ["UnitPowerMax"] = "UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_MAXRUNIC_POWER", + ["UnitPowerType"] = "UNIT_DISPLAYPOWER", + ["UnitIsDead"] = "UNIT_HEALTH", + ["UnitIsGhost"] = "UNIT_HEALTH", + ["UnitIsConnected"] = "UNIT_HEALTH", + ["UnitIsAFK"] = "PLAYER_FLAGS_CHANGED", + ["UnitIsDND"] = "PLAYER_FLAGS_CHANGED", + ["UnitIsPVP"] = "PLAYER_FLAGS_CHANGED UNIT_FACTION", + ["UnitIsPartyLeader"] = "PARTY_LEADER_CHANGED PARTY_MEMBERS_CHANGED", + ["UnitIsPVPFreeForAll"] = "PLAYER_FLAGS_CHANGED UNIT_FACTION", + ["UnitCastingInfo"] = "UNIT_SPELLCAST_START UNIT_SPELLCAST_STOP UNIT_SPELLCAST_FAILED UNIT_SPELLCAST_INTERRUPTED UNIT_SPELLCAST_DELAYED", + ["UnitChannelInfo"] = "UNIT_SPELLCAST_CHANNEL_START UNIT_SPELLCAST_CHANNEL_STOP UNIT_SPELLCAST_CHANNEL_INTERRUPTED UNIT_SPELLCAST_CHANNEL_UPDATE", + ["UnitAura"] = "UNIT_AURA", + ["UnitBuff"] = "UNIT_AURA", + ["UnitDebuff"] = "UNIT_AURA", + ["UnitXPMax"] = "UNIT_PET_EXPERIENCE PLAYER_XP_UPDATE PLAYER_LEVEL_UP", + ["UnitXP%("] = "UNIT_PET_EXPERIENCE PLAYER_XP_UPDATE PLAYER_LEVEL_UP", + ["GetTotemInfo"] = "PLAYER_TOTEM_UPDATE", + ["GetXPExhaustion"] = "UPDATE_EXHAUSTION", + ["GetWatchedFactionInfo"] = "UPDATE_FACTION", + ["GetRuneCooldown"] = "RUNE_POWER_UPDATE", + ["GetRuneType"] = "RUNE_TYPE_UPDATE", + ["GetRaidTargetIndex"] = "RAID_TARGET_UPDATE", + ["GetComboPoints"] = "UNIT_COMBO_POINTS", + ["GetNumPartyMembers"] = "PARTY_MEMBERS_CHANGED", + ["GetNumRaidMembers"] = "RAID_ROSTER_UPDATE", + ["GetRaidRosterInfo"] = "RAID_ROSTER_UPDATE", + ["GetPetHappiness"] = "UNIT_HAPPINESS", + ["GetReadyCheckStatus"] = "READY_CHECK READY_CHECK_CONFIRM READY_CHECK_FINISHED", + ["GetLootMethod"] = "PARTY_LOOT_METHOD_CHANGED", + ["GetThreatStatusColor"] = "UNIT_THREAT_SITUATION_UPDATE", + ["UnitThreatSituation"] = "UNIT_THREAT_SITUATION_UPDATE", + ["UnitDetailedThreatSituation"] = "UNIT_THREAT_SITUATION_UPDATE", + } +end + +-- Scan the actual tag code to find the events it uses +local alreadyScanned = {} +function Tags:IdentifyEvents(code, parentTag) + -- Already scanned this tag, prevents infinite recursion + if( parentTag and alreadyScanned[parentTag] ) then + return "" + -- Flagged that we already took care of this + elseif( parentTag ) then + alreadyScanned[parentTag] = true + else + for k in pairs(alreadyScanned) do alreadyScanned[k] = nil end + loadAPIEvents() + end + + -- Scan our function list to see what APIs are used + local eventList = "" + for func, events in pairs(self.APIEvents) do + if( string.match(code, func) ) then + eventList = eventList .. events .. " " + end + end + + -- Scan if they use any tags, if so we need to check them as well to see what content is used + for tag in string.gmatch(code, "tagFunc\.(%w+)%(") do + local code = ShadowUF.Tags.defaultTags[tag] or ShadowUF.db.profile.tags[tag] and ShadowUF.db.profile.tags[tag].func + eventList = eventList .. " " .. self:IdentifyEvents(code, tag) + end + + -- Remove any duplicate events + if( not parentTag ) then + local tagEvents = {} + for event in string.gmatch(string.trim(eventList), "%S+") do + tagEvents[event] = true + end + + eventList = "" + for event in pairs(tagEvents) do + eventList = eventList .. event .. " " + end + end + + -- And give them our nicely outputted data + return string.trim(eventList or "") +end + + +-- Checker function, makes sure tags are all happy +--[===[@debug@ +function Tags:Verify() + local fine = true + for tag, events in pairs(self.defaultEvents) do + if( not self.defaultTags[tag] ) then + print(string.format("Found event for %s, but no tag associated with it.", tag)) + fine = nil + end + end + + for tag, data in pairs(self.defaultTags) do + if( not self.defaultTags[tag] ) then + print(string.format("Found tag for %s, but no event associated with it.", tag)) + fine = nil + end + + if( not self.defaultHelp[tag] ) then + print(string.format("Found tag for %s, but no help text associated with it.", tag)) + fine = nil + end + + if( not self.defaultNames[tag] ) then + print(string.format("Found tag for %s, but no name associated with it.", tag)) + fine = nil + end + + if( not self.defaultCategories[tag] ) then + print(string.format("Found tag for %s, but no category associated with it.", tag)) + fine = nil + end + + local funct, msg = loadstring("return " .. data) + if( not funct and msg ) then + print(string.format("Failed to load tag %s.", tag)) + print(msg) + fine = nil + else + funct("player") + end + end + + if( fine ) then + print("Verified tags, everything is fine.") + end +end +--@end-debug@]===] diff --git a/ShadowedUnitFrames/modules/totems.lua b/ShadowedUnitFrames/modules/totems.lua new file mode 100644 index 0000000..fd9e92d --- /dev/null +++ b/ShadowedUnitFrames/modules/totems.lua @@ -0,0 +1,111 @@ +local Totems = {} +local totemColors = {} +local MAX_TOTEMS = MAX_TOTEMS + +-- Death Knights untalented ghouls are guardians and are considered totems........... so set it up for them +if( select(2, UnitClass("player")) == "DEATHKNIGHT" ) then + MAX_TOTEMS = 1 + ShadowUF:RegisterModule(Totems, "totemBar", ShadowUF.L["Guardian bar"], true, "DEATHKNIGHT") +else + ShadowUF:RegisterModule(Totems, "totemBar", ShadowUF.L["Totem bar"], true, "SHAMAN") +end + +function Totems:OnEnable(frame) + if( not frame.totemBar ) then + frame.totemBar = CreateFrame("Frame", nil, frame) + frame.totemBar.totems = {} + + for id=1, MAX_TOTEMS do + local totem = ShadowUF.Units:CreateBar(frame) + totem:SetFrameLevel(1) + totem:SetMinMaxValues(0, 1) + totem:SetValue(0) + totem.id = MAX_TOTEMS == 1 and 1 or TOTEM_PRIORITIES[id] + + if( id > 1 ) then + totem:SetPoint("TOPLEFT", frame.totemBar.totems[id - 1], "TOPRIGHT", 1, 0) + else + totem:SetPoint("TOPLEFT", frame.totemBar, "TOPLEFT", 0, 0) + end + + table.insert(frame.totemBar.totems, totem) + end + + if( MAX_TOTEMS == 1 ) then + totemColors[1] = ShadowUF.db.profile.classColors.PET + else + totemColors[1] = {r = 1, g = 0, b = 0.4} + totemColors[2] = {r = 0, g = 1, b = 0.4} + totemColors[3] = {r = 0, g = 0.4, b = 1} + totemColors[4] = {r = 0.90, g = 0.90, b = 0.90} + end + end + + frame:RegisterNormalEvent("PLAYER_TOTEM_UPDATE", self, "Update") + frame:RegisterUpdateFunc(self, "Update") +end + +function Totems:OnDisable(frame) + frame:UnregisterAll(self) +end + +function Totems:OnLayoutApplied(frame) + if( frame.visibility.totemBar ) then + local barWidth = (frame.totemBar:GetWidth() - (MAX_TOTEMS - 1)) / MAX_TOTEMS + + for _, totem in pairs(frame.totemBar.totems) do + if( ShadowUF.db.profile.units[frame.unitType].totemBar.background ) then + local color = ShadowUF.db.profile.bars.backgroundColor or ShadowUF.db.profile.units[frame.unitType].totemBar.backgroundColor or totemColors[totem.id] + totem.background:SetTexture(ShadowUF.Layout.mediaPath.statusbar) + totem.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + totem.background:Show() + else + totem.background:Hide() + end + + totem:SetHeight(frame.totemBar:GetHeight()) + totem:SetWidth(barWidth) + totem:SetStatusBarTexture(ShadowUF.Layout.mediaPath.statusbar) + totem:SetStatusBarColor(totemColors[totem.id].r, totemColors[totem.id].g, totemColors[totem.id].b, ShadowUF.db.profile.bars.alpha) + totem:GetStatusBarTexture():SetHorizTile(false) + end + end +end + +local function totemMonitor(self, elapsed) + local time = GetTime() + self:SetValue(self.endTime - time) + + if( time >= self.endTime ) then + self:SetValue(0) + self:SetScript("OnUpdate", nil) + end +end + +function Totems:Update(frame) + local totalActive = 0 + for _, indicator in pairs(frame.totemBar.totems) do + local have, name, start, duration = GetTotemInfo(indicator.id) + if( have and start > 0 ) then + indicator.have = true + indicator.endTime = start + duration + indicator:SetMinMaxValues(0, duration) + indicator:SetValue(indicator.endTime - GetTime()) + indicator:SetScript("OnUpdate", totemMonitor) + indicator:SetAlpha(1.0) + + totalActive = totalActive + 1 + + elseif( indicator.have ) then + indicator.have = nil + indicator:SetScript("OnUpdate", nil) + indicator:SetMinMaxValues(0, 1) + indicator:SetValue(0) + end + end + + -- Only guardian timers should auto hide, nothing else + if( MAX_TOTEMS == 1 ) then + ShadowUF.Layout:SetBarVisibility(frame, "totemBar", totalActive > 0) + end +end diff --git a/ShadowedUnitFrames/modules/units.lua b/ShadowedUnitFrames/modules/units.lua new file mode 100644 index 0000000..8255da4 --- /dev/null +++ b/ShadowedUnitFrames/modules/units.lua @@ -0,0 +1,1173 @@ +local Units = {headerFrames = {}, unitFrames = {}, frameList = {}, unitEvents = {}} +Units.childUnits = {["partytarget"] = "party", ["partypet"] = "party", ["maintanktarget"] = "maintank", ["mainassisttarget"] = "mainassist", ["bosstarget"] = "boss", ["arenatarget"] = "arena", ["arenapet"] = "arena"} +Units.zoneUnits = {["arena"] = "arena", ["boss"] = "raid"} + +local stateMonitor = CreateFrame("Frame", nil, nil, "SecureHandlerBaseTemplate") +local playerClass = select(2, UnitClass("player")) +local unitFrames, headerFrames, frameList, unitEvents, childUnits, queuedCombat = Units.unitFrames, Units.headerFrames, Units.frameList, Units.unitEvents, Units.childUnits, {} +local _G = getfenv(0) + +ShadowUF.Units = Units +ShadowUF:RegisterModule(Units, "units") + +-- 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 + +-- Register an event that should always call the frame +local function RegisterNormalEvent(self, event, handler, func) + -- 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 + + self:RegisterEvent(event) + 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] ) 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), event, 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 + 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 + +-- 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 + +-- Event handling +local function OnEvent(self, event, unit, ...) + if( not unitEvents[event] or self.unit == 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 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 + +-- *target units do not give events, polling is necessary here +local function TargetUnitUpdate(self, elapsed) + self.timeElapsed = self.timeElapsed + elapsed + + if( self.timeElapsed >= 0.50 ) then + self.timeElapsed = self.timeElapsed - 0.50 + + -- Have to make sure the unit exists or else the frame will flash offline for a second until it hides + if( UnitExists(self.unit) ) then + self:FullUpdate() + end + end +end + +-- Deal with enabling modules inside a zone +local function SetVisibility(self) + local layoutUpdate + local instanceType = select(2, IsInInstance()) + + -- 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 + 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) ) 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 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 ShadowUF.db.profile.units[frame.unitType].disableVehicle ) ) then + frame.inVehicle = false + frame.unit = frame.unitOwner + frame.unitGUID = UnitGUID(frame.unit) + frame:FullUpdate() + end + + -- Keep track of the actual players unit so we can quickly see what unit to scan + --[[ + if( frame.unitOwner == "player" and ShadowUF.playerUnit ~= frame.unit ) then + ShadowUF.playerUnit = frame.unit + + if( not ShadowUF.db.profile.hidden.buffs and ShadowUF.db.profile.units.player.enabled and BuffFrame:IsVisible() ) then + PlayerFrame.unit = frame.unit + BuffFrame_Update() + end + 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 = guid + frame:FullUpdate() + else + frame.unitGUID = UnitGUID(frame.unit) + frame:FullUpdate() + end +end + +local function ShowMenu(self) + if( UnitIsUnit(self.unit, "player") ) then + ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "cursor") + elseif( self.unit == "pet" or self.unit == "vehicle" ) then + ToggleDropDownMenu(1, nil, PetFrameDropDown, "cursor") + elseif( self.unit == "target" ) then + ToggleDropDownMenu(1, nil, TargetFrameDropDown, "cursor") + elseif( self.unitType == "boss" ) then + ToggleDropDownMenu(1, nil, _G["Boss" .. self.unitID .. "TargetFrameDropDown"], "cursor") + elseif( self.unit == "focus" ) then + ToggleDropDownMenu(1, nil, FocusFrameDropDown, "cursor") + elseif( self.unitRealType == "party" ) then + ToggleDropDownMenu(1, nil, _G["PartyMemberFrame" .. self.unitID .. "DropDown"], "cursor") + elseif( self.unitRealType == "raid" ) then + HideDropDownMenu(1) + + local menuFrame = FriendsDropDown + menuFrame.displayMode = "MENU" + menuFrame.initialize = RaidFrameDropDown_Initialize + menuFrame.userData = self.unitID + menuFrame.unit = self.unitOwner + menuFrame.name = UnitName(self.unitOwner) + menuFrame.id = self.unitID + ToggleDropDownMenu(1, nil, menuFrame, "cursor") + 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 + +-- 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 + if( self.unit and unitFrames[self.unit] == self ) then unitFrames[self.unit] = 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.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[unit] = 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:FullUpdate() + return + end + + self.unitInitialized = true + + -- Add to Clique + ClickCastFrames = ClickCastFrames or {} + ClickCastFrames[self] = true + + -- 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 + + -- 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: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) ) 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", 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") + + -- Automatically do a full update on focus change + elseif( self.unit == "focus" ) then + self.isUnitVolatile = true + self:RegisterNormalEvent("PLAYER_FOCUS_CHANGED", Units, "CheckUnitStatus") + + elseif( self.unit == "player" ) then + self:SetAttribute("toggleForVehicle", true) + + -- 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") + + -- Update boss + elseif( self.unitType == "boss" ) then + self.timeElapsed = 0 + self:SetScript("OnUpdate", TargetUnitUpdate) + self:RegisterNormalEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT", self, "FullUpdate") + + -- Check for a unit guid to do a full update + elseif( self.unitRealType == "raid" ) then + self:RegisterNormalEvent("RAID_ROSTER_UPDATE", Units, "CheckGroupedUnitStatus") + self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus") + + -- Party members need to watch for changes + elseif( self.unitRealType == "party" ) then + self:RegisterNormalEvent("PARTY_MEMBERS_CHANGED", Units, "CheckGroupedUnitStatus") + self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus") + + -- *target units are not real units, thus they do not receive events and must be polled for data + elseif( ShadowUF.fakeUnits[self.unitRealType] ) then + self.timeElapsed = 0 + self:SetScript("OnUpdate", TargetUnitUpdate) + + -- 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 == "raid" ) then + self.unitRealOwner = ShadowUF.raidUnits[self.unitID] + elseif( self.unitRealType == "arenatarget" ) then + self.unitRealOwner = ShadowUF.arenaUnits[self.unitID] + 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.menu = ShowMenu + self:SetVisibility() + Units:CheckUnitStatus(self) +end + +Units.OnAttributeChanged = OnAttributeChanged + +-- Header unit initialized +local function initializeUnit(self) + local unitType = self:GetParent().unitType + local config = ShadowUF.db.profile.units[unitType] + + self.ignoreAnchor = true + self.unitType = unitType + self:SetAttribute("initial-height", config.height) + self:SetAttribute("initial-width", config.width) + self:SetAttribute("initial-scale", config.scale) + self:SetAttribute("toggleForVehicle", true) + + Units:CreateUnit(self) +end + +-- Show tooltip +local function OnEnter(self) + if( not ShadowUF.db.profile.tooltipCombat or not InCombatLockdown() ) then + UnitFrame_OnEnter(self) + end +end + +-- Reset the fact that we clamped the dropdown to the screen to be safe +DropDownList1:HookScript("OnHide", function(self) + self:SetClampedToScreen(false) +end) + +-- Reposition the dropdown +local function PostClick(self) + if( UIDROPDOWNMENU_OPEN_MENU and DropDownList1:IsShown() ) then + DropDownList1:ClearAllPoints() + DropDownList1:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, 0) + DropDownList1:SetClampedToScreen(true) + end +end + +-- Create the generic things that we want in every secure frame regardless if it's a button or a header +function Units:CreateUnit(...) + local frame = select("#", ...) > 1 and CreateFrame(...) or select(1, ...) + frame.fullUpdates = {} + frame.registeredEvents = {} + frame.visibility = {} + 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.FullUpdate = FullUpdate + frame.SetVisibility = SetVisibility + 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:SetScript("OnAttributeChanged", OnAttributeChanged) + frame:SetScript("OnEvent", OnEvent) + frame:SetScript("OnEnter", OnEnter) + frame:SetScript("OnLeave", UnitFrame_OnLeave) + frame:SetScript("OnShow", OnShow) + frame:SetScript("OnHide", OnHide) + frame:SetScript("PostClick", PostClick) + + frame:RegisterForClicks("AnyUp") + frame:SetAttribute("*type1", "target") + frame:SetAttribute("*type2", "menu") + + 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:FireModuleEvent("OnLayoutReload", type) + ShadowUF.Layout:AnchorFrame(UIParent, headerFrames[type], ShadowUF.db.profile.positions[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, xMod, yMod = 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 and childHeader:IsVisible() ) 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 % config.groupsPerRow == 1 ) 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 * xMod, config.columnSpacing * yMod) + end + + lastHeader = childHeader + end + 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") + + if( config.groupBy == "CLASS" ) then + frame:SetAttribute("groupingOrder", "DEATHKNIGHT,DRUID,HUNTER,MAGE,PALADIN,PRIEST,ROGUE,SHAMAN,WARLOCK,WARRIOR") + frame:SetAttribute("groupBy", "CLASS") + 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" ) then + frame:SetWidth(config.width) + 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) + end + + -- Update the raid frames to if they should be showing raid or party + if( type == "party" or type == "raid" ) then + self:CheckGroupVisibility() + + -- Need to update our flags on the state monitor so it knows what to do + stateMonitor:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.party.hideSemiRaid) + stateMonitor:SetAttribute("hideAnyRaid", ShadowUF.db.profile.units.party.hideAnyRaid) + end +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, UIParent, "SecureUnitButtonTemplate") + frame:SetAttribute("unit", unit) + frame.hasStateWatch = unit == "pet" + + -- Annd lets get this going + RegisterUnitWatch(frame, frame.hasStateWatch) +end + +function Units:LoadSplitGroupHeader(type) + if( headerFrames.raid ) then headerFrames.raid:Hide() end + headerFrames.raidParent = nil + + 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, UIParent, "SecureGroupHeaderTemplate") + frame:SetAttribute("template", "SecureUnitButtonTemplate") + frame:SetAttribute("initial-unitWatch", true) + frame:SetAttribute("showRaid", true) + frame:SetAttribute("groupFilter", id) + frame:UnregisterEvent("UNIT_NAME_UPDATE") + frame.initialConfigFunction = initializeUnit + frame.isHeaderFrame = true + frame.unitType = 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) + + headerFrames["raid" .. id] = frame + end + + frame:Show() + + if( not headerFrames.raidParent or headerFrames.raidParent.groupID > id ) then + headerFrames.raidParent = frame + end + + 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) + -- Any frames that were split out in this group need to be hidden + for _, headerFrame in pairs(headerFrames) do + if( headerFrame.splitParent == type ) then + headerFrame:Hide() + end + end + + -- Already created, so just reshow and we out + if( headerFrames[type] ) then + headerFrames[type]:Show() + + if( type == "party" ) then + stateMonitor:SetAttribute("partyDisabled", nil) + end + + if( type == "party" or type == "raid" ) then + self:CheckGroupVisibility() + end + return + end + + local headerFrame = CreateFrame("Frame", "SUFHeader" .. type, UIParent, type == "raidpet" and "SecureGroupPetHeaderTemplate" or "SecureGroupHeaderTemplate") + headerFrames[type] = headerFrame + + self:SetHeaderAttributes(headerFrame, type) + + headerFrame:SetAttribute("template", "SecureUnitButtonTemplate") + headerFrame:SetAttribute("initial-unitWatch", true) + headerFrame.initialConfigFunction = initializeUnit + headerFrame.isHeaderFrame = true + headerFrame.unitType = type + headerFrame:UnregisterEvent("UNIT_NAME_UPDATE") + + 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:SetFrameRef("partyHeader", headerFrame) + stateMonitor:WrapScript(stateMonitor, "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, "raidmonitor", "[target=raid6, exists] raid6; [target=raid1, exists] raid1; none") + else + headerFrame:Show() + 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() + return + end + + local headerFrame = CreateFrame("Frame", "SUFHeader" .. type, UIParent) + headerFrame.isHeaderFrame = true + headerFrame.unitType = type + headerFrame:SetClampedToScreen(true) + headerFrame:SetMovable(true) + headerFrame:SetHeight(0.1) + headerFrame.children = {} + headerFrames[type] = headerFrame + + if( type == "arena" ) then + headerFrame:SetScript("OnAttributeChanged", function(self, key, value) + if( key == "childChanged" and value and self.children[value] and self:IsVisible() ) then + self.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, "SecureUnitButtonTemplate") + frame.ignoreAnchor = true + frame:SetAttribute("unit", unit) + frame:Hide() + + headerFrame.children[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 + frame:SetAttribute("unitID", id) + frame.hasStateWatch = true + + 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 + ]]) + + RegisterUnitWatch(frame, frame.hasStateWatch) + else + RegisterUnitWatch(frame) + end + end + + + self:SetHeaderAttributes(headerFrame, type) + + ShadowUF.Layout:AnchorFrame(UIParent, headerFrame, ShadowUF.db.profile.positions[type]) +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 + + -- Now we can create the actual frame + local frame = self:CreateUnit("Button", "SUFChild" .. type .. string.match(parent:GetName(), "(%d+)"), parent, "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", string.match(type, "pet$") and "pet" or "target") + 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.zoneUnits[type] ) then + self:LoadZoneHeader(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 + else + self:LoadUnit(type) + end +end + +-- Uninitialize units +function Units:UninitializeFrame(type) + -- Disables showing party in raid automatically if raid frames are disabled + if( type == "party" ) then + stateMonitor:SetAttribute("partyDisabled", true) + end + if( type == "party" or type == "raid" ) then + self:CheckGroupVisibility() + 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 + 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: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 + +-- 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()) + 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 + +local centralFrame = CreateFrame("Frame") +centralFrame:RegisterEvent("PLAYER_REGEN_ENABLED") +centralFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA") +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 + + -- They're alive again so they "officially" changed zone types now + elseif( event == "PLAYER_UNGHOST" ) then + Units:CheckPlayerZone() + + -- 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) \ No newline at end of file diff --git a/ShadowedUnitFrames/modules/xp.lua b/ShadowedUnitFrames/modules/xp.lua new file mode 100644 index 0000000..6f2c28f --- /dev/null +++ b/ShadowedUnitFrames/modules/xp.lua @@ -0,0 +1,161 @@ +local XP = {} +local L = ShadowUF.L +ShadowUF:RegisterModule(XP, "xpBar", L["XP/Rep bar"], true) + +local function OnEnter(self) + if( self.tooltip ) then + GameTooltip:SetOwner(self, "ANCHOR_BOTTOMLEFT") + GameTooltip:SetText(self.tooltip) + end +end + +local function OnLeave(self) + GameTooltip:Hide() +end + +function XP:OnEnable(frame) + if( not frame.xpBar ) then + frame.xpBar = CreateFrame("Frame", nil, frame) + frame.xpBar:SetScript("OnEnter", OnEnter) + frame.xpBar:SetScript("OnLeave", OnLeave) + frame.xpBar:EnableMouse(true) + + frame.xpBar.xp = ShadowUF.Units:CreateBar(frame.xpBar) + frame.xpBar.xp:SetPoint("BOTTOMLEFT", frame.xpBar) + frame.xpBar.xp:SetPoint("BOTTOMRIGHT", frame.xpBar) + + if( frame.unitType == "player" ) then + frame.xpBar.rep = ShadowUF.Units:CreateBar(frame.xpBar) + frame.xpBar.rep:SetPoint("TOPLEFT", frame.xpBar) + frame.xpBar.rep:SetPoint("TOPRIGHT", frame.xpBar) + end + + frame.xpBar.rested = CreateFrame("StatusBar", nil, frame.xpBar.xp) + frame.xpBar.rested:SetFrameLevel(frame.xpBar.xp:GetFrameLevel() - 1) + frame.xpBar.rested:SetAllPoints(frame.xpBar.xp) + end + + frame:RegisterNormalEvent("ENABLE_XP_GAIN", self, "Update") + frame:RegisterNormalEvent("DISABLE_XP_GAIN", self, "Update") + + if( frame.unitType == "player" ) then + frame:RegisterNormalEvent("PLAYER_XP_UPDATE", self, "Update") + frame:RegisterNormalEvent("UPDATE_EXHAUSTION", self, "Update") + frame:RegisterNormalEvent("PLAYER_LEVEL_UP", self, "Update") + frame:RegisterNormalEvent("UPDATE_FACTION", self, "Update") + else + frame:RegisterNormalEvent("UNIT_PET_EXPERIENCE", self, "Update") + frame:RegisterUnitEvent("UNIT_LEVEL", self, "Update") + end + + frame:RegisterUpdateFunc(self, "Update") +end + +function XP:OnDisable(frame) + frame:UnregisterAll(self) +end + +function XP:OnLayoutApplied(frame) + if( frame.visibility.xpBar ) then + frame.xpBar.xp:SetStatusBarTexture(ShadowUF.Layout.mediaPath.statusbar) + frame.xpBar.xp:SetStatusBarColor(ShadowUF.db.profile.xpColors.normal.r, ShadowUF.db.profile.xpColors.normal.g, ShadowUF.db.profile.xpColors.normal.b, ShadowUF.db.profile.bars.alpha) + + frame.xpBar.xp.background:SetVertexColor(ShadowUF.db.profile.xpColors.normal.r, ShadowUF.db.profile.xpColors.normal.g, ShadowUF.db.profile.xpColors.normal.b, ShadowUF.db.profile.bars.backgroundAlpha) + frame.xpBar.xp.background:SetTexture(ShadowUF.Layout.mediaPath.statusbar) + + frame.xpBar.rested:SetStatusBarTexture(ShadowUF.Layout.mediaPath.statusbar) + frame.xpBar.rested:SetStatusBarColor(ShadowUF.db.profile.xpColors.rested.r, ShadowUF.db.profile.xpColors.rested.g, ShadowUF.db.profile.xpColors.rested.b, ShadowUF.db.profile.bars.alpha) + + if( frame.xpBar.rep ) then + frame.xpBar.rep:SetStatusBarTexture(ShadowUF.Layout.mediaPath.statusbar) + frame.xpBar.rep.background:SetTexture(ShadowUF.Layout.mediaPath.statusbar) + end + end +end + +-- Format 5000 into 5,000 +local function formatNumber(number) + local found + while( true ) do + number, found = string.gsub(number, "^(-?%d+)(%d%d%d)", "%1,%2") + if( found == 0 ) then break end + end + + return number +end + +function XP:UpdateRep(frame) + if( not frame.xpBar.rep ) then return end + local name, reaction, min, max, current = GetWatchedFactionInfo() + if( not name ) then + frame.xpBar.rep:Hide() + return + end + + -- Blizzard stores faction info related to Exalted, not your current level, so get more mathier to find the current reputation using the current standing tier + current = math.abs(min - current) + max = math.abs(min - max) + + local color = FACTION_BAR_COLORS[reaction] + frame.xpBar.rep:SetMinMaxValues(0, max) + frame.xpBar.rep:SetValue(reaction == 8 and max or current) + frame.xpBar.rep.tooltip = string.format(L["%s (%s): %s/%s (%.2f%% done)"], name, GetText("FACTION_STANDING_LABEL" .. reaction, UnitSex("player")), formatNumber(current), formatNumber(max), reaction == 8 and 100 or (current / max) * 100) + frame.xpBar.rep:SetStatusBarColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) + frame.xpBar.rep.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + frame.xpBar.rep:Show() +end + +function XP:UpdateXP(frame) + -- At the level cap or XP is disabled, or the pet is actually a vehicle right now, swap to reputation bar (or hide it) + if( UnitLevel(frame.unitOwner) == MAX_PLAYER_LEVEL or IsXPUserDisabled() or ( frame.unitOwner == "pet" and UnitExists("vehicle") ) ) then + frame.xpBar.xp:Hide() + return + end + + local current, max + if( frame.unitOwner == "player" ) then + current, max = UnitXP(frame.unitOwner), UnitXPMax(frame.unitOwner) + else + current, max = GetPetExperience() + end + + local min = math.min(0, current) + frame.xpBar.xp:SetMinMaxValues(min, max) + frame.xpBar.xp:SetValue(current) + frame.xpBar.xp:Show() + + if( frame.unitOwner == "player" and GetXPExhaustion() ) then + frame.xpBar.rested:SetMinMaxValues(min, max) + frame.xpBar.rested:SetValue(math.min(current + GetXPExhaustion(), max)) + frame.xpBar.rested:Show() + frame.xpBar.xp.tooltip = string.format(L["Level %s - %s: %s/%s (%.2f%% done), %s rested."], UnitLevel(frame.unitOwner), UnitLevel(frame.unitOwner) + 1, formatNumber(current), formatNumber(max), (current / max) * 100, formatNumber(GetXPExhaustion())) + else + frame.xpBar.rested:Hide() + frame.xpBar.xp.tooltip = string.format(L["Level %s - %s: %s/%s (%.2f%% done)"], UnitLevel(frame.unitOwner), UnitLevel(frame.unitOwner) + 1, formatNumber(current), formatNumber(max), (current / max) * 100) + end +end + +function XP:Update(frame) + self:UpdateRep(frame) + self:UpdateXP(frame) + + if( ( not frame.xpBar.rep or not frame.xpBar.rep:IsShown() ) and not frame.xpBar.xp:IsShown() ) then + ShadowUF.Layout:SetBarVisibility(frame, "xpBar", false) + return + end + + ShadowUF.Layout:SetBarVisibility(frame, "xpBar", true) + if( frame.xpBar.rep and frame.xpBar.rep:IsVisible() and frame.xpBar.xp:IsVisible() ) then + frame.xpBar.rep:SetHeight(frame.xpBar:GetHeight() * 0.48) + frame.xpBar.xp:SetHeight(frame.xpBar:GetHeight() * 0.48) + frame.xpBar.tooltip = frame.xpBar.rep.tooltip .. "\n" .. frame.xpBar.xp.tooltip + + elseif( frame.xpBar.rep and frame.xpBar.rep:IsVisible() ) then + frame.xpBar.rep:SetHeight(frame.xpBar:GetHeight()) + frame.xpBar.tooltip = frame.xpBar.rep.tooltip + + elseif( frame.xpBar.xp:IsVisible() ) then + frame.xpBar.xp:SetHeight(frame.xpBar:GetHeight()) + frame.xpBar.tooltip = frame.xpBar.xp.tooltip + end +end