Compare commits

..
Author SHA1 Message Date
omicron 289a555cee Fix font and status bars to use new API arguments 2026-10-28 18:24:52 +01:00
omicron eaebe470b9 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-06-26 01:25:10 +02:00
omicron 8be0732930 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-06-25 21:58:04 +02:00
omicron 86e5ed78b0 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-06-24 00:37:15 +02:00
omicron 77ae4ddad3 Make UnitGroup trigger sort on UNIT_PET 2026-06-20 00:24:18 +02:00
omicron 5ac84782d2 Add raid pets to the hardcoded configuration 2026-06-20 00:21:25 +02:00
omicron 24a72cb02f 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-03-18 02:41:39 +01:00
omicron 851fb5740b 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-03-18 01:29:43 +01:00
omicron b9d16f5ba2 update config for TBC shaman play 2026-03-18 01:27:07 +01:00
omicron 90d71a54cf temporary enhancement hack: show debuffs in a suboptimal way
In new dungeons you don't really know what debuffs you want to see so
it's hard to configure them. To deal with this I added a quick but
suboptimal feature to show up to 5 debuffs automatically on the unit
frames. It's not great, but it's not terrible either. A hardcoded list
of debuffs to ignore is added too.
2026-03-07 13:21:12 +01:00
omicron abeac379f3 Enhance IconIndicator with tooltips, fix stacks/applications mixup 2026-03-07 13:17:25 +01:00
omicron 763bf0bdae Remove old unused textures for squares 2025-08-24 12:37:22 +02:00
omicron f181c0022c Apply stylua to the entire codebase 2025-08-24 12:28:06 +02:00
omicron 0cced419a6 Make devloop do an initial install on first run 2025-08-24 12:28:06 +02:00
omicron 5bbbde3ca7 Update default configuration
This comprises many different configuration changes over a longer time
for multiple classes.
2025-08-24 12:28:06 +02:00
omicron 6f954b9d15 Add more immunity and burn spells to auralist 2025-08-24 12:28:06 +02:00
omicron c56b847710 Add IconIndicator
Similar functionality to a SquareIndicator but uses an icon instead of a
solid color.
2025-08-24 12:28:06 +02:00
omicron dc62b031a9 Update UnitAuraSlots API for TWW 2025-08-24 12:28:06 +02:00
omicron ec7e2c74ff Update range check API to for TWW 2025-08-24 12:28:06 +02:00
omicron b1dca1639d Properly update unit frames on GROUP_ROSTER_UPDATE 2025-08-24 12:28:06 +02:00
omicron 47542c7147 Expand config with more auras 2025-08-24 12:28:06 +02:00
omicron 1fce6017bd Add bar overlays for absorb and heal absorb 2025-08-24 12:27:56 +02:00
omicron ff9d8b2289 Add/change monk and priest keybinds and auras 2023-11-05 19:20:16 +01:00
omicron 24c571658c Apply auto formatting to all source files 2023-09-10 12:37:25 +02:00
omicron e20887d46b Implement fade and flash behavior for square indicators 2023-07-28 14:15:50 +02:00
omicron 877560cb58 Rework profiler for 10.1.0
Since GetFunctionCPUUsage was removed in 10.1.0 the entire profler
stopped working. This has been replaced by wrapping the functions in a
closure that runs GetTimePreciseSec before and after and keeps track of
the total runtime per function.
2023-07-27 15:29:08 +02:00
omicron b201ef789a Partially fix unicode name truncation.
This fix ensures that name truncation always happens on codepoint
boundaries and that a name has exactly 5 visible codepoints. This should
cover all possible cases unless names can have grapheme clusters, afaik
this is not the case.
2023-07-26 03:15:13 +02:00
omicron 30d8248832 Expand spell ids for default AuraList statuses 2023-05-12 16:51:39 +02:00
omicron 5668a5b4db Fix a bug where AuraList wasn't processing a full update
This fixes issue #8
2023-05-12 15:50:34 +02:00
omicron 263b3f1330 Update default configuration indicators/keybinds/layout 2023-05-12 15:45:54 +02:00
omicron 45129a8635 Square- and BorderIndicator now use trigger data for changing color
If the field color is supplied from trigger data, these indicators will
now change their color. Currently this change is permanent and not bound to the
trigger.
2023-04-14 15:40:39 +02:00
omicron 1f95899781 Make Trigger responsible for default data
Trigger now has a new method Trigger:SetData and two new fields
Trigger.defaultData and Trigger.data

Whenever SetData is used it ensures that all the keys and values from
defaultData are present in the new data.

The data argument from Trigger:SetState has been removed and it will now
always use the Trigger.Data field for notifying indicators
2023-04-14 15:37:57 +02:00
23 changed files with 1521 additions and 293 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
indent_type = "Spaces"
+4
View File
@@ -11,6 +11,10 @@ if [[ ! -d "${WOW_ADDON_DIR}" ]]; then
exit 1 exit 1
fi fi
# Initial build / install
make -s build
rsync -q -a ./build/OmicronFrames "${WOW_ADDON_DIR}/" --delete
# Every time the source or texture directory tree is changed, wait 1 second and # Every time the source or texture directory tree is changed, wait 1 second and
# build the addon, then install it to the directory passed on the command line # build the addon, then install it to the directory passed on the command line
while true; do while true; do
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -17,6 +17,7 @@ types/statusbar.lua
types/auralist.lua types/auralist.lua
types/indicators/indicator.lua types/indicators/indicator.lua
types/indicators/squareindicator.lua types/indicators/squareindicator.lua
types/indicators/iconindicator.lua
types/indicators/borderindicator.lua types/indicators/borderindicator.lua
types/unitgroup.lua types/unitgroup.lua
types/unitframe.lua types/unitframe.lua
+636 -109
View File
@@ -17,29 +17,50 @@
local omif = select(2, ...) local omif = select(2, ...)
local colors = { local colors = {
cyan = {0.0, 0.8, 0.8}, cyan = { 0.0, 0.8, 0.8 },
white = {1.0, 1.0, 1.0}, white = { 1.0, 1.0, 1.0 },
orange = {1.0, 0.5 , 0}, orange = { 1.0, 0.5, 0 },
violet = {1.0, 0, 1.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 },
red = { 1, 0, 0 },
blue = { 0.4, 0.4, 1 },
light_blue = { 0.7, 0.7, 1 },
} }
---@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 function RangeConfig() local function RangeConfig()
local _, class = UnitClass("player") local _, class = UnitClass("player")
if class == "SHAMAN" then if class == "SHAMAN" then
return { return {
friendly = "Healing Surge", friendly = "Healing Wave",
enemy = "Lightning Bolt", enemy = "Lightning Bolt",
fade = 0.2 fade = 0.2,
} }
elseif class == "PRIEST" then elseif class == "PRIEST" then
return { return {
friendly = "Flash Heal", friendly = "Heal",
enemy = "Smite", enemy = "Smite",
fade = 0.2 fade = 0.2,
} }
else else
return { return {
fade = 0.2 fade = 0.2,
} }
end end
end end
@@ -49,142 +70,641 @@ local function MouseConfig()
if class == "SHAMAN" then if class == "SHAMAN" then
return { return {
-- No modifier -- No modifier
{button="mouse1", mods={}, kind="target"}, { button = "mouse1", mods = {}, kind = "target" },
{button="mouse2", mods={}, kind="macro", data="/use [@UNIT,dead,help]Ancestral Vision; [@UNIT,help]Chain Heal"}, {
{button="mouse3", mods={}, kind="macro", data="/use [@UNIT,dead,help]Ancestral Spirit; [@UNIT,help]Purify Spirit"}, button = "mouse2",
{button="wheel-up", mods={}, kind="macro", data="/use [@UNIT,help]Healing Surge"}, mods = {},
{button="wheel-down", mods={}, kind="macro", data="/use [@UNIT,help]Riptide"}, kind = "macro",
data = "/use [@UNIT,dead,help]Ancestral Spirit; [@UNIT,help]Chain Heal",
},
{
button = "mouse3",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Cure Poison",
},
{
button = "wheel-up",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Lesser Healing Wave",
},
{
button = "wheel-down",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Healing Wave",
},
-- alt -- 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-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 -- Shift
{button="mouse2", mods={shift=true}, kind="togglemenu"}, { button = "mouse2", mods = { shift = true }, kind = "togglemenu" },
{
button = "wheel-up",
mods = { shift = true },
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 elseif class == "PRIEST" then
return { return {
-- No modifier -- No modifier
{button="mouse1", mods={}, kind="target"}, { button = "mouse1", mods = {}, kind = "target" },
{button="mouse2", mods={}, kind="macro", data="/use [@UNIT,dead,help]Mass Resurrection; [@UNIT,help]Power Word: Radiance"}, {
{button="mouse3", mods={}, kind="macro", data="/use [@UNIT,dead,help]Resurrection; [@UNIT,help]Purify"}, button = "mouse2",
{button="mouse4", mods={}, kind="spell", data="Shadow Covenant"}, mods = {},
{button="wheel-up", mods={}, kind="macro", data="/use [@UNIT,help]Flash Heal"}, kind = "macro",
{button="wheel-down", mods={}, kind="macro", data="/use [@UNIT,help]Renew"}, data = "/use [@UNIT,dead,help]Mass Resurrection; [@UNIT,help]Power Word: Radiance",
},
{
button = "mouse3",
mods = {},
kind = "macro",
data = "/use [@UNIT,dead,help]Resurrection; [@UNIT,help]Purify",
},
{ button = "mouse4", mods = {}, kind = "spell", data = "Shadow Covenant" },
{
button = "wheel-up",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Lesser Heal",
},
{ button = "wheel-down", mods = {}, kind = "macro", data = "/use [@UNIT,help]Heal" },
-- alt -- alt
{button="mouse2", mods={alt=true}, kind="spell", data="Pain Suppression"}, { button = "mouse1", mods = { alt = true }, kind = "spell", data = "Power Word: Life" },
{button="mouse3", mods={alt=true}, kind="spell", data="Power Infusion"}, { button = "mouse2", mods = { alt = true }, kind = "spell", data = "Pain Suppression" },
{button="wheel-up", mods={alt=true}, kind="spell", data="Rapture"}, { button = "mouse3", mods = { alt = true }, kind = "spell", data = "Power Infusion" },
{button="wheel-down", mods={alt=true}, kind="spell", data="Power Word: Shield"}, { button = "wheel-up", mods = { alt = true }, kind = "spell", data = "Renew" },
{ button = "wheel-down", mods = { alt = true }, kind = "spell", data = "Power Word: Shield" },
-- Shift -- Shift
{button="mouse2", mods={shift=true}, kind="togglemenu"}, { button = "mouse2", mods = { shift = true }, kind = "togglemenu" },
{button="wheel-up", mods={shift=true}, kind="macro", data="/cast [@UNIT,help]Levitate;\n/stopspelltarget"}, {
{button="wheel-down", mods={shift=true}, kind="macro", data="/cast [@UNIT,help]Leap of Faith;\n/stopspelltarget"}, button = "wheel-up",
mods = { shift = true },
kind = "macro",
data = "/cast [@UNIT,help]Levitate;\n/stopspelltarget",
},
{
button = "wheel-down",
mods = { shift = true },
kind = "macro",
data = "/cast [@UNIT,help]Leap of Faith;\n/stopspelltarget",
},
}
elseif class == "MONK" then
return {
-- No modifier
{ button = "mouse1", mods = {}, kind = "target" },
{
button = "mouse2",
mods = {},
kind = "macro",
data = (
"/use [@UNIT,known:Reawaken,dead,help]Reawaken; "
.. "[@UNIT,dead,help]Resuscitate; "
.. "[@UNIT,help]Tiger's Lust"
),
},
{
button = "mouse3",
mods = {},
kind = "macro",
data = "/use [@UNIT,dead,help]Resuscitate; [@UNIT,help]Detox",
},
{ button = "mouse4", mods = {}, kind = "spell", data = "Shadow Covenant" },
{ button = "wheel-up", mods = {}, kind = "macro", data = "/use [@UNIT,help]Vivify" },
{
button = "wheel-down",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Soothing Mist",
},
-- 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-down", mods = { alt = true }, kind = "spell", data = "Power Word: Shield" },
-- Shift
{ button = "mouse2", mods = { shift = true }, kind = "togglemenu" },
{
button = "wheel-up",
mods = { shift = true },
kind = "macro",
data = "/cast [@UNIT,help]Levitate;\n/stopspelltarget",
},
{
button = "wheel-down",
mods = { shift = true },
kind = "macro",
data = "/cast [@UNIT,help]Leap of Faith;\n/stopspelltarget",
},
}
elseif class == "PALADIN" then
return {
-- No modifier
{ button = "mouse1", mods = {}, kind = "target" },
{
button = "mouse2",
mods = {},
kind = "macro",
data = (
"/use [@UNIT,combat,dead,help]Intercession; "
.. "[@UNIT,known:Absolution,dead,help]Absolution; "
.. "[@UNIT,dead,help]Redemption; "
.. "[@UNIT,help]Blessing of Freedom"
),
},
{
button = "mouse3",
mods = {},
kind = "macro",
data = "/use [@UNIT,dead,help]Redemption; [@UNIT,help]Cleanse Toxins",
},
{
button = "mouse4",
mods = {},
kind = "spell",
data = "Word of Glory",
},
{
button = "mouse5",
mods = {},
kind = "spell",
data = "Blessing of Summer",
},
{
button = "wheel-up",
mods = {},
kind = "macro",
data = "/use [@UNIT,help]Flash of Light",
},
{
button = "wheel-down",
mods = {},
kind = "macro",
data = "/use [@UNIT,help,known:Holy Shock]Holy Shock; [@UNIT,help]Word of Glory",
},
-- Shift
{ button = "mouse2", mods = { shift = true }, kind = "togglemenu" },
-- alt
{ button = "mouse1", mods = { alt = true }, kind = "spell", data = "Blessing of Sacrifice" },
{ button = "mouse2", mods = { alt = true }, kind = "spell", data = "Blessing of Protection" },
{ button = "mouse3", mods = { alt = true }, kind = "spell", data = "Blessing of Spellwarding" },
{ button = "wheel-up", mods = { alt = true }, kind = "spell", data = "Holy Light" },
{ button = "wheel-down", mods = { alt = true }, kind = "spell", data = "Lay on Hands" },
} }
else else
return { return {
-- Super basic defaults -- Super basic defaults
{button="mouse1", mods={}, kind="target"}, { button = "mouse1", mods = {}, kind = "target" },
{button="mouse2", mods={shift=true}, kind="togglemenu"}, { button = "mouse2", mods = { shift = true }, kind = "togglemenu" },
} }
end end
end end
local function TriggerConfig() local function TriggerClassConfig()
local _, class = UnitClass("player") local _, class = UnitClass("player")
if class == "SHAMAN" then if class == "SHAMAN" then
return { return {
{ {
kind="AuraTrigger", spellId=383648, own=true, -- Second Earth Shield on yourself kind = "AuraTrigger",
indicator={ spellIds = set({ 32594 }),
kind="SquareIndicator", own = true, -- Second Earth Shield on yourself
size=14, indicator = {
point="TOPLEFT", kind = "SquareIndicator",
x=2, y=-2, size = 17,
color=colors.white, point = "TOPLEFT",
} x = 3,
y = -3,
color = colors.white,
showStacks = true,
},
}, },
{ {
kind="AuraTrigger", spellId=974, own=true, -- Second Earth Shield on yourself kind = "AuraTrigger",
indicator={ spellIds = set({ 29203 }),
kind="SquareIndicator", own = true, -- Healing Way
size=14, indicator = {
point="TOPLEFT", kind = "SquareIndicator",
x=2, y=-2, size = 17,
color=colors.white, point = "TOPLEFT",
} x = 20,
y = -3,
color = colors.yellow,
showStacks = true,
},
}, },
{ {
kind="AuraTrigger", spellId=61295, own=true, -- Riptide kind = "AuraTrigger",
indicator={ spellIds = set({ 16237 }),
kind="SquareIndicator", own = true, -- Ancestral Fortitude
size=14, indicator = {
point="BOTTOMLEFT", kind = "SquareIndicator",
x=2, y=2, size = 17,
color=colors.cyan, point = "TOPLEFT",
} x = 40,
y = -3,
color = colors.orange,
showStacks = false,
},
},
{
kind = "StatusTrigger",
status = "Burn",
invert = false,
indicator = {
kind = "BorderIndicator",
thickness = 3.0,
color = colors.red,
level = 0,
},
}, },
} }
elseif class == "PRIEST" then elseif class == "PRIEST" then
return { return {
{ {
kind="AuraTrigger", spellId=17, own=true, -- Power Word: Shield kind = "AuraTrigger",
indicator={ spellIds = AURAS_PRIEST_PWS,
kind="SquareIndicator", own = true, -- Power Word: Shield
size=14, indicator = {
point="TOPLEFT", kind = "SquareIndicator",
x=2, y=-2, size = 17,
color=colors.white, point = "TOPLEFT",
} x = 3,
y = -3,
color = colors.white,
fadeTime = 10.0,
},
}, },
{ {
kind="AuraTrigger", spellId=33206, own=true, -- Pain Suppression kind = "AuraTrigger",
indicator={ spellIds = set({ 33206 }),
kind="SquareIndicator", own = true, -- Pain Suppression
size=14, indicator = {
point="TOPLEFT", kind = "SquareIndicator",
x=18, y=-2, size = 17,
color=colors.orange, point = "TOPLEFT",
} x = 22,
y = -3,
color = colors.orange,
fadeTime = 10.0,
flashTime = 1.3,
},
}, },
{ {
kind="AuraTrigger", spellId=139, own=true, -- Renew kind = "AuraTrigger",
indicator={ spellIds = set({ 41635 }),
kind="SquareIndicator", own = true, -- Prayer of Mending
size=14, indicator = {
point="BOTTOMLEFT", kind = "SquareIndicator",
x=2, y=2, size = 17,
color=colors.cyan, point = "TOPLEFT",
} x = 41,
y = -3,
color = { 0.7, 0.9, 1 },
showStacks = true,
},
}, },
{ {
kind="AuraTrigger", spellId=194384, own=true, -- Renew kind = "AuraTrigger",
indicator={ spellIds = AURAS_PRIEST_RENEW,
kind="SquareIndicator", own = true, -- Renew
size=14, indicator = {
point="BOTTOMLEFT", kind = "SquareIndicator",
x=18, y=2, size = 17,
color=colors.white, point = "BOTTOMLEFT",
} x = 3,
y = 3,
color = colors.cyan,
fadeTime = 10.0,
},
}, },
{ {
kind="AuraTrigger", spellId=10060, own=true, -- Renew kind = "AuraTrigger",
indicator={ spellIds = set({ 194384 }),
kind="SquareIndicator", own = true, -- Atonement
size=14, indicator = {
point="BOTTOMLEFT", kind = "SquareIndicator",
x=34, y=2, size = 17,
color=colors.violet, point = "BOTTOMLEFT",
x = 22,
y = 3,
color = colors.white,
fadeTime = 10.0,
flashTime = 1.3,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 10060 }),
own = true, -- Power Infusion
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 41,
y = 3,
color = colors.violet,
fadeTime = 10.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",
spellIds = set({ 287280 }), -- Glimmer of Light
own = true,
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 3,
y = -3,
color = colors.white,
fadeTime = 10.0,
flashTime = 1.3,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 25771 }), -- Forbearance
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 22,
y = -3,
color = colors.red,
fadeTime = 10.0,
flashTime = 1.3,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 53563 }), -- Beacon of Light
own = true,
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 41,
y = -3,
color = colors.white,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 156910 }), -- Beacon of Faith
own = true,
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 41,
y = -3,
color = colors.light_blue,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 200025 }), -- Beacon of Virtue
own = true,
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 41,
y = -3,
color = colors.white,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 1044 }), -- Blessing of Freedom
own = true,
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 3,
y = 3,
color = colors.orange,
flashTime = 1.3,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 1111 }), -- Blessing of Spellwarding
own = true,
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 41,
y = 3,
color = colors.cyan,
flashTime = 1.3,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 1022 }), -- Blessing of Protection
own = true,
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 41,
y = 3,
color = colors.cyan,
flashTime = 1.3,
},
},
{
kind = "AuraTrigger",
spellIds = set({ 6940 }), -- Blessing of Saccrifice
own = true,
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 61,
y = 3,
color = colors.red,
flashTime = 1.3,
},
},
{
kind = "StatusTrigger",
status = "Burn",
invert = false,
indicator = {
kind = "BorderIndicator",
thickness = 3.0,
color = colors.red,
level = 0,
},
}, },
} }
else else
return {} return {
{
kind = "StatusTrigger",
status = "Burn",
invert = false,
indicator = {
kind = "BorderIndicator",
thickness = 3.0,
color = colors.red,
level = 0,
},
},
}
end end
end end
-- Quick helper function to create a config for debuff triggers
local function MakeDebuffTrigger(slot, spellid, color, stacks)
if stacks == nil then
stacks = false
end
return {
kind = "AuraTrigger",
spellIds = set({ spellid }),
indicator = {
kind = "SquareIndicator",
size = 15,
point = "TOPRIGHT",
x = -17 * slot,
y = -3,
color = color,
showStacks = stacks,
},
}
end
local function TriggerConfig()
local triggers = TriggerClassConfig()
print("Class triggers:", #triggers)
-- slot, spellid, color, stacks
local debuffs = {
-- Kezzara, Aberrus
{ 1, 406525, colors.orange }, -- Dread Rift
{ 2, 402253, colors.red }, -- Ray of Anguish
{ 3, 404743, colors.violet, true }, -- Terror Claws
-- Forgotten Experiment, Aberrus
{ 1, 406365, colors.red, true }, -- Rending Charge (p1)
{ 1, 407327, colors.cyan, true }, -- Unstable Essence (p2/p3)
{ 3, 407313, colors.violet, true }, -- Infused Strikes
-- Uldaman: Legacy of Tyr
{ 1, 377510, colors.cyan, true }, -- Stolen Time
-- Brackenhide Hollow
{ 1, 367521, colors.cyan, true }, -- Bone Bolt
-- The Underrot
--{1, 273226, colors.green, true}, -- Decaying Spores
-- Sarkareth, Aberrus
{ 3, 401330, colors.violet }, -- Burning Claws (P1 tank)
{ 3, 411241, colors.violet }, -- Void Claws (P2 tank)
{ 3, 408429, colors.violet }, -- Void Slash (P3 tank)
{ 2, 404218, colors.blue }, -- Void Fracture (carry bomb)
{ 1, 401951, colors.cyan, true }, -- Oblivion
-- Darkheart Thicket
{ 3, 225484, colors.violet }, -- Grievous Rip
{ 3, 196376, colors.violet }, -- Grievous Tear
-- Throne of the Tides
{ 1, 426660, colors.red, true }, -- Razor Jaws
-- Nymue, Amirdrassil
{ 1, 429785, colors.red }, -- Loom (line stun)
{ 1, 417807, colors.red, true }, -- Fyrakk
-- Algeth'ar Academy
{ 1, 389033, colors.poison, true },
{ 1, 388912, colors.red, true },
{ 2, 391977, colors.cyan, true },
{ 2, 389011, colors.cyan, true },
-- Nokhud Offensive
{ 1, 381692, colors.red, true },
-- Tazavesh
{ 1, 1240102, colors.orange, true },
-- Priory of the Sacred Flame
{ 1, 424414, colors.violet, true },
}
for _, debuff in ipairs(debuffs) do
table.insert(triggers, MakeDebuffTrigger(debuff[1], debuff[2], debuff[3], debuff[4]))
end
print("All triggers:", #triggers)
-- Some more debuffs in the bottom row
table.insert(triggers, {
kind = "AuraTrigger",
spellIds = set({ 240443 }), -- Bursting
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMRIGHT",
x = -3,
y = 3,
color = colors.red,
fadeTime = 3.0,
showStacks = true,
},
})
return triggers
end
local types = omif.GetModule("types") local types = omif.GetModule("types")
local UnitFrame = types.UnitFrame local UnitFrame = types.UnitFrame
local UnitGroup = types.UnitGroup local UnitGroup = types.UnitGroup
@@ -198,7 +718,7 @@ function HideBlizzardFrames()
TargetFrame, TargetFrame,
FocusFrame, FocusFrame,
PartyFrame, PartyFrame,
CompactRaidFrameContainer CompactRaidFrameContainer,
} }
for _, frame in ipairs(blizzardFrames) do for _, frame in ipairs(blizzardFrames) do
@@ -212,11 +732,13 @@ function CreateRaidFrames(left, top, config)
local width = config.size.width local width = config.size.width
local height = config.size.height local height = config.size.height
local group = UnitGroup:new(left, top, width, height) local group = UnitGroup:new(left, top, width, height)
for i=0,5 do for i = 0, 5 do
for j=0,4 do for j = 0, 4 do
local num = i*5 + j + 1 local num = i * 5 + j + 1
local frame = UnitFrame:new("raid" .. num, config) local frame = UnitFrame:new("raid" .. num, config)
group:AddUnitFrame(frame) group:AddUnitFrame(frame)
local raidpet = UnitFrame:new("raidpet" .. num, config)
group:AddUnitFrame(raidpet)
end end
end end
end end
@@ -227,9 +749,13 @@ function CreatePartyFrames(left, top, config)
local group = UnitGroup:new(left, top, width, height) local group = UnitGroup:new(left, top, width, height)
local player = UnitFrame:new("player", config) local player = UnitFrame:new("player", config)
group:AddUnitFrame(player) group:AddUnitFrame(player)
for i=1,4 do local pet = UnitFrame:new("pet", config)
group:AddUnitFrame(pet)
for i = 1, 4 do
local frame = UnitFrame:new("party" .. i, config) local frame = UnitFrame:new("party" .. i, config)
group:AddUnitFrame(frame) group:AddUnitFrame(frame)
local partypet = UnitFrame:new("partypet" .. i, config)
group:AddUnitFrame(partypet)
end end
group:Sort() group:Sort()
end end
@@ -241,16 +767,16 @@ function CreateTargetFrames(left, top, config)
focus:SetPosition(left, top) focus:SetPosition(left, top)
local target = UnitFrame:new("target", config) local target = UnitFrame:new("target", config)
target:SetPosition(left, top - height) target:SetPosition(left, top - height)
for i=1,4 do for i = 1, 4 do
local boss = UnitFrame:new("boss" .. i, config) local boss = UnitFrame:new("boss" .. i, config)
boss:SetPosition(left, top-(i+1)*height) boss:SetPosition(left, top - (i + 1) * height)
end end
end end
function CreateFrames() function CreateFrames()
config = { local config = {
size = { size = {
width = 110, width = 111,
height = 45, height = 45,
}, },
range = RangeConfig(), range = RangeConfig(),
@@ -259,10 +785,11 @@ function CreateFrames()
triggers = TriggerConfig(), triggers = TriggerConfig(),
} }
CreatePartyFrames(0, -290, config) CreatePartyFrames(-0.05, -240.05, config)
config.hideInRaid = false config.hideInRaid = false
CreateRaidFrames(0, -290, config) CreateRaidFrames(-0.05, -240.05, config)
CreateTargetFrames(110*3+50, -245, config) CreateTargetFrames(110 * 3 + 50 - 0.05, -240.05, config)
HideBlizzardFrames() HideBlizzardFrames()
end end
omif.SetEventHandler("OMICRON_LOADING", CreateFrames) omif.SetEventHandler("OMICRON_LOADING", CreateFrames)
+3 -1
View File
@@ -19,7 +19,7 @@ omif.events = {} -- event (str) to list of handlers
omif.db = nil omif.db = nil
OmicronFrames = {} OmicronFrames = {}
omif.modules = {public=OmicronFrames} omif.modules = { public = OmicronFrames }
-- Simple function that creates or returns a local module table -- Simple function that creates or returns a local module table
function omif.GetModule(name) function omif.GetModule(name)
if omif.modules[name] == nil then if omif.modules[name] == nil then
@@ -66,6 +66,7 @@ function omif.OnAddonLoaded(name)
omif.MainEventHandler(nil, "OMICRON_LOADING") omif.MainEventHandler(nil, "OMICRON_LOADING")
print("Loaded", AddonName) print("Loaded", AddonName)
end end
omif.SetEventHandler("ADDON_LOADED", omif.OnAddonLoaded) omif.SetEventHandler("ADDON_LOADED", omif.OnAddonLoaded)
-- Fire custom event OMICRON_FULLY_LOADED. This event is fired when the player -- Fire custom event OMICRON_FULLY_LOADED. This event is fired when the player
@@ -75,6 +76,7 @@ function omif.FireFullyLoadedEvent(initialLogin, reloadUI)
omif.MainEventHandler(nil, "OMICRON_FULLY_LOADED") omif.MainEventHandler(nil, "OMICRON_FULLY_LOADED")
end end
end end
omif.SetEventHandler("PLAYER_ENTERING_WORLD", omif.FireFullyLoadedEvent) omif.SetEventHandler("PLAYER_ENTERING_WORLD", omif.FireFullyLoadedEvent)
function omif.SlashCommand(args) function omif.SlashCommand(args)
+92 -31
View File
@@ -26,56 +26,117 @@ local Printf = Commander.Printf
local types = omi.GetModule("types") local types = omi.GetModule("types")
local data = {} local data = {}
local startTime
local stopTime
local functionTimers = {}
local functionNames = {}
Commander.RegisterCommand("omi-pstart", { -- Permanently wrap all functions to do profiling
description="Start/reset the OmicronFrames profiler", local function EnableFunctionProfiler()
command=function() if #functionTimers ~= 0 then
local profiler = C_CVar.GetCVar("scriptProfile")
if profiler ~= "1" then
PrintLn("scriptProfiler is off")
PrintLn("set it to on with `/console scriptProfile 1`, then reload the UI.")
return return
end end
PrintLn("OmicronFrames: start profiling") local GetTimePreciseSec = GetTimePreciseSec
ResetCPUUsage() local counter = 1
for typeName, T in pairs(types) do
if type(T) == "table" then
PrintLn("Type:", typeName, T)
for fnName, fn in pairs(T) do
if type(fn) == "function" then
local fnIdx = counter
counter = counter + 1
local name = string.format("%s:%s", typeName, fnName)
functionTimers[fnIdx] = 0
functionNames[fnIdx] = name
local wrapped = function(...)
local start = GetTimePreciseSec()
local a, b, c, d, e, f, g, h = fn(...)
local elapsed = GetTimePreciseSec() - start
functionTimers[fnIdx] = functionTimers[fnIdx] + elapsed
return a, b, c, d, e, f, g, h
end end
T[fnName] = wrapped
end
end
end
end
end
-- Set all function timers back to 0
local function ResetFunctionProfiler()
for i, _ in ipairs(functionTimers) do
functionTimers[i] = 0
end
end
Commander.RegisterCommand("omi-pstart", {
description = "Start/reset the OmicronFrames profiler",
command = function()
local profiler = C_CVar.GetCVar("scriptProfile")
PrintLn("OmicronFrames: start profiling")
EnableFunctionProfiler()
ResetFunctionProfiler()
startTime = GetTimePreciseSec()
stopTime = nil
if profiler == "1" then
ResetCPUUsage()
else
PrintLn("WoW scriptProfiler is off. Addon totals will not be shown,")
PrintLn("only individual functions.The profiler will not show total")
PrintLn("addon time, only function profiler.")
PrintLn("")
PrintLn("To enable the script profiler run `/console scriptProfile 1`, then reload the UI.")
return
end
end,
}) })
Commander.RegisterCommand("omi-pstop", { Commander.RegisterCommand("omi-pstop", {
description="Stop the OmicronFrames profiler", description = "Stop the OmicronFrames profiler",
command=function() command = function()
if not startTime then
PrintLn("Profiling not started. Run omi-pstart first.")
return
end
PrintLn("OmicronFrames: stop profiling") PrintLn("OmicronFrames: stop profiling")
local profiler = C_CVar.GetCVar("scriptProfile") == "1"
data = {}
stopTime = GetTimePreciseSec()
if profiler then
UpdateAddOnCPUUsage() UpdateAddOnCPUUsage()
local total = GetAddOnCPUUsage(addonName) local total = GetAddOnCPUUsage(addonName)
data = {{ table.insert(data, {
name = "Total OmicronFrames time", name = "Total OmicronFrames time",
time = total, time = total,
pct = 1.0
}}
for typeName, T in pairs(types) do
if type(T) == "table" then
for fnName, fn in pairs(T) do
if type(fn) == "function" then
local time = GetFunctionCPUUsage(fn, true)
table.insert(data, {
name = string.format("%s:%s", typeName, fnName),
time = time,
pct = time/total
}) })
end end
for i, time in ipairs(functionTimers) do
local name = functionNames[i]
table.insert(data, {
name = name,
time = time * 1000,
})
end end
end table.sort(data, function(a, b)
end return a.time < b.time
table.sort(data, function(a, b) return a.time > b.time end) end)
end, end,
}) })
Commander.RegisterCommand("omi-pprint", { Commander.RegisterCommand("omi-pprint", {
description="Print the collected OmicronFrames profiling data", description = "Print the collected OmicronFrames profiling data",
command=function() command = function()
if not stopTime then
PrintLn("Profiling data not collected yet. Run omi-pstop first.")
return
end
local top = data[#data].time
local total = stopTime - startTime
for _, item in ipairs(data) do for _, item in ipairs(data) do
Printf("% 5.1f%% % 5fms %s\n", item.pct*100, item.time, item.name) local time = item.time
end local pct = time / top * 100
local ms_per_sec = time / total
Printf("% 6.1f%% % 7.2fms %7.2fms/s %s\n", pct, time, ms_per_sec, item.name)
end end
end,
}) })
+136 -23
View File
@@ -16,6 +16,8 @@
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>. -- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...) local omi = select(2, ...)
local types = omi.GetModule("types") local types = omi.GetModule("types")
local AuraTrigger = types.AuraTrigger
local StatusTrigger = types.StatusTrigger
--- AuraList keeps track of all the auras attached to a unitframe --- AuraList keeps track of all the auras attached to a unitframe
---@class AuraList: Object ---@class AuraList: Object
@@ -25,26 +27,118 @@ types.AuraList = AuraList
local statusLists = { local statusLists = {
Immune = { Immune = {
[642] = true, -- Divine Shield, Paladin [642] = true, -- Divine Shield, Paladin
[186265] = true -- Aspect of the Turtle, Hunter [186265] = true, -- Aspect of the Turtle, Hunter
[45438] = true, -- Ice Block
[31224] = true, -- Cloak of Shadows
[196555] = true, -- Netherwalk
}, },
Bomb = { Bomb = {
[642] = true, -- Divine Shield, Paladin
[186265] = true, -- Aspect of the Turtle, Hunter
[381615] = true, -- Raszageth, Static Charge [381615] = true, -- Raszageth, Static Charge
[377467] = true -- Raszageth, Fulminating Charge [377467] = true, -- Raszageth, Fulminating Charge
[399713] = true, -- Raszageth, Magnetic Charge
[397797] = true, -- Corrupted Vortex, Jade Serpent Temple
[387843] = true, -- Astral Bomb, Algeth'ar Academy
[386181] = true, -- Mana Bomb, Algeth'ar Academy
[401330] = true, -- Sarkareth, Burning Claws (p1 tank)
[411241] = true, -- Sarkareth, Void Claws (p2 tank)
[408429] = true, -- Sarkareth, Void Slash (p3 tank)
[404218] = true, -- Sarkareth, Void fracture (bombs)
[427722] = true, -- Nymue, Weaver's Burden (hehe jk it's private because reasons)
}, },
Burn = { Burn = {
[642] = true, -- Divine Shield, Paladin -- Jade Serpent Temple
[186265] = true -- Aspect of the Turtle, Hunter [114803] = true, -- Throw Torch
} [397914] = true, -- Defiling Mist
[106114] = true, -- Touch of Nothingness
-- Shadow moon Burial Ground
[152819] = true, -- Shadow Word: Frailty
[153524] = true, -- Plague Spit
-- Court of Stars
[397907] = true, -- Impending Doom
[209516] = true, -- Mana Fang
[207980] = true, -- Disintegration Beam
[211464] = true, -- Fel Detonation
-- Algeth'ar Academy
[376997] = true, -- Savage Peck, Algeth'ar Academy
[388912] = true, -- Severing Slash, Algeth'ar Academy
[388866] = true, -- Mana Void, Algeth'ar Academy
-- Vault of the Incarnates
[390911] = true, -- Raszageth, Lingering Charge
-- Atal'Dazar
[255582] = true, -- Priestess Alun'za, Molten Gold
[250096] = true, -- Yazma, Wracking pain
[255434] = true, -- Rezan, Serrated Teeth
-- The MOTHERLODE!!
[259853] = true, -- Rixxa Fluxflame, Chemical Burn
-- Halls of Infusion
-- Uldaman: Legacy of Tyr
[372718] = true, -- Earthen Shards
-- Brackenhide Hollow
[367521] = true, -- Bone Bolt
[367484] = true, -- Vicious Clawmangle
[378020] = true, -- Gash Frenzy
[385058] = true, -- Withering Poison
[384575] = true, -- Crippling Bite
-- Neltharus
[373735] = true, -- Dragon Strike
[372224] = true, -- Draogonbone Axe
[377018] = true, -- Molten Gold
[372570] = true, -- Bold Ambush
-- Neltharion's Lair
-- Freehold
[413131] = true, -- Whirling Dagger
-- The Underrot
[265019] = true, -- Savage Cleave
[265568] = true, -- Dark Omen
[273226] = true, -- Decaying Spores
-- Vortex Pinnacle
-- Aberrus
[404010] = true, -- Zkarn, ???
[405462] = true, -- Zkarn, ???
-- Dawn of the infinite: Galakrond's fall
[407406] = true,
-- Darkheart Thicket
-- Amirdrassil
[427721] = true, -- Nymue, Weaver's burden (dot part)
-- Waycrest Manor
[263943] = true, -- Etch
[264378] = true, -- Fragment Soul
-- Ruby Lifepools
[381862] = true, -- Kyrakka, Infernocore
-- Nokhud Offensive
[381692] = true, -- Swift Stab
-- Priory of the Sacred Flame
[447439] = true, -- Savage Mauling
},
} }
-- helper function that goes over several aura slots for a unit. Runs a -- helper function that goes over several aura slots for a unit. Runs a
-- callback function with UnitAuraInfo table as argument -- callback function with UnitAuraInfo table as argument
local function ForEachAuraSlots(unit, fn, continuationToken, ...) local function ForEachAuraSlots(unit, fn, continuationToken, ...)
local GetAuraDataBySlot = C_UnitAuras.GetAuraDataBySlot local GetAuraDataBySlot = C_UnitAuras.GetAuraDataBySlot
local n = select('#', ...) local n = select("#", ...)
for i=1, n do for i = 1, n do
local slot = select(i, ...) local slot = select(i, ...)
fn(GetAuraDataBySlot(unit, slot)) fn(GetAuraDataBySlot(unit, slot))
end end
@@ -53,11 +147,11 @@ end
-- Helper function that goes over all aura slots for a given filter -- Helper function that goes over all aura slots for a given filter
local function ForEachAuraFiltered(unit, filter, fn) local function ForEachAuraFiltered(unit, filter, fn)
local UnitAuraSlots = UnitAuraSlots local UnitAuraSlots = C_UnitAuras.GetAuraSlots
local continuationToken = nil local continuationToken = nil
repeat repeat
continuationToken = ForEachAuraSlots(unit, fn, UnitAuraSlots(unit, filter, nil, continuationToken)) continuationToken = ForEachAuraSlots(unit, fn, UnitAuraSlots(unit, filter, nil, continuationToken))
until (continuationToken == nil) until continuationToken == nil
end end
-- Similar to what AuraUtils.ForEachAura does except the callback fn takes a -- Similar to what AuraUtils.ForEachAura does except the callback fn takes a
@@ -67,7 +161,6 @@ local function ForEachAura(unit, fn)
ForEachAuraFiltered(unit, "HARMFUL", fn) ForEachAuraFiltered(unit, "HARMFUL", fn)
end end
---@return AuraList ---@return AuraList
function AuraList.new(cls, ...) function AuraList.new(cls, ...)
--- I really dislike duplicating this everywhere but it makes --- I really dislike duplicating this everywhere but it makes
@@ -84,27 +177,40 @@ function AuraList:Init(unitframe)
self.statusCount = {} self.statusCount = {}
self.auras = {} -- map AuraInstanceID to UnitAuraInfo table self.auras = {} -- map AuraInstanceID to UnitAuraInfo table
self.triggers = {} self.triggers = {}
self.triggersBySpell = {} self.triggersBySpell = {}
self.triggersByStatus = {} self.triggersByStatus = {}
end end
---@param trigger AuraTrigger | StatusTrigger ---@param trigger StatusTrigger
function AuraList:AddTrigger(trigger) function AuraList:AddStatusTrigger(trigger)
table.insert(self.triggers, trigger) table.insert(self.triggers, trigger)
local spellId = trigger.spellId
local status = trigger.status local status = trigger.status
if spellId then
if self.triggersBySpell[spellId] == nil then
self.triggersBySpell[spellId] = {}
end
table.insert(self.triggersBySpell[spellId], trigger)
else
if self.triggersByStatus[status] == nil then if self.triggersByStatus[status] == nil then
self.triggersByStatus[status] = {} self.triggersByStatus[status] = {}
end end
table.insert(self.triggersByStatus[status], trigger) 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
error("AddTrigger: trigger is neither an AuraTrigger nor a StatusTrigger")
end end
end end
@@ -179,7 +285,7 @@ end
---@param aura UnitAuraInfo ---@param aura UnitAuraInfo
function AuraList:UpdateAura(aura) function AuraList:UpdateAura(aura)
for _, trigger in ipairs(self.triggersBySpell[aura.spellId] or {}) do 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 end
self.auras[aura.auraInstanceID] = aura self.auras[aura.auraInstanceID] = aura
end end
@@ -213,8 +319,15 @@ function AuraList:RemoveAura(iid)
return statusChanged return statusChanged
end end
-- FIXME: param wrong
---@param info UnitAuraInfo ---@param info UnitAuraInfo
function AuraList:Update(info) function AuraList:Update(info)
if info.isFullUpdate then
self:Reset()
-- FIXME: return value might not reflect truth but true is the more prudent choice
return true
end
local GetAuraDataByAuraInstanceID = C_UnitAuras.GetAuraDataByAuraInstanceID local GetAuraDataByAuraInstanceID = C_UnitAuras.GetAuraDataByAuraInstanceID
local statusChanged = false local statusChanged = false
for _, aura in ipairs(info.addedAuras or {}) do for _, aura in ipairs(info.addedAuras or {}) do
+10 -2
View File
@@ -70,17 +70,26 @@ end
--- Show the square indicator. --- Show the square indicator.
---@param data table ---@param data table
function BorderIndicator:Show(data) function BorderIndicator:Show(data)
if data.color then
self.color = data.color
end
local frame, stacks = self:GetFrame() local frame, stacks = self:GetFrame()
frame:Show() frame:Show()
end end
function BorderIndicator:Update(data)
if data.color then
self:SetColor(data.color)
end
end
function BorderIndicator:GetFrame() function BorderIndicator:GetFrame()
local frame, new = borderPool:Acquire() local frame, new = borderPool:Acquire()
if new then if new then
frame.backdropInfo = { frame.backdropInfo = {
edgeFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\pixel_edge", edgeFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\pixel_edge",
edgeSize = 1, edgeSize = 1,
insets = {left=0, right=0, top=0, bottom=0}, insets = { left = 0, right = 0, top = 0, bottom = 0 },
} }
end end
@@ -99,4 +108,3 @@ function BorderIndicator:Hide()
self.frame = nil self.frame = nil
borderPool:Release(frame) borderPool:Release(frame)
end end
+215
View File
@@ -0,0 +1,215 @@
-- Copyright 2025 <omicron.me@protonmail.com>
--
-- This file is part of Omicron Frames
--
-- Omicron Frames is free software: you can redistribute it and/or modify it
-- under the terms of the GNU General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option)
-- any later version.
--
-- Omicron Frames is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-- more details.
--
-- You should have received a copy of the GNU General Public License along with
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
local iconPool = CreateFramePool("Frame", nil, "BackdropTemplate")
---IconIndicator is an indicator that displays an icon with border
---@class IconIndicator: Indicator
local IconIndicator = types.CreateClass("IconIndicator", Indicator)
types.IconIndicator = IconIndicator
---@return IconIndicator
function IconIndicator.new(cls, ...)
--- I really dislike duplicating this everywhere but it makes
--lua-language-server able to deduce the type of Object:new calls and that
--is honestly worth it
return types.Object.new(cls, ...)
end
--- Initialize a new IconIndicator
-- unitframe Unitframe it is attached to
-- size Size of the indicator square
-- point Attachment point to the unitframe's overlay frame
-- x, y x and y offset for the attachment point
-- color Color of the square
---@param unitframe UnitFrame
---@param size number
---@param point string
---@param x number
---@param y number
---@param showStacks boolean
---@param doFade nil|number
---@param doFlash nil|number
---@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
self.point = point
self.x = x
self.y = y
self.icon = 134400
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.applications or 0
self.auraInstanceID = data.auraInstanceID
self.spellId = data.spellId
self.frame = frame
self.stacks = stacks
if self.showStacks and numStacks > 0 then
stacks:SetText(numStacks)
stacks:Show()
end
if self.flashTime or self.fadeTime then
self.frame:SetScript("OnUpdate", function(frame, dt)
self:OnUpdate(dt)
end)
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
function IconIndicator:SetIcon(icon)
self.icon = icon
local frame = self.frame
if frame then
frame.iconTexture:SetTexture(icon)
end
end
function IconIndicator:GetFrame()
local frame, new = iconPool:Acquire()
local stacks
if new then
local iconTexture = frame:CreateTexture(nil, "BACKGROUND")
iconTexture:SetAllPoints()
frame.iconTexture = iconTexture
frame.backdropInfo = {
bgFile = nil,
edgeFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\pixel_edge",
edgeSize = 2,
insets = { left = 0, right = 0, top = 0, bottom = 0 },
}
frame:ApplyBackdrop()
stacks = frame:CreateFontString(nil, "OVERLAY")
frame.stacks = stacks
stacks:SetFont("Interface\\AddOns\\OmicronFrames\\media\\fonts\\roboto\\Roboto-Medium.ttf", 12, "OUTLINE")
stacks:SetPoint("CENTER")
stacks:SetTextColor(1, 1, 1)
end
frame:SetSize(self.size, self.size)
frame:SetParent(self.frameParent)
frame:SetPoint(self.point, self.x, self.y)
frame:SetBackdropBorderColor(0, 0, 0, 1)
frame:SetAlpha(1)
stacks = frame.stacks
return frame, stacks
end
---@param data table
function IconIndicator:Update(data)
if data.icon then
self:SetIcon(data.icon)
end
local numStacks = data.applications or 0
if numStacks > 0 then
self.stacks:SetText(numStacks)
self.stacks:Show()
end
self.expirationTime = data.expirationTime
self.auraInstanceID = data.auraInstanceID
self.spellId = data.spellId
self:UpdateAlpha()
end
function IconIndicator:UpdateAlpha()
if self.expirationTime == 0 then
self.frame:SetAlpha(1)
return
end
local timeLeft = self.expirationTime - GetTime()
local fadeTime = self.fadeTime or 0
local flashTime = self.flashTime or 0
if timeLeft < flashTime then
local alpha = ((timeLeft * 4) % 1)
self.frame:SetAlpha(alpha)
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
+3 -7
View File
@@ -26,14 +26,10 @@ local Indicator = types.CreateClass("Indicator")
types.Indicator = Indicator types.Indicator = Indicator
--- Show the indicator and supply it with data --- Show the indicator and supply it with data
function Indicator:Show(data) function Indicator:Show(data) end
end
--- Update the indicator with new data --- Update the indicator with new data
function Indicator:Update(data) function Indicator:Update(data) end
end
--- Hide the indicator --- Hide the indicator
function Indicator:Hide() function Indicator:Hide() end
end
+46 -2
View File
@@ -45,7 +45,9 @@ end
---@param y number ---@param y number
---@param color number[] ---@param color number[]
---@param showStacks boolean ---@param showStacks boolean
function SquareIndicator:Init(unitframe, size, point, x, y, color, showStacks) ---@param doFade nil|number
---@param doFlash nil|number
function SquareIndicator:Init(unitframe, size, point, x, y, color, showStacks, fadeTime, flashTime)
self.unitframe = unitframe self.unitframe = unitframe
self.frameParent = unitframe.overlays self.frameParent = unitframe.overlays
self.size = size self.size = size
@@ -54,6 +56,8 @@ function SquareIndicator:Init(unitframe, size, point, x, y, color, showStacks)
self.y = y self.y = y
self.color = color self.color = color
self.showStacks = showStacks self.showStacks = showStacks
self.fadeTime = fadeTime
self.flashTime = flashTime
end end
--- Set the color of the Square Indicator --- Set the color of the Square Indicator
@@ -77,7 +81,18 @@ function SquareIndicator:Show(data)
stacks:SetText(numStacks) stacks:SetText(numStacks)
stacks:Show() stacks:Show()
end end
if self.flashTime or self.fadeTime then
self.frame:SetScript("OnUpdate", function(frame, dt)
self:OnUpdate(dt)
end)
self.expirationTime = data.expirationTime
self.updateThrottle = 0
end
if data.color then
self:SetColor(data.color)
else
frame:SetBackdropColor(unpack(self.color)) frame:SetBackdropColor(unpack(self.color))
end
frame:Show() frame:Show()
end end
@@ -89,7 +104,7 @@ function SquareIndicator:GetFrame()
bgFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\square_white", bgFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\square_white",
edgeFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\pixel_edge", edgeFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\pixel_edge",
edgeSize = 2, edgeSize = 2,
insets = {left=0, right=0, top=0, bottom=0}, insets = { left = 0, right = 0, top = 0, bottom = 0 },
} }
frame:ApplyBackdrop() frame:ApplyBackdrop()
@@ -104,17 +119,45 @@ function SquareIndicator:GetFrame()
frame:SetParent(self.frameParent) frame:SetParent(self.frameParent)
frame:SetPoint(self.point, self.x, self.y) frame:SetPoint(self.point, self.x, self.y)
frame:SetBackdropBorderColor(0, 0, 0, 1) frame:SetBackdropBorderColor(0, 0, 0, 1)
frame:SetAlpha(1)
stacks = frame.stacks stacks = frame.stacks
return frame, stacks return frame, stacks
end end
---@param data table ---@param data table
function SquareIndicator:Update(data) function SquareIndicator:Update(data)
if data.color then
self:SetColor(data.color)
end
local numStacks = data.stacks or 0 local numStacks = data.stacks or 0
if numStacks > 0 then if numStacks > 0 then
self.stacks:SetText(numStacks) self.stacks:SetText(numStacks)
self.stacks:Show() self.stacks:Show()
end end
self.expirationTime = data.expirationTime
self.frame:SetAlpha(1)
end
function SquareIndicator:OnUpdate(dt)
dt = self.updateThrottle + dt
if dt < 0.04 then
self.updateThrottle = dt
return
else
self.updateThrottle = 0
end
local timeLeft = self.expirationTime - GetTime()
local fadeTime = self.fadeTime or 0
local flashTime = self.flashTime or 0
if timeLeft < flashTime then
local alpha = ((timeLeft * 4) % 1)
self.frame:SetAlpha(alpha)
elseif timeLeft < fadeTime then
local alpha = timeLeft / fadeTime
self.frame:SetAlpha(alpha)
end
end end
--- Hide the square indicator. --- Hide the square indicator.
@@ -123,5 +166,6 @@ function SquareIndicator:Hide()
self.stacks:Hide() self.stacks:Hide()
self.frame = nil self.frame = nil
self.stacks = nil self.stacks = nil
frame:SetScript("OnUpdate", nil)
squarePool:Release(frame) squarePool:Release(frame)
end end
+18 -8
View File
@@ -20,8 +20,10 @@ local types = omi.GetModule("types")
types.StatusBar = types.CreateClass("StatusBar") types.StatusBar = types.CreateClass("StatusBar")
local StatusBar = types.StatusBar local StatusBar = types.StatusBar
function StatusBar:Init(parent, width, height, level, top) function StatusBar:Init(parent, width, height, level, top, texture, bgtexture)
if top == nil then top = true end if top == nil then
top = true
end
level = level or 0 level = level or 0
-- parent: the parent frame -- parent: the parent frame
self.parent = parent self.parent = parent
@@ -38,15 +40,17 @@ function StatusBar:Init(parent, width, height, level, top)
end end
bar:SetSize(width, height) bar:SetSize(width, height)
bar:SetStatusBarTexture("Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal") bar:SetStatusBarTexture(texture)
bar:GetStatusBarTexture():SetHorizTile(false) bar:GetStatusBarTexture():SetHorizTile(false)
bar:GetStatusBarTexture():SetVertTile(false) bar:GetStatusBarTexture():SetVertTile(false)
self.bar = bar self.bar = bar
if bgtexture ~= nil then
local bg = bar:CreateTexture(nil, "BACKGROUND") local bg = bar:CreateTexture(nil, "BACKGROUND")
bg:SetTexture("Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal") bg:SetTexture(bgtexture)
bg:SetAllPoints(true) bg:SetAllPoints(true)
self.bg = bg self.bg = bg
end
self:SetRange(0, 1) self:SetRange(0, 1)
self:SetValue(1) self:SetValue(1)
@@ -61,6 +65,10 @@ function StatusBar:SetValue(value)
self.bar:SetValue(value) self.bar:SetValue(value)
end end
function StatusBar:SetFillStyle(style)
self.bar:SetFillStyle(style)
end
function StatusBar:Show() function StatusBar:Show()
self.bar:Show() self.bar:Show()
end end
@@ -70,13 +78,15 @@ function StatusBar:Hide()
end end
function StatusBar:SetInterpolatedColor(start, stop, progress) function StatusBar:SetInterpolatedColor(start, stop, progress)
local r = start[1] + (stop[1] - start[1])*progress local r = start[1] + (stop[1] - start[1]) * progress
local g = start[2] + (stop[2] - start[2])*progress local g = start[2] + (stop[2] - start[2]) * progress
local b = start[3] + (stop[3] - start[3])*progress local b = start[3] + (stop[3] - start[3]) * progress
self:SetColor(r, g, b) self:SetColor(r, g, b)
end end
function StatusBar:SetColor(r, g, b) function StatusBar:SetColor(r, g, b)
self.bar:SetStatusBarColor(r, g, b) self.bar:SetStatusBarColor(r, g, b)
self.bg:SetVertexColor(0.2*r, 0.2*g, 0.2*b) if self.bg ~= nil then
self.bg:SetVertexColor(0.2 * r, 0.2 * g, 0.2 * b)
end
end end
+68 -18
View File
@@ -21,25 +21,20 @@ local Trigger = types.Trigger
---AuraTrigger is a Trigger that can be attached to AuraList as datasource ---AuraTrigger is a Trigger that can be attached to AuraList as datasource
---@class AuraTrigger: Trigger ---@class AuraTrigger: Trigger
---@field spellId number ---@field spellIds table<number, true>
---@field count number ---@field count number
---@field requiredCount number ---@field requiredCount number
local AuraTrigger = types.CreateClass("AuraTrigger", Trigger) local AuraTrigger = types.CreateClass("AuraTrigger", Trigger)
types.AuraTrigger = AuraTrigger types.AuraTrigger = AuraTrigger
---Creates a new AuraTrigger from a config description and attaches it to the ---Creates a new AuraTrigger from a config description and attaches it to the
---correct data source ---correct data source
---@param unit UnitFrame ---@param unit UnitFrame
---@param config table ---@param config table
---@return AuraTrigger ---@return AuraTrigger
function AuraTrigger.CreateFromConfig(unit, config) function AuraTrigger.CreateFromConfig(unit, config)
local trigger = AuraTrigger:new( local trigger =
config.spellId, AuraTrigger:new(config.spellIds, config.own, config.requiredCount, config.invert, config.defaultData)
config.own,
config.requiredCount,
config.invert
)
unit.auras:AddTrigger(trigger) unit.auras:AddTrigger(trigger)
return trigger return trigger
end end
@@ -53,28 +48,33 @@ function AuraTrigger.new(cls, ...)
end end
--- Initialize a new AuraTrigger object --- Initialize a new AuraTrigger object
---@param spellId number ---@param spellIds table<number, true>
---@param own boolean ---@param own boolean
---@param requiredCount number | nil ---@param requiredCount number | nil
---@param invert boolean | nil ---@param invert boolean | nil
function AuraTrigger:Init(spellId, own, requiredCount, invert) ---@param defaultData table | nil
Trigger.Init(self, invert) function AuraTrigger:Init(spellIds, own, requiredCount, invert, defaultData)
self.spellId = spellId Trigger.Init(self, invert, defaultData)
self.spellIds = spellIds
self.instanceData = {}
self.requiredCount = requiredCount or 1 self.requiredCount = requiredCount or 1
self.own = own self.own = own
self.count = 0 self.count = 0
self.bestAuraID = nil
end end
--- Reset the AuraTrigger to the default state --- Reset the AuraTrigger to the default state
function AuraTrigger:Reset() function AuraTrigger:Reset()
self.count = 0 self.count = 0
self:SetState(self.count >= self.requiredCount, nil, false) self.bestAuraID = nil
table.wipe(self.instanceData)
self:SetStateAndData(self.count >= self.requiredCount, {})
end end
---Check if a given aura matches this AuraTrigger ---Check if a given aura matches this AuraTrigger
---@param aura UnitAuraInfo ---@param aura UnitAuraInfo
function AuraTrigger:IsMatching(aura) function AuraTrigger:IsMatching(aura)
if aura.spellId ~= aura.spellId then if not self.spellIds[aura.spellId] then
return false return false
end end
if self.own and aura.sourceUnit ~= "player" then if self.own and aura.sourceUnit ~= "player" then
@@ -83,23 +83,68 @@ function AuraTrigger:IsMatching(aura)
return true return true
end 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 ---Inform the trigger about an added aura
---@param aura UnitAuraInfo ---@param aura UnitAuraInfo
function AuraTrigger:AddAura(aura) function AuraTrigger:AddAura(aura)
if not self:IsMatching(aura) then if not self:IsMatching(aura) then
return return
end end
local aid = aura.auraInstanceID
self.instanceData[aid] = aura
self.count = self.count + 1 self.count = self.count + 1
self:SetState(self.count >= self.requiredCount, {stacks=aura.applications}, false) local newData = self:BestAuraData(nil)
self:SetStateAndData(self.count >= self.requiredCount, newData)
end end
---Inform the trigger about an updated aura ---Inform the trigger about an updated aura
---@param before UnitAuraInfo ---@param before UnitAuraInfo
---@param after UnitAuraInfo ---@param after UnitAuraInfo
function AuraTrigger:UpdateAura(before, after) function AuraTrigger:UpdateAura(before, after)
if self.active then if not self:IsMatching(after) then
self.indicator:Update({stacks=after.applications}) return
end end
local aid = after.auraInstanceID
self.instanceData[aid] = after
local newData = self:BestAuraData(aid)
self:SetStateAndData(self.count >= self.requiredCount, newData)
end end
---Inform the trigger about an aura that got removed ---Inform the trigger about an aura that got removed
@@ -108,6 +153,11 @@ function AuraTrigger:RemoveAura(aura)
if not self:IsMatching(aura) then if not self:IsMatching(aura) then
return return
end end
local aid = aura.auraInstanceID
self.instanceData[aid] = nil
self.count = self.count - 1 self.count = self.count - 1
self:SetState(self.count >= self.requiredCount, nil, false)
local newData = self:BestAuraData(nil)
self:SetStateAndData(self.count >= self.requiredCount, newData)
end end
+10 -6
View File
@@ -73,7 +73,7 @@ function MultiTrigger:AddTrigger(trigger)
end, end,
Hide = function(_, data) Hide = function(_, data)
self:OnChildDeactivate(idx, trigger, data) self:OnChildDeactivate(idx, trigger, data)
end end,
}) })
end end
@@ -92,7 +92,7 @@ function MultiTrigger:OnChildActivate(idx, trigger, data)
table.sort(activeChildren) table.sort(activeChildren)
-- The highest priority active trigger has changed -- The highest priority active trigger has changed
if activeChild ~= activeChildren[1] then if activeChild ~= activeChildren[1] then
self:SetState(true, data) self:SetStateAndData(true, data)
end end
end end
@@ -113,8 +113,13 @@ function MultiTrigger:OnChildDeactivate(idx, trigger, data)
end end
end end
table.remove(activeChildren, found) table.remove(activeChildren, found)
if activeChild ~= activeChildren[1] then local newActiveChild = activeChildren[1]
self:SetState(activeChildren[1] ~= nil, childrenData[1], true) if activeChild ~= newActiveChild then
if activeChildren[1] ~= nil then
self:SetStateAndData(true, childrenData[newActiveChild])
else
self:SetStateAndData(false, {})
end
end end
end end
@@ -131,6 +136,5 @@ end
-- Reset the trigger to the default state. If this changes the activation of the -- Reset the trigger to the default state. If this changes the activation of the
-- trigger then the target will be notified. -- trigger then the target will be notified.
function MultiTrigger:Reset() function MultiTrigger:Reset()
self:SetState(false) self:SetStateAndData(false, {})
end end
+4 -5
View File
@@ -29,8 +29,7 @@ types.StatusTrigger = StatusTrigger
---@param unit UnitFrame ---@param unit UnitFrame
---@param config table ---@param config table
function StatusTrigger.CreateFromConfig(unit, config) function StatusTrigger.CreateFromConfig(unit, config)
local trigger = StatusTrigger:new(config.status, config.requiredCount, local trigger = StatusTrigger:new(config.status, config.requiredCount, config.invert, config.defaultData)
config.invert)
unit.auras:AddTrigger(trigger) unit.auras:AddTrigger(trigger)
return trigger return trigger
end end
@@ -47,8 +46,8 @@ end
---@param status string The kind of status to trigger on ---@param status string The kind of status to trigger on
---@param requiredCount number | nil ---@param requiredCount number | nil
---@param invert boolean | nil ---@param invert boolean | nil
function StatusTrigger:Init(status, requiredCount, invert) function StatusTrigger:Init(status, requiredCount, invert, defaultData)
Trigger.Init(self, invert) Trigger.Init(self, invert, defaultData)
self.status = status self.status = status
self.requiredCount = requiredCount or 1 self.requiredCount = requiredCount or 1
self.invert = invert or false self.invert = invert or false
@@ -59,5 +58,5 @@ end
-- status: -- status:
-- Must be a valid UnitAuraInfo -- Must be a valid UnitAuraInfo
function StatusTrigger:UpdateStatus(count) function StatusTrigger:UpdateStatus(count)
self:SetState(count >= self.requiredCount, nil, false) self:SetStateAndData(count >= self.requiredCount, nil)
end end
+38 -10
View File
@@ -23,10 +23,11 @@ local types = omi.GetModule("types")
---@class Trigger: Object ---@class Trigger: Object
---@field active boolean ---@field active boolean
---@field invert boolean ---@field invert boolean
---@field indicator Indicator
---@field data table The most recent data updated by the data source
local Trigger = types.CreateClass("Trigger") local Trigger = types.CreateClass("Trigger")
types.Trigger = Trigger types.Trigger = Trigger
---@param unit UnitFrame ---@param unit UnitFrame
---@param config table ---@param config table
---@return Trigger ---@return Trigger
@@ -36,10 +37,27 @@ end
--- Initialize a new Trigger object --- Initialize a new Trigger object
---@param invert boolean|nil ---@param invert boolean|nil
function Trigger:Init(invert) ---@param defaultData table|nil default data that is always present when this trigger updates an indicator
function Trigger:Init(invert, defaultData)
invert = invert or false invert = invert or false
self.invert = invert self.invert = invert
self.active = invert self.active = invert
self.defaultData = defaultData
self:SetData({})
end
---Save new data from the datasource into the trigger. This method will make
---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
for k, v in pairs(self.defaultData) do
data[k] = v
end
end
self.data = data
end end
--- Set the target of the Trigger. --- Set the target of the Trigger.
@@ -49,30 +67,40 @@ end
function Trigger:SetTarget(target) function Trigger:SetTarget(target)
self.indicator = target self.indicator = target
if self.active then if self.active then
target:Show() target:Show(self.data)
end end
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.
---@param data table<string,any> | nil ---
--- 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 ---@param state boolean
function Trigger:SetState(state, data, 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 state = state ~= self.invert -- state xor inverted
if self.active ~= state then if self.active ~= state then
if state then if state then
self.indicator:Show(data) self.indicator:Show(self.data)
else else
self.indicator:Hide() self.indicator:Hide()
end end
self.active = state self.active = state
elseif doUpdate and self.active then elseif doUpdate and self.active then
self.indicator:Update(data) self.indicator:Update(self.data)
end end
end end
-- Reset the trigger to the default state. If this changes the activation of the -- Reset the trigger to the default state. If this changes the activation of the
-- trigger then the target will be notified. -- trigger then the target will be notified.
function Trigger:Reset() function Trigger:Reset()
self:SetState(false, nil, false) self:SetStateAndData(false, nil)
end end
+2 -2
View File
@@ -24,7 +24,7 @@ local function CreateClassRaw(name, parent)
c.__typeInfo = { c.__typeInfo = {
name = name, name = name,
class = c, class = c,
parent = parent parent = parent,
} }
c.__index = c c.__index = c
if parent ~= nil then if parent ~= nil then
@@ -48,6 +48,6 @@ function types.IsDerivedFrom(a, b)
return true return true
end end
current = current.__typeInfo.parent current = current.__typeInfo.parent
until current == nil; until current == nil
return false return false
end end
+219 -55
View File
@@ -17,6 +17,11 @@
local omif = select(2, ...) local omif = select(2, ...)
local types = omif.GetModule("types") 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 ---@class StatusBar
local StatusBar = types.StatusBar local StatusBar = types.StatusBar
@@ -26,6 +31,9 @@ local AuraList = types.AuraList
---@class SquareIndicator ---@class SquareIndicator
local SquareIndicator = types.SquareIndicator local SquareIndicator = types.SquareIndicator
---@class IconIndicator
local IconIndicator = types.IconIndicator
---@class SquareIndicator ---@class SquareIndicator
local BorderIndicator = types.BorderIndicator local BorderIndicator = types.BorderIndicator
@@ -33,26 +41,42 @@ local BorderIndicator = types.BorderIndicator
local UnitFrame = types.CreateClass("UnitFrame") local UnitFrame = types.CreateClass("UnitFrame")
types.UnitFrame = UnitFrame types.UnitFrame = UnitFrame
local colors = { local colors = {
hostile = {0.5, 0.0, 0.0}, hostile = { 0.5, 0.0, 0.0 },
neutral = {0.7, 0.7, 0.0}, neutral = { 0.7, 0.7, 0.0 },
healthy = {0, 0.1, 0}, healthy = { 0, 0.1, 0 },
high = {0, 1.0, 0}, high = { 0, 1.0, 0 },
mid = {1.0, 1.0, 0}, mid = { 1.0, 1.0, 0 },
low = {1.0, 0.0, 0}, low = { 1.0, 0.0, 0 },
offline = {0.7, 0.7, 0.7}, offline = { 0.7, 0.7, 0.7 },
dead = {0.7, 0.7, 0.7}, dead = { 0.7, 0.7, 0.7 },
magic = {0.4, 0.4, 1.0}, magic = { 0.4, 0.4, 1.0 },
disease = {0.4, 0.2, 0.0}, disease = { 0.4, 0.2, 0.0 },
poison = {0.0, 0.7, 0.7}, poison = { 0.0, 0.7, 0.7 },
curse = {0.7, 0.0, 0.7}, curse = { 0.7, 0.0, 0.7 },
immune = {0.0, 0.2, 0.4}, immune = { 0.0, 0.2, 0.4 },
bomb = {1.0, 0.7, 0.7}, bomb = { 1.0, 0.7, 0.7 },
cyan = {0.0, 0.8, 0.8}, cyan = { 0.0, 0.8, 0.8 },
white = {1.0, 1.0, 1.0} white = { 1.0, 1.0, 1.0 },
shield = { 0.85, 0.95, 1.0 },
absorb = { 0.5, 0.4, 0.75 },
} }
-- This trucates _codepoints_ not graphemes. If combination codepoints are
-- contained in the string, it will not properly truncate and may return
-- incorrect graphemes
local function utf8_truncate(s, n)
local init = 0
for _ = 1, n do
local _, last = string.find(s, ".[\128-\191]*", init + 1)
if not last then
return s
end
init = last
end
return string.sub(s, 1, init)
end
---@param unit string ---@param unit string
---@param config table ---@param config table
function UnitFrame:Init(unit, config) function UnitFrame:Init(unit, config)
@@ -71,9 +95,25 @@ function UnitFrame:Init(unit, config)
self:SetMouseBinds(config.mouse) self:SetMouseBinds(config.mouse)
self.hp = StatusBar:new(self, width, height, 0, true) local texture = "Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal"
self.power = StatusBar:new(self, width, 6, 2, false) local texture_top = "Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal_top"
local texture_bot = "Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal_bottom"
self.hp = StatusBar:new(self, width, height, 0, true, texture, texture)
self.power = StatusBar:new(self, width, 6, 2, false, texture, texture)
self.power:Hide() self.power:Hide()
self.absorb = StatusBar:new(self, width, height, 1, true, texture_top)
self.absorb:SetColor(unpack(colors.absorb))
self.absorb:SetFillStyle(Enum.StatusBarFillStyle.Reverse)
self.shield = StatusBar:new(self, width, height, 1, true, texture_bot)
self.shield:SetColor(unpack(colors.shield))
self.shield:SetRange(0, 1)
self.shield:SetValue(0.6)
self.shield:SetFillStyle(Enum.StatusBarFillStyle.Reverse)
self.auras = AuraList:new(self) self.auras = AuraList:new(self)
local overlays = CreateFrame("Frame", nil, secure) local overlays = CreateFrame("Frame", nil, secure)
@@ -86,6 +126,8 @@ function UnitFrame:Init(unit, config)
self:CreateTriggers(config.triggers) self:CreateTriggers(config.triggers)
self:InitDebuffs()
self:RegisterEvents() self:RegisterEvents()
self:Enable() self:Enable()
end end
@@ -110,14 +152,23 @@ function UnitFrame:CreateIndicator(indicator)
indicator.x, indicator.x,
indicator.y, indicator.y,
indicator.color, indicator.color,
indicator.showStacks indicator.showStacks,
indicator.fadeTime,
indicator.flashTime
)
elseif kind == "IconIndicator" then
return IconIndicator:new(
self,
indicator.size,
indicator.point,
indicator.x,
indicator.y,
indicator.showStacks,
indicator.fadeTime,
indicator.flashTime
) )
elseif kind == "BorderIndicator" then elseif kind == "BorderIndicator" then
return BorderIndicator:new( return BorderIndicator:new(self, indicator.thickness, indicator.color)
self,
indicator.thickness,
indicator.color
)
else else
error(string.format("Invalid Indicator kind `%s` requested", indicator.kind)) error(string.format("Invalid Indicator kind `%s` requested", indicator.kind))
end end
@@ -127,7 +178,7 @@ function UnitFrame:StartRangeTicker()
if self.rangeTicker then if self.rangeTicker then
return return
end end
local delta = 0.45 + (fastrandom(0, 100)/1000) local delta = 0.45 + (fastrandom(0, 100) / 1000)
self.rangeTicker = C_Timer.NewTicker(delta, function() self.rangeTicker = C_Timer.NewTicker(delta, function()
self:UpdateRange() self:UpdateRange()
end) end)
@@ -138,10 +189,8 @@ function UnitFrame:CreateName()
local name = self.overlays:CreateFontString(nil, "BACKGROUND") local name = self.overlays:CreateFontString(nil, "BACKGROUND")
self.name = name 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:SetTextColor(0.8, 0.6, 0.1)
name:SetShadowColor(0, 0, 0)
name:SetShadowOffset(1, -1)
name:SetPoint("CENTER") name:SetPoint("CENTER")
name:SetText("") name:SetText("")
name:Show() name:Show()
@@ -165,9 +214,9 @@ function UnitFrame:IsInRange()
-- Prefer to use configured spells -- Prefer to use configured spells
if friendlySpell and UnitCanAssist("player", unit) then if friendlySpell and UnitCanAssist("player", unit) then
return IsSpellInRange(friendlySpell, unit) == 1 return C_Spell.IsSpellInRange(friendlySpell, unit) == true
elseif enemySpell and UnitCanAttack("player", unit) then elseif enemySpell and UnitCanAttack("player", unit) then
return IsSpellInRange(enemySpell, unit) == 1 return C_Spell.IsSpellInRange(enemySpell, unit) == true
end end
-- Fall back to raid/party only range check -- Fall back to raid/party only range check
@@ -246,9 +295,15 @@ function UnitFrame:PrepareWheelBinds(bindings)
if bind.button == "wheel-up" or bind.button == "wheel-down" then if bind.button == "wheel-up" or bind.button == "wheel-down" then
local prefix = ModifiersToPrefix(bind.mods) local prefix = ModifiersToPrefix(bind.mods)
local button = bind.button == "wheel-up" and "MOUSEWHEELUP" or "MOUSEWHEELDOWN" local button = bind.button == "wheel-up" and "MOUSEWHEELUP" or "MOUSEWHEELDOWN"
table.insert(bindScript, table.insert(
string.format([[self:SetBindingClick(false, "%s%s", "OmicronSecureFrame%s", "%s%s")]], bindScript,
prefix, button, unit, prefix, bind.button string.format(
[[self:SetBindingClick(false, "%s%s", "OmicronSecureFrame%s", "%s%s")]],
prefix,
button,
unit,
prefix,
bind.button
) )
) )
end end
@@ -339,10 +394,9 @@ function UnitFrame:Disable()
if self.hideInRaid then if self.hideInRaid then
UnregisterAttributeDriver(self.secureFrame, "state-visibility") UnregisterAttributeDriver(self.secureFrame, "state-visibility")
elseif self.unit ~= "player" then elseif self.unit ~= "player" then
UnregisterUnitWatch(self.secure_frame, self.condition) UnregisterUnitWatch(self.secureFrame, false)
else
self.secureFrame:Hide()
end end
self.secureFrame:Hide()
end end
-- returns true if this unit is a target unit -- returns true if this unit is a target unit
@@ -389,10 +443,13 @@ function UnitFrame:RegisterEvents()
end end
end end
secure:RegisterEvent("GROUP_ROSTER_UPDATE")
secure:RegisterUnitEvent("UNIT_AURA", unit) secure:RegisterUnitEvent("UNIT_AURA", unit)
secure:RegisterUnitEvent("UNIT_HEALTH", unit) secure:RegisterUnitEvent("UNIT_HEALTH", unit)
secure:RegisterUnitEvent("UNIT_MAXHEALTH", unit) secure:RegisterUnitEvent("UNIT_MAXHEALTH", unit)
secure:RegisterUnitEvent("UNIT_NAME_UPDATE", unit) secure:RegisterUnitEvent("UNIT_NAME_UPDATE", unit)
secure:RegisterUnitEvent("UNIT_ABSORB_AMOUNT_CHANGED", unit)
secure:RegisterUnitEvent("UNIT_HEAL_ABSORB_AMOUNT_CHANGED", unit)
end end
-- returns whether or not the unit guid has changed since the last call to this -- returns whether or not the unit guid has changed since the last call to this
@@ -413,6 +470,97 @@ function UnitFrame:HasUnitChanged()
end end
end end
function UnitFrame:UNIT_HEAL_ABSORB_AMOUNT_CHANGED()
if self:HasUnitChanged() then
self:UpdateAll(true)
return
end
self:UpdateAbsorb()
end
function UnitFrame:UNIT_ABSORB_AMOUNT_CHANGED()
if self:HasUnitChanged() then
self:UpdateAll(true)
return
end
self:UpdateShield()
end
function UnitFrame:GROUP_ROSTER_UPDATE()
if self:HasUnitChanged() then
self:UpdateAll(true)
return
end
end
-- FIXME: This is a total hack job, just quickly want to get debuffs rendering on my unit frames to learn new dungeons
function UnitFrame:InitDebuffs()
local icons = {}
self.debuffIndicators = icons
for i = 1, 5 do
local icon = IconIndicator:new(self, 17, "BOTTOMRIGHT", -2 - (i - 1) * 19, 2, true, 10.0, nil, true)
icons[i] = icon
end
end
local ignoredDebuffs = {
[57723] = true, -- Sated, shaman lust debuff (alliance)
[57724] = true, -- Sated, shaman lust debuff (horde)
[80354] = true, -- Temporal displacement
[390435] = true, -- Exhausstion
[264689] = true, -- Fatigued
[87024] = true, -- Cauterized
[45181] = true, -- Cheated Death
[41425] = true, -- hypothermia
[124275] = true, -- Light stagger
[124274] = true, -- moderate stagger
[124273] = true, -- heavy stagger
[25771] = true, -- Forbearance
[456447] = true, -- Battle-Scarred Veteran
[382912] = true, -- Well-Honed Instincts
[1223611] = true, -- Ethereal Exhaustion
[456779] = true, -- Will of the Dawn
[448005] = true, -- Light of the Martyr
[157131] = true, -- Recently Saved by the Light
[387847] = true,
[206151] = true, -- Challenger's Burden
[113942] = true, -- Demonic Gateway
[1216943] = true, -- void pulsar
[462661] = true, -- Voidbinding
[1239229] = true, -- k'areshi surge
[449042] = true, -- Radiant Light
-- blood draw
}
function UnitFrame:UpdateDebuffs()
local auras = {}
local i = 1
while #auras < 5 do
local aura = C_UnitAuras.GetAuraDataByIndex(self.unit, i, "HARMFUL")
if aura == nil then
break
end
if not ignoredDebuffs[aura.spellId] then
table.insert(auras, aura)
end
i = i + 1
end
for i = 1, 5 do
local aura = auras[i]
local icon = self.debuffIndicators[i]
if aura then
if icon.frame then
icon:Update(aura)
else
icon:Show(aura)
end
elseif icon.frame then
icon:Hide()
end
end
end
function UnitFrame:UNIT_AURA(unit, info) function UnitFrame:UNIT_AURA(unit, info)
if self:HasUnitChanged() then if self:HasUnitChanged() then
self:UpdateAll(true) self:UpdateAll(true)
@@ -421,6 +569,8 @@ function UnitFrame:UNIT_AURA(unit, info)
if self.auras:Update(info) then if self.auras:Update(info) then
self:UpdateHealthColor() self:UpdateHealthColor()
end end
self:UpdateDebuffs()
end end
function UnitFrame:UNIT_HEALTH() function UnitFrame:UNIT_HEALTH()
@@ -519,9 +669,12 @@ function UnitFrame:UpdateAll(unitChanged)
end end
self:UpdateHealth() self:UpdateHealth()
self:UpdateHealthColor() self:UpdateHealthColor()
self:UpdateAbsorb()
self:UpdateShield()
self:UpdateRange() self:UpdateRange()
self:UpdateName() self:UpdateName()
self:UpdateRole() -- Also calls UpdatePower if power is visible self:UpdateRole() -- Also calls UpdatePower if power is visible
self:UpdateDebuffs()
end end
function UnitFrame:UpdateHealth() function UnitFrame:UpdateHealth()
@@ -533,6 +686,32 @@ function UnitFrame:UpdateHealth()
self.hp:SetValue(val) self.hp:SetValue(val)
end end
function UnitFrame:UpdateShield()
local unit = self.unit
local val = UnitGetTotalAbsorbs(unit)
local max = UnitHealthMax(unit)
if val <= 0 then
self.shield:Hide()
else
self.shield:SetRange(0, max)
self.shield:SetValue(val)
self.shield:Show()
end
end
function UnitFrame:UpdateAbsorb()
local unit = self.unit
local val = UnitGetTotalHealAbsorbs(unit)
local max = UnitHealthMax(unit)
if val <= 0 then
self.absorb:Hide()
else
self.absorb:SetRange(0, max)
self.absorb:SetValue(val)
self.absorb:Show()
end
end
function UnitFrame:UpdateHealthColor() function UnitFrame:UpdateHealthColor()
local unit = self.unit local unit = self.unit
local val = UnitHealth(unit) local val = UnitHealth(unit)
@@ -563,7 +742,7 @@ function UnitFrame:UpdateHealthColor()
elseif isFriend and pct >= 0.90 then elseif isFriend and pct >= 0.90 then
self.hp:SetColor(unpack(colors.healthy)) self.hp:SetColor(unpack(colors.healthy))
elseif isFriend and pct >= 0.75 then elseif isFriend and pct >= 0.75 then
local progress = (pct - 0.75) / (0.90-0.75) local progress = (pct - 0.75) / (0.90 - 0.75)
self.hp:SetInterpolatedColor(colors.mid, colors.high, progress) self.hp:SetInterpolatedColor(colors.mid, colors.high, progress)
else else
local progress = pct / 0.75 local progress = pct / 0.75
@@ -580,25 +759,10 @@ function UnitFrame:UpdateName()
-- TODO: UnitClass can return Unknown and the color can be nil. Having -- TODO: UnitClass can return Unknown and the color can be nil. Having
-- the unit exist but not fully loaded is possibly a state we want to -- the unit exist but not fully loaded is possibly a state we want to
-- handle more generally -- handle more generally
local color = RAID_CLASS_COLORS[class] or {r=1, g=1, b=1} local color = RAID_CLASS_COLORS[class] or { r = 1, g = 1, b = 1 }
self.name:SetTextColor(color.r, color.g, color.b) self.name:SetTextColor(color.r, color.g, color.b)
else else
self.name:SetTextColor(1, 1, 1) self.name:SetTextColor(1, 1, 1)
end end
self.name:SetText(UnitName(self.unit):sub(1, 5)) self.name:SetText(utf8_truncate((UnitName(self.unit)), 5))
end end
--[[
-- UNIT_AURA
-- UNIT_CLASSIFICATION_CHANGED
-- UNIT_COMBAT
-- UNIT_CONNECTION
-- UNIT_DISPLAYPOWER
-- UNIT_FACTION
-- UNIT_FLAGS
-- UNIT_LEVEL
-- UNIT_MANA
-- UNIT_HEALTH_PREDICTION
-- UNIT_PHASE
]]--
+15 -14
View File
@@ -32,9 +32,10 @@ local function RoleValue(role)
end end
local function UnitFrameDefaultCompare(lhs, rhs) local function UnitFrameDefaultCompare(lhs, rhs)
-- visible < invisible -- exists < not exists
if lhs.secureFrame:IsShown() ~= rhs.secureFrame:IsShown() then local lexists, rexists = UnitExists(lhs.unit), UnitExists(rhs.unit)
return lhs.secureFrame:IsShown() if lexists ~= rexists then
return lexists
end end
local lunit, runit = lhs.unit, rhs.unit local lunit, runit = lhs.unit, rhs.unit
@@ -69,7 +70,6 @@ function UnitGroup:Init(left, top, width, height)
self.top = top self.top = top
self.left = left self.left = left
self.units = {} self.units = {}
self.sortAfterCombat = false
self:RegisterEvents() self:RegisterEvents()
end end
@@ -84,6 +84,7 @@ function UnitGroup:RegisterEvents()
frame:RegisterEvent("GROUP_ROSTER_UPDATE") frame:RegisterEvent("GROUP_ROSTER_UPDATE")
frame:RegisterEvent("PLAYER_REGEN_ENABLED") frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:RegisterEvent("PLAYER_ENTERING_WORLD") frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("UNIT_PET")
end end
function UnitGroup:GROUP_ROSTER_UPDATE() function UnitGroup:GROUP_ROSTER_UPDATE()
@@ -94,23 +95,23 @@ function UnitGroup:PLAYER_ENTERING_WORLD()
self:OnRosterUpdate() self:OnRosterUpdate()
end end
function UnitGroup:UNIT_PET()
self:OnRosterUpdate()
end
function UnitGroup:PLAYER_REGEN_ENABLED() function UnitGroup:PLAYER_REGEN_ENABLED()
self:OnLeaveCombat() self:OnLeaveCombat()
end end
function UnitGroup:OnLeaveCombat() function UnitGroup:OnLeaveCombat()
if self.sortAfterCombat then
self:Sort() self:Sort()
self.sortAfterCombat = false
end
end end
function UnitGroup:OnRosterUpdate() function UnitGroup:OnRosterUpdate()
if InCombatLockdown() then if InCombatLockdown() then
self.sortAfterCombat = true return
else
self:Sort()
end end
self:Sort()
end end
function UnitGroup:AddUnitFrame(unit) function UnitGroup:AddUnitFrame(unit)
@@ -121,14 +122,14 @@ function UnitGroup:Sort()
table.sort(self.units, UnitFrameDefaultCompare) table.sort(self.units, UnitFrameDefaultCompare)
local left, top, width, height = self.left, self.top, self.width, self.height local left, top, width, height = self.left, self.top, self.width, self.height
local maxFrameIndex = #self.units local maxFrameIndex = #self.units
for y=0,5 do for y = 0, 5 do
for x=0,4 do for x = 0, 4 do
local num = y*5 + x + 1 local num = y * 5 + x + 1
if num > maxFrameIndex then if num > maxFrameIndex then
return return
end end
local frame = self.units[num] local frame = self.units[num]
frame:SetPosition(left + (x-2)*width, top - y*height) frame:SetPosition(left + (x - 2) * width, top - y * height)
end end
end end
end end