diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f6c8f0..89b4d9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: show-progress: false @@ -41,7 +41,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: show-progress: false diff --git a/.luacheckrc b/.luacheckrc index 9be238f..d220f25 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -11,7 +11,15 @@ read_globals = { -- API C_UnitAuras = { - fields = { 'GetAuraByAuraInstanceID', 'GetAuraDataBySlot', 'GetAuraSlots' } + fields = { + 'GetAuraApplicationDisplayCount', + 'GetAuraByAuraInstanceID', + 'GetAuraDataBySlot', + 'GetAuraDispelTypeColor', + 'GetAuraDuration', + 'GetAuraSlots', + 'IsAuraFilteredOutByInstanceID', + }, }, 'CreateFrame', 'IsPlayerSpell', diff --git a/README.md b/README.md index a4bf5b0..12408b1 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,14 @@ It does nothing by itself and requires layout support to do its magic. oUF_Dispellable provides functionality to highlight debuffs dispellable by the player. It can display either a texture colored by the debuff type, or an icon representing the found dispellable debuff, or both. -It enables and disables itself automatically based on whether the player can dispel or not and keeps an always updated -list of the dispel spells available to the player. It also keeps track of self-dispels like [Grimoire: Imp](http://www.wowdb.com/spells/111859) -and [Cleansed by Flame](http://www.wowdb.com/spells/205625) to only highlight the player frame when only those are known. +Since Midnight the addon relies on the `HARMFUL|RAID` aura filter to get debuffs dispellable by the player. The addon +does not know in advance if and what debuff types you can dispel and is thus always active, even when the player does +not know any dispelling spells. ## How to use (for layout authors) The element is fully documented and follows the current oUF guidelines for documentation. Please take a look at the code -for details and examples. You could also consult the [wiki](https://github.com/Rainrider/oUF_Dispellable/wiki). +for details and examples. Please consider making oUF_Dispellable optional for your users. The easiest way is to distribute it with your layout as a separate addon and use something like `if not IsAddOnLoaded('oUF_Dispellable') then return end` before calling its diff --git a/oUF_Dispellable.lua b/oUF_Dispellable.lua index 6844737..8054bb7 100644 --- a/oUF_Dispellable.lua +++ b/oUF_Dispellable.lua @@ -12,6 +12,15 @@ Highlights debuffs that are dispelable by the player .dispelIcon - A `Button` to represent the icon of a dispellable debuff. .dispelTexture - A `Texture` to be colored according to the debuff type. +## Options + +The element uses oUF's `dispel` colors to apply colors to the sub-widgets. + +.dispelColorCurve - A [`color curve object`](https://warcraft.wiki.gg/wiki/ScriptObject_ColorCurveObject) used to color + the sub-widgets by the dispel type. +.resetColor - A [`ColorMixin`](https://warcraft.wiki.gg/wiki/ColorMixin) used to reset the color of the + sub-widgets when no dispellable debuff is found. + ## Notes At least one of the sub-widgets should be present for the element to work. @@ -25,8 +34,6 @@ display a tooltip. If `.dispelIcon` and `.dispelIcon.cd` are defined without a global name, one will be set accordingly by the element to prevent /fstack errors. -The element uses oUF's `debuff` colors table to apply colors to the sub-widgets. - ## .dispelIcon Sub-Widgets .cd - used to display the cooldown spiral for the remaining debuff duration (Cooldown) @@ -74,14 +81,12 @@ The element uses oUF's `debuff` colors table to apply colors to the sub-widgets. local texture = self.Health:CreateTexture(nil, 'OVERLAY') texture:SetTexture('Interface\\ChatFrame\\ChatFrameBackground') texture:SetAllPoints() - texture:SetVertexColor(1, 1, 1, 0) -- hide in case the class can't dispel at all -- Register with oUF button.cd = cd button.icon = icon button.overlay = overlay button.count = count - button:Hide() -- hide in case the class can't dispel at all Dispellable.dispelIcon = button Dispellable.dispelTexture = texture @@ -93,50 +98,9 @@ local _, ns = ... local oUF = ns.oUF or oUF assert(oUF, 'oUF_Dispellable requires oUF.') -local LPS = LibStub('LibPlayerSpells-1.0') -assert(LPS, 'oUF_Dispellable requires LibPlayerSpells-1.0.') - -local dispelTypeFlags = { - Curse = LPS.constants.CURSE, - Disease = LPS.constants.DISEASE, - Magic = LPS.constants.MAGIC, - Poison = LPS.constants.POISON, -} - -local band = bit.band local wipe = table.wipe -local IsPlayerSpell = IsPlayerSpell -local IsSpellKnown = IsSpellKnown local UnitCanAssist = UnitCanAssist -local _, playerClass = UnitClass('player') -local _, playerRace = UnitRace('player') -local dispels = {} - -for id, _, _, _, _, _, types in LPS:IterateSpells('HELPFUL PERSONAL', 'DISPEL ' .. playerClass) do - dispels[id] = types -end - -if playerRace == 'Dwarf' then - dispels[20594] = select(6, LPS:GetSpellInfo(20594)) -- Stoneform -end - -if playerRace == 'DarkIronDwarf' then - dispels[265221] = select(6, LPS:GetSpellInfo(265221)) -- Fireblood -end - -if not next(dispels) then - return -end - -local canDispel = {} - -local function IsDispellable(aura, unit) - local dispellable = canDispel[aura.dispelName] - - return dispellable == true or dispellable == unit -end - --[[ Override: Dispellable.dispelIcon:UpdateTooltip() Called to update the widget's tooltip. @@ -159,24 +123,31 @@ local function OnLeave() GameTooltip:Hide() end ---[[ Override: Dispellable.dispelTexture:UpdateColor(debuffType, r, g, b, a) +--[[ Override: Dispellable:UpdateColor(unit, debuff) Called to update the widget's color. -* self - the dispelTexture sub-widget -* debuffType - the type of the dispellable debuff (string?)['Curse', 'Disease', 'Magic', 'Poison'] -* r - the red color component (number)[0-1] -* g - the green color component (number)[0-1] -* b - the blue color component (number)[0-1] -* a - the alpha color component (number)[0-1] +* self - the Dispellable element +* unit - the unit on which the displayed debuff has been applied or removed (string) +* debuff - the displayed debuff or nil if none (UnitAuraInfo?) --]] -local function UpdateColor(dispelTexture, _, r, g, b, a) - dispelTexture:SetVertexColor(r, g, b, a) +local function UpdateColor(element, unit, debuff) + local color = debuff and C_UnitAuras.GetAuraDispelTypeColor(unit, debuff.auraInstanceID, element.dispelColorCurve) + or element.resetColor + + local icon = element.dispelIcon + if icon and icon.overlay then + icon.overlay:SetVertexColor(color:GetRGBA()) + end + + if element.dispelTexture then + element.dispelTexture:SetVertexColor(color:GetRGBA()) + end end -local function UpdateDebuffs(self, updateInfo) - local unit = self.unit +local function UpdateDebuffs(self, unit, updateInfo) local element = self.Dispellable local debuffs = element.debuffs + local filter = 'HARMFUL|RAID' if not UnitCanAssist('player', unit) then wipe(debuffs) @@ -186,27 +157,24 @@ local function UpdateDebuffs(self, updateInfo) if not updateInfo or updateInfo.isFullUpdate then wipe(debuffs) - local slots = { C_UnitAuras.GetAuraSlots(unit, 'HARMFUL') } + local slots = { C_UnitAuras.GetAuraSlots(unit, filter) } for i = 2, #slots do local debuff = C_UnitAuras.GetAuraDataBySlot(unit, slots[i]) - if IsDispellable(debuff, unit) then - debuffs[debuff.auraInstanceID] = debuff - end + debuffs[debuff.auraInstanceID] = debuff end else for _, aura in next, updateInfo.addedAuras or {} do - if aura.isHarmful and IsDispellable(aura, unit) then + if not C_UnitAuras.IsAuraFilteredOutByInstanceID(unit, aura.auraInstanceID, filter) then debuffs[aura.auraInstanceID] = aura end end for _, auraInstanceID in next, updateInfo.updatedAuraInstanceIDs or {} do - local aura = C_UnitAuras.GetAuraByAuraInstanceID(unit, auraInstanceID) - - if aura.isHarmful and IsDispellable(aura, unit) then - debuffs[aura.auraInstanceID] = aura + if not C_UnitAuras.IsAuraFilteredOutByInstanceID(unit, auraInstanceID, filter) then + local aura = C_UnitAuras.GetAuraByAuraInstanceID(unit, auraInstanceID) + debuffs[auraInstanceID] = aura end end @@ -216,7 +184,7 @@ local function UpdateDebuffs(self, updateInfo) end end -local function UpdateDisplay(self) +local function UpdateDisplay(self, unit) local element = self.Dispellable local lowestID = nil @@ -226,37 +194,24 @@ local function UpdateDisplay(self) end end - local dispelTexture = element.dispelTexture local dispelIcon = element.dispelIcon + local debuff = lowestID and element.debuffs[lowestID] - if lowestID and lowestID ~= element.__current then - element.__current = lowestID - local debuff = element.debuffs[lowestID] - local debuffType = debuff.dispelName - local r, g, b = self.colors.debuff[debuffType]:GetRGB() - - if dispelTexture then - dispelTexture:UpdateColor(debuffType, r, g, b, dispelTexture.dispelAlpha) - end - + if debuff then if dispelIcon then dispelIcon.unit = self.unit dispelIcon.id = lowestID if dispelIcon.icon then dispelIcon.icon:SetTexture(debuff.icon) end - if dispelIcon.overlay then - dispelIcon.overlay:SetVertexColor(r, g, b) - end if dispelIcon.count then - local count = debuff.applications - dispelIcon.count:SetText(count and count > 1 and count or '') + dispelIcon.count:SetText(C_UnitAuras.GetAuraApplicationDisplayCount(unit, debuff.auraInstanceID)) end if dispelIcon.cd then - local duration = debuff.duration + local duration = C_UnitAuras.GetAuraDuration(unit, debuff.auraInstanceID) - if duration > 0 then - dispelIcon.cd:SetCooldown(debuff.expirationTime - duration, duration, debuff.timeMod) + if duration then + dispelIcon.cd:SetCooldownFromDurationObject(duration) dispelIcon.cd:Show() else dispelIcon.cd:Hide() @@ -265,18 +220,15 @@ local function UpdateDisplay(self) dispelIcon:Show() end - - return debuff - elseif not lowestID and element.__current ~= nil then - element.__current = nil - - if dispelTexture then - dispelTexture:UpdateColor(nil, 1, 1, 1, dispelTexture.noDispelAlpha) - end + else if dispelIcon then dispelIcon:Hide() end end + + element:UpdateColor(unit, debuff) + + return debuff end local function Update(self, _, unit, updateInfo) @@ -295,8 +247,8 @@ local function Update(self, _, unit, updateInfo) element:PreUpdate() end - UpdateDebuffs(self, updateInfo) - local displayed = UpdateDisplay(self) + UpdateDebuffs(self, unit, updateInfo) + local displayed = UpdateDisplay(self, unit) --[[ Callback: Dispellable:PostUpdate(debuffType, texture, count, duration, expiration) Called after the element has been updated. @@ -334,13 +286,6 @@ local function Enable(self) element.debuffs = {} element.ForceUpdate = ForceUpdate - local dispelTexture = element.dispelTexture - if dispelTexture then - dispelTexture.dispelAlpha = dispelTexture.dispelAlpha or 1 - dispelTexture.noDispelAlpha = dispelTexture.noDispelAlpha or 0 - dispelTexture.UpdateColor = dispelTexture.UpdateColor or UpdateColor - end - local dispelIcon = element.dispelIcon if dispelIcon then -- prevent /fstack errors @@ -366,13 +311,34 @@ local function Enable(self) end end - if not self.colors.debuff then - self.colors.debuff = {} - for debuffType, color in next, oUF.colors.debuff do - self.colors.debuff[debuffType] = color + local dispelTexture = element.dispelTexture + if dispelTexture then + dispelTexture.dispelAlpha = dispelTexture.dispelAlpha or 1 + dispelTexture.noDispelAlpha = dispelTexture.noDispelAlpha or 0 + end + + if not element.dispelColorCurve then + local curve = _G.C_CurveUtil.CreateColorCurve() + curve:SetType(_G.Enum.LuaCurveType.Step) + + for _, dispel in next, oUF.Enum.DispelType do + local color = self.colors.dispel[dispel] + + if color then + local r, g, b = color:GetRGB() + curve:AddPoint(dispel, _G.CreateColor(r, g, b, dispelTexture and dispelTexture.dispelAlpha)) + end end + + element.dispelColorCurve = curve end + if not element.resetColor then + element.resetColor = _G.CreateColor(1, 1, 1, dispelTexture and dispelTexture.noDispelAlpha) + end + + element.UpdateColor = element.UpdateColor or UpdateColor + self:RegisterEvent('UNIT_AURA', Path) return true @@ -384,74 +350,13 @@ local function Disable(self) return end + self:UnregisterEvent('UNIT_AURA', Path) + if element.dispelIcon then element.dispelIcon:Hide() end - if element.dispelTexture then - element.dispelTexture:UpdateColor(nil, 1, 1, 1, element.dispelTexture.noDispelAlpha) - end - self:UnregisterEvent('UNIT_AURA', Path) + element:UpdateColor(self.unit) end oUF:AddElement('Dispellable', Path, Enable, Disable) - -local function ToggleElement(enable) - for _, object in next, oUF.objects do - local element = object.Dispellable - if element then - if enable then - object:EnableElement('Dispellable') - element:ForceUpdate() - else - object:DisableElement('Dispellable') - end - end - end -end - --- shallow comparison of primitive key/value types -local function TablesMatch(a, b) - for k, v in next, a do - if b[k] ~= v then - return false - end - end - for k, v in next, b do - if a[k] ~= v then - return false - end - end - - return true -end - -local function UpdateDispels() - local available = {} - for id, types in next, dispels do - if IsSpellKnown(id, id == 89808) or IsPlayerSpell(id) then - for debuffType, flags in next, dispelTypeFlags do - if band(types, flags) > 0 and available[debuffType] ~= true then - available[debuffType] = band(LPS:GetSpellInfo(id), LPS.constants.PERSONAL) > 0 and 'player' or true - end - end - end - end - - if next(available) then - if not TablesMatch(available, canDispel) then - wipe(canDispel) - for debuffType in next, available do - canDispel[debuffType] = available[debuffType] - end - ToggleElement(true) - end - elseif next(canDispel) then - wipe(canDispel) - ToggleElement() - end -end - -local frame = CreateFrame('Frame') -frame:SetScript('OnEvent', UpdateDispels) -frame:RegisterEvent('SPELLS_CHANGED') diff --git a/oUF_Dispellable.toc b/oUF_Dispellable.toc index 0315e5b..bdcb4c6 100644 --- a/oUF_Dispellable.toc +++ b/oUF_Dispellable.toc @@ -1,18 +1,12 @@ -## Interface: 110002 +## Interface: 120000 ## Title: oUF_Dispellable ## Notes: oUF element for highlighting debuffs that are dispellable by the player ## Author: Rainrider ## Version: @project-version@ ## RequiredDeps: oUF -## OptionalDeps: LibStub, LibPlayerSpells-1.0 ## X-Curse-Project-ID: 285395 ## X-Wago-ID: baNDRgKo ## X-WoWI-ID: 24540 -#@non-debug@ -# libs\LibStub\LibStub.lua -# libs\LibPlayerSpells-1.0\lib.xml -#@end-non-debug@ - oUF_Dispellable.lua