From 3c14ccad2150dabb844fe66d3db9d042dd1bd92c Mon Sep 17 00:00:00 2001 From: Andrew Mordecai Date: Sat, 16 Jul 2016 13:31:11 -0700 Subject: [PATCH] Lots of work --- .gitignore | 2 + Auras.lua | 243 ++++++++++++++++++++++++++ Config.lua | 33 ++-- Elements/AFK.lua | 101 +++++++++++ Elements/AuraStack.lua | 150 +++++++++++++++++ Elements/BurningEmbers.lua | 9 +- Elements/DemonicFury.lua | 9 +- Elements/DispelIcon.lua | 87 ++++++++++ Fonts/LatoBold.ttf | Bin 0 -> 59940 bytes Functions.lua | 313 +++++++++++++++++++++++++--------- Layout.lua | 402 +++++++++++++++++++++++++++++--------------- Textures/statusbar4.tga | Bin 0 -> 530 bytes oUF_Stardust.toc | 8 + 13 files changed, 1107 insertions(+), 250 deletions(-) create mode 100644 Auras.lua create mode 100644 Elements/AFK.lua create mode 100755 Elements/AuraStack.lua create mode 100644 Elements/DispelIcon.lua create mode 100644 Fonts/LatoBold.ttf create mode 100755 Textures/statusbar4.tga diff --git a/.gitignore b/.gitignore index 586299e..5cd0e19 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ Libs/ +Textures/ +!Textures/statusbar4.tga diff --git a/Auras.lua b/Auras.lua new file mode 100644 index 0000000..1bc2212 --- /dev/null +++ b/Auras.lua @@ -0,0 +1,243 @@ +--[[----------------------------------------------------------------------- + oUF: Stardust - a layout for the oUF framework + Copyright (c) 2016 Andrew Mordecai + This code is released under the zlib license; see LICENSE for details. +-------------------------------------------------------------------------]] + +local _, Stardust = ... + +local _, class = UnitClass("player") + +local b = { + ALL = 0, + SELF = 1, + MINE = 2, + FRIEND = 4, + FRIEND_OOC = 8, -- for long term buffs and non-combat effects + ENEMY = 16, +} + +local t = { + [ 2825] = b.ALL, -- Bloodlust + [ 32182] = b.ALL, -- Heroism + -- Crowd Control >= 10 seconds + [ 710] = b.ALL, -- Banish + [ 2094] = b.ALL, -- Blind + [ 33796] = b.ALL, -- Cyclone + [ 605] = b.ALL, -- Dominate Mind + [ 339] = b.ALL, -- Entangling Roots + [118699] = b.ALL, -- Fear + [ 3355] = b.ALL, -- Freezing Trap + [ 51514] = b.ALL, -- Hex + -- TODO: find spell ID for Hex with Glyph of the Compy + [ 5484] = b.ALL, -- Howl of Terror + [115078] = b.ALL, -- Paralysis + [ 118] = b.ALL, -- Polymorph + [161353] = b.ALL, -- Polymorph (bear cub) + [ 61305] = b.ALL, -- Polymorph (cat) + [161354] = b.ALL, -- Polymorph (monkey) + [161372] = b.ALL, -- Polymorph (peacock) + [161355] = b.ALL, -- Polymorph (penguin) + [ 28272] = b.ALL, -- Polymorph (pig) + [126819] = b.ALL, -- Polymorph (porcupine) + [ 61721] = b.ALL, -- Polymorph (rabbit) + [ 61780] = b.ALL, -- Polymorph (turkey) + [ 28271] = b.ALL, -- Polymorph (turtle) + [ 20066] = b.ALL, -- Repentance + [ 82691] = b.ALL, -- Ring of Frost + [ 6770] = b.ALL, -- Sap + [ 9484] = b.ALL, -- Shackle Undead + [ 10326] = b.ALL, -- Turn Evil + [ 19386] = b.ALL, -- Wyvern Sting + -- Racial Abilities + [ 25046] = b.MINE, -- Arcane Torrent (energy) + [ 28730] = b.MINE, -- Arcane Torrent (mana) + [ 50613] = b.MINE, -- Arcane Torrent (runic power) + [ 69179] = b.MINE, -- Arcane Torrent (rage) + [ 80483] = b.MINE, -- Arcane Torrent (focus) + [129597] = b.MINE, -- Arcane Torrent (chi) + [155145] = b.MINE, -- Arcane Torrent (holy power) + [ 26297] = b.MINE, -- Berserking + [ 20572] = b.MINE, -- Blood Fury (attack power) + [ 33697] = b.MINE, -- Blood Fury (melee attack power + spell power) + [ 33702] = b.MINE, -- Blood Fury (spell power) + [ 68992] = b.MINE, -- Darkflight + [107079] = b.MINE, -- Quaking Palm + [ 58984] = b.MINE, -- Shadowmeld + [ 20594] = b.MINE, -- Stoneform + [ 20549] = b.MINE, -- War Stomp +} +if class == "DEATHKNIGHT" then +elseif class == "DEMONHUNTER" then +elseif class == "DRUID" then + t[ 22812] = b.MINE -- Barkskin + t[ 50334] = b.MINE -- Berserk (bear) + t[106951] = b.MINE -- Berserk (cat) + t[155835] = b.MINE -- Bristling Fur + t[112071] = b.MINE -- Celestial Alignment + t[102351] = b.MINE -- Cenarion Ward (buff) + t[102352] = b.MINE -- Cenarion Ward (heal) + t[ 16870] = b.MINE -- Clearcasting + t[135700] = b.MINE -- Clearcasting (from Omen of Clarity) + t[ 1850] = b.MINE -- Dash + t[137452] = b.MINE -- Displacer Beast + t[145162] = b.MINE -- Dream of Cenarius + t[157228] = b.MINE -- Empowered Moonkin + t[ 6795] = b.MINE -- Growl + t[108291] = b.MINE -- Heart of the Wild (balance) + t[108292] = b.MINE -- Heart of the Wild (feral) + t[108293] = b.MINE -- Heart of the Wild (guardian) + t[108294] = b.MINE -- Heart of the Wild (restoration) + t[ 99] = b.MINE -- Incapacitating Roar + t[102560] = b.MINE -- Incarnation: Chosen of Elune + t[102543] = b.MINE -- Incarnation: King of the Jungle + t[102558] = b.MINE -- Incarnation: Son of Ursoc + t[ 33891] = b.MINE -- Incarnation: Tree of Life + t[102342] = b.MINE -- Ironbark + t[ 33745] = b.MINE -- Lacerate + t[ 33763] = b.MINE -- Lifebloom + t[164547] = b.MINE -- Lunar Empowerment (from Starsurge) + t[ 22570] = b.MINE -- Maim + -- TODO: find spell ID for mangle slow debuff + t[ 1126] = b.FRIEND_OOC -- Mark of the Wild + t[102359] = b.MINE -- Mass Entanglement + t[ 5211] = b.MINE -- Mighty Bash + t[164812] = b.MINE -- Moonfire (TODO?) + t[132158] = b.MINE -- Nature's Swiftness + t[124974] = b.MINE -- Nature's Vigil + t[ 69369] = b.MINE -- Predatory Swiftness + t[ 5215] = b.MINE -- Prowl + t[158792] = b.MINE -- Pulverize + t[155722] = b.MINE -- Rake + t[ 8936] = b.MINE -- Regrowth + t[ 774] = b.MINE -- Rejuvenation + t[ 1079] = b.MINE -- Rip + t[132402] = b.MINE -- Savage Defense + t[ 52610] = b.MINE -- Savage Roar + t[164545] = b.MINE -- Solar Empowerment (from Starsurge) + t[114108] = b.MINE -- Soul of the Forest + t[106989] = b.MINE -- Stampeding Roar + t[184989] = b.MINE -- Starfall + t[152221] = b.MINE -- Stellar Flare + t[164815] = b.MINE -- Sunfire + t[ 61336] = b.MINE -- Survival Instincts + t[ 77758] = b.MINE -- Thrash (bear) + t[106830] = b.MINE -- Thrash (cat) + t[ 5217] = b.MINE -- Tiger's Fury + t[102416] = b.MINE -- Wild Charge (aquatic) + t[ 16979] = b.MINE -- Wild Charge (bear) + t[ 49376] = b.MINE -- Wild Charge (cat) + t[ 48438] = b.MINE -- Wild Growth +elseif class == "HUNTER" then +elseif class == "MAGE" then +elseif class == "MONK" then +elseif class == "PALADIN" then +elseif class == "PRIEST" then +elseif class == "ROGUE" then +elseif class == "SHAMAN" then + t[108281] = b.MINE -- Ancestral Guidance + t[ 16188] = b.MINE -- Ancestral Swiftness + t[114050] = b.MINE -- Ascendance (elemental) + t[114051] = b.MINE -- Ascendance (enhancement) + t[114052] = b.MINE -- Ascendance (restoration) + t[108271] = b.MINE -- Astral Shift + t[ 974] = b.MINE -- Earth Shield + t[ 3600] = b.MINE -- Earthbind (Totem) + t[ 64695] = b.MINE -- Earthgrab (Totem) + t[157174] = b.MINE -- Elemental Fusion + t[ 16166] = b.MINE -- Elemental Mastery +-- t[162557] = b.MINE -- Enhanced Unleash + t[ 8050] = b.MINE -- Flame Shock + t[ 8056] = b.MINE -- Frost Shock + t[ 63685] = b.MINE -- Frozen Power (from Frost Shock with talent) + t[ 8178] = b.SELF -- Grounding Totem Effect + t[ 89523] = b.SELF -- Grounding Totem (with Glyph of Grounding Totem) + t[ 77762] = b.MINE -- Lava Surge +-- t[ 31616] = b.SELF -- Nature's Guardian + t[ 61295] = b.MINE -- Riptide + t[ 30823] = b.MINE -- Shamanistic Rage + t[ 98007] = b.FRIEND -- Spirit Link Totem (TODO?) + t[151175] = b.FRIEND -- Spirit Link Totem (TODO?) + t[ 58875] = b.MINE -- Spirit Walk + t[ 79206] = b.MINE -- Spiritwalker's Grace + t[118905] = b.MINE -- Static Charge (from Capacitor Totem) + t[114893] = b.MINE -- Stone Bulwark + t[ 17364] = b.MINE -- Stormstrike + t[ 53390] = b.MINE -- Tidal Waves + t[165462] = b.MINE -- Unleash Flame + t[ 73685] = b.MINE -- Unleash Life + t[118470] = b.MINE -- Unleashed Fury (elemental) + t[118473] = b.MINE -- Unleashed Fury (restoration) + t[ 546] = b.FRIEND_OOC -- Water Walking +-- t[114896] = b.MINE -- Windwalk Totem + t[115356] = b.MINE -- Windstrike (replaces Stormstrike during Ascendance) +elseif class == "WARLOCK" then + t[ 980] = b.MINE -- Agony + t[117828] = b.MINE -- Backdraft + t[111397] = b.MINE -- Blood Horror (self buff) + t[137143] = b.MINE -- Blood Horror (debuff) + t[111400] = b.MINE -- Burning Rush + t[124915] = b.MINE -- Chaos Wave + t[ 17962] = b.MINE -- Conflagrate + t[146739] = b.MINE -- Corruption + t[110913] = b.MINE -- Dark Bargain (absorb) + t[110914] = b.MINE -- Dark Bargain (dot) + t[109773] = b.FRIEND_OOC -- Dark Intent + t[108359] = b.MINE -- Dark Regeneration + t[113858] = b.MINE -- Dark Soul: Instability + t[113861] = b.MINE -- Dark Soul: Knowledge + t[113860] = b.MINE -- Dark Soul: Misery + t[ 603] = b.MINE -- Doom + t[114635] = b.MINE -- Ember Tap (with glyph) + t[ 1098] = b.ALL -- Enslave Demon + t[108683] = b.MINE -- Fire and Brimstone + t[ 47960] = b.MINE -- Hand of Gul'dan + t[ 48181] = b.MINE -- Haunt + t[ 80240] = b.MINE -- Havoc + t[ 6262] = b.MINE -- Healthstone (with glyph) (TODO?) + t[157736] = b.MINE -- Immolate + t[108686] = b.MINE -- Immolate (with Fire and Brimstone) + t[137587] = b.MINE -- Kil'jaeden's Cunning + t[ 1454] = b.MINE -- Life Tab (with glyph) (TODO?) + t[171018] = b.MINE -- Meteor Strike + t[ 6789] = b.MINE -- Mortal Coil + t[122355] = b.MINE -- Molten Core + t[104232] = b.MINE -- Rain of Fire + t[108416] = b.MINE -- Sacrificial Pact + t[ 27243] = b.MINE -- Seed of Corruption + t[114790] = b.MINE -- Seed of Corruption (with Soulburn) + t[ 30283] = b.MINE -- Shadowfury + t[ 86211] = b.MINE -- Soul Swap + t[ 74434] = b.MINE -- Soulburn + t[ 20707] = b.FRIEND -- Soulstone + t[ 5697] = b.FRIEND_OOC -- Unending Breath + t[104773] = b.MINE -- Unending Resolve + t[ 30108] = b.MINE -- Unstable Affliction +elseif class == "WARRIOR" then +end + +--------------------------- +-- Functions +--------------------------- + +local UnitAffectingCombat = UnitAffectingCombat +local UnitIsUnit = UnitIsUnit +local UnitReaction = UnitReaction + +function Stardust.AuraFilter(element, unit, icon, name, _, _, count, dispelType, duration, expires, caster, isStealable, _, spellID, canApplyAura, isBossDebuff) + local x = t[spellID] + if isBossDebuff or caster == "vehicle" or x == b.ALL then + return true + elseif x == b.MINE then + return icon.isPlayer or caster == "pet" + elseif x == b.FRIEND then + return UnitReaction(unit, "player") >= 4 + elseif x == b.FRIEND_OOC then + return UnitReaction(unit, "player") >= 4 and not UnitAffectingCombat("player") + elseif x == b.ENEMY then + return UnitReaction(unit, "player") < 4 + elseif x == b.SELF then + return UnitIsUnit(unit, "player") + end +end + diff --git a/Config.lua b/Config.lua index 1f58cee..862dfe7 100644 --- a/Config.lua +++ b/Config.lua @@ -6,16 +6,23 @@ local _, Stardust = ... +local LibSharedMedia = LibStub("LibSharedMedia-3.0") +LibSharedMedia:Register("font", "Lato Bold", "Interface\\AddOns\\oUF_Stardust\\Fonts\\LatoBold.ttf", bit.bor(LibSharedMedia.LOCALE_BIT_western, LibSharedMedia.LOCALE_BIT_ruRU)) +--LibSharedMedia:Register("statusbar", "Qulight", "Interface\\AddOns\\oUF_Stardust\\Textures\\statusbar4") + Stardust.config = { -- Style barTexture = "Qulight", bgMultiplier = 0.35, - numberFont = "Blizzard Bold", - textFont = "Blizzard", + numberFont = "Lato Bold", + numberLarge = 26, + numberSmall = 14, + textFont = "Lato Bold", + textSize = 16, -- Layout width = 220, - height = 32, + height = 38, power = true, powerHeight = 5, @@ -42,18 +49,12 @@ Stardust.config = { } } -Stardust.colors = { - power = { - MANA = { 0, 0.8, 1 } - }, - totems = { - [1] = { 0.6, 1, 0.2 }, -- Earth - [2] = { 1, 0.6, 0.2 }, -- Fire - [3] = { 0.2, 0.8, 1 }, -- Water - [4] = { 0.8, 0.4, 1 }, -- Air - } -} +oUF.colors.power.MANA = { 0, 0.8, 1 } -setmetatable(Stardust.colors, { __index = oUF.colors }) -setmetatable(Stardust.colors.power, { __index = oUF.colors.power }) +oUF.colors.totems = { + [1] = { 0.6, 1, 0.2 }, -- Earth + [2] = { 1, 0.6, 0.2 }, -- Fire + [3] = { 0.2, 0.8, 1 }, -- Water + [4] = { 0.8, 0.4, 1 }, -- Air +} diff --git a/Elements/AFK.lua b/Elements/AFK.lua new file mode 100644 index 0000000..d6a9cad --- /dev/null +++ b/Elements/AFK.lua @@ -0,0 +1,101 @@ +--[[----------------------------------------------------------------------- + oUF_Phanx/Elements/AFK.lua + Element to display AFK times on oUF frames. + Based on code from oUF_Smurf by Merl@chainweb.net. + Written and distributed by Phanx with permission. + + Additional changes for oUF Stardust: + - Added PreUpdate and PostUpdate callbacks. + + Usage: + self.AFK = self:CreateFontString(nil, "OVERLAY") + self.AFK:SetPoint("CENTER") +----------------------------------------------------------------------]] + +if select(4, GetAddOnInfo("oUF_AFK")) then return end + +local _, ns = ... +local oUF = ns.oUF or oUF +assert(oUF, "AFK element requires oUF") + +local times = {} +local elements = {} + +local updater = CreateFrame("Frame") +updater:Hide() + +local lastUpdate = 0 +local floor, mod, next, pairs, GetTime = floor, mod, next, pairs, GetTime +updater:SetScript("OnUpdate", function(self, elapsed) + lastUpdate = lastUpdate + elapsed + if lastUpdate > 0.2 then + lastUpdate = 0 + if not next(times) then + self:Hide() + end + for element, unit in pairs(elements) do + local t = times[unit] + if t then + t = GetTime() - t + element:SetFormattedText("AFK %d:%02.0f", floor(t / 60), mod(t, 60)) + else + elements[element] = nil + element:SetText("") -- nil gives it 0 height which might disturb the layout + end + end + end +end) + +local function Update(self, event, unit) + if unit ~= self.unit then return end + + local element = self.AFK + if element.PreUpdate then + element:PreUpdate() + end + + local afk = UnitIsAFK(unit) + + if afk and not times[unit] then + times[unit] = GetTime() + elements[element] = unit + updater:Show() + elseif times[unit] and not afk then + times[unit] = nil + element:SetText("") + end + + if element.PostUpdate then + element:PostUpdate(afk) + end +end + +local ForceUpdate = function(element) + return Update(element.__owner, "ForceUpdate", element.__owner.unit) +end + +local function Enable(self) + local element = self.AFK + if not element then return end + + element.__owner = self + element.ForceUpdate = Update + + if not element:GetFont() then + element:SetFontObject("GameFontHighlightSmall") + end + + self:RegisterEvent("PLAYER_FLAGS_CHANGED", Update) + return true +end + +local function Disable(self) + local element = self.AFK + if not element then return end + + self:UnregisterEvent("PLAYER_FLAGS_CHANGED", Update) + element:Hide() +end + +oUF:AddElement("AFK", Update, Enable, Disable) + diff --git a/Elements/AuraStack.lua b/Elements/AuraStack.lua new file mode 100755 index 0000000..fe25d14 --- /dev/null +++ b/Elements/AuraStack.lua @@ -0,0 +1,150 @@ +--[[-------------------------------------------------------------------- + oUF_Phanx/Elements/AuraStack.lua + Element to track stacking self-(de)buffs like combo points. + Copyright (c) 2008-2015 Phanx . All rights reserved. + + You may embed this module in your own layout, but please do not + distribute it as a standalone plugin. + + Modified for oUF Stardust: + - Added .spec key to hide in wrong specs +------------------------------------------------------------------------ + Usage: + + self.AuraStack = { + aura = GetSpellInfo(53817) -- Localized spell name for Maelstrom Weapon + filter = "HELPFUL", -- Optional filter to pass to UnitAura, defaults to "HELPFUL" + } + for i = 1, 5 do + self.AuraStack[i] = CreateTexture(nil, "OVERLAY") + self.AuraStack[i]:SetSize(20, 20) + if i == 1 then + self.AuraStack[i]:SetPoint("LEFT", self, "BOTTOMLEFT", 5, 0) + else + self.AuraStack[i]:SetPoint("LEFT", self.AuraStack[i-1], "RIGHT", 0, 0) + end + end +------------------------------------------------------------------------ + Notes: + + Only supports one aura per frame. + + Supports Override or PreUpdate/PostUpdate. + + Does not support spell IDs, as the WoW API cannot look up a buff + directly by its ID and looping is not desirable here. + + The buff to track can be changed dynamically (for example, when + the player's spec changes) by the layout. + + The individual objects do not have to be textures. They can be + frames, fontstrings, or even basic tables, as long as they have + Show, Hide, SetAlpha, and IsObjectType methods. +----------------------------------------------------------------------]] + +local _, ns = ... +local oUF = ns.oUF or oUF +assert(oUF, "AuraStack element requires oUF") + +local UnitBuff = UnitBuff + +local UpdateVisibility, Update, Path, ForceUpdate, Enable, Disable + +function UpdateVisibility(self, event) + local element = self.AuraStack + + if UnitHasVehicleUI(self.unit) or (element.spec and element.spec ~= GetSpecialization()) then + element.__disabled = true + for i = 1, #element do + element[i]:Hide() + end + return self:UnregisterEvent("UNIT_AURA", Path) + end + + element.__disabled = false + self:RegisterEvent("UNIT_AURA", Path) + return Path(self, "UpdateVisibility", self.unit) +end + +function Update(self, event, unit) + if unit ~= self.unit then return end + local element = self.AuraStack + if element.__disabled or not element.aura then return end + + local _, _, _, count = UnitAura(unit, element.aura, nil, element.filter or "HELPFUL") + count = count or 0 + + if count == element.__count then return end + + if element.PreUpdate then + element:PreUpdate() + end + + element.__count = count + + for i = 1, #element do + local obj = element[i] + obj:SetShown(count > 0 and i <= element.__max) + end + + if element.PostUpdate then + element:PostUpdate(count) + end +end + +function Path(self, ...) + return (self.AuraStack.Override or Update)(self, ...) +end + +function ForceUpdate(element) + return Path(element.__owner, "ForceUpdate", element.__owner.unit) +end + +function Enable(self) + local element = self.AuraStack + if not element then return end + + element.__name = "AuraStack" + element.__owner = self + element.ForceUpdate = ForceUpdate + + if not element.__max then + element.__max = #element + end + + for i = 1, #element do + local obj = element[i] + obj.__owner = self + if obj:IsObjectType("Texture") and not obj:GetTexture() then + obj:SetTexture([[Interface\ComboFrame\ComboPoint]]) + obj:SetTexCoord(0, 0.375, 0, 1) + end + end + + self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED", UpdateVisibility) + self:RegisterEvent("UNIT_ENTERING_VEHICLE", UpdateVisibility) + self:RegisterEvent("UNIT_EXITED_VEHICLE", UpdateVisibility) + + UpdateVisibility(self, "Enable") + + return true +end + +function Disable(self) + local element = self.AuraStack + if not element then return end + + self:UnregisterEvent("UNIT_AURA", Path) + self:UnregisterEvent("UNIT_ENTERING_VEHICLE", UpdateVisibility) + self:UnregisterEvent("UNIT_EXITED_VEHICLE", UpdateVisibility) + + for i = 1, #element do + element[i]:Hide() + end + if element.SetShown then + element:SetShown(false) + end +end + +oUF:AddElement("AuraStack", Path, Enable, Disable) + diff --git a/Elements/BurningEmbers.lua b/Elements/BurningEmbers.lua index 395dfe2..9c5de26 100644 --- a/Elements/BurningEmbers.lua +++ b/Elements/BurningEmbers.lua @@ -1,12 +1,7 @@ --[[-------------------------------------------------------------------- - oUF_Phanx - Fully-featured PVE-oriented layout for oUF. - Copyright (c) 2008-2015 Phanx . All rights reserved. - http://www.wowinterface.com/downloads/info13993-oUF_Phanx.html - http://www.curse.com/addons/wow/ouf-phanx - https://github.com/Phanx/oUF_Phanx ------------------------------------------------------------------------- + oUF_Phanx/Elements/BurningEmbers.lua Element to display burning embers on oUF frames. + Copyright (c) 2008-2015 Phanx . All rights reserved. You may embed this module in your own layout, but please do not distribute it as a standalone plugin. diff --git a/Elements/DemonicFury.lua b/Elements/DemonicFury.lua index ef8c32a..9bf39a4 100644 --- a/Elements/DemonicFury.lua +++ b/Elements/DemonicFury.lua @@ -1,12 +1,7 @@ --[[-------------------------------------------------------------------- - oUF_Phanx - Fully-featured PVE-oriented layout for oUF. - Copyright (c) 2008-2015 Phanx . All rights reserved. - http://www.wowinterface.com/downloads/info13993-oUF_Phanx.html - http://www.curse.com/addons/wow/ouf-phanx - https://github.com/Phanx/oUF_Phanx ------------------------------------------------------------------------- + oUF_Phanx/Elements/DemonicFury.lua Element to display demonic fury on oUF frames. + Copyright (c) 2008-2015 Phanx . All rights reserved. You may embed this module in your own layout, but please do not distribute it as a standalone plugin. diff --git a/Elements/DispelIcon.lua b/Elements/DispelIcon.lua new file mode 100644 index 0000000..b6271e0 --- /dev/null +++ b/Elements/DispelIcon.lua @@ -0,0 +1,87 @@ +--[[----------------------------------------------------------------------- + oUF: Stardust - a layout for the oUF framework + Copyright (c) 2016 Andrew Mordecai + This code is released under the zlib license; see LICENSE for details. +--------------------------------------------------------------------------- + This file provides a "DispelIcon" element, which may be used in any + oUF layout and does not depend on any other files in this addon. + + self.DispelIcon = self:CreateTexture(nil, "OVERLAY") + self.DispelIcon:SetPoint("CENTER") + self.DispelIcon:SetSize(16, 16) + + To use Blizzard's graphical icons for each debuff type, set + .showIcons = true on the element. Otherwise, the element's existing + texture will be colored according to the debuff type. +-------------------------------------------------------------------------]] + +local _, ns = ... +local oUF = ns.oUF or oUF +assert(oUF, "DispelIcon element requires oUF") + +function Update(self, event, unit) + local element = self.DispelIcon + if element.PreUpdate then + element:PreUpdate() + end + + local _, _, _, _, dispelType = UnitDebuff(unit, 1, "RAID") + if dispelType == "" then dispelType = nil end + + local color, r, g, b = dispelType and DebuffTypeColor[dispelType] + if element.showIcons then + if dispelType then + element:SetTexture("Interface\\RAIDFRAME\\Raid-Icon-Debuff"..dispelType) + element:SetVertexColor(1, 1, 1) + element:Show() + else + element:SetTexture("") + element:Hide() + end + else + if dispelType then + r, g, b = color.r, color.g, color.b + element:SetVertexColor(r, g, b) + element:Show() + else + element:SetVertexColor(1, 1, 1) + element:Hide() + end + end + + if element.PostUpdate then + element:PostUpdate(dispelType, r, g, b) + end +end + +function Path(self, ...) + return (self.DispelIcon.Override or Update)(self, ...) +end + +function ForceUpdate(element) + return Path(element.__owner, "ForceUpdate", element.__owner.unit) +end + +function Enable(self, unit) + local element = self.DispelIcon + if not element then return end + + element.__owner = self + element.ForceUpdate = ForceUpdate + + self:RegisterEvent("UNIT_AURA", Path) + + return true +end + +function Disable(self) + local element = self.DispelIcon + if not element then return end + + self:UnregisterEvent("UNIT_AURA", Path) + + element:Hide() +end + +oUF:AddElement("DispelIcon", Path, Enable, Disable) + diff --git a/Fonts/LatoBold.ttf b/Fonts/LatoBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9aa2d0c44ef6feb3cbac3d09d0776d79c2237830 GIT binary patch literal 59940 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 Mmwf%{Z!z}&07spajsO4v literal 0 HcmV?d00001 diff --git a/Functions.lua b/Functions.lua index 6ffe6e6..aff1d5b 100644 --- a/Functions.lua +++ b/Functions.lua @@ -5,21 +5,21 @@ -------------------------------------------------------------------------]] local _, Stardust = ... +local config = Stardust.config ---------------------------- --- General utility functions ---------------------------- +local LibSharedMedia = LibStub("LibSharedMedia-3.0") local function abbrev(n) - if n > 1000000000 then + local v = abs(n) + if v > 1000000000 then return format("%.0fb", n / 1000000000) - elseif n > 10000000 then + elseif v > 10000000 then return format("%.0fm", n / 1000000) - elseif n > 1000000 then + elseif v > 1000000 then return format("%.1fm", n / 1000000) - elseif n > 10000 then + elseif v > 10000 then return format("%.0fk", n / 1000) - elseif n > 1000 then + elseif v > 1000 then return format("%.1fk", n / 1000) else return n @@ -27,24 +27,56 @@ local function abbrev(n) end --------------------------- +-- Frames +--------------------------- + +function Stardust.OnEnter(self) + self.isMouseOver = true + UnitFrame_OnEnter(self) + self:UpdateAllElements("OnEnter") +end + +function Stardust.OnLeave(self) + self.isMouseOver = false + UnitFrame_OnLeave(self) + self:UpdateAllElements("OnLeave") +end + +--------------------------- -- Health --------------------------- -function Stardust.PostUpdateHealth(self, unit, cur, max) - self:SetValue(max - cur) - self.value:SetText(cur < max and abbrev(-max + cur) or "") - self.bg:SetVertexColor(0.15, 0.15, 0.15) +function Stardust.PostUpdateHealth(element, unit, cur, max) + element:SetValue(max - cur) + element.value:SetText(element.__owner.isMouseOver and abbrev(cur) or cur < max and abbrev(-max + cur) or "") end --------------------------- --- Power --- DruidMana --- DemonicFury +-- Power / DruidMana / DemonicFury --------------------------- -function Stardust.PostUpdatePower(self, unit, cur, max) - self:SetShown(max > 0) - self.value:SetText(abbrev(cur)) +local UnitIsDead = UnitIsDead + +function Stardust.PostUpdatePower(element, unit, cur, max) + if UnitIsDead(unit) then + local t = oUF.colors.disconnected + local r, g, b = t[1], t[2], t[3] + local mu = element.bg.multiplier or 1 + element:SetStatusBarColor(r, g, b) + element.bg:SetVertexColor(r * mu, g * mu, b * mu) + element.value:SetText("") + element:SetValue(0) + else + element.value:SetText(max > 0 and element.__owner.isMouseOver and abbrev(cur) or "") + end +end + +--------------------------- +-- Stagger +--------------------------- + +function Stardust.PostUpdateStagger(element, maxHealth, stagger, staggerPercent, r, g, b) + element.value:SetFormattedText("%d%%", staggerPercent) end --------------------------- @@ -53,8 +85,7 @@ end function Stardust.ResizeClassIcons(element) local COUNT = element.__max - local SPACING = 3 - local widthTotal = ((Stardust.config.width - SPACING - SPACING) * 0.6) - (COUNT * SPACING) + local widthTotal = ((Stardust.config.width - 6) * 0.7) - (COUNT * 3) local widthEach = floor(widthTotal / COUNT + 0.5) for i = 1, COUNT do element[i]:SetWidth(widthEach) @@ -64,20 +95,35 @@ end function Stardust.PostUpdateClassIcons(element, cur, max, maxChanged, event) if maxChanged then Stardust.ResizeClassIcons(element) + if element.__owner.Stagger then + element.__owner.Stagger:SetPoint("TOPLEFT", element[max], 3, 0) + end end end --------------------------- --- CPoints --- BurningEmbers +-- CPoints / AuraStack / BurningEmbers --------------------------- -function Stardust.PostUpdateCPoints(element, cur) - -- The number of combo points isn't dynamic, so they only need to be resized once per frame. - element.PostUpdate = nil +function Stardust.PreUpdateCPoints(element) + element.PreUpdate = nil Stardust.ResizeClassIcons(element) end +function Stardust.PostUpdateCPoints(element, cur) + if cur == 0 or element.__disabled then return end + + for i = 1, element.__max or #element do + local bar = element[i] + if i > cur then + bar:SetValue(0) + bar:Show() + else + bar:SetValue(1) + end + end +end + --------------------------- -- Runes --------------------------- @@ -98,25 +144,41 @@ end -- Partly adapted from oUF_Phanx --------------------------- -local function OnUpdateTotem(bar, elapsed) - local t = bar.duration - elapsed - if t > 0 then - bar.duration = t - bar:SetValue(t) - else - bar:SetValue(0) +local OnUpdateTotem +do + local ceil = ceil + local unpack = unpack + local SMOOTH_COLORS = oUF.colors.smooth + local ColorGradient = oUF.ColorGradient + + function OnUpdateTotem(bar, elapsed) + local t = bar.remaining - elapsed + if t > 0 then + bar.remaining = t + bar:SetValue(t) + if bar.__owner.isMouseOver then + bar.value:SetText(ceil(t)) + bar.value:SetTextColor(ColorGradient(t, bar.max, unpack(SMOOTH_COLORS))) + else + bar.value:SetText("") + end + else + bar:SetValue(0) + bar.value:SetText("") + end end end function Stardust.PostUpdateTotem(element, index, _, name, start, duration) local bar = element[index] - bar.duration, bar.max = duration, duration + bar.remaining, bar.max = start + duration - GetTime(), duration if duration > 0 then bar:SetMinMaxValues(0, duration) bar:SetScript("OnUpdate", OnUpdateTotem) else bar:SetScript("OnUpdate", nil) bar:SetValue(0) + bar.value:SetText("") end end @@ -130,43 +192,121 @@ end -- Status icons --------------------------- -function Stardust.PostUpdateStatusIcon(self) - if not self:IsShown() then return end - local icons, x = self.__owner.StatusIcons, 2 +function Stardust.PostUpdateStatusIcon(element) + if not element:IsShown() then return end + local icons, x = element.__owner.StatusIcons, 2 for i = 1, #icons do local icon = icons[i] - if icon == self then + if icon == element then break elseif icon:IsShown() and icon:GetTexture() then x = x + icon:GetWidth() end end - self:SetPoint("LEFT", self.__owner.Health, "TOPLEFT", x + self.offsetX, 0 + self.offsetY) + element:SetPoint("LEFT", element.__owner.Health, "TOPLEFT", x + element.offsetX, 0 + element.offsetY) end local updatingCombatOrResting -function Stardust.PostUpdateCombat(self) +function Stardust.PostUpdateCombat(element) if not updatingCombatOrResting then updatingCombatOrResting = true - self.__owner.Resting:ForceUpdate() + element.__owner.Resting:ForceUpdate() updatingCombatOrResting = nil end end -function Stardust.PostUpdateResting(self) +function Stardust.PostUpdateResting(element) if not updatingCombatOrResting then updatingCombatOrResting = true - self.__owner.Combat:ForceUpdate() + element.__owner.Combat:ForceUpdate() updatingCombatOrResting = nil end end --------------------------- --- Glow +-- AFK text +--------------------------- + +function Stardust.PostUpdateAFK(element, isAFK) + local Power = element.__owner.Power + if Power and Power.value then + Power.value:SetShown(not isAFK) + end +end + +--------------------------- +-- DispelIcon (redirected to frame.Shadows) --------------------------- -local GLOW_SEGMENTS = { +Stardust.DispelIconStub = { + SetTexture = nop, + SetVertexColor = nop, + Show = nop, + Hide = nop, +} + +function Stardust.PostUpdateDispelIcon(element, dispelType, r, g, b) + local frame = element.__owner + if not frame.__shadowSize then + frame.__shadowSize = frame.Shadows[1]:GetWidth() + end + if dispelType then + frame:SetShadowColor(r, g, b) + frame:SetShadowSize(frame.__shadowSize * 2) + else + frame:SetShadowColor() + frame:SetShadowSize(frame.__shadowSize) + end +end + +--------------------------- +-- Auras +--------------------------- + +function Stardust.PostCreateAuraIcon(element, button) + button.cd:SetDrawBling(false) + button.cd:SetDrawEdge(false) + button.cd:SetReverse(true) + + button.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) + + button.count:SetFont(LibSharedMedia:Fetch("font", config.numberFont), config.numberSmall, "OUTLINE") + button.count:SetShadowOffset(0, 0) + button.count:SetTextColor(1, 1, 1) + + button.backdrop = button:CreateTexture(nil, "BACKGROUND") + button.backdrop:SetPoint("BOTTOMLEFT", -1, -1) + button.backdrop:SetPoint("TOPRIGHT", 1, 1) + button.backdrop:SetTexture("Interface\\BUTTONS\\WHITE8X8") + + Stardust.AddShadow(button, 6) +end + +function Stardust.PostUpdateAuraIcon(element, unit, button, index, offset) + local _, _, _, _, dispelType, duration, timeLeft, _, isStealable, _, spellID, canApplyAura, isBossDebuff = UnitAura(unit, index, button.filter) + + if timeLeft > 600 then + button.cd:Hide() + end + + local color = button.isDebuff and dispelType and DebuffTypeColor[dispelType] + if color then + button.backdrop:SetVertexColor(color.r, color.g, color.b) + else + button.backdrop:SetVertexColor(0, 0, 0) + end +end + +--------------------------- +-- Shadow +--------------------------- + +local SHADOW_SIZE = 3 +local SHADOW_COLOR = { 0, 0, 0 } + +local SHADOW_SEGMENTS = { + -- 1 point, 2 coordLeft, 3 coordRight, 4 coordTop, 5 coordBottom, 6 offsetMultiplierX, 7 offsetMultiplierY { "TOPLEFT", 0, 1/3, 0, 1/3, -1, 1 }, { "TOPRIGHT", 2/3, 1, 0, 1/3, 1, 1 }, { "BOTTOMRIGHT", 2/3, 1, 2/3, 1, 1, -1 }, @@ -177,63 +317,72 @@ local GLOW_SEGMENTS = { { "LEFT", 0, 1/3, 1/3, 2/3, -1, 0 }, } -local function SetGlowColor(self, r, g, b, a) - for i = 1, #self.Glow do - self.Glow[i]:SetVertexColor(r, g, b, a) +local function SetShadowColor(self, r, g, b, a) + if not r or not g or not b then + r, g, b = unpack(SHADOW_COLOR) + end + for i = 1, #self.Shadows do + self.Shadows[i]:SetVertexColor(r, g, b, a or 1) end end -local function SetGlowSize(self, size, offset) - local Glow = self.Glow - for i = 1, #Glow do - Glow[i]:SetSize(size, size) +local function SetShadowSize(self, size, offset) + if not size then size = SHADOW_SIZE end + + local Shadows = self.Shadows + for i = 1, #Shadows do + Shadows[i]:SetSize(size, size) end local d = offset or floor(size * 2 / 3 + 0.5) - Glow[1]:SetPoint("TOPLEFT", -d, d) - Glow[2]:SetPoint("TOPRIGHT", d, d) - Glow[3]:SetPoint("BOTTOMRIGHT", d, -d) - Glow[4]:SetPoint("BOTTOMLEFT", -d, -d) ---[[ - Glow[5]:SetShown(Glow[2]:GetLeft() > Glow[1]:GetRight()) - Glow[6]:SetShown(Glow[2]:GetBottom() > Glow[3]:GetTop()) - Glow[7]:SetShown(Glow[3]:GetLeft() > Glow[4]:GetLeft()) - Glow[8]:SetShown(Glow[1]:GetBottom() > Glow[4]:GetTop()) -]] -end - -function Stardust.CreateGlow(self) - local Glow = {} - for i = 1, #GLOW_SEGMENTS do - local seg = GLOW_SEGMENTS[i] + Shadows[1]:SetPoint("TOPLEFT", -d, d) + Shadows[2]:SetPoint("TOPRIGHT", d, d) + Shadows[3]:SetPoint("BOTTOMRIGHT", d, -d) + Shadows[4]:SetPoint("BOTTOMLEFT", -d, -d) + + local showH = self:GetWidth() > (2 * (size - d)) + local showV = self:GetHeight() > (2 * (size - d)) + Shadows[5]:SetShown(showH) + Shadows[6]:SetShown(showV) + Shadows[7]:SetShown(showH) + Shadows[8]:SetShown(showV) +end + +function Stardust.AddShadow(self, size, r, g, b, a) + local Shadows = {} + for i = 1, #SHADOW_SEGMENTS do + local seg = SHADOW_SEGMENTS[i] local tex = self:CreateTexture(nil, "BACKGROUND") - tex:SetTexture("Interface\\AddOns\\oUF_Stardust\\textures\\glow") + tex:SetTexture("Interface\\AddOns\\oUF_Stardust\\Textures\\glow") tex:SetTexCoord(seg[2], seg[3], seg[4], seg[5]) tex:SetVertexColor(0, 0, 0) - Glow[i] = tex + Shadows[i] = tex end -- TOP - Glow[5]:SetPoint("LEFT", Glow[1], "RIGHT") - Glow[5]:SetPoint("RIGHT", Glow[2], "LEFT") + Shadows[5]:SetPoint("LEFT", Shadows[1], "RIGHT") + Shadows[5]:SetPoint("RIGHT", Shadows[2], "LEFT") -- RIGHT - Glow[6]:SetPoint("TOP", Glow[2], "BOTTOM") - Glow[6]:SetPoint("BOTTOM", Glow[3], "TOP") + Shadows[6]:SetPoint("TOP", Shadows[2], "BOTTOM") + Shadows[6]:SetPoint("BOTTOM", Shadows[3], "TOP") -- BOTTOM - Glow[7]:SetPoint("LEFT", Glow[4], "RIGHT") - Glow[7]:SetPoint("RIGHT", Glow[3], "LEFT") + Shadows[7]:SetPoint("LEFT", Shadows[4], "RIGHT") + Shadows[7]:SetPoint("RIGHT", Shadows[3], "LEFT") -- LEFT - Glow[8]:SetPoint("TOP", Glow[1], "BOTTOM") - Glow[8]:SetPoint("BOTTOM", Glow[4], "TOP") + Shadows[8]:SetPoint("TOP", Shadows[1], "BOTTOM") + Shadows[8]:SetPoint("BOTTOM", Shadows[4], "TOP") + + self.Shadows = Shadows + self.SetBackdropBorderColor = SetShadowColor + self.SetShadowColor = SetShadowColor + self.SetShadowSize = SetShadowSize - self.Glow = Glow - self.SetBackdropBorderColor = SetGlowColor - self.SetGlowColor = SetGlowColor - self.SetGlowSize = SetGlowSize + self:SetShadowColor(r, g, b, a) + self:SetShadowSize(size) - return Glow + return Shadows end diff --git a/Layout.lua b/Layout.lua index 7f7828a..001e248 100644 --- a/Layout.lua +++ b/Layout.lua @@ -6,7 +6,7 @@ local _, Stardust = ... local config = Stardust.config -local colors = Stardust.colors +local colors = oUF.colors local _, class = UnitClass("player") local LibSharedMedia = LibStub("LibSharedMedia-3.0") @@ -15,15 +15,17 @@ local LibSharedMedia = LibStub("LibSharedMedia-3.0") -- Widget creation --------------------------- -local function NewStatusBar(parent, textFont, textSize, noBG, noBackdrop) +local function NewStatusBar(parent, textSize, noBG, noBorder, noShadow) local TEXTURE = LibSharedMedia:Fetch("statusbar", config.barTexture) local bar = CreateFrame("StatusBar", nil, parent) bar:SetStatusBarTexture(TEXTURE) - if textFont and textSize then - bar.value = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight") - bar.value:SetFont(LibSharedMedia:Fetch("font", config.textFont), textSize) + if textSize then + bar.value = bar:CreateFontString(nil, "OVERLAY") + bar.value:SetFont(LibSharedMedia:Fetch("font", config.numberFont), textSize, "OUTLINE") + bar.value:SetShadowOffset(0, 0) + bar.value:SetTextColor(1, 1, 1) bar.value:SetPoint("CENTER") end @@ -34,34 +36,31 @@ local function NewStatusBar(parent, textFont, textSize, noBG, noBackdrop) bar.bg.multiplier = config.bgMultiplier end - if not noBackdrop then + if not noBorder then bar.backdrop = bar:CreateTexture(nil, "BACKGROUND") bar.backdrop:SetPoint("BOTTOMLEFT", -1, -1) bar.backdrop:SetPoint("TOPRIGHT", 1, 1) bar.backdrop:SetTexture(0, 0, 0) end + if not noShadow then + Stardust.AddShadow(bar, 3) + end + return bar end -local function NewBarGroup(parent, count, width, height) - local group = { __max = count } - - local color = Stardust.colors.class[class] - local r, g, b = color[1], color[2], color[3] +local function NewBarGroup(parent, count, width, height, textSize) + local group = { + __max = count + } for i = 1, count do - local bar = NewStatusBar(parent) + local bar = NewStatusBar(parent, textSize) bar:SetSize(width, height) bar:SetMinMaxValues(0, 1) bar:SetValue(1) - if not noColor then - local mu = bar.bg.multiplier - bar:SetStatusBarColor(r, g, b) - bar.bg:SetVertexColor(r * mu, g * mu, b * mu) - end - -- ClassIcons expects texture objects bar.SetVertexColor = bar.SetStatusBarColor @@ -97,178 +96,284 @@ end local function ApplyStyle(self, unit, isSingle) unit = unit:gsub("%d+", "") - local uconfig = config.units[unit] + local unitConfig = config.units[unit] + local classColor = oUF.colors.class[class] + local classR, classG, classB = classColor[1], classColor[2], classColor[3] --------------------------- -- Frame --------------------------- - self:SetScript("OnEnter", UnitFrame_OnEnter) - self:SetScript("OnLeave", UnitFrame_OnLeave) + self:SetScript("OnEnter", Stardust.OnEnter) + self:SetScript("OnLeave", Stardust.OnLeave) self:RegisterForClicks("AnyUp") if (isSingle) then - self:SetSize(config.width * (uconfig and uconfig.width or 1), config.height * (uconfig and uconfig.height or 1)) + self:SetSize(config.width * (unitConfig and unitConfig.width or 1), config.height * (unitConfig and unitConfig.height or 1)) end - --------------------------- - -- Outer glow - --------------------------- - - self.Glow = Stardust.CreateGlow(self) + Stardust.AddShadow(self, 6) --------------------------- -- Health bar --------------------------- - local Health = NewStatusBar(self, config.numberFont, 25) + local Health = NewStatusBar(self, config.numberLarge) Health:SetPoint("BOTTOMLEFT") Health:SetPoint("TOPRIGHT") Health:SetReverseFill(true) + Health:SetStatusBarColor(0.3, 0.3, 0.3) + Health.bg:SetVertexColor(0.15, 0.15, 0.15) + Health.value:ClearAllPoints() - Health.value:SetPoint("RIGHT", Health, "TOPRIGHT", 0, 3) + Health.value:SetPoint("BOTTOMRIGHT", 0, 3 + config.powerHeight + 2) - Health.colorClass = true - Health.colorDisconnected = true - Health.colorReaction = true - Health.colorTapping = true Health.frequentUpdates = true + Health.PostUpdate = Stardust.PostUpdateHealth + + self:SmoothBar(Health) self.Health = Health --------------------------- + -- Name text + --------------------------- + + local Name = self.Health:CreateFontString(nil, "OVERLAY") + Name:SetPoint("BOTTOMLEFT", 2, 3 + config.powerHeight + 2) + Name:SetPoint("RIGHT", self.Health.value, "LEFT") + Name:SetJustifyH("LEFT") + Name:SetFont(LibSharedMedia:Fetch("font", config.textFont), config.textSize, "OUTLINE") + Name:SetShadowOffset(0, 0) + Name:SetTextColor(1, 1, 1) + self:Tag(Name, "[unitcolor][name]") + self.Name = Name + + --------------------------- -- Power bar --------------------------- - local Power = NewStatusBar(Health, config.numberFont, 15) + local Power = NewStatusBar(Health, config.numberSmall) Power:SetPoint("BOTTOMLEFT", 3, 3) Power:SetPoint("BOTTOMRIGHT", -3, 3) Power:SetHeight(config.powerHeight) + Power.colorClass = true Power.colorDisconnected = true - Power.colorPower = true + Power.colorReaction = true + Power.colorTapping = true Power.frequentUpdates = true + Power.PostUpdate = Stardust.PostUpdatePower + + self:SmoothBar(Power) self.Power = Power + Stardust.AddShadow(Power, 3) + --------------------------- - -- Secondary resources + -- AFK text --------------------------- - if class == "WARLOCK" then - local BurningEmbers = NewBarGroup(self.Health, 4, 30, config.powerHeight) - BurningEmbers[1]:SetPoint("TOPLEFT", self.Health, 3, -3) - BurningEmbers.PostUpdate = Stardust.PostUpdateCPoints - self.BurningEmbers = BurningEmbers + if unit == "player" or unit == "party" then + local AFK = self:CreateFontString(nil, "OVERLAY") + AFK:SetPoint("CENTER", self.Power) + AFK:SetFont(LibSharedMedia:Fetch("font", config.numberFont), config.numberSmall, "OUTLINE") + AFK:SetShadowOffset(0, 0) + AFK:SetTextColor(1, 1, 1) + AFK.PostUpdate = Stardust.PostUpdateAFK + self.AFK = AFK end - if class == "PALADIN" or class == "PRIEST" or class == "WARLOCK" then - local ClassIcons = NewBarGroup(self.Health, 6, 30, config.powerHeight) - ClassIcons[1]:SetPoint("TOPLEFT", self.Health, 3, -3) - ClassIcons.PostUpdate = Stardust.PostUpdateClassIcons - self.ClassIcons = ClassIcons - end + --------------------------- + -- Secondary resources + --------------------------- - if class == "DRUID" or class == "ROGUE" then - local CPoints = NewBarGroup(self.Health, 5, 30, config.powerHeight) - CPoints[1]:SetPoint("TOPLEFT", self.Health, 3, -3) - CPoints.PostUpdate = Stardust.PostUpdateCPoints - self.CPoints = CPoints - end + if unit == "player" then - if class == "DEATHKNIGHT" then - local Runes = NewBarGroup(self.Health, 6, 30, config.powerHeight) - Runes[1]:SetPoint("TOPLEFT", self.Health, 3, -3) - Runes.PostUpdateRune = Stardust.PostUpdateRune - Runes.PostUpdateRuneType = Stardust.PostUpdateRuneType - self.Runes = Runes - end + --------------------------- + -- Runes @ TOPLEFT + --------------------------- + + if class == "DEATHKNIGHT" then + local Runes = NewBarGroup(self.Health, 6, 30, config.powerHeight) + Runes[1]:SetPoint("TOPLEFT", self.Health, 3, -3) + Runes.PostUpdateRune = Stardust.PostUpdateRune + Runes.PostUpdateRuneType = Stardust.PostUpdateRuneType + self.Runes = Runes + end + + --------------------------- + -- Combo Points @ TOPLEFT + --------------------------- + + if class == "DRUID" or class == "ROGUE" then + local CPoints = NewBarGroup(self.Health, 5, 30, config.powerHeight) + CPoints[1]:SetPoint("TOPLEFT", self.Health, 3, -3) + CPoints.PreUpdate = Stardust.PreUpdateCPoints + self.CPoints = CPoints + + for i = 1, #CPoints do + local bar = CPoints[i] + local mu = bar.bg.multiplier + bar:SetStatusBarColor(classR, classG, classB) + bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu) + end + end + + --------------------------- + -- Totems @ TOPLEFT + --------------------------- - if class == "DRUID" or class == "SHAMAN" then - -- TODO: This can also show death knight ghouls. - -- How do these work? Are they important? - -- How to show them at the same time as runes? - local Totems = NewBarGroup(self.Health, 4, 30, config.powerHeight) - Totems[1]:SetPoint("TOPLEFT", self.Health, 3, -3) if class == "SHAMAN" then - local mu = config.bgMultiplier + local Totems = NewBarGroup(self.Health, 4, 30, config.powerHeight, config.numberSmall) + Totems[1]:SetPoint("TOPLEFT", self.Health, 3, -3) + Totems.PreUpdate = Stardust.PreUpdateTotems + Totems.PostUpdate = Stardust.PostUpdateTotem + self.Totems = Totems + for i = 1, 4 do - local totem = Totems[i] - totem:EnableMouse(true) + local bar = Totems[i] + bar:EnableMouse(true) + bar:SetHitRectInsets(0, 0, -6, -3) + bar.__owner = self -- oUF sets it on Totems, but not Totems[i] - local color = Stardust.colors.totems[i] + local color = oUF.colors.totems[i] local r, g, b = color[1], color[2], color[3] - totem:SetStatusBarColor(r, g, b) - totem.bg:SetVertexColor(r * mu, b * mu, g * mu) + local mu = bar.bg.multiplier + bar:SetStatusBarColor(r, g, b) + bar.bg:SetVertexColor(r * mu, g * mu, b * mu) - local value = totem:CreateFontString(nil, "OVERLAY", "GameFontHighlight") - value:SetFont(LibSharedMedia:Fetch("font", config.numberFont), 15) - value:SetPoint("CENTER") + bar.value:SetPoint("CENTER", 0, 3) end end - Totems.PreUpdate = Stardust.PreUpdateTotems - self.Totems = Totems - end - -- TODO: burning embers + --------------------------- + -- Chi / Holy Power / Shadow Orbs / Soul Shards @ TOPLEFT + --------------------------- - --------------------------- - -- Druid mana - -- Warlock demonic fury - --------------------------- + if class == "MONK" or class == "PALADIN" or class == "PRIEST" or class == "WARLOCK" then + local ClassIcons = NewBarGroup(self.Health, 6, 30, config.powerHeight) + ClassIcons[1]:SetPoint("TOPLEFT", self.Health, 3, -3) + ClassIcons.PostUpdate = Stardust.PostUpdateClassIcons + self.ClassIcons = ClassIcons + end - if class == "DRUID" or class == "WARLOCK" then - local bar = NewStatusBar(Health) - bar:SetPoint("TOPLEFT", 3, -3) - bar:SetPoint("TOPRIGHT", -3, -3) - bar:SetHeight(config.powerHeight) - if class == "DRUID" then - bar:SetPoint("TOPLEFT", self.Totems[4], 3, 0) - bar.colorPower = true - bar.PostUpdate = PostUpdatePower - self.DruidMana = bar - else - bar.colorPower = true - bar.PostUpdate = PostUpdatePower - self.DemonicFury = bar + --------------------------- + -- Druid Mana @ TOP / Stagger @ TOPRIGHT / Demonic Fury @ TOP + --------------------------- + + if class == "DRUID" or class == "MONK" or class == "WARLOCK" then + local bar = NewStatusBar(Health, config.numberSmall) + bar:SetPoint("TOPLEFT", 3, -3) + bar:SetPoint("TOPRIGHT", -3, -3) + bar:SetHeight(config.powerHeight) + self:SmoothBar(bar) + if class == "DRUID" then + bar.colorPower = true + bar.PostUpdate = PostUpdatePower + self.DruidMana = bar + elseif class == "MONK" then + bar:SetPoint("TOPLEFT", self.ClassIcons[4], 3, 0) + bar.PostUpdate = PostUpdateStagger + self.Stagger = bar + elseif class == "WARLOCK" then + bar.colorPower = true + bar.PostUpdate = PostUpdatePower + self.DemonicFury = bar + end end + + --------------------------- + -- Maelstrom Weapon @ TOPRIGHT + --------------------------- + + if class == "SHAMAN" then + local AuraStack = NewBarGroup(self.Health, 5, config.powerHeight * 2, config.powerHeight) + AuraStack[1]:SetPoint("TOPRIGHT", self.Health, -3, -3) + AuraStack.PostUpdate = Stardust.PostUpdateCPoints + self.AuraStack = AuraStack + + AuraStack.aura = GetSpellInfo(51530) -- Maelstrom Weapon + AuraStack.spec = 2 -- Enhancement + + for i = 1, #AuraStack do + local bar = AuraStack[i] + + local mu = bar.bg.multiplier + bar:SetStatusBarColor(classR, classG, classB) + bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu) + + if i > 1 then + bar:ClearAllPoints() + bar:SetPoint("RIGHT", AuraStack[i-1], "LEFT", -3, 0) + end + end + end + + --------------------------- + -- Burning Embers @ TOPLEFT + --------------------------- + + if class == "WARLOCK" then + local BurningEmbers = NewBarGroup(self.Health, 4, 30, config.powerHeight) + BurningEmbers[1]:SetPoint("TOPLEFT", self.Health, 3, -3) + BurningEmbers.PreUpdate = Stardust.PreUpdateCPoints + self.BurningEmbers = BurningEmbers + + for i = 1, #BurningEmbers do + local bar = BurningEmbers[i] + local mu = bar.bg.multiplier + bar:SetStatusBarColor(classR, classG, classB) + bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu) + self:SmoothBar(bar) + end + end + end --------------------------- - -- TODO + -- Alternate Power Bar + -- TODO: layout --------------------------- - -- AltPowerBar - -- EclipseBar - -- HealPrediction - -- Stagger + local AltPowerBar = NewStatusBar(self.Health, config.numberSmall) + AltPowerBar:SetPoint("TOPLEFT", self.Power, "BOTTOMLEFT", 0, -3) + AltPowerBar:SetPoint("TOPRIGHT", self.Power, "BOTTOMRIGHT", 0, 3) + AltPowerBar:SetHeight(config.powerHeight) + AltPowerBar:EnableMouse(true) - -- Castbar + AltPowerBar.colorTexture = true + AltPowerBar.PostUpdate = Stardust.PostUpdatePower - -- Auras + self:SmoothBar(AltPowerBar) + self.AltPowerBar = AltPowerBar + + Stardust.AddShadow(AltPowerBar, 3) --------------------------- - -- Name text + -- TODO --------------------------- - local Name = self:CreateFontString(nil, "OVERLAY", "GameFontNormal") - Name:SetPoint("BOTTOMLEFT", Power, "TOPLEFT", 0, 3) - Name:SetFont(LibSharedMedia:Fetch("font", config.valueFont), 14) - self:Tag(Name, "[unitcolor][name]") - self.Name = Name + -- EclipseBar + -- HealPrediction + + -- Castbar --------------------------- -- Group status icons --------------------------- - AddStatusIcon(self, "LFDRole", 24) - AddStatusIcon(self, "RaidRole") -- maintank, mainassist - AddStatusIcon(self, "Leader") - AddStatusIcon(self, "Assistant") - AddStatusIcon(self, "MasterLooter", nil, 0, 1) - AddStatusIcon(self, "PvP", 32, 0, -5) -- TODO: move it somewhere else? + if not unit:match("pet") and not unit:match(".+target") then + AddStatusIcon(self, "LFDRole", 24) + AddStatusIcon(self, "RaidRole") -- maintank, mainassist + AddStatusIcon(self, "Leader") + AddStatusIcon(self, "Assistant") + AddStatusIcon(self, "MasterLooter", nil, 0, 1) + AddStatusIcon(self, "PvP", 32, 0, -5) -- TODO: move it somewhere else? + end --------------------------- -- Combat icon @@ -276,13 +381,13 @@ local function ApplyStyle(self, unit, isSingle) if unit == "player" then local Combat = self.Health:CreateTexture(nil, "OVERLAY") - Combat:SetSize(16, 16) + Combat:SetSize(32, 32) Combat:SetPoint("RIGHT", self.Health, "TOPRIGHT", -3, 0) Combat.PostUpdate = Stardust.PostUpdateCombat self.Combat = Combat local Resting = self.Health:CreateTexture(nil, "OVERLAY") - Resting:SetSize(16, 16) + Resting:SetSize(32, 32) Resting:SetPoint("RIGHT", self.Health, "TOPRIGHT", -3, 0) Resting.PostUpdate = Stardust.PostUpdateResting self.Resting = Resting @@ -292,7 +397,7 @@ local function ApplyStyle(self, unit, isSingle) -- Phasing icon --------------------------- - if not unit:match(".+target$") and not unit:match(".+pet") then + if not unit:match("pet") and not unit:match(".+target") then local PhaseIcon = self.Health:CreateTexture(nil, "OVERLAY") PhaseIcon:SetPoint("TOP") PhaseIcon:SetPoint("BOTTOM") @@ -312,8 +417,8 @@ local function ApplyStyle(self, unit, isSingle) if unit == "target" then local QuestIcon = self.Health:CreateTexture(nil, "OVERLAY") - QuestIcon:SetSize(16, 16) - QuestIcon:SetPoint("RIGHT", self.Health, "TOPRIGHT", -3, 0) + QuestIcon:SetSize(32, 32) + QuestIcon:SetPoint("CENTER", self.Health, "LEFT", -3, 0) self.QuestIcon = QuestIcon end @@ -324,7 +429,7 @@ local function ApplyStyle(self, unit, isSingle) if unit == "player" or unit == "target" or unit == "party" then local RaidIcon = self.Health:CreateTexture(nil, "OVERLAY") RaidIcon:SetSize(24, 24) - RaidIcon:SetPoint("CENTER", 0, 3) -- TODO + RaidIcon:SetPoint("CENTER", self.Health, "TOP", 0, 6) -- TODO self.RaidIcon = RaidIcon end @@ -343,7 +448,7 @@ local function ApplyStyle(self, unit, isSingle) -- Resurrection icon --------------------------- - if not unit:match(".+target$") and not unit:match("pet") then + if not unit:match("pet") and not unit:match(".+target") then local ResurrectIcon = self.Health:CreateTexture(nil, "OVERLAY") ResurrectIcon:SetSize(24, 24) ResurrectIcon:SetPoint("CENTER", 0, 3) -- TODO @@ -352,6 +457,7 @@ local function ApplyStyle(self, unit, isSingle) --------------------------- -- Threat level icon + -- TODO: colorize and enlarge frame shadow for aggro --------------------------- if unit == "target" or unit == "focus" then @@ -362,28 +468,48 @@ local function ApplyStyle(self, unit, isSingle) end --------------------------- - -- Range + -- DispelIcon (redirected to frame glow) --------------------------- - self.Range = { - insideAlpha = 1, - outsideAlpha = 0.5 - } + if not unit:match(".+target$") then + local DispelIcon = setmetatable({}, { __index = Stardust.DispelIconStub }) + DispelIcon.PostUpdate = Stardust.PostUpdateDispelIcon + self.DispelIcon = DispelIcon + end --------------------------- - -- oUF_SmoothUpdate plugin support + -- Auras --------------------------- - if self.SmoothBar then - self:SmoothBar(self.Health) - self:SmoothBar(self.Power) - if self.DemonicFury then - self:SmoothBar(self.DemonicFury) - end - if self.DruidMana then - self:SmoothBar(self.DruidMana) - end + if unit == "player" or unit == "target" then + local Auras = CreateFrame("Frame", nil, self) + Auras:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 0, 9) + Auras:SetSize(config.width, 16) + + Auras.gap = true + Auras['growth-x'] = "LEFT" + Auras['growth-y'] = "UP" + Auras.initialAnchor = "BOTTOMRIGHT" + Auras.showDebuffType = true + Auras.showStealableBuffs = true + Auras.size = 24 + Auras.spacing = 6 + + Auras.CustomFilter = Stardust.AuraFilter + Auras.PostCreateIcon = Stardust.PostCreateAuraIcon + Auras.PostUpdateIcon = Stardust.PostUpdateAuraIcon + + self.Auras = Auras end + + --------------------------- + -- Range + --------------------------- + + self.Range = { + insideAlpha = 1, + outsideAlpha = 0.5 + } end oUF:Factory(function(self) diff --git a/Textures/statusbar4.tga b/Textures/statusbar4.tga new file mode 100755 index 0000000000000000000000000000000000000000..e2fc1fa578b905f848cdc373a9d67972d7327b68 GIT binary patch literal 530 zcmbu6&!>h!6o%isy??=7-v57{w1_s*Dp89hl87WFDJ94G-jmPgn(ZAP^PCy;>mA@5 zpZ4{OA7Gwm?fYJcIl8XXq9}xzqiGt=vP_6M>blk>NrafAEK7~!Sco~ePNOIiVvan| zH4H-`=HNaW1c4Cqr}lkc$Wy0js-EWw0p0jlZRh>n-#y>GpL>7zeeV0+eBAt)t5#Jd z1eWnI4BED>5OYk^^vR!&Wm$9_M~%v R*L5k!#pdX^?_1f++g}S{fwuqv literal 0 HcmV?d00001 diff --git a/oUF_Stardust.toc b/oUF_Stardust.toc index 3cf61c6..7ddd773 100644 --- a/oUF_Stardust.toc +++ b/oUF_Stardust.toc @@ -1,11 +1,13 @@ ## Interface: 60200 ## Dependencies: oUF +## Version: 6.2.4.0 ## Title: oUF: Stardust ## Author: Akkorian ## X-Credits: Phanx, Qulight ## X-Email: armordecai@protonmail.ch ## X-License: zlib/libpng License +## X-Website: https://gitlab.com/akkorian/ouf-stardust Libs/LibStub.lua Libs/CallbackHandler-1.0.lua @@ -13,11 +15,17 @@ Libs/LibSharedMedia-3.0.lua Plugins/Smooth.lua +Elements/AFK.lua +Elements/AuraStack.lua Elements/BurningEmbers.lua Elements/DemonicFury.lua +Elements/DispelIcon.lua Config.lua + +Auras.lua Functions.lua Tags.lua Layout.lua + -- 1.7.9.5