diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..25d54b558 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Shadowed Unit Frames + +## [v4.3.9-classic](https://github.com/Nevcairiel/ShadowedUnitFrames/tree/v4.3.9-classic) (2026-01-18) +[Full Changelog](https://github.com/Nevcairiel/ShadowedUnitFrames/compare/v4.3.8-classic...v4.3.9-classic) [Previous Releases](https://github.com/Nevcairiel/ShadowedUnitFrames/releases) + +- Update faction API for TBC Anniversary diff --git a/ExternalAPI.lua b/ExternalAPI.lua new file mode 100644 index 000000000..15ba7ad77 --- /dev/null +++ b/ExternalAPI.lua @@ -0,0 +1,18 @@ +--[[ + API overrides from external addons that augment the data missing in the Classic API +]] +ShadowUF = select(2, ...) +ShadowUF.API = {} + +-- Threat colors +function ShadowUF.API.GetThreatStatusColor(state) + if( state == 3 ) then + return 1, 0, 0 + elseif( state == 2 ) then + return 1, 0.6, 0 + elseif( state == 1 ) then + return 1, 1, 0.47 + else + return 0.69, 0.69, 0.69 + end +end diff --git a/ShadowedUnitFrames.lua b/ShadowedUnitFrames.lua index d0db7dc1b..f41d348dc 100644 --- a/ShadowedUnitFrames.lua +++ b/ShadowedUnitFrames.lua @@ -1,28 +1,35 @@ ---[[ - Shadowed Unit Frames, Shadow of Mal'Ganis (US) PvP +--[[ + Shadowed Unit Frames, Shadowed of Mal'Ganis (US) PvP ]] ShadowUF = select(2, ...) + local L = ShadowUF.L -ShadowUF.dbRevision = 1 +ShadowUF.dbRevision = 61 +ShadowUF.dbRevisionClassic = 6 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"]} - +ShadowUF.unitList = {"player", "pet", "pettarget", "target", "targettarget", "targettargettarget", "focus", "focustarget", "party", "partypet", "partytarget", "partytargettarget", "raid", "raidpet", "boss", "bosstarget", "maintank", "maintanktarget", "mainassist", "mainassisttarget", "arena", "arenatarget", "arenapet", "battleground", "battlegroundtarget", "battlegroundpet", "arenatargettarget", "battlegroundtargettarget", "maintanktargettarget", "mainassisttargettarget", "bosstargettarget"} +ShadowUF.fakeUnits = {["targettarget"] = true, ["targettargettarget"] = true, ["pettarget"] = true, ["arenatarget"] = true, ["arenatargettarget"] = true, ["focustarget"] = true, ["focustargettarget"] = true, ["partytarget"] = true, ["raidtarget"] = true, ["bosstarget"] = true, ["maintanktarget"] = true, ["mainassisttarget"] = true, ["battlegroundtarget"] = true, ["partytargettarget"] = true, ["battlegroundtargettarget"] = true, ["maintanktargettarget"] = true, ["mainassisttargettarget"] = true, ["bosstargettarget"] = true} +L.units = {["raidpet"] = L["Raid pet"], ["PET"] = L["Pet"], ["VEHICLE"] = L["Vehicle"], ["arena"] = L["Arena"], ["arenapet"] = L["Arena Pet"], ["arenatarget"] = L["Arena Target"], ["arenatargettarget"] = L["Arena Target of 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"], ["battleground"] = L["Battleground"], ["battlegroundpet"] = L["Battleground Pet"], ["battlegroundtarget"] = L["Battleground Target"], ["partytargettarget"] = L["Party Target of Target"], ["battlegroundtargettarget"] = L["Battleground Target of Target"], ["maintanktargettarget"] = L["Main Tank Target of Target"], ["mainassisttargettarget"] = L["Main Assist Target of Target"], ["bosstargettarget"] = L["Boss Target of Target"]} +L.shortUnits = {["battleground"] = L["BG"], ["battlegroundtarget"] = L["BG Target"], ["battlegroundpet"] = L["BG Pet"], ["battlegroundtargettarget"] = L["BG ToT"], ["arenatargettarget"] = L["Arena ToT"], ["partytargettarget"] = L["Party ToT"], ["bosstargettarget"] = L["Boss ToT"], ["maintanktargettarget"] = L["MT ToT"], ["mainassisttargettarget"] = L["MA ToT"]} -- 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.partyUnits, ShadowUF.raidUnits, ShadowUF.raidPetUnits, ShadowUF.bossUnits, ShadowUF.arenaUnits, ShadowUF.battlegroundUnits = {}, {}, {}, {}, {}, {} 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 +for i=1, 4 do ShadowUF.battlegroundUnits[i] = "arena" .. i end + +local TagEnv = setmetatable({ + GetThreatStatusColor = ShadowUF.API.GetThreatStatusColor, +}, { __index = _G, __newindex = function(k,v) _G[k] = v end }) function ShadowUF:OnInitialize() self.defaults = { @@ -31,24 +38,25 @@ function ShadowUF:OnInitialize() advanced = false, tooltipCombat = false, omnicc = false, + blizzardcc = true, tags = {}, units = {}, positions = {}, range = {}, - filters = {zonewhite = {}, zoneblack = {}, whitelists = {}, blacklists = {}}, + filters = {zonewhite = {}, zoneblack = {}, zoneoverride = {}, whitelists = {}, blacklists = {}, overridelists = {}}, 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}, + hidden = {cast = false, playerPower = true, buffs = false, party = true, raid = false, player = true, pet = true, target = true, focus = true, boss = true, arena = true, playerAltPower = false}, }, } - + 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) @@ -56,25 +64,30 @@ function ShadowUF:OnInitialize() 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 - + + setfenv(func, TagEnv) + tbl[index] = func return tbl[index] end}) - + if( not self.db.profile.loadedLayout ) then self:LoadDefaultLayout() else self:CheckUpgrade() + self:CheckBuild() + self:ShowInfoPanel() end - + self.db.profile.revision = self.dbRevision + self.db.profile.revisionClassic = self.dbRevisionClassic self:FireModuleEvent("OnInitialize") self:HideBlizzardFrames() self.Layout:LoadSML() @@ -82,83 +95,198 @@ function ShadowUF:OnInitialize() self.modules.movers:Update() end +function ShadowUF.UnitAuraBySpell(unit, spell, filter) + local index = 0 + while true do + index = index + 1 + local name, _, _, _, _, _, _, _, _, spellID = UnitAura(unit, index, filter) + if not name then break end + if (type(spell) == "string" and spell == name) or (type(spell) == "number" and spell == spellID) then + return UnitAura(unit, index, filter) + end + end +end + +function ShadowUF:CheckBuild() + local build = select(4, GetBuildInfo()) + if( self.db.profile.wowBuild == build ) then return end + + -- Nothing to add here right now + self.db.profile.wowBuild = build +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 + local revisionClassic = self.db.profile.revisionClassic or (self.db.profile.revision and 1 or self.dbRevisionClassic) + local revision = self.db.profile.revision or self.dbRevision + if( revisionClassic <= 5 ) then + local config = self.db.profile.units + config.player.soulShards = {anchorTo = "$parent", order = 60, height = 0.40, anchorPoint = "BR", x = -8, y = 6, size = 12, spacing = -2, growth = "LEFT", isBar = true, showAlways = true} + config.player.holyPower = {anchorTo = "$parent", order = 60, height = 0.40, anchorPoint = "BR", x = -3, y = 6, size = 14, spacing = -4, growth = "LEFT", isBar = true, showAlways = true} + + self.db.profile.classColors.VEHICLE = {r = 0.23, g = 0.41, b = 0.23} + self.db.profile.healthColors.aggro = {r = 0.9, g = 0, b = 0} + end + if( revisionClassic <= 4 ) then + -- new resources + self.db.profile.powerColors.RUNES_BLOOD = {r = 0.95, g = 0.0, b = 0.08} + self.db.profile.powerColors.RUNES_FROST = {r = 0.0, g = 0.85, b = 1.0} + self.db.profile.powerColors.RUNES_UNHOLY = {r = 0.0, g = 1.0, b = 0.35} + self.db.profile.powerColors.RUNES_DEATH = {r = 0.69, g = 0.15, b = 1.0} + + -- new bars + local config = self.db.profile.units + config.player.runeBar = {enabled = true, background = false, height = 0.40, order = 70} + local hasRuneText = false + for i, text in ipairs(config.player.text) do + if text and text.anchorTo == "$runeBar" and text == "[rune:timer]" then + hasRuneText = true end end + if not hasRuneText then + table.insert(config.player.text, {enabled = true, width = 1, name = L["Timer Text"], text = "[rune:timer]", anchorTo = "$runeBar", anchorPoint = "C", size = 0, x = 0, y = 0, default = true, block = true}) + 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 + if( revisionClassic <= 4 or not self.db.profile.revisionClassic ) then + ShadowUF:LoadDefaultLayout(true) + end + if( revisionClassic <= 1 ) then + local indicators = self.db.profile.units.pet.indicators or {} + self.db.profile.units.pet.indicators = indicators + if( revision <= 59 or not indicators.happiness ) then + indicators.happiness = {enabled = true, anchorPoint = "RC", size = 20, x = 0, y = 0, anchorTo = "$parent"} + else + local happiness = indicators.happiness + if happiness.size == 0 then + happiness.size = 20 + end + if not happiness.anchorPoint then + happiness.anchorPoint = "RC" + end + if not happiness.anchorTo then + happiness.anchorTo = "$parent" 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 + if( revision <= 58 ) then + for unit, config in pairs(self.db.profile.units) do + if config.text then + local i = 1 + while i <= #config.text do + local text + if rawget(config.text, i) or i <= #(self.defaults.profile.units[unit].text) then + text = config.text[i] + end + + if not text then + table.remove(config.text, i) + elseif text.anchorTo == "$demonicFuryBar" or text.anchorTo == "$eclipseBar" or text.anchorTo == "$burningEmbersBar" or text.anchorTo == "$monkBar" then + table.remove(config.text, i) + elseif i > 6 and text.default and text.anchorTo == "$emptyBar" then + table.remove(config.text, i) + else + if text.anchorTo == "$emptyBar" and text.name == L["Left text"] then + text.width = 0.50 + end + + i = i + 1 + end + end + + if not config.text[6] or config.text[6].anchorTo ~= "$emptyBar" then + table.insert(config.text, 6, {enabled = true, width = 0.60, name = L["Right text"], text = "", anchorTo = "$emptyBar", anchorPoint = "CRI", size = 0, x = -3, y = 0, default = true}) + else + config.text[6].width = 0.60 + config.text[6].name = L["Right text"] + config.text[6].anchorPoint = "CRI" + config.text[6].size = 0 + config.text[6].x = -3 + config.text[6].y = 0 + config.text[6].default = true + end end end - - for key, value in pairs(castTime) do - if( config.castBar.time[key] == nil ) then - config.castBar.time[key] = value + end + + if( revision <= 56 ) then + -- new classes + self.db.profile.classColors.DEMONHUNTER = {r = 0.64, g = 0.19, b = 0.79} + + -- new power types + self.db.profile.powerColors.INSANITY = {r = 0.40, g = 0, b = 0.80} + self.db.profile.powerColors.MAELSTROM = {r = 0.00, g = 0.50, b = 1.00} + self.db.profile.powerColors.FURY = {r = 0.788, g = 0.259, b = 0.992} + self.db.profile.powerColors.PAIN = {r = 1, g = 0, b = 0} + self.db.profile.powerColors.LUNAR_POWER = {r = 0.30, g = 0.52, b = 0.90} + self.db.profile.powerColors.ARCANECHARGES = {r = 0.1, g = 0.1, b = 0.98} + + -- new bars + local config = self.db.profile.units + config.player.priestBar = {enabled = true, background = true, height = 0.40, order = 70} + config.player.shamanBar = {enabled = true, background = true, height = 0.40, order = 70} + config.player.arcaneCharges = {enabled = true, anchorTo = "$parent", order = 60, height = 0.40, anchorPoint = "BR", x = -8, y = 6, size = 12, spacing = -2, growth = "LEFT", isBar = true, showAlways = true} + + -- clean out old bars + config.player.demonicFuryBar = nil + config.player.burningEmbersBar = nil + config.player.shadowOrbs = nil + config.player.eclipseBar = nil + config.player.monkBar = nil + end + + if( revision <= 49 ) then + ShadowUF:LoadDefaultLayout(true) + end + + if( revision <= 49 ) then + if( ShadowUF.db.profile.font.extra == "MONOCHROME" ) then + ShadowUF.db.profile.font.extra = "" + end + end + + if( revision <= 47 ) then + local config = self.db.profile.units + config.player.comboPoints = config.target.comboPoints + end + + if( revision <= 45 ) then + for unit, config in pairs(self.db.profile.units) do + if( config.auras ) then + for _, key in pairs({"buffs", "debuffs"}) do + local aura = config.auras[key] + aura.show = aura.show or {} + aura.show.player = true + aura.show.boss = true + aura.show.raid = true + aura.show.misc = true + end end end end end - + +local function zoneEnabled(zone, zoneList) + if( type(zoneList) == "string" ) then + return zone == zoneList + end + + for id, row in pairs(zoneList) do + if( zone == row ) then return true end + end + + return false +end + function ShadowUF:LoadUnits() -- CanHearthAndResurrectFromArea() returns true for world pvp areas, according to BattlefieldFrame.lua local instanceType = CanHearthAndResurrectFromArea() and "pvp" or select(2, IsInInstance()) - + if( instanceType == "scenario" ) then instanceType = "party" end + + if( not instanceType ) then instanceType = "none" end + 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 + if( ShadowUF.Units.zoneUnits[type] ) then + enabled = enabled and zoneEnabled(instanceType, ShadowUF.Units.zoneUnits[type]) elseif( instanceType ~= "none" ) then if( self.db.profile.visibility[instanceType][type] == false ) then enabled = false @@ -166,9 +294,9 @@ function ShadowUF:LoadUnits() enabled = true end end - + self.enabledUnits[type] = enabled - + if( enabled ) then self.Units:InitializeFrame(type) else @@ -180,7 +308,7 @@ 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] = { @@ -191,28 +319,33 @@ function ShadowUF:LoadUnitDefaults() 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}, + {enabled = true, name = L["Left text"], text = "[name]", anchorPoint = "CLI", anchorTo = "$healthBar", width = 0.50, size = 0, x = 3, y = 0, default = true}, + {enabled = true, name = L["Right text"], text = "[curmaxhp]", anchorPoint = "CRI", anchorTo = "$healthBar", width = 0.60, size = 0, x = -3, y = 0, default = true}, + {enabled = true, name = L["Left text"], text = "[level] [race]", anchorPoint = "CLI", anchorTo = "$powerBar", width = 0.50, size = 0, x = 3, y = 0, default = true}, + {enabled = true, name = L["Right text"], text = "[curmaxpp]", anchorPoint = "CRI", anchorTo = "$powerBar", width = 0.60, size = 0, x = -3, y = 0, default = true}, + {enabled = true, name = L["Left text"], text = "", anchorTo = "$emptyBar", anchorPoint = "CLI", width = 0.50, size = 0, x = 3, y = 0, default = true}, + {enabled = true, name = L["Right text"], text = "", anchorTo = "$emptyBar", anchorPoint = "CRI", width = 0.60, size = 0, x = -3, y = 0, default = true}, + ['*'] = {enabled = true, text = "", anchorTo = "", anchorPoint = "C", size = 0, x = 0, y = 0}, }, - indicators = {raidTarget = {enabled = true, size = 0}}, + indicators = {raidTarget = {enabled = true, size = 0}}, highlight = {}, + auraIndicators = {enabled = false}, 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}, + buffs = {enabled = false, perRow = 10, maxRows = 4, selfScale = 1.30, prioritize = true, show = {player = true, boss = true, raid = true, misc = true}, enlarge = {}, timers = {ALL = true}}, + debuffs = {enabled = false, perRow = 10, maxRows = 4, selfScale = 1.30, show = {player = true, boss = true, raid = true, misc = true}, enlarge = {SELF = true}, timers = {ALL = 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} + if( unit ~= "battleground" and unit ~= "battlegroundpet" and unit ~= "arena" and unit ~= "arenapet" and unit ~= "boss" ) then + self.defaults.profile.units[unit].incHeal = {enabled = true, cap = 1.20} + self.defaults.profile.units[unit].incAbsorb = {enabled = true, cap = 1.30} + self.defaults.profile.units[unit].healAbsorb = {enabled = true, cap = 1.30} end end - + if( unit ~= "player" ) then self.defaults.profile.units[unit].range = {enabled = false, oorAlpha = 0.80, inAlpha = 1.0} @@ -220,21 +353,27 @@ function ShadowUF:LoadUnitDefaults() 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 + + if( unit == "player" or unit == "party" or unit == "target" or unit == "raid" or unit == "focus" or unit == "mainassist" or unit == "maintank" ) 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.resurrect = {enabled = true} + + if( unit ~= "focus" and unit ~= "target" ) then self.defaults.profile.units[unit].indicators.ready = {enabled = true, size = 0} end end + + if( unit == "battleground" ) then + self.defaults.profile.units[unit].indicators.pvp = {enabled = true, size = 0} + end + + self.defaults.profile.units[unit].altPowerBar = {enabled = not ShadowUF.fakeUnits[unit]} end - + -- PLAYER self.defaults.profile.units.player.enabled = true self.defaults.profile.units.player.healthBar.predicted = true @@ -244,24 +383,32 @@ function ShadowUF:LoadUnitDefaults() 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.player.fader = {enabled = false} + self.defaults.profile.units.player.comboPoints = {enabled = true, isBar = true} + self.defaults.profile.units.player.holyPower = {enabled = true, isBar = true} + self.defaults.profile.units.player.soulShards = {enabled = true, isBar = true} + table.insert(self.defaults.profile.units.player.text, {enabled = true, text = "", anchorTo = "", anchorPoint = "C", size = 0, x = 0, y = 0, default = true}) + table.insert(self.defaults.profile.units.player.text, {enabled = true, text = "", anchorTo = "", anchorPoint = "C", size = 0, x = 0, y = 0, default = true}) + table.insert(self.defaults.profile.units.player.text, {enabled = true, text = "", anchorTo = "", anchorPoint = "C", size = 0, x = 0, y = 0, default = true}) + + -- 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.pet.indicators.happiness = {enabled = true, size = 0, x = 0, y = 0} + -- 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} + self.defaults.profile.units.focus.indicators.questBoss = {enabled = true, 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} + self.defaults.profile.units.target.indicators.questBoss = {enabled = true, size = 0, x = 0, y = 0} + self.defaults.profile.units.target.comboPoints = {enabled = false, isBar = true} + self.defaults.profile.units.target.auras.buffs.approximateEnemyData = true -- TARGETTARGET/TARGETTARGETTARGET self.defaults.profile.units.targettarget.enabled = true self.defaults.profile.units.targettargettarget.enabled = true @@ -271,7 +418,7 @@ function ShadowUF:LoadUnitDefaults() 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} + self.defaults.profile.units.party.indicators.phase = {enabled = true, size = 0, x = 0, y = 0} -- ARENA self.defaults.profile.units.arena.enabled = false self.defaults.profile.units.arena.attribPoint = "TOP" @@ -279,6 +426,13 @@ function ShadowUF:LoadUnitDefaults() 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 + -- BATTLEGROUND + self.defaults.profile.units.battleground.enabled = false + self.defaults.profile.units.battleground.attribPoint = "TOP" + self.defaults.profile.units.battleground.attribAnchorPoint = "LEFT" + self.defaults.profile.units.battleground.auras.debuffs.maxRows = 1 + self.defaults.profile.units.battleground.auras.buffs.maxRows = 1 + self.defaults.profile.units.battleground.offset = 0 -- BOSS self.defaults.profile.units.boss.enabled = false self.defaults.profile.units.boss.attribPoint = "TOP" @@ -286,6 +440,7 @@ function ShadowUF:LoadUnitDefaults() 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 + self.defaults.profile.units.boss.altPowerBar.enabled = true -- RAID self.defaults.profile.units.raid.groupBy = "GROUP" self.defaults.profile.units.raid.sortOrder = "ASC" @@ -307,6 +462,7 @@ function ShadowUF:LoadUnitDefaults() 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.roleFilter = "TANK" self.defaults.profile.units.maintank.groupFilter = "MAINTANK" self.defaults.profile.units.maintank.groupBy = "GROUP" self.defaults.profile.units.maintank.sortOrder = "ASC" @@ -332,13 +488,116 @@ function ShadowUF:LoadUnitDefaults() 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} + -- PARTYTARGETTARGET + 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} + + -- Aura indicators + self.defaults.profile.auraIndicators = { + disabled = {}, + missing = {}, + linked = {}, + indicators = { + ["tl"] = {name = L["Top Left"], anchorPoint = "TLI", anchorTo = "$parent", height = 8, width = 8, alpha = 1.0, x = 4, y = -4, friendly = true, hostile = true}, + ["tr"] = {name = L["Top Right"], anchorPoint = "TRI", anchorTo = "$parent", height = 8, width = 8, alpha = 1.0, x = -3, y = -3, friendly = true, hostile = true}, + ["bl"] = {name = L["Bottom Left"], anchorPoint = "BLI", anchorTo = "$parent", height = 8, width = 8, alpha = 1.0, x = 4, y = 4, friendly = true, hostile = true}, + ["br"] = {name = L["Bottom Right"], anchorPoint = "BRI", anchorTo = "$parent", height = 8, width = 8, alpha = 1.0, x = -4, y = -4, friendly = true, hostile = true}, + ["c"] = {name = L["Center"], anchorPoint = "C", anchorTo = "$parent", height = 20, width = 20, alpha = 1.0, x = 0, y = 0, friendly = true, hostile = true}, + }, + filters = { + ["tl"] = {boss = {priority = 100}, curable = {priority = 100}}, + ["tr"] = {boss = {priority = 100}, curable = {priority = 100}}, + ["bl"] = {boss = {priority = 100}, curable = {priority = 100}}, + ["br"] = {boss = {priority = 100}, curable = {priority = 100}}, + ["c"] = {boss = {priority = 100}, curable = {priority = 100}}, + }, + auras = { + ["20707"] = [[{indicator = '', group = "Warlock", priority = 10, r = 0.42, g = 0.21, b = 0.65}]], + ["116849"] = [[{r=0.19607843137255, group="Monk", indicator="c", g=1, player=false, duration=true, b=0.3843137254902, alpha=1, priority=0, icon=true, iconTexture="Interface\\Icons\\ability_monk_chicocoon"}]], + ["155777"] = [[{r=0.57647058823529, group="Druid", indicator="tr", g=0.28235294117647, player=true, duration=true, b=0.6156862745098, priority=100, alpha=1, iconTexture="Interface\\Icons\\Spell_Nature_Rejuvenation"}]], + ["121176"] = [[{alpha=1, b=0, priority=0, r=0.062745098039216, group="PvP Flags", indicator="bl", g=1, iconTexture="Interface\\Icons\\INV_BannerPVP_03"}]], + ["19705"] = [[{r=0.80392156862745, group="Food", indicator="", g=0.76470588235294, missing=true, duration=true, priority=0, alpha=1, b=0.24313725490196}]], + ["53563"] = [[{r=0.64313725490196, group="Paladin", indicator="tr", g=0.24705882352941, player=true, alpha=1, b=0.73333333333333, priority=100, duration=false, iconTexture="Interface\\Icons\\Ability_Paladin_BeaconofLight"}]], + ["774"] = [[{r=0.57647058823529, group="Druid", indicator="tr", g=0.28235294117647, player=true, duration=true, b=0.6156862745098, priority=100, alpha=1, iconTexture="Interface\\Icons\\Spell_Nature_Rejuvenation"}]], + ["33206"] = [[{r=0, group="Priest", indicator="c", g=0, b=0, duration=true, priority=0, icon=true, iconTexture="Interface\\Icons\\Spell_Holy_PainSupression"}]], + ["6788"] = [[{b=0.29019607843137, group="Priest", indicator="tl", alpha=1, player=false, g=0.56862745098039, duration=true, r=0.83921568627451, priority=20, icon=false, iconTexture="Interface\\Icons\\Spell_Holy_AshesToAshes"}]], + ["33763"] = [[{r=0.23137254901961, group="Druid", indicator="tl", g=1, player=true, duration=true, alpha=1, priority=0, b=0.2, iconTexture="Interface\\Icons\\INV_Misc_Herb_Felblossom"}]], + ["139"] = [[{r=0.23921568627451, group="Priest", indicator="tr", g=1, player=true, alpha=1, duration=true, b=0.39607843137255, priority=10, icon=false, iconTexture="Interface\\Icons\\Spell_Holy_Renew"}]], + ["41635"] = [[{r=1, group="Priest", indicator="br", g=0.90196078431373, missing=false, player=true, duration=false, alpha=1, b=0, priority=50, icon=false, iconTexture="Interface\\Icons\\Spell_Holy_PrayerOfMendingtga"}]], + ["47788"] = [[{r=0, group="Priest", indicator="c", g=0, b=0, duration=true, priority=0, icon=true, iconTexture="Interface\\Icons\\Spell_Holy_GuardianSpirit"}]], + ["61295"] = [[{r=0.17647058823529, group="Shaman", indicator="tl", g=0.4, player=true, alpha=1, duration=true, b=1, priority=0, icon=false, iconTexture="Interface\\Icons\\spell_nature_riptide"}]], + ["17"] = [[{r=1, group="Priest", indicator="tl", g=0.41960784313725, player=true, alpha=1, duration=true, b=0.5843137254902, priority=0, icon=false, iconTexture="Interface\\Icons\\Spell_Holy_PowerWordShield"}]], + ["152118"] = [[{r=1, group="Priest", indicator="tl", g=0.41960784313725, player=true, alpha=1, duration=true, b=0.5843137254902, priority=0, icon=false, iconTexture="Interface\\Icons\\Ability_Priest_ClarityOfWill"}]], + ["23335"] = [[{r=0, group="PvP Flags", indicator="bl", g=0, duration=false, b=0, priority=0, icon=true, iconTexture="Interface\\Icons\\INV_BannerPVP_02"}]], + ["102342"] = [[{r=0, group="Druid", indicator="c", g=0, duration=true, b=0, priority=0, icon=true, iconTexture="Interface\\Icons\\spell_druid_ironbark"}]], + ["121177"] = [[{r=0.78039215686275, group="PvP Flags", indicator="bl", g=0.42352941176471, alpha=1, b=0, priority=0, icon=false, iconTexture="Interface\\Icons\\INV_BannerPVP_03"}]], + ["586"] = [[{r=0, group="Priest", indicator="", g=0.85882352941176, selfColor={alpha=1, b=1, g=0.93725490196078, r=0, }, alpha=1, priority=0, b=1, iconTexture="Interface\\Icons\\Spell_Magic_LesserInvisibilty"}]], + ["23333"] = [[{icon=true, b=0, priority=0, r=0, group="PvP Flags", indicator="bl", g=0, iconTexture="Interface\\Icons\\INV_BannerPVP_01"}]], + ["119611"] = [[{r=0.26274509803922, group="Monk", indicator="tl", g=0.76078431372549, player=true, duration=true, alpha=1, b=0.53725490196078, priority=0, icon=false, iconTexture="Interface\\Icons\\ability_monk_renewingmists"}]], + ["8936"] = [[{r=0.12156862745098, group="Druid", indicator="br", g=0.45882352941176, player=true, duration=true, b=0.12156862745098, priority=100, alpha=1, iconTexture="Interface\\Icons\\Spell_Nature_ResistNature"}]], + ["34976"] = [[{r=0, group="PvP Flags", indicator="bl", g=0, player=false, b=0, priority=0, icon=true, iconTexture="Interface\\Icons\\INV_BannerPVP_03"}]], + ["121164"] = [[{alpha=1, b=1, priority=0, r=0, group="PvP Flags", indicator="bl", g=0.003921568627451, iconTexture="Interface\\Icons\\INV_BannerPVP_03"}]], + ["48438"] = [[{r=0.55294117647059, group="Druid", indicator="31685", g=1, player=true, duration=true, b=0.3921568627451, priority=100, alpha=1, iconTexture="Interface\\Icons\\Ability_Druid_Flourish"}]], + ["1022"] = [[{r=0, group="Paladin", indicator="c", g=0, player=false, duration=true, b=0, priority=0, icon=true, iconTexture="Interface\\Icons\\Spell_Holy_SealOfProtection"}]], + ["121175"] = [[{r=1, group="PvP Flags", indicator="bl", g=0.24705882352941, b=0.90196078431373, alpha=1, priority=0, icon=false, iconTexture="Interface\\Icons\\INV_BannerPVP_03"}]], + ["64844"] = [[{r=0.67843137254902, group="Priest", indicator="31685", g=0.30588235294118, player=true, alpha=1, priority=0, b=0.14117647058824, iconTexture="Interface\\Icons\\Spell_Holy_DivineProvidence"}]], + ["124081"] = [[{r=0.51372549019608, group="Monk", indicator="br", g=1, player=true, duration=true, b=0.90588235294118, alpha=1, priority=100, icon=false, iconTexture="Interface\\Icons\\ability_monk_forcesphere"}]], + ["29166"] = [[{r=0, group="Druid", indicator="c", g=0, b=0, duration=true, priority=0, icon=true, iconTexture="Interface\\Icons\\Spell_Nature_Lightning"}]], + ["189895"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\INV_Enchant_VoidSphere\";}", + ["189627"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\achievement_zone_cataclysmgreen\";}", + ["181306"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Ability_Mage_LivingBomb\";}", + ["181753"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_felarakkoa_feldetonation_green\";}", + ["188929"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Ability_Hunter_MarkedForDeath\";}", + ["189032"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_malkorok_blightofyshaarj_green\";}", + ["182826"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Fire_FelFlameRing\";}", + ["181515"] = "{b=0;g=0;priority=1;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Shadow_SeedOfDestruction\";}", + ["179219"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_felarakkoa_feldetonation_red\";}", + ["179864"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Arcane_PrismaticCloak\";}", + ["184124"] = "{b=0;g=0;priority=1;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Shadow_AntiMagicShell\";}", + ["184450"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_bossfelorcs_necromancer_purple\";}", + ["186134"] = "{icon=true;b=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"tr\";g=0;iconTexture=\"Interface\\\\Icons\\\\spell_fel_elementaldevastation\";}", + ["185066"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_bossfelorcs_necromancer_red\";}", + ["183817"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Ability_Warlock_EverlastingAffliction\";}", + ["179978"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_bossgorefiend_touchofdoom\";}", + ["182280"] = "{icon=true;b=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";g=0;iconTexture=\"Interface\\\\Icons\\\\Ability_Hunter_MarkedForDeath\";}", + ["189031"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_malkorok_blightofyshaarj_yellow\";}", + ["186500"] = "{b=0;g=0;priority=1;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"INTERFACE\\\\ICONS\\\\inv_misc_steelweaponchain\";}", + ["188666"] = "{b=0;g=0;priority=1;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"INTERFACE\\\\ICONS\\\\ability_warlock_soulsiphon\";}", + ["186333"] = "{b=0;g=0;priority=1;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Shadow_DevouringPlague\";}", + ["186135"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"tr\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Shadow_AntiShadow\";}", + ["181275"] = "{icon=true;b=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";g=0;iconTexture=\"Interface\\\\Icons\\\\spell_warlock_summonterrorguard\";}", + ["181957"] = "{b=0;g=0;priority=2;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Frost_ArcticWinds\";}", + ["189030"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_malkorok_blightofyshaarj_red\";}", + ["182178"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_arakkoa_spinning_blade\";}", + ["189777"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"INTERFACE\\\\ICONS\\\\inv_misc_steelweaponchain\";}", + ["186407"] = "{r=0;indicator=\"c\";b=0;group=\"Hellfire Citadel\";priority=2;g=0;iconTexture=\"Interface\\\\Icons\\\\spell_fel_incinerate\";}", + ["180166"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"tr\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Shadow_ChillTouch\";}", + ["180079"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\inv_blacksmithdye_black\";}", + ["184449"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_bossfelorcs_necromancer_purple\";}", + ["185510"] = "{r=0;icon=true;indicator=\"tr\";b=0;group=\"Hellfire Citadel\";priority=1;g=0;iconTexture=\"INTERFACE\\\\ICONS\\\\inv_misc_steelweaponchain\";}", + ["185065"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_bossfelorcs_necromancer_orange\";}", + ["182769"] = "{r=0;icon=true;indicator=\"tr\";b=0;group=\"Hellfire Citadel\";priority=0;g=0;iconTexture=\"Interface\\\\Icons\\\\ability_fixated_state_purple\";}", + ["181508"] = "{b=0;group=\"Hellfire Citadel\";indicator=\"c\";g=0;alpha=1;r=0;priority=1;icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Shadow_SeedOfDestruction\";}", + ["181099"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Shadow_AuraOfDarkness\";}", + ["181307"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\Spell_Shadow_Requiem\";}", + ["183865"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_warlock_shadowfurytga\";}", + ["186961"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_warlock_moltencoregreen\";}", + ["182200"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_arakkoa_spinning_blade\";}", + ["181597"] = "{b=0;g=0;priority=0;r=0;group=\"Hellfire Citadel\";indicator=\"c\";icon=true;iconTexture=\"Interface\\\\Icons\\\\ability_bossmannoroth_mannorothsgaze\";}", + } + } + + for classToken in pairs(RAID_CLASS_COLORS) do + self.defaults.profile.auraIndicators.disabled[classToken] = {} + end 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")) +function ShadowUF:RegisterModule(module, key, name, isBar, class, spec, level) + -- Prevent duplicate registration for deprecated plugin + if( key == "auraIndicators" and C_AddOns.IsAddOnLoaded("ShadowedUF_Indicators") and self.modules.auraIndicators ) then + self:Print(L["WARNING! ShadowedUF_Indicators has been deprecated as v4 and is now built in. Please delete ShadowedUF_Indicators, your configuration will be saved."]) return end @@ -348,7 +607,18 @@ function ShadowUF:RegisterModule(module, key, name, isBar, class) module.moduleHasBar = isBar module.moduleName = name module.moduleClass = class - + module.moduleLevel = level + + if( type(spec) == "number" ) then + module.moduleSpec = {} + module.moduleSpec[spec] = true + elseif( type(spec) == "table" ) then + module.moduleSpec = {} + for _, id in pairs(spec) do + module.moduleSpec[id] = true + end + end + table.insert(self.moduleOrder, module) end @@ -370,28 +640,32 @@ local resetTimer function ShadowUF:ProfileReset() if( not resetTimer ) then resetTimer = CreateFrame("Frame") - resetTimer:SetScript("OnUpdate", function(self) + resetTimer:SetScript("OnUpdate", function(f) ShadowUF:ProfilesChanged() - self:Hide() + f: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() + self:CheckBuild() end - + + self.db.profile.revision = self.dbRevision + self.db.profile.revisionClassic = self.dbRevisionClassic + self:FireModuleEvent("OnProfileChange") self:LoadUnits() self:HideBlizzardFrames() @@ -400,120 +674,207 @@ function ShadowUF:ProfilesChanged() 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() +ShadowUF.hiddenFrame = CreateFrame("Frame") +ShadowUF.hiddenFrame:Hide() + +local rehideFrame = function(self) + if( not InCombatLockdown() ) then + self:Hide() end - - if( ShadowUF.db.profile.hidden.cast ) then - CastingBarFrame:UnregisterAllEvents() - PetCastingBarFrame:UnregisterAllEvents() +end + +local function basicHideBlizzardFrames(...) + for i=1, select("#", ...) do + local frame = select(i, ...) + frame:UnregisterAllEvents() + frame:HookScript("OnShow", rehideFrame) + frame:Hide() end +end - if( ShadowUF.db.profile.hidden.party ) then - for i=1, MAX_PARTY_MEMBERS do - local name = "PartyMemberFrame" .. i - local frame = _G[name] +local function hideBlizzardFrames(taint, ...) + for i=1, select("#", ...) do + local frame = select(i, ...) + UnregisterUnitWatch(frame) + frame:UnregisterAllEvents() + frame:Hide() - frame:UnregisterAllEvents() - frame.Show = self.noop - frame:Hide() + if( frame.manabar ) then frame.manabar:UnregisterAllEvents() end + if( frame.healthbar ) then frame.healthbar:UnregisterAllEvents() end + if( frame.spellbar ) then frame.spellbar:UnregisterAllEvents() end + if( frame.powerBarAlt ) then frame.powerBarAlt:UnregisterAllEvents() end - _G[name .. "HealthBar"]:UnregisterAllEvents() - _G[name .. "ManaBar"]:UnregisterAllEvents() + if( taint ) then + frame.Show = ShadowUF.noop + else + frame:SetParent(ShadowUF.hiddenFrame) + frame:HookScript("OnShow", rehideFrame) end 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] +local active_hiddens = {} +function ShadowUF:HideBlizzardFrames() + if( self.db.profile.hidden.cast and not active_hiddens.cast ) then + hideBlizzardFrames(true, CastingBarFrame, PetCastingBarFrame) + end + + if( self.db.profile.hidden.party and not active_hiddens.party ) then + if( PartyFrame ) then + hideBlizzardFrames(false, PartyFrame) + for memberFrame in PartyFrame.PartyMemberFramePool:EnumerateActive() do + if memberFrame.HealthBarContainer and memberFrame.HealthBarContainer.HealthBar then + hideBlizzardFrames(false, memberFrame, memberFrame.HealthBarContainer.HealthBar, memberFrame.ManaBar) + else + hideBlizzardFrames(false, memberFrame, memberFrame.HealthBar, memberFrame.ManaBar) + end + end + PartyFrame.PartyMemberFramePool:ReleaseAll() + else + for i=1, MAX_PARTY_MEMBERS do + local name = "PartyMemberFrame" .. i + hideBlizzardFrames(false, _G[name], _G[name .. "HealthBar"], _G[name .. "ManaBar"]) + end + end - frame:UnregisterAllEvents() - frame.Show = self.noop - frame:Hide() + -- This stops the compact party frame from being shown + UIParent:UnregisterEvent("GROUP_ROSTER_UPDATE") - _G[name .. "HealthBar"]:UnregisterAllEvents() - _G[name .. "ManaBar"]:UnregisterAllEvents() + -- This just makes sure + if( CompactPartyFrame ) then + hideBlizzardFrames(false, CompactPartyFrame) 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) + if( CompactRaidFrameManager ) then + if( self.db.profile.hidden.raid and not active_hiddens.raidTriggered ) then + active_hiddens.raidTriggered = true + + local function hideRaid() + CompactRaidFrameManager:UnregisterAllEvents() + CompactRaidFrameContainer:UnregisterAllEvents() + if( InCombatLockdown() ) then return end + + CompactRaidFrameManager:Hide() + local shown = CompactRaidFrameManager_GetSetting("IsShown") + if( shown and shown ~= "0" ) then + CompactRaidFrameManager_SetSetting("IsShown", "0") end end + + hooksecurefunc("CompactRaidFrameManager_UpdateShown", function() + if( self.db.profile.hidden.raid ) then + hideRaid() + end + end) + + hideRaid() + CompactRaidFrameContainer:HookScript("OnShow", hideRaid) + CompactRaidFrameManager:HookScript("OnShow", hideRaid) + end + end + + if( self.db.profile.hidden.buffs and not active_hiddens.buffs ) then + hideBlizzardFrames(false, BuffFrame, TemporaryEnchantFrame) + end + + if( self.db.profile.hidden.player and not active_hiddens.player ) then + hideBlizzardFrames(false, PlayerFrame) + + -- We keep these in case someone is still using the default auras, otherwise it messes up vehicle stuff + PlayerFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + PlayerFrame:SetMovable(true) + PlayerFrame:SetUserPlaced(true) + PlayerFrame:SetDontSavePosition(true) + end + + if( self.db.profile.hidden.pet and not active_hiddens.pet ) then + hideBlizzardFrames(false, PetFrame) + end + + if( self.db.profile.hidden.target and not active_hiddens.target ) then + hideBlizzardFrames(false, TargetFrame, ComboFrame, TargetFrameToT) + end + + if( self.db.profile.hidden.focus and not active_hiddens.focus and FocusFrame and FocusFrameToT ) then + hideBlizzardFrames(false, FocusFrame, FocusFrameToT) + end + + if( self.db.profile.hidden.boss and not active_hiddens.boss ) then + for i=1, MAX_BOSS_FRAMES do + local name = "Boss" .. i .. "TargetFrame" + hideBlizzardFrames(false, _G[name], _G[name .. "HealthBar"], _G[name .. "ManaBar"]) + end + end + + -- As a reload is required to reset the hidden hooks, we can just set this to true if anything is true + for type, flag in pairs(self.db.profile.hidden) do + if( flag ) then + active_hiddens[type] = true end end end +-- Upgrade info +local infoMessages = { + -- Old messages we don't need anymore + {}, {}, + { + L["You must restart Shadowed Unit Frames."], + L["If you don't, you will be unable to use any combo point features (Chi, Holy Power, Combo Points, Aura Points, etc) until you do so."] + } +} + +function ShadowUF:ShowInfoPanel() + local infoID = ShadowUF.db.global.infoID or 0 + if( ShadowUF.ComboPoints and infoID < 3 ) then infoID = 3 end + + ShadowUF.db.global.infoID = #(infoMessages) + if( infoID < 0 or infoID >= #(infoMessages) ) then return end + + local frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate" or nil) + frame:SetClampedToScreen(true) + frame:SetFrameStrata("HIGH") + frame:SetToplevel(true) + frame:SetWidth(500) + frame:SetHeight(285) + 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, 100) + + 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", "GameFontHighlight") + frame.text:SetText(table.concat(infoMessages[ShadowUF.db.global.infoID], "\n")) + frame.text:SetPoint("TOPLEFT", 12, -22) + frame.text:SetWidth(frame:GetWidth() - 20) + frame.text:SetJustifyH("LEFT") + frame:SetHeight(frame.text:GetHeight() + 70) + + frame.hide = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") + frame.hide:SetText(L["Ok"]) + frame.hide:SetHeight(20) + frame.hide:SetWidth(100) + frame.hide:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 8, 8) + frame.hide:SetScript("OnClick", function(f) + f:GetParent():Hide() + end) +end + function ShadowUF:Print(msg) DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99Shadow UF|r: " .. msg) end @@ -526,7 +887,7 @@ CONFIGMODE_CALLBACKS["Shadowed Unit Frames"] = function(mode) elseif( mode == "OFF" ) then ShadowUF.db.profile.locked = true end - + ShadowUF.modules.movers:Update() end @@ -538,7 +899,7 @@ 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) @@ -546,25 +907,28 @@ SlashCmdList["SHADOWEDUF"] = function(msg) return end end - + ShadowUF:Print(string.format(L["Cannot find any profiles named \"%s\"."], profile)) return end - - local loaded, reason = LoadAddOn("ShadowedUF_Options") + + local loaded, reason = C_AddOns.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:RegisterEvent("ADDON_LOADED") frame:SetScript("OnEvent", function(self, event, addon) if( event == "PLAYER_LOGIN" ) then ShadowUF:OnInitialize() self:UnregisterEvent("PLAYER_LOGIN") + elseif( event == "ADDON_LOADED" and ( addon == "Blizzard_ArenaUI" or addon == "Blizzard_CompactRaidFrames" ) ) then + ShadowUF:HideBlizzardFrames() end -end) \ No newline at end of file +end) diff --git a/ShadowedUnitFrames.toc b/ShadowedUnitFrames.toc index 718f941a9..a95984bcf 100755 --- a/ShadowedUnitFrames.toc +++ b/ShadowedUnitFrames.toc @@ -1,51 +1,62 @@ -## Interface: 30300 -## Title: Shadowed Unit Frames -## Notes: An apple a day keeps the raptor away, or so they say -## Author: Shadowed -## Version: @project-version@ -## SavedVariables: ShadowedUFDB -## OptionalDeps: Ace3, LibSharedMedia-3.0, LibHealComm-4.0, AceGUI-3.0-SharedMediaWidgets - -#@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 +## Interface: 11508,20505,50503 +## Title: Shadowed Unit Frames +## Notes: Moooooooooooooooooo +## Author: Shadowed +## Version: v4.3.9-classic +## SavedVariables: ShadowedUFDB +## X-Website: https://www.wowace.com/addons/shadowed-unit-frames/ +## OptionalDeps: Ace3, LibSharedMedia-3.0, AceGUI-3.0-SharedMediaWidgets, LibSpellRange-1.0, UTF8, Clique, LibClassicDurations, LibClassicCasterino +## X-Curse-Project-ID: 19268 +## X-WoWI-ID: 13494 + +#@no-lib-strip@ +libs\LibStub\LibStub.lua +libs\CallbackHandler-1.0\CallbackHandler-1.0.xml +libs\LibSharedMedia-3.0\lib.xml +libs\AceDB-3.0\AceDB-3.0.xml +libs\LibSpellRange-1.0\lib.xml +libs\LibClassicDurations\LibClassicDurations.xml [AllowLoadGameType vanilla] +libs\LibClassicCasterino\LibClassicCasterino.lua [AllowLoadGameType vanilla] +libs\UTF8\utf8.lua +#@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 + +ExternalAPI.lua +ShadowedUnitFrames.xml +ShadowedUnitFrames.lua +modules\helpers.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\incheal.lua +modules\xp.lua +modules\cast.lua +modules\auras.lua +modules\auraindicators.lua +modules\combattext.lua +modules\totems.lua +modules\fader.lua +modules\basecombopoints.lua +modules\combopoints.lua +modules\range.lua +modules\empty.lua modules\druid.lua -#@do-not-package@ -options\config.lua -#@end-do-not-package@ \ No newline at end of file +modules\runes.lua +modules\holypower.lua +modules\soulshards.lua diff --git a/ShadowedUnitFrames.xml b/ShadowedUnitFrames.xml new file mode 100644 index 000000000..9ce821a83 --- /dev/null +++ b/ShadowedUnitFrames.xml @@ -0,0 +1,4 @@ + + + diff --git a/i18n-scanner.rb b/i18n-scanner.rb new file mode 100644 index 000000000..14462bb16 --- /dev/null +++ b/i18n-scanner.rb @@ -0,0 +1,16 @@ +require 'set' +strings = Set.new + +Dir["**/*.lua"].each do |file| + next if file =~ /localization/ + next if file =~ /libs\// + contents = File.read(file) + + contents.scan(/L\["(.+?)"\]/).each do |match| + strings << match[0] + end +end + +strings.sort.each do |string| + puts "L[\"#{string}\"] = true" +end \ No newline at end of file diff --git a/libs/AceDB-3.0/AceDB-3.0.lua b/libs/AceDB-3.0/AceDB-3.0.lua new file mode 100644 index 000000000..276c56ffb --- /dev/null +++ b/libs/AceDB-3.0/AceDB-3.0.lua @@ -0,0 +1,797 @@ +--- **AceDB-3.0** manages the SavedVariables of your addon. +-- It offers profile management, smart defaults and namespaces for modules.\\ +-- Data can be saved in different data-types, depending on its intended usage. +-- The most common data-type is the `profile` type, which allows the user to choose +-- the active profile, and manage the profiles of all of his characters.\\ +-- The following data types are available: +-- * **char** Character-specific data. Every character has its own database. +-- * **realm** Realm-specific data. All of the players characters on the same realm share this database. +-- * **class** Class-specific data. All of the players characters of the same class share this database. +-- * **race** Race-specific data. All of the players characters of the same race share this database. +-- * **faction** Faction-specific data. All of the players characters of the same faction share this database. +-- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database. +-- * **locale** Locale specific data, based on the locale of the players game client. +-- * **global** Global Data. All characters on the same account share this database. +-- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used. +-- +-- Creating a new Database using the `:New` function will return a new DBObject. A database will inherit all functions +-- of the DBObjectLib listed here. \\ +-- If you create a new namespaced child-database (`:RegisterNamespace`), you'll get a DBObject as well, but note +-- that the child-databases cannot individually change their profile, and are linked to their parents profile - and because of that, +-- the profile related APIs are not available. Only `:RegisterDefaults` and `:ResetProfile` are available on child-databases. +-- +-- For more details on how to use AceDB-3.0, see the [[AceDB-3.0 Tutorial]]. +-- +-- You may also be interested in [[libdualspec-1-0|LibDualSpec-1.0]] to do profile switching automatically when switching specs. +-- +-- @usage +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("DBExample") +-- +-- -- declare defaults to be used in the DB +-- local defaults = { +-- profile = { +-- setting = true, +-- } +-- } +-- +-- function MyAddon:OnInitialize() +-- -- Assuming the .toc says ## SavedVariables: MyAddonDB +-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) +-- end +-- @class file +-- @name AceDB-3.0.lua +-- @release $Id: AceDB-3.0.lua 1364 2025-07-05 16:01:08Z nevcairiel $ +local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 33 +local AceDB = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) + +if not AceDB then return end -- No upgrade needed + +-- Lua APIs +local type, pairs, next, error = type, pairs, next, error +local setmetatable, rawset, rawget = setmetatable, rawset, rawget + +-- WoW APIs +local _G = _G + +AceDB.db_registry = AceDB.db_registry or {} +AceDB.frame = AceDB.frame or CreateFrame("Frame") + +local CallbackHandler +local CallbackDummy = { Fire = function() end } + +local DBObjectLib = {} + +--[[------------------------------------------------------------------------- + AceDB Utility Functions +---------------------------------------------------------------------------]] + +-- Simple shallow copy for copying defaults +local function copyTable(src, dest) + if type(dest) ~= "table" then dest = {} end + if type(src) == "table" then + for k,v in pairs(src) do + if type(v) == "table" then + -- try to index the key first so that the metatable creates the defaults, if set, and use that table + v = copyTable(v, dest[k]) + end + dest[k] = v + end + end + return dest +end + +-- Called to add defaults to a section of the database +-- +-- When a ["*"] default section is indexed with a new key, a table is returned +-- and set in the host table. These tables must be cleaned up by removeDefaults +-- in order to ensure we don't write empty default tables. +local function copyDefaults(dest, src) + -- this happens if some value in the SV overwrites our default value with a non-table + --if type(dest) ~= "table" then return end + for k, v in pairs(src) do + if k == "*" or k == "**" then + if type(v) == "table" then + -- This is a metatable used for table defaults + local mt = { + -- This handles the lookup and creation of new subtables + __index = function(t,k2) + if k2 == nil then return nil end + local tbl = {} + copyDefaults(tbl, v) + rawset(t, k2, tbl) + return tbl + end, + } + setmetatable(dest, mt) + -- handle already existing tables in the SV + for dk, dv in pairs(dest) do + if not rawget(src, dk) and type(dv) == "table" then + copyDefaults(dv, v) + end + end + else + -- Values are not tables, so this is just a simple return + local mt = {__index = function(t,k2) return k2~=nil and v or nil end} + setmetatable(dest, mt) + end + elseif type(v) == "table" then + if not rawget(dest, k) then rawset(dest, k, {}) end + if type(dest[k]) == "table" then + copyDefaults(dest[k], v) + if src['**'] then + copyDefaults(dest[k], src['**']) + end + end + else + if rawget(dest, k) == nil then + rawset(dest, k, v) + end + end + end +end + +-- Called to remove all defaults in the default table from the database +local function removeDefaults(db, defaults, blocker) + -- remove all metatables from the db, so we don't accidentally create new sub-tables through them + setmetatable(db, nil) + -- loop through the defaults and remove their content + for k,v in pairs(defaults) do + if k == "*" or k == "**" then + if type(v) == "table" then + -- Loop through all the actual k,v pairs and remove + for key, value in pairs(db) do + if type(value) == "table" then + -- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables + if defaults[key] == nil and (not blocker or blocker[key] == nil) then + removeDefaults(value, v) + -- if the table is empty afterwards, remove it + if next(value) == nil then + db[key] = nil + end + -- if it was specified, only strip ** content, but block values which were set in the key table + elseif k == "**" then + removeDefaults(value, v, defaults[key]) + end + end + end + elseif k == "*" then + -- check for non-table default + for key, value in pairs(db) do + if defaults[key] == nil and v == value then + db[key] = nil + end + end + end + elseif type(v) == "table" and type(db[k]) == "table" then + -- if a blocker was set, dive into it, to allow multi-level defaults + removeDefaults(db[k], v, blocker and blocker[k]) + if next(db[k]) == nil then + db[k] = nil + end + else + -- check if the current value matches the default, and that its not blocked by another defaults table + if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then + db[k] = nil + end + end + end +end + +-- This is called when a table section is first accessed, to set up the defaults +local function initSection(db, section, svstore, key, defaults) + local sv = rawget(db, "sv") + + local tableCreated + if not sv[svstore] then sv[svstore] = {} end + if not sv[svstore][key] then + sv[svstore][key] = {} + tableCreated = true + end + + local tbl = sv[svstore][key] + + if defaults then + copyDefaults(tbl, defaults) + end + rawset(db, section, tbl) + + return tableCreated, tbl +end + +-- Metatable to handle the dynamic creation of sections and copying of sections. +local dbmt = { + __index = function(t, section) + local keys = rawget(t, "keys") + local key = keys[section] + if key then + local defaultTbl = rawget(t, "defaults") + local defaults = defaultTbl and defaultTbl[section] + + if section == "profile" then + local new = initSection(t, section, "profiles", key, defaults) + if new then + -- Callback: OnNewProfile, database, newProfileKey + t.callbacks:Fire("OnNewProfile", t, key) + end + elseif section == "profiles" then + local sv = rawget(t, "sv") + if not sv.profiles then sv.profiles = {} end + rawset(t, "profiles", sv.profiles) + elseif section == "global" then + local sv = rawget(t, "sv") + if not sv.global then sv.global = {} end + if defaults then + copyDefaults(sv.global, defaults) + end + rawset(t, section, sv.global) + else + initSection(t, section, section, key, defaults) + end + end + + return rawget(t, section) + end +} + +local function validateDefaults(defaults, keyTbl, offset) + if not defaults then return end + offset = offset or 0 + for k in pairs(defaults) do + if not keyTbl[k] or k == "profiles" then + error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset) + end + end +end + +local preserve_keys = { + ["callbacks"] = true, + ["RegisterCallback"] = true, + ["UnregisterCallback"] = true, + ["UnregisterAllCallbacks"] = true, + ["children"] = true, +} + +local realmKey = GetRealmName() +local charKey = UnitName("player") .. " - " .. realmKey +local _, classKey = UnitClass("player") +local _, raceKey = UnitRace("player") +local factionKey = UnitFactionGroup("player") +local factionrealmKey = factionKey .. " - " .. realmKey +local localeKey = GetLocale():lower() + +local regionTable = { "US", "KR", "EU", "TW", "CN" } +local regionKey = regionTable[GetCurrentRegion()] or GetCurrentRegionName() or "TR" +local factionrealmregionKey = factionrealmKey .. " - " .. regionKey + +-- Actual database initialization function +local function initdb(sv, defaults, defaultProfile, olddb, parent) + -- Generate the database keys for each section + + -- map "true" to our "Default" profile + if defaultProfile == true then defaultProfile = "Default" end + + local profileKey + if not parent then + -- Make a container for profile keys + if not sv.profileKeys then sv.profileKeys = {} end + + -- Try to get the profile selected from the char db + profileKey = sv.profileKeys[charKey] or defaultProfile or charKey + + -- save the selected profile for later + sv.profileKeys[charKey] = profileKey + else + -- Use the profile of the parents DB + profileKey = parent.keys.profile or defaultProfile or charKey + + -- clear the profileKeys in the DB, namespaces don't need to store them + sv.profileKeys = nil + end + + -- This table contains keys that enable the dynamic creation + -- of each section of the table. The 'global' and 'profiles' + -- have a key of true, since they are handled in a special case + local keyTbl= { + ["char"] = charKey, + ["realm"] = realmKey, + ["class"] = classKey, + ["race"] = raceKey, + ["faction"] = factionKey, + ["factionrealm"] = factionrealmKey, + ["factionrealmregion"] = factionrealmregionKey, + ["profile"] = profileKey, + ["locale"] = localeKey, + ["global"] = true, + ["profiles"] = true, + } + + validateDefaults(defaults, keyTbl, 1) + + -- This allows us to use this function to reset an entire database + -- Clear out the old database + if olddb then + for k,v in pairs(olddb) do if not preserve_keys[k] then olddb[k] = nil end end + end + + -- Give this database the metatable so it initializes dynamically + local db = setmetatable(olddb or {}, dbmt) + + if not rawget(db, "callbacks") then + -- try to load CallbackHandler-1.0 if it loaded after our library + if not CallbackHandler then CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0", true) end + db.callbacks = CallbackHandler and CallbackHandler:New(db) or CallbackDummy + end + + -- Copy methods locally into the database object, to avoid hitting + -- the metatable when calling methods + + if not parent then + for name, func in pairs(DBObjectLib) do + db[name] = func + end + else + -- hack this one in + db.RegisterDefaults = DBObjectLib.RegisterDefaults + db.ResetProfile = DBObjectLib.ResetProfile + end + + -- Set some properties in the database object + db.profiles = sv.profiles + db.keys = keyTbl + db.sv = sv + --db.sv_name = name + db.defaults = defaults + db.parent = parent + + -- store the DB in the registry + AceDB.db_registry[db] = true + + return db +end + +-- handle PLAYER_LOGOUT +-- strip all defaults from all databases +-- and cleans up empty sections +local function logoutHandler(frame, event) + if event == "PLAYER_LOGOUT" then + for db in pairs(AceDB.db_registry) do + db.callbacks:Fire("OnDatabaseShutdown", db) + db:RegisterDefaults(nil) + + -- cleanup sections that are empty without defaults + local sv = rawget(db, "sv") + for section in pairs(rawget(db, "keys")) do + if rawget(sv, section) then + -- global is special, all other sections have sub-entrys + -- also don't delete empty profiles on main dbs, only on namespaces + if section ~= "global" and (section ~= "profiles" or rawget(db, "parent")) then + for key in pairs(sv[section]) do + if not next(sv[section][key]) then + sv[section][key] = nil + end + end + end + if not next(sv[section]) then + sv[section] = nil + end + end + end + end + + -- second pass after everything else is cleaned up to remove empty namespaces + -- can't be run in-loop above since there is no guaranteed order + for db in pairs(AceDB.db_registry) do + local sv = rawget(db, "sv") + local namespaces = rawget(sv, "namespaces") + if namespaces then + for name in pairs(namespaces) do + -- cleanout empty profiles table, if still present + if namespaces[name].profiles and not next(namespaces[name].profiles) then + namespaces[name].profiles = nil + end + + -- remove entire namespace, if needed + if not next(namespaces[name]) then + namespaces[name] = nil + end + end + end + end + end +end + +AceDB.frame:RegisterEvent("PLAYER_LOGOUT") +AceDB.frame:SetScript("OnEvent", logoutHandler) + + +--[[------------------------------------------------------------------------- + AceDB Object Method Definitions +---------------------------------------------------------------------------]] + +--- Sets the defaults table for the given database object by clearing any +-- that are currently set, and then setting the new defaults. +-- @param defaults A table of defaults for this database +function DBObjectLib:RegisterDefaults(defaults) + if defaults and type(defaults) ~= "table" then + error(("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected, got %q."):format(type(defaults)), 2) + end + + validateDefaults(defaults, self.keys) + + -- Remove any currently set defaults + if self.defaults then + for section,key in pairs(self.keys) do + if self.defaults[section] and rawget(self, section) then + removeDefaults(self[section], self.defaults[section]) + end + end + end + + -- Set the DBObject.defaults table + self.defaults = defaults + + -- Copy in any defaults, only touching those sections already created + if defaults then + for section,key in pairs(self.keys) do + if defaults[section] and rawget(self, section) then + copyDefaults(self[section], defaults[section]) + end + end + end +end + +--- Changes the profile of the database and all of it's namespaces to the +-- supplied named profile +-- @param name The name of the profile to set as the current profile +function DBObjectLib:SetProfile(name) + if type(name) ~= "string" then + error(("Usage: AceDBObject:SetProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) + end + + -- changing to the same profile, dont do anything + if name == self.keys.profile then return end + + local oldProfile = self.profile + local defaults = self.defaults and self.defaults.profile + + -- Callback: OnProfileShutdown, database + self.callbacks:Fire("OnProfileShutdown", self) + + if oldProfile and defaults then + -- Remove the defaults from the old profile + removeDefaults(oldProfile, defaults) + end + + self.profile = nil + self.keys["profile"] = name + + -- if the storage exists, save the new profile + -- this won't exist on namespaces. + if self.sv.profileKeys then + self.sv.profileKeys[charKey] = name + end + + -- populate to child namespaces + if self.children then + for _, db in pairs(self.children) do + DBObjectLib.SetProfile(db, name) + end + end + + -- Callback: OnProfileChanged, database, newProfileKey + self.callbacks:Fire("OnProfileChanged", self, name) +end + +--- Returns a table with the names of the existing profiles in the database. +-- You can optionally supply a table to re-use for this purpose. +-- @param tbl A table to store the profile names in (optional) +function DBObjectLib:GetProfiles(tbl) + if tbl and type(tbl) ~= "table" then + error(("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected, got %q."):format(type(tbl)), 2) + end + + -- Clear the container table + if tbl then + for k,v in pairs(tbl) do tbl[k] = nil end + else + tbl = {} + end + + local curProfile = self.keys.profile + + local i = 0 + for profileKey in pairs(self.profiles) do + i = i + 1 + tbl[i] = profileKey + if curProfile and profileKey == curProfile then curProfile = nil end + end + + -- Add the current profile, if it hasn't been created yet + if curProfile then + i = i + 1 + tbl[i] = curProfile + end + + return tbl, i +end + +--- Returns the current profile name used by the database +function DBObjectLib:GetCurrentProfile() + return self.keys.profile +end + +--- Deletes a named profile. This profile must not be the active profile. +-- @param name The name of the profile to be deleted +-- @param silent If true, do not raise an error when the profile does not exist +function DBObjectLib:DeleteProfile(name, silent) + if type(name) ~= "string" then + error(("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) + end + + if self.keys.profile == name then + error(("Cannot delete the active profile (%q) in an AceDBObject."):format(name), 2) + end + + if not rawget(self.profiles, name) and not silent then + error(("Cannot delete profile %q as it does not exist."):format(name), 2) + end + + self.profiles[name] = nil + + -- populate to child namespaces + if self.children then + for _, db in pairs(self.children) do + DBObjectLib.DeleteProfile(db, name, true) + end + end + + -- remove from unloaded namespaces + if self.sv.namespaces then + for nsname, data in pairs(self.sv.namespaces) do + if self.children and self.children[nsname] then + -- already a mapped namespace + elseif data.profiles then + data.profiles[name] = nil + end + end + end + + -- switch all characters that use this profile back to the default + if self.sv.profileKeys then + for key, profile in pairs(self.sv.profileKeys) do + if profile == name then + self.sv.profileKeys[key] = nil + end + end + end + + -- Callback: OnProfileDeleted, database, profileKey + self.callbacks:Fire("OnProfileDeleted", self, name) +end + +--- Copies a named profile into the current profile, overwriting any conflicting +-- settings. +-- @param name The name of the profile to be copied into the current profile +-- @param silent If true, do not raise an error when the profile does not exist +function DBObjectLib:CopyProfile(name, silent) + if type(name) ~= "string" then + error(("Usage: AceDBObject:CopyProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) + end + + if name == self.keys.profile then + error(("Cannot have the same source and destination profiles (%q)."):format(name), 2) + end + + if not rawget(self.profiles, name) and not silent then + error(("Cannot copy profile %q as it does not exist."):format(name), 2) + end + + -- Reset the profile before copying + DBObjectLib.ResetProfile(self, nil, true) + + local profile = self.profile + local source = self.profiles[name] + + copyTable(source, profile) + + -- populate to child namespaces + if self.children then + for _, db in pairs(self.children) do + DBObjectLib.CopyProfile(db, name, true) + end + end + + -- copy unloaded namespaces + if self.sv.namespaces then + for nsname, data in pairs(self.sv.namespaces) do + if self.children and self.children[nsname] then + -- already a mapped namespace + elseif data.profiles then + -- reset the current profile + data.profiles[self.keys.profile] = {} + -- copy data + copyTable(data.profiles[name], data.profiles[self.keys.profile]) + end + end + end + + -- Callback: OnProfileCopied, database, sourceProfileKey + self.callbacks:Fire("OnProfileCopied", self, name) +end + +--- Resets the current profile to the default values (if specified). +-- @param noChildren if set to true, the reset will not be populated to the child namespaces of this DB object +-- @param noCallbacks if set to true, won't fire the OnProfileReset callback +function DBObjectLib:ResetProfile(noChildren, noCallbacks) + local profile = self.profile + + for k,v in pairs(profile) do + profile[k] = nil + end + + local defaults = self.defaults and self.defaults.profile + if defaults then + copyDefaults(profile, defaults) + end + + -- populate to child namespaces + if self.children and not noChildren then + for _, db in pairs(self.children) do + DBObjectLib.ResetProfile(db, nil, noCallbacks) + end + end + + -- reset unloaded namespaces + if self.sv.namespaces and not noChildren then + for nsname, data in pairs(self.sv.namespaces) do + if self.children and self.children[nsname] then + -- already a mapped namespace + elseif data.profiles then + -- reset the current profile + data.profiles[self.keys.profile] = nil + end + end + end + + -- Callback: OnProfileReset, database + if not noCallbacks then + self.callbacks:Fire("OnProfileReset", self) + end +end + +--- Resets the entire database, using the string defaultProfile as the new default +-- profile. +-- @param defaultProfile The profile name to use as the default +function DBObjectLib:ResetDB(defaultProfile) + if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then + error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2) + end + + local sv = self.sv + for k,v in pairs(sv) do + sv[k] = nil + end + + initdb(sv, self.defaults, defaultProfile, self) + + -- fix the child namespaces + if self.children then + if not sv.namespaces then sv.namespaces = {} end + for name, db in pairs(self.children) do + if not sv.namespaces[name] then sv.namespaces[name] = {} end + initdb(sv.namespaces[name], db.defaults, self.keys.profile, db, self) + end + end + + -- Callback: OnDatabaseReset, database + self.callbacks:Fire("OnDatabaseReset", self) + -- Callback: OnProfileChanged, database, profileKey + self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"]) + + return self +end + +--- Creates a new database namespace, directly tied to the database. This +-- is a full scale database in it's own rights other than the fact that +-- it cannot control its profile individually +-- @param name The name of the new namespace +-- @param defaults A table of values to use as defaults +function DBObjectLib:RegisterNamespace(name, defaults) + if type(name) ~= "string" then + error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected, got %q."):format(type(name)), 2) + end + if defaults and type(defaults) ~= "table" then + error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected, got %q."):format(type(defaults)), 2) + end + if self.children and self.children[name] then + error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace called %q already exists."):format(name), 2) + end + + local sv = self.sv + if not sv.namespaces then sv.namespaces = {} end + if not sv.namespaces[name] then + sv.namespaces[name] = {} + end + + local newDB = initdb(sv.namespaces[name], defaults, self.keys.profile, nil, self) + + if not self.children then self.children = {} end + self.children[name] = newDB + return newDB +end + +--- Returns an already existing namespace from the database object. +-- @param name The name of the new namespace +-- @param silent if true, the addon is optional, silently return nil if its not found +-- @usage +-- local namespace = self.db:GetNamespace('namespace') +-- @return the namespace object if found +function DBObjectLib:GetNamespace(name, silent) + if type(name) ~= "string" then + error(("Usage: AceDBObject:GetNamespace(name): 'name' - string expected, got %q."):format(type(name)), 2) + end + if not silent and not (self.children and self.children[name]) then + error(("Usage: AceDBObject:GetNamespace(name): 'name' - namespace %q does not exist."):format(name), 2) + end + if not self.children then self.children = {} end + return self.children[name] +end + +--[[------------------------------------------------------------------------- + AceDB Exposed Methods +---------------------------------------------------------------------------]] + +--- Creates a new database object that can be used to handle database settings and profiles. +-- By default, an empty DB is created, using a character specific profile. +-- +-- You can override the default profile used by passing any profile name as the third argument, +-- or by passing //true// as the third argument to use a globally shared profile called "Default". +-- +-- Note that there is no token replacement in the default profile name, passing a defaultProfile as "char" +-- will use a profile named "char", and not a character-specific profile. +-- @param tbl The name of variable, or table to use for the database +-- @param defaults A table of database defaults +-- @param defaultProfile The name of the default profile. If not set, a character specific profile will be used as the default. +-- You can also pass //true// to use a shared global profile called "Default". +-- @usage +-- -- Create an empty DB using a character-specific default profile. +-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB") +-- @usage +-- -- Create a DB using defaults and using a shared default profile +-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) +function AceDB:New(tbl, defaults, defaultProfile) + if type(tbl) == "string" then + local name = tbl + tbl = _G[name] + if not tbl then + tbl = {} + _G[name] = tbl + end + end + + if type(tbl) ~= "table" then + error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected, got %q."):format(type(tbl)), 2) + end + + if defaults and type(defaults) ~= "table" then + error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected, got %q."):format(type(defaults)), 2) + end + + if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then + error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2) + end + + return initdb(tbl, defaults, defaultProfile) +end + +-- upgrade existing databases +for db in pairs(AceDB.db_registry) do + if not db.parent then + for name,func in pairs(DBObjectLib) do + db[name] = func + end + else + db.RegisterDefaults = DBObjectLib.RegisterDefaults + db.ResetProfile = DBObjectLib.ResetProfile + end +end diff --git a/libs/AceDB-3.0/AceDB-3.0.xml b/libs/AceDB-3.0/AceDB-3.0.xml new file mode 100644 index 000000000..46b20ba9d --- /dev/null +++ b/libs/AceDB-3.0/AceDB-3.0.xml @@ -0,0 +1,4 @@ + +