14 Commits
Author SHA1 Message Date
Andreas 5823bf839c Add a status bar for incoming heals
This shows a prediction of incoming direct heals.
2026-11-01 16:34:52 +01:00
Andreas 0569c100a1 Make status colors configurable
The aura based status colors can now be configured in frames.lua. This
is important in TBC where some healers don't care about e.g. magic
debuffs.
2026-10-31 11:59:51 +00:00
Andreas 4567bea3f9 Override role as TANK based on MT assignments
This is particularly helpful in fights like High King Maulgar,
Illidari Council or Kael'Thas where a DPS player is temporarily
made into a tank.
2026-10-29 12:54:24 +01:00
Andreas 949e3686aa Update config with TBC druid settings 2026-10-29 09:52:17 +01:00
omicron 76851011fe Fix font and status bars to use new API arguments 2026-10-29 09:28:49 +01:00
omicron 7f3269949f Add multiple spell ids support to AuraTrigger
Before AuraTrigger only supported a single spell id but now multiple
spells can activate the same trigger.

Related changes:
- Change the glue between AuraList and AuraTrigger to match this new functionality.
- Update the default frame config
2026-10-29 09:28:49 +01:00
omicron b9f1bad81d Replace Trigger:SetState by Trigger:SetStateAndData
Update the use of the old API in most triggers except AuraTrigger. This
means the code isn't in a working state at this commit.
2026-10-29 09:28:48 +01:00
omicron 80859c1e44 Change the sort algorithm to use UnitExist instead of frame:IsShown
When UNIT_PET fires, pet units will exist but the UnitWatch will not yet
have shown the frame.
2026-10-29 09:28:48 +01:00
omicron 575e829025 Make UnitGroup trigger sort on UNIT_PET 2026-10-29 09:28:48 +01:00
omicron b473f7bf3d Add raid pets to the hardcoded configuration 2026-10-29 09:28:48 +01:00
omicron 14aeb2c43e Fixed typos and logic error in UnitFrame:Disable
This function is currently unused so these changes are not tested. The
Disable function now always hides the UnitFrame and calls
UnregisterUnitWatch with the correct parameters.
2026-10-29 09:28:47 +01:00
omicron 991bc5d0fe Fix type in AuraList:UpdateAura
Due to a typo AuraTrigger:UpdateAura always received nil as its first
(before) parameter. This parameter is currently unused so it did not
trigger an error.
2026-10-29 09:28:47 +01:00
omicron 2a24e64554 update config for TBC shaman play 2026-10-29 09:28:47 +01:00
omicron abeac379f3 Enhance IconIndicator with tooltips, fix stacks/applications mixup 2026-03-07 13:17:25 +01:00
10 changed files with 530 additions and 143 deletions

No files matched your search

+259 -41
View File
@@ -21,15 +21,35 @@ local colors = {
white = { 1.0, 1.0, 1.0 },
orange = { 1.0, 0.5, 0 },
violet = { 1.0, 0, 1.0 },
yellow = { 1.0, 1.0, 0 },
magic = { 0.4, 0.4, 1.0 },
disease = { 0.4, 0.2, 0.0 },
poison = { 0.0, 0.7, 0.7 },
curse = { 0.7, 0.0, 0.7 },
immune = { 0.0, 0.2, 0.4 },
bomb = { 1.0, 0.7, 0.7 },
red = { 1, 0, 0 },
blue = { 0.4, 0.4, 1 },
light_blue = { 0.7, 0.7, 1 },
green = { 0.2, 0.9, 0.2 },
}
---@param list number[]
---@return table<number, true>
local function set(list)
local s = {}
for _, v in ipairs(list) do
s[v] = true
end
return s
end
local AURAS_PRIEST_RENEW = set({ 139, 6074, 6075, 6076, 6077, 6078, 10927, 10928, 10929, 25315, 25221, 25222 })
local AURAS_PRIEST_PWS = set({ 17, 592, 600, 3747, 6065, 6066, 10898, 10899, 10900, 10901, 25217, 25218 })
local AURAS_DRUID_LIFEBLOOM = set({ 33763 })
local AURAS_DRUID_REJUV = set({ 774, 1058, 1430, 2090, 2091, 3627, 8910, 9839, 9840, 9841, 25299, 26981, 26982 })
local AURAS_DRUID_REGROWTH = set({ 8936, 8938, 8939, 8940, 8941, 9750, 9856, 9857, 9858, 26980 })
local function RangeConfig()
local _, class = UnitClass("player")
if class == "SHAMAN" then
@@ -40,10 +60,16 @@ local function RangeConfig()
}
elseif class == "PRIEST" then
return {
friendly = "Flash Heal",
friendly = "Heal",
enemy = "Smite",
fade = 0.2,
}
elseif class == "DRUID" then
return {
friendly = "Rejuvenation",
enemy = "Wrath",
fade = 0.2,
}
else
return {
fade = 0.2,
@@ -61,25 +87,41 @@ local function MouseConfig()
button = "mouse2",
mods = {},
kind = "macro",
data = "/use [@UNIT,dead,help]Ancestral Vision; [@UNIT,help]Chain Heal",
data = "/use [@UNIT,dead,help]Ancestral Spirit; [@UNIT,help]Chain Heal",
},
{
button = "mouse3",
mods = {},
kind = "macro",
data = "/use [@UNIT,dead,help]Ancestral Spirit; [@UNIT,help]Purify Spirit",
data = "/use [@UNIT,help]Cure Poison",
},
{
button = "wheel-up",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Healing Surge",
data = "/use [@UNIT,help]Lesser Healing Wave",
},
{
button = "wheel-down",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Healing Wave",
},
{ button = "wheel-down", mods = {}, kind = "macro", data = "/use [@UNIT,help]Riptide" },
-- alt
{ button = "wheel-up", mods = { alt = true }, kind = "spell", data = "Healing Wave" },
{ button = "wheel-down", mods = { alt = true }, kind = "spell", data = "Earth Shield" },
{
button = "wheel-up",
mods = { alt = true },
kind = "macro",
data = "/use [@UNIT,help,nodead]Nature's Swiftness\n/use [@UNIT,help]Healing Wave",
},
{
button = "mouse3",
mods = { alt = true },
kind = "macro",
data = "/use [@UNIT,help]Cure Disease",
},
-- Shift
{ button = "mouse2", mods = { shift = true }, kind = "togglemenu" },
@@ -89,6 +131,12 @@ local function MouseConfig()
kind = "macro",
data = "/cast [@UNIT,help]Water Walking;\n/stopspelltarget",
},
{
button = "wheel-down",
mods = { shift = true },
kind = "macro",
data = "/use [@UNIT,help]Healing Wave(rank 1)",
},
}
elseif class == "PRIEST" then
return {
@@ -111,15 +159,15 @@ local function MouseConfig()
button = "wheel-up",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Flash Heal",
data = "/use [@UNIT,help]Lesser Heal",
},
{ button = "wheel-down", mods = {}, kind = "macro", data = "/use [@UNIT,help]Renew" },
{ button = "wheel-down", mods = {}, kind = "macro", data = "/use [@UNIT,help]Heal" },
-- alt
{ button = "mouse1", mods = { alt = true }, kind = "spell", data = "Power Word: Life" },
{ button = "mouse2", mods = { alt = true }, kind = "spell", data = "Pain Suppression" },
{ button = "mouse3", mods = { alt = true }, kind = "spell", data = "Power Infusion" },
{ button = "wheel-up", mods = { alt = true }, kind = "spell", data = "Rapture" },
{ button = "wheel-up", mods = { alt = true }, kind = "spell", data = "Renew" },
{ button = "wheel-down", mods = { alt = true }, kind = "spell", data = "Power Word: Shield" },
-- Shift
@@ -137,6 +185,66 @@ local function MouseConfig()
data = "/cast [@UNIT,help]Leap of Faith;\n/stopspelltarget",
},
}
elseif class == "DRUID" then
return {
-- No modifier
{ button = "mouse1", mods = {}, kind = "target" },
{
button = "mouse2",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Rejuvenation",
},
{
button = "wheel-up",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Lifebloom",
},
{
button = "wheel-down",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Regrowth",
},
-- ctrl
{
button = "wheel-up",
mods = { ctrl = true },
kind = "macro",
data = "/use [@UNIT,help]Abolish Poison"
},
{
button = "wheel-down",
mods = { ctrl = true },
kind = "macro",
data = "/use [@UNIT,help]Remove Curse"
},
-- alt
{
button = "mouse1",
mods = { alt = true },
kind = "macro",
data = "/use [@UNIT,help]Innervate",
},
{
button = "mouse2",
mods = { alt = true },
kind = "macro",
data = "/use [@UNIT,dead,help]Rebirth",
},
-- Shift
{
button = "wheel-down",
mods = { shift = true },
kind = "macro",
data = "/use [@UNIT,help]Swiftmend",
},
{ button = "mouse2", mods = { shift = true }, kind = "togglemenu" },
}
elseif class == "MONK" then
return {
-- No modifier
@@ -259,7 +367,7 @@ local function TriggerClassConfig()
return {
{
kind = "AuraTrigger",
spellId = 383648,
spellIds = set({ 32594 }),
own = true, -- Second Earth Shield on yourself
indicator = {
kind = "SquareIndicator",
@@ -273,29 +381,30 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 974,
own = true, -- Second Earth Shield on yourself
spellIds = set({ 29203 }),
own = true, -- Healing Way
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 3,
x = 20,
y = -3,
color = colors.white,
color = colors.yellow,
showStacks = true,
},
},
{
kind = "AuraTrigger",
spellId = 61295,
own = true, -- Riptide
spellIds = set({ 16237 }),
own = true, -- Ancestral Fortitude
indicator = {
kind = "IconIndicator",
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 3,
y = 3,
fadeTime = 10.0,
point = "TOPLEFT",
x = 40,
y = -3,
color = colors.orange,
showStacks = false,
},
},
{
@@ -314,7 +423,7 @@ local function TriggerClassConfig()
return {
{
kind = "AuraTrigger",
spellId = 17,
spellIds = AURAS_PRIEST_PWS,
own = true, -- Power Word: Shield
indicator = {
kind = "SquareIndicator",
@@ -328,7 +437,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 33206,
spellIds = set({ 33206 }),
own = true, -- Pain Suppression
indicator = {
kind = "SquareIndicator",
@@ -343,7 +452,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 41635,
spellIds = set({ 41635 }),
own = true, -- Prayer of Mending
indicator = {
kind = "SquareIndicator",
@@ -357,7 +466,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 139,
spellIds = AURAS_PRIEST_RENEW,
own = true, -- Renew
indicator = {
kind = "SquareIndicator",
@@ -371,7 +480,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 194384,
spellIds = set({ 194384 }),
own = true, -- Atonement
indicator = {
kind = "SquareIndicator",
@@ -386,7 +495,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 10060,
spellIds = set({ 10060 }),
own = true, -- Power Infusion
indicator = {
kind = "SquareIndicator",
@@ -410,11 +519,69 @@ local function TriggerClassConfig()
},
},
}
elseif class == "DRUID" then
return {
{
kind = "AuraTrigger",
spellIds = AURAS_DRUID_LIFEBLOOM,
own = true, -- Lifebloom
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 3,
y = 3,
color = colors.green,
showStacks = true,
fadeTime = 7.0,
flashTime = 2,
},
},
{
kind = "AuraTrigger",
spellIds = AURAS_DRUID_REGROWTH,
own = true, -- Regrowth
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 22,
y = 3,
color = colors.yellow,
fadeTime = 21.0,
},
},
{
kind = "AuraTrigger",
spellIds = AURAS_DRUID_REJUV,
own = true, -- Rejuvenation
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 41,
y = 3,
color = colors.violet,
fadeTime = 12.0,
},
},
{
kind = "StatusTrigger",
status = "Burn",
invert = false,
indicator = {
kind = "BorderIndicator",
thickness = 3.0,
color = colors.red,
level = 0,
},
},
}
elseif class == "PALADIN" then
return {
{
kind = "AuraTrigger",
spellId = 287280, -- Glimmer of Light
spellIds = set({ 287280 }), -- Glimmer of Light
own = true,
indicator = {
kind = "SquareIndicator",
@@ -429,7 +596,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 25771, -- Forbearance
spellIds = set({ 25771 }), -- Forbearance
indicator = {
kind = "SquareIndicator",
size = 17,
@@ -443,7 +610,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 53563, -- Beacon of Light
spellIds = set({ 53563 }), -- Beacon of Light
own = true,
indicator = {
kind = "SquareIndicator",
@@ -456,7 +623,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 156910, -- Beacon of Faith
spellIds = set({ 156910 }), -- Beacon of Faith
own = true,
indicator = {
kind = "SquareIndicator",
@@ -469,7 +636,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 200025, -- Beacon of Virtue
spellIds = set({ 200025 }), -- Beacon of Virtue
own = true,
indicator = {
kind = "SquareIndicator",
@@ -482,7 +649,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 1044, -- Blessing of Freedom
spellIds = set({ 1044 }), -- Blessing of Freedom
own = true,
indicator = {
kind = "SquareIndicator",
@@ -496,7 +663,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 1111, -- Blessing of Spellwarding
spellIds = set({ 1111 }), -- Blessing of Spellwarding
own = true,
indicator = {
kind = "SquareIndicator",
@@ -510,7 +677,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 1022, -- Blessing of Protection
spellIds = set({ 1022 }), -- Blessing of Protection
own = true,
indicator = {
kind = "SquareIndicator",
@@ -524,7 +691,7 @@ local function TriggerClassConfig()
},
{
kind = "AuraTrigger",
spellId = 6940, -- Blessing of Saccrifice
spellIds = set({ 6940 }), -- Blessing of Saccrifice
own = true,
indicator = {
kind = "SquareIndicator",
@@ -572,7 +739,7 @@ local function MakeDebuffTrigger(slot, spellid, color, stacks)
end
return {
kind = "AuraTrigger",
spellId = spellid,
spellIds = set({ spellid }),
indicator = {
kind = "SquareIndicator",
size = 15,
@@ -652,7 +819,7 @@ local function TriggerConfig()
-- Some more debuffs in the bottom row
table.insert(triggers, {
kind = "AuraTrigger",
spellId = 240443, -- Bursting
spellIds = set({ 240443 }), -- Bursting
indicator = {
kind = "SquareIndicator",
size = 17,
@@ -668,6 +835,51 @@ local function TriggerConfig()
return triggers
end
local function StatusColorClassConfig()
local _, class = UnitClass("player")
if class == "DRUID" then
return {
{ status = "Curse", color = colors.curse },
{ status = "Poison", color = colors.poison },
}
elseif class == "MAGE" then
return {
{ status = "Curse", color = colors.curse },
}
elseif class == "PALADIN" then
return {
{ status = "Magic", color = colors.magic },
{ status = "Poison", color = colors.poison },
{ status = "Disease", color = colors.disease },
}
elseif class == "PRIEST" then
return {
{ status = "Magic", color = colors.magic },
{ status = "Disease", color = colors.disease },
}
elseif class == "SHAMAN" then
return {
{ status = "Poison", color = colors.poison },
{ status = "Disease", color = colors.disease },
}
end
return {}
end
local function StatusColorConfig()
local statusColors = {
{ status = "Immune", color = colors.immune },
{ status = "Bomb", color = colors.bomb },
}
for _, entry in ipairs(StatusColorClassConfig()) do
table.insert(statusColors, entry)
end
return statusColors
end
local types = omif.GetModule("types")
local UnitFrame = types.UnitFrame
local UnitGroup = types.UnitGroup
@@ -700,6 +912,8 @@ function CreateRaidFrames(left, top, config)
local num = i * 5 + j + 1
local frame = UnitFrame:new("raid" .. num, config)
group:AddUnitFrame(frame)
local raidpet = UnitFrame:new("raidpet" .. num, config)
group:AddUnitFrame(raidpet)
end
end
end
@@ -710,9 +924,13 @@ function CreatePartyFrames(left, top, config)
local group = UnitGroup:new(left, top, width, height)
local player = UnitFrame:new("player", config)
group:AddUnitFrame(player)
local pet = UnitFrame:new("pet", config)
group:AddUnitFrame(pet)
for i = 1, 4 do
local frame = UnitFrame:new("party" .. i, config)
group:AddUnitFrame(frame)
local partypet = UnitFrame:new("partypet" .. i, config)
group:AddUnitFrame(partypet)
end
group:Sort()
end
@@ -742,10 +960,10 @@ function CreateFrames()
triggers = TriggerConfig(),
}
CreatePartyFrames(-0.05, -290.05, config)
CreatePartyFrames(-0.05, -240.05, config)
config.hideInRaid = false
CreateRaidFrames(-0.05, -290.05, config)
CreateTargetFrames(110 * 3 + 50 - 0.05, -245.05, config)
CreateRaidFrames(-0.05, -240.05, config)
CreateTargetFrames(110 * 3 + 50 - 0.05, -240.05, config)
HideBlizzardFrames()
end
+25 -9
View File
@@ -16,6 +16,8 @@
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
local AuraTrigger = types.AuraTrigger
local StatusTrigger = types.StatusTrigger
--- AuraList keeps track of all the auras attached to a unitframe
---@class AuraList: Object
@@ -180,21 +182,35 @@ function AuraList:Init(unitframe)
self.triggersByStatus = {}
end
---@param trigger AuraTrigger | StatusTrigger
function AuraList:AddTrigger(trigger)
---@param trigger StatusTrigger
function AuraList:AddStatusTrigger(trigger)
table.insert(self.triggers, trigger)
local spellId = trigger.spellId
local status = trigger.status
if spellId then
if self.triggersByStatus[status] == nil then
self.triggersByStatus[status] = {}
end
table.insert(self.triggersByStatus[status], trigger)
end
---@param trigger AuraTrigger
function AuraList:AddAuraTrigger(trigger)
table.insert(self.triggers, trigger)
for spellId in pairs(trigger.spellIds) do
if self.triggersBySpell[spellId] == nil then
self.triggersBySpell[spellId] = {}
end
table.insert(self.triggersBySpell[spellId], trigger)
end
end
---@param trigger AuraTrigger | StatusTrigger
function AuraList:AddTrigger(trigger)
if trigger:IsInstanceOf(AuraTrigger) then
self:AddAuraTrigger(trigger)
elseif trigger:IsInstanceOf(StatusTrigger) then
self:AddStatusTrigger(trigger)
else
if self.triggersByStatus[status] == nil then
self.triggersByStatus[status] = {}
end
table.insert(self.triggersByStatus[status], trigger)
error("AddTrigger: trigger is neither an AuraTrigger nor a StatusTrigger")
end
end
@@ -269,7 +285,7 @@ end
---@param aura UnitAuraInfo
function AuraList:UpdateAura(aura)
for _, trigger in ipairs(self.triggersBySpell[aura.spellId] or {}) do
trigger:UpdateAura(self.auras[aura.auraInstanceId], aura)
trigger:UpdateAura(self.auras[aura.auraInstanceID], aura)
end
self.auras[aura.auraInstanceID] = aura
end
+52 -10
View File
@@ -46,7 +46,8 @@ end
---@param showStacks boolean
---@param doFade nil|number
---@param doFlash nil|number
function IconIndicator:Init(unitframe, size, point, x, y, showStacks, fadeTime, flashTime)
---@param showTooltip nil|boolean
function IconIndicator:Init(unitframe, size, point, x, y, showStacks, fadeTime, flashTime, showTooltip)
self.unitframe = unitframe
self.frameParent = unitframe.overlays
self.size = size
@@ -57,13 +58,21 @@ function IconIndicator:Init(unitframe, size, point, x, y, showStacks, fadeTime,
self.showStacks = showStacks
self.fadeTime = fadeTime
self.flashTime = flashTime
self.showTooltip = showTooltip or false
self.instanceId = 0
self.spellId = 0
end
--- Show the square indicator
---@param data table
function IconIndicator:Show(data)
local frame, stacks = self:GetFrame()
local numStacks = data.stacks or 0
local numStacks = data.applications or 0
self.auraInstanceID = data.auraInstanceID
self.spellId = data.spellId
self.frame = frame
self.stacks = stacks
if self.showStacks and numStacks > 0 then
@@ -77,12 +86,23 @@ function IconIndicator:Show(data)
self.expirationTime = data.expirationTime
self.updateThrottle = 0
end
if self.showTooltip then
frame:SetScript("OnEnter", function()
GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
GameTooltip:SetSpellByID(self.spellId)
GameTooltip:Show()
end)
frame:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
end
if data.icon then
self:SetIcon(data.icon)
else
self:SetIcon(self.icon)
end
frame:Show()
self:UpdateAlpha()
end
--- Set the icon of the Icon Indicator
@@ -131,22 +151,23 @@ function IconIndicator:Update(data)
if data.icon then
self:SetIcon(data.icon)
end
local numStacks = data.stacks or 0
local numStacks = data.applications or 0
if numStacks > 0 then
self.stacks:SetText(numStacks)
self.stacks:Show()
end
self.expirationTime = data.expirationTime
self.frame:SetAlpha(1)
self.auraInstanceID = data.auraInstanceID
self.spellId = data.spellId
self:UpdateAlpha()
end
function IconIndicator:OnUpdate(dt)
dt = self.updateThrottle + dt
if dt < 0.04 then
self.updateThrottle = dt
function IconIndicator:UpdateAlpha()
if self.expirationTime == 0 then
self.frame:SetAlpha(1)
return
else
self.updateThrottle = 0
end
local timeLeft = self.expirationTime - GetTime()
@@ -159,15 +180,36 @@ function IconIndicator:OnUpdate(dt)
elseif timeLeft < fadeTime then
local alpha = timeLeft / fadeTime
self.frame:SetAlpha(alpha)
else
self.frame:SetAlpha(1)
end
end
function IconIndicator:OnUpdate(dt)
dt = self.updateThrottle + dt
if dt < 0.04 then
self.updateThrottle = dt
return
else
self.updateThrottle = 0
end
self:UpdateAlpha()
end
--- Hide the square indicator.
function IconIndicator:Hide()
local frame = self.frame
if frame == nil then
return
end
self.stacks:Hide()
self.frame = nil
self.stacks = nil
self.instanceId = nil
self.spellId = nil
frame:SetScript("OnUpdate", nil)
frame:SetScript("OnLeave", nil)
frame:SetScript("OnEnter", nil)
iconPool:Release(frame)
end
+14
View File
@@ -90,3 +90,17 @@ function StatusBar:SetColor(r, g, b)
self.bg:SetVertexColor(0.2 * r, 0.2 * g, 0.2 * b)
end
end
function StatusBar:SetAlpha(alpha)
self.bar:SetAlpha(alpha)
end
function StatusBar:GetColor()
local r, g, b = self.bar:GetStatusBarColor()
return r, g, b
end
function StatusBar:AttachTo(statusBar, point, relativePoint)
self.bar:ClearAllPoints()
self.bar:SetPoint(point, statusBar.bar:GetStatusBarTexture(), relativePoint)
end
+66 -24
View File
@@ -21,7 +21,7 @@ local Trigger = types.Trigger
---AuraTrigger is a Trigger that can be attached to AuraList as datasource
---@class AuraTrigger: Trigger
---@field spellId number
---@field spellIds table<number, true>
---@field count number
---@field requiredCount number
local AuraTrigger = types.CreateClass("AuraTrigger", Trigger)
@@ -33,7 +33,8 @@ types.AuraTrigger = AuraTrigger
---@param config table
---@return AuraTrigger
function AuraTrigger.CreateFromConfig(unit, config)
local trigger = AuraTrigger:new(config.spellId, config.own, config.requiredCount, config.invert, config.defaultData)
local trigger =
AuraTrigger:new(config.spellIds, config.own, config.requiredCount, config.invert, config.defaultData)
unit.auras:AddTrigger(trigger)
return trigger
end
@@ -47,29 +48,33 @@ function AuraTrigger.new(cls, ...)
end
--- Initialize a new AuraTrigger object
---@param spellId number
---@param spellIds table<number, true>
---@param own boolean
---@param requiredCount number | nil
---@param invert boolean | nil
---@param defaultData table | nil
function AuraTrigger:Init(spellId, own, requiredCount, invert, defaultData)
function AuraTrigger:Init(spellIds, own, requiredCount, invert, defaultData)
Trigger.Init(self, invert, defaultData)
self.spellId = spellId
self.spellIds = spellIds
self.instanceData = {}
self.requiredCount = requiredCount or 1
self.own = own
self.count = 0
self.bestAuraID = nil
end
--- Reset the AuraTrigger to the default state
function AuraTrigger:Reset()
self.count = 0
self:SetState(self.count >= self.requiredCount, false)
self.bestAuraID = nil
table.wipe(self.instanceData)
self:SetStateAndData(self.count >= self.requiredCount, {})
end
---Check if a given aura matches this AuraTrigger
---@param aura UnitAuraInfo
function AuraTrigger:IsMatching(aura)
if aura.spellId ~= aura.spellId then
if not self.spellIds[aura.spellId] then
return false
end
if self.own and aura.sourceUnit ~= "player" then
@@ -78,35 +83,68 @@ function AuraTrigger:IsMatching(aura)
return true
end
-- Get the ID of the current best matching aura in AuraTrigger.instanceData
function AuraTrigger:BestAuraID()
local found = nil
for k, v in pairs(self.instanceData) do
if not found then
found = k
elseif v.expirationTime > self.instanceData[found].expirationTime then
found = k
end
end
return found
end
---Grab the best aura data and update the current recorded best aura
---It returns data if the current best aura is different from the previous
---@param updatedInstanceId number|nil the aura id that was recently updated
function AuraTrigger:BestAuraData(updatedInstanceId)
local bid = self:BestAuraID()
if bid == nil then
self.bestAuraID = nil
return nil
end
local data = nil
if bid ~= self.bestAuraID or (updatedInstanceId and bid == updatedInstanceId) then
local aura = self.instanceData[bid]
data = {
stacks = aura.applications,
duration = aura.duration,
expirationTime = aura.expirationTime,
icon = aura.icon,
}
self.bestAuraID = bid
end
return data
end
---Inform the trigger about an added aura
---@param aura UnitAuraInfo
function AuraTrigger:AddAura(aura)
if not self:IsMatching(aura) then
return
end
local aid = aura.auraInstanceID
self.instanceData[aid] = aura
self.count = self.count + 1
self:SetData({
stacks = aura.applications,
duration = aura.duration,
expirationTime = aura.expirationTime,
icon = aura.icon,
})
self:SetState(self.count >= self.requiredCount, false)
local newData = self:BestAuraData(nil)
self:SetStateAndData(self.count >= self.requiredCount, newData)
end
---Inform the trigger about an updated aura
---@param before UnitAuraInfo
---@param after UnitAuraInfo
function AuraTrigger:UpdateAura(before, after)
self:SetData({
stacks = after.applications,
duration = after.duration,
expirationTime = after.expirationTime,
icon = after.icon,
})
if self.active then
self.indicator:Update(self.data)
if not self:IsMatching(after) then
return
end
local aid = after.auraInstanceID
self.instanceData[aid] = after
local newData = self:BestAuraData(aid)
self:SetStateAndData(self.count >= self.requiredCount, newData)
end
---Inform the trigger about an aura that got removed
@@ -115,7 +153,11 @@ function AuraTrigger:RemoveAura(aura)
if not self:IsMatching(aura) then
return
end
local aid = aura.auraInstanceID
self.instanceData[aid] = nil
self.count = self.count - 1
self:SetData({})
self:SetState(self.count >= self.requiredCount, false)
local newData = self:BestAuraData(nil)
self:SetStateAndData(self.count >= self.requiredCount, newData)
end
+4 -7
View File
@@ -92,8 +92,7 @@ function MultiTrigger:OnChildActivate(idx, trigger, data)
table.sort(activeChildren)
-- The highest priority active trigger has changed
if activeChild ~= activeChildren[1] then
self:SetData(data)
self:SetState(true, true)
self:SetStateAndData(true, data)
end
end
@@ -117,11 +116,9 @@ function MultiTrigger:OnChildDeactivate(idx, trigger, data)
local newActiveChild = activeChildren[1]
if activeChild ~= newActiveChild then
if activeChildren[1] ~= nil then
self:SetData(childrenData[newActiveChild])
self:SetState(true, true)
self:SetStateAndData(true, childrenData[newActiveChild])
else
self:SetData({})
self:SetState(false, false)
self:SetStateAndData(false, {})
end
end
end
@@ -139,5 +136,5 @@ end
-- Reset the trigger to the default state. If this changes the activation of the
-- trigger then the target will be notified.
function MultiTrigger:Reset()
self:SetState(false)
self:SetStateAndData(false, {})
end
+1 -1
View File
@@ -58,5 +58,5 @@ end
-- status:
-- Must be a valid UnitAuraInfo
function StatusTrigger:UpdateStatus(count)
self:SetState(count >= self.requiredCount, false)
self:SetStateAndData(count >= self.requiredCount, nil)
end
+18 -4
View File
@@ -47,7 +47,9 @@ function Trigger:Init(invert, defaultData)
end
---Save new data from the datasource into the trigger. This method will make
---sure the default data is always present
---sure the default data is always present. This method will not ensure the
---target gets notified of new state or new data. Subclasses of Trigger should
---use Trigger:SetStateAndData instead.
---@param data table
function Trigger:SetData(data)
if self.defaultData then
@@ -69,9 +71,21 @@ function Trigger:SetTarget(target)
end
end
---Set the state of the trigger. Will activate the indicator if needed.
---Set the data and state of the trigger. Will update the trigger as needed.
---
--- If the state changes this function will call Show or Hide on the indicator.
--- If the state remains the same and the data is not nil, it will call Update
--- on the indicator.
---
---@param state boolean
function Trigger:SetState(state, doUpdate)
---@param data table|nil
function Trigger:SetStateAndData(state, data)
local doUpdate = false
if data then
doUpdate = true
self:SetData(data)
end
state = state ~= self.invert -- state xor inverted
if self.active ~= state then
if state then
@@ -88,5 +102,5 @@ end
-- Reset the trigger to the default state. If this changes the activation of the
-- trigger then the target will be notified.
function Trigger:Reset()
self:SetState(false, false)
self:SetStateAndData(false, nil)
end
+77 -34
View File
@@ -17,6 +17,11 @@
local omif = select(2, ...)
local types = omif.GetModule("types")
local NameFont = CreateFont("OicronFrames_NameFont")
NameFont:SetFont("Interface\\AddOns\\OmicronFrames\\media\\fonts\\roboto\\Roboto-Bold.ttf", 13, "")
NameFont:SetShadowColor(0, 0, 0)
NameFont:SetShadowOffset(1, -1)
---@class StatusBar
local StatusBar = types.StatusBar
@@ -45,12 +50,6 @@ local colors = {
low = { 1.0, 0.0, 0 },
offline = { 0.7, 0.7, 0.7 },
dead = { 0.7, 0.7, 0.7 },
magic = { 0.4, 0.4, 1.0 },
disease = { 0.4, 0.2, 0.0 },
poison = { 0.0, 0.7, 0.7 },
curse = { 0.7, 0.0, 0.7 },
immune = { 0.0, 0.2, 0.4 },
bomb = { 1.0, 0.7, 0.7 },
cyan = { 0.0, 0.8, 0.8 },
white = { 1.0, 1.0, 1.0 },
shield = { 0.85, 0.95, 1.0 },
@@ -85,6 +84,8 @@ function UnitFrame:Init(unit, config)
self.rangeEnemy = config.range.enemy
self.rangeFade = config.range.fade
self.statusColors = config.statusColors
local secure = self:CreateSecureFrame(width, height)
secure:Hide()
@@ -96,18 +97,22 @@ function UnitFrame:Init(unit, config)
self.hp = StatusBar:new(self, width, height, 0, true, texture, texture)
self.power = StatusBar:new(self, width, 6, 2, false, texture, texture)
self.power = StatusBar:new(self, width, 6, 3, false, texture, texture)
self.power:Hide()
self.absorb = StatusBar:new(self, width, height, 1, true, texture_top)
self.absorb:SetColor(unpack(colors.absorb))
self.absorb:SetFillStyle("REVERSE")
self.incomingHeals = StatusBar:new(self, width, height, 1, true, texture)
self.incomingHeals:AttachTo(self.hp, "TOPLEFT", "TOPRIGHT")
self.incomingHeals:Hide()
self.shield = StatusBar:new(self, width, height, 1, true, texture_bot)
self.absorb = StatusBar:new(self, width, height, 2, true, texture_top)
self.absorb:SetColor(unpack(colors.absorb))
self.absorb:SetFillStyle(Enum.StatusBarFillStyle.Reverse)
self.shield = StatusBar:new(self, width, height, 2, true, texture_bot)
self.shield:SetColor(unpack(colors.shield))
self.shield:SetRange(0, 1)
self.shield:SetValue(0.6)
self.shield:SetFillStyle("REVERSE")
self.shield:SetFillStyle(Enum.StatusBarFillStyle.Reverse)
self.auras = AuraList:new(self)
@@ -182,10 +187,8 @@ function UnitFrame:CreateName()
local name = self.overlays:CreateFontString(nil, "BACKGROUND")
self.name = name
name:SetFont("Interface\\AddOns\\OmicronFrames\\media\\fonts\\roboto\\Roboto-Bold.ttf", 13, "")
name:SetFontObject(NameFont)
name:SetTextColor(0.8, 0.6, 0.1)
name:SetShadowColor(0, 0, 0)
name:SetShadowOffset(1, -1)
name:SetPoint("CENTER")
name:SetText("")
name:Show()
@@ -389,10 +392,9 @@ function UnitFrame:Disable()
if self.hideInRaid then
UnregisterAttributeDriver(self.secureFrame, "state-visibility")
elseif self.unit ~= "player" then
UnregisterUnitWatch(self.secure_frame, self.condition)
else
self.secureFrame:Hide()
UnregisterUnitWatch(self.secureFrame, false)
end
self.secureFrame:Hide()
end
-- returns true if this unit is a target unit
@@ -446,6 +448,7 @@ function UnitFrame:RegisterEvents()
secure:RegisterUnitEvent("UNIT_NAME_UPDATE", unit)
secure:RegisterUnitEvent("UNIT_ABSORB_AMOUNT_CHANGED", unit)
secure:RegisterUnitEvent("UNIT_HEAL_ABSORB_AMOUNT_CHANGED", unit)
secure:RegisterUnitEvent("UNIT_HEAL_PREDICTION", unit)
end
-- returns whether or not the unit guid has changed since the last call to this
@@ -506,6 +509,7 @@ function UnitFrame:UNIT_HEALTH()
end
self:UpdateHealth()
self:UpdateHealthColor()
self:UpdateIncomingHeals()
end
function UnitFrame:UNIT_MAXHEALTH()
@@ -515,6 +519,15 @@ function UnitFrame:UNIT_MAXHEALTH()
end
self:UpdateHealth()
self:UpdateHealthColor()
self:UpdateIncomingHeals()
end
function UnitFrame:UNIT_HEAL_PREDICTION()
if self:HasUnitChanged() then
self:UpdateAll(true)
return
end
self:UpdateIncomingHeals()
end
function UnitFrame:UNIT_TARGET()
@@ -539,10 +552,20 @@ function UnitFrame:UNIT_POWER_UPDATE()
self:UpdatePower()
end
function UnitFrame:UpdateRole(role)
function UnitFrame:GetRole(role)
if role == nil then
role = UnitGroupRolesAssigned(self.unit)
end
if GetPartyAssignment("MAINTANK", self.unit) then
role = "TANK"
end
return role
end
function UnitFrame:UpdateRole(role)
role = self:GetRole(role)
if role == "HEALER" then
self:EnablePower(Enum.PowerType.Mana)
elseif role == "TANK" and select(3, UnitClass(self.unit)) == 6 then
@@ -595,6 +618,7 @@ function UnitFrame:UpdateAll(unitChanged)
end
self:UpdateHealth()
self:UpdateHealthColor()
self:UpdateIncomingHeals()
self:UpdateAbsorb()
self:UpdateShield()
self:UpdateRange()
@@ -650,23 +674,24 @@ function UnitFrame:UpdateHealthColor()
if not isFriend then
self.hp:SetColor(unpack(colors.hostile))
elseif self.auras.statusCount["Immune"] then
self.hp:SetColor(unpack(colors.immune))
elseif self.auras.statusCount["Bomb"] then
self.hp:SetColor(unpack(colors.bomb))
elseif self.auras.statusCount["Magic"] then
self.hp:SetColor(unpack(colors.magic))
elseif self.auras.statusCount["Curse"] then
self.hp:SetColor(unpack(colors.curse))
elseif self.auras.statusCount["Poison"] then
self.hp:SetColor(unpack(colors.poison))
elseif self.auras.statusCount["Disease"] then
self.hp:SetColor(unpack(colors.disease))
elseif isFriend and UnitIsDead(unit) then
return
end
local statusColor = nil
for _, entry in ipairs(self.statusColors) do
if self.auras.statusCount[entry.status] then
statusColor = entry.color
break
end
end
if statusColor then
self.hp:SetColor(unpack(statusColor))
elseif UnitIsDead(unit) then
self.hp:SetColor(unpack(colors.dead))
elseif isFriend and pct >= 0.90 then
elseif pct >= 0.90 then
self.hp:SetColor(unpack(colors.healthy))
elseif isFriend and pct >= 0.75 then
elseif pct >= 0.75 then
local progress = (pct - 0.75) / (0.90 - 0.75)
self.hp:SetInterpolatedColor(colors.mid, colors.high, progress)
else
@@ -691,3 +716,21 @@ function UnitFrame:UpdateName()
end
self.name:SetText(utf8_truncate((UnitName(self.unit)), 5))
end
function UnitFrame:UpdateIncomingHeals()
local incoming = UnitGetIncomingHeals(self.unit) or 0
local health = UnitHealth(self.unit)
local maxHealth = UnitHealthMax(self.unit)
incoming = math.min(incoming, maxHealth - health)
if incoming <= 0 then
self.incomingHeals:Hide()
else
local r, g, b = self.hp:GetColor()
self.incomingHeals:SetColor(r, g, b)
self.incomingHeals:SetAlpha(0.4)
self.incomingHeals:SetRange(0, maxHealth)
self.incomingHeals:SetValue(incoming)
self.incomingHeals:Show()
end
end
+14 -13
View File
@@ -32,9 +32,10 @@ local function RoleValue(role)
end
local function UnitFrameDefaultCompare(lhs, rhs)
-- visible < invisible
if lhs.secureFrame:IsShown() ~= rhs.secureFrame:IsShown() then
return lhs.secureFrame:IsShown()
-- exists < not exists
local lexists, rexists = UnitExists(lhs.unit), UnitExists(rhs.unit)
if lexists ~= rexists then
return lexists
end
local lunit, runit = lhs.unit, rhs.unit
@@ -52,8 +53,8 @@ local function UnitFrameDefaultCompare(lhs, rhs)
end
-- tank < healer < damage < ?
local lrole = RoleValue(UnitGroupRolesAssigned(lunit))
local rrole = RoleValue(UnitGroupRolesAssigned(runit))
local lrole = RoleValue(lhs:GetRole())
local rrole = RoleValue(rhs:GetRole())
if lrole ~= rrole then
return lrole < rrole
@@ -69,7 +70,6 @@ function UnitGroup:Init(left, top, width, height)
self.top = top
self.left = left
self.units = {}
self.sortAfterCombat = false
self:RegisterEvents()
end
@@ -84,6 +84,7 @@ function UnitGroup:RegisterEvents()
frame:RegisterEvent("GROUP_ROSTER_UPDATE")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("UNIT_PET")
end
function UnitGroup:GROUP_ROSTER_UPDATE()
@@ -94,23 +95,23 @@ function UnitGroup:PLAYER_ENTERING_WORLD()
self:OnRosterUpdate()
end
function UnitGroup:UNIT_PET()
self:OnRosterUpdate()
end
function UnitGroup:PLAYER_REGEN_ENABLED()
self:OnLeaveCombat()
end
function UnitGroup:OnLeaveCombat()
if self.sortAfterCombat then
self:Sort()
self.sortAfterCombat = false
end
self:Sort()
end
function UnitGroup:OnRosterUpdate()
if InCombatLockdown() then
self.sortAfterCombat = true
else
self:Sort()
return
end
self:Sort()
end
function UnitGroup:AddUnitFrame(unit)