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
25 changed files with 1596 additions and 368 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
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
# build the addon, then install it to the directory passed on the command line
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/indicators/indicator.lua
types/indicators/squareindicator.lua
types/indicators/iconindicator.lua
types/indicators/borderindicator.lua
types/unitgroup.lua
types/unitframe.lua
+639 -112
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,33 +13,54 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omif = select(2, ...)
local colors = {
cyan = {0.0, 0.8, 0.8},
white = {1.0, 1.0, 1.0},
orange = {1.0, 0.5 , 0},
violet = {1.0, 0, 1.0},
cyan = { 0.0, 0.8, 0.8 },
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 },
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 _, class = UnitClass("player")
if class == "SHAMAN" then
return {
friendly = "Healing Surge",
friendly = "Healing Wave",
enemy = "Lightning Bolt",
fade = 0.2
fade = 0.2,
}
elseif class == "PRIEST" then
return {
friendly = "Flash Heal",
friendly = "Heal",
enemy = "Smite",
fade = 0.2
fade = 0.2,
}
else
return {
fade = 0.2
fade = 0.2,
}
end
end
@@ -49,142 +70,641 @@ local function MouseConfig()
if class == "SHAMAN" then
return {
-- No modifier
{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="wheel-up", mods={}, kind="macro", data="/use [@UNIT,help]Healing Surge"},
{button="wheel-down", mods={}, kind="macro", data="/use [@UNIT,help]Riptide"},
{ button = "mouse1", mods = {}, kind = "target" },
{
button = "mouse2",
mods = {},
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
{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
{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
return {
-- No modifier
{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="mouse4", mods={}, kind="spell", data="Shadow Covenant"},
{button="wheel-up", mods={}, kind="macro", data="/use [@UNIT,help]Flash Heal"},
{button="wheel-down", mods={}, kind="macro", data="/use [@UNIT,help]Renew"},
{ 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 = "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
{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"},
{ 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 = "Renew" },
{ 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"},
{ 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 == "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
return {
-- Super basic defaults
{button="mouse1", mods={}, kind="target"},
{button="mouse2", mods={shift=true}, kind="togglemenu"},
{ button = "mouse1", mods = {}, kind = "target" },
{ button = "mouse2", mods = { shift = true }, kind = "togglemenu" },
}
end
end
local function TriggerConfig()
local function TriggerClassConfig()
local _, class = UnitClass("player")
if class == "SHAMAN" then
return {
{
kind="AuraTrigger", spellId=383648, own=true, -- Second Earth Shield on yourself
indicator={
kind="SquareIndicator",
size=14,
point="TOPLEFT",
x=2, y=-2,
color=colors.white,
}
kind = "AuraTrigger",
spellIds = set({ 32594 }),
own = true, -- Second Earth Shield on yourself
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 3,
y = -3,
color = colors.white,
showStacks = true,
},
},
{
kind="AuraTrigger", spellId=974, own=true, -- Second Earth Shield on yourself
indicator={
kind="SquareIndicator",
size=14,
point="TOPLEFT",
x=2, y=-2,
color=colors.white,
}
kind = "AuraTrigger",
spellIds = set({ 29203 }),
own = true, -- Healing Way
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 20,
y = -3,
color = colors.yellow,
showStacks = true,
},
},
{
kind="AuraTrigger", spellId=61295, own=true, -- Riptide
indicator={
kind="SquareIndicator",
size=14,
point="BOTTOMLEFT",
x=2, y=2,
color=colors.cyan,
}
kind = "AuraTrigger",
spellIds = set({ 16237 }),
own = true, -- Ancestral Fortitude
indicator = {
kind = "SquareIndicator",
size = 17,
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
return {
{
kind="AuraTrigger", spellId=17, own=true, -- Power Word: Shield
indicator={
kind="SquareIndicator",
size=14,
point="TOPLEFT",
x=2, y=-2,
color=colors.white,
}
kind = "AuraTrigger",
spellIds = AURAS_PRIEST_PWS,
own = true, -- Power Word: Shield
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 3,
y = -3,
color = colors.white,
fadeTime = 10.0,
},
},
{
kind="AuraTrigger", spellId=33206, own=true, -- Pain Suppression
indicator={
kind="SquareIndicator",
size=14,
point="TOPLEFT",
x=18, y=-2,
color=colors.orange,
}
kind = "AuraTrigger",
spellIds = set({ 33206 }),
own = true, -- Pain Suppression
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 22,
y = -3,
color = colors.orange,
fadeTime = 10.0,
flashTime = 1.3,
},
},
{
kind="AuraTrigger", spellId=139, own=true, -- Renew
indicator={
kind="SquareIndicator",
size=14,
point="BOTTOMLEFT",
x=2, y=2,
color=colors.cyan,
}
kind = "AuraTrigger",
spellIds = set({ 41635 }),
own = true, -- Prayer of Mending
indicator = {
kind = "SquareIndicator",
size = 17,
point = "TOPLEFT",
x = 41,
y = -3,
color = { 0.7, 0.9, 1 },
showStacks = true,
},
},
{
kind="AuraTrigger", spellId=194384, own=true, -- Renew
indicator={
kind="SquareIndicator",
size=14,
point="BOTTOMLEFT",
x=18, y=2,
color=colors.white,
}
kind = "AuraTrigger",
spellIds = AURAS_PRIEST_RENEW,
own = true, -- Renew
indicator = {
kind = "SquareIndicator",
size = 17,
point = "BOTTOMLEFT",
x = 3,
y = 3,
color = colors.cyan,
fadeTime = 10.0,
},
},
{
kind="AuraTrigger", spellId=10060, own=true, -- Renew
indicator={
kind="SquareIndicator",
size=14,
point="BOTTOMLEFT",
x=34, y=2,
color=colors.violet,
}
kind = "AuraTrigger",
spellIds = set({ 194384 }),
own = true, -- Atonement
indicator = {
kind = "SquareIndicator",
size = 17,
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
return {}
return {
{
kind = "StatusTrigger",
status = "Burn",
invert = false,
indicator = {
kind = "BorderIndicator",
thickness = 3.0,
color = colors.red,
level = 0,
},
},
}
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 UnitFrame = types.UnitFrame
local UnitGroup = types.UnitGroup
@@ -198,7 +718,7 @@ function HideBlizzardFrames()
TargetFrame,
FocusFrame,
PartyFrame,
CompactRaidFrameContainer
CompactRaidFrameContainer,
}
for _, frame in ipairs(blizzardFrames) do
@@ -212,11 +732,13 @@ function CreateRaidFrames(left, top, config)
local width = config.size.width
local height = config.size.height
local group = UnitGroup:new(left, top, width, height)
for i=0,5 do
for j=0,4 do
local num = i*5 + j + 1
for i = 0, 5 do
for j = 0, 4 do
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
@@ -227,9 +749,13 @@ function CreatePartyFrames(left, top, config)
local group = UnitGroup:new(left, top, width, height)
local player = UnitFrame:new("player", config)
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)
group:AddUnitFrame(frame)
local partypet = UnitFrame:new("partypet" .. i, config)
group:AddUnitFrame(partypet)
end
group:Sort()
end
@@ -241,16 +767,16 @@ function CreateTargetFrames(left, top, config)
focus:SetPosition(left, top)
local target = UnitFrame:new("target", config)
target:SetPosition(left, top - height)
for i=1,4 do
for i = 1, 4 do
local boss = UnitFrame:new("boss" .. i, config)
boss:SetPosition(left, top-(i+1)*height)
boss:SetPosition(left, top - (i + 1) * height)
end
end
function CreateFrames()
config = {
local config = {
size = {
width = 110,
width = 111,
height = 45,
},
range = RangeConfig(),
@@ -259,10 +785,11 @@ function CreateFrames()
triggers = TriggerConfig(),
}
CreatePartyFrames(0, -290, config)
CreatePartyFrames(-0.05, -240.05, config)
config.hideInRaid = false
CreateRaidFrames(0, -290, config)
CreateTargetFrames(110*3+50, -245, config)
CreateRaidFrames(-0.05, -240.05, config)
CreateTargetFrames(110 * 3 + 50 - 0.05, -240.05, config)
HideBlizzardFrames()
end
omif.SetEventHandler("OMICRON_LOADING", CreateFrames)
+2 -2
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omif = select(2, ...)
-- Run all functions that start with Setup
+6 -4
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,13 +13,13 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local AddonName, omif = ...
omif.events = {} -- event (str) to list of handlers
omif.db = nil
OmicronFrames = {}
omif.modules = {public=OmicronFrames}
omif.modules = { public = OmicronFrames }
-- Simple function that creates or returns a local module table
function omif.GetModule(name)
if omif.modules[name] == nil then
@@ -29,7 +29,7 @@ function omif.GetModule(name)
end
-- Adds a given function to the event handler list for a given event
-- This will later in the addon
-- This will later in the addon
function omif.SetEventHandler(event, fn)
if omif.events[event] == nil then
omif.events[event] = {}
@@ -66,6 +66,7 @@ function omif.OnAddonLoaded(name)
omif.MainEventHandler(nil, "OMICRON_LOADING")
print("Loaded", AddonName)
end
omif.SetEventHandler("ADDON_LOADED", omif.OnAddonLoaded)
-- 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")
end
end
omif.SetEventHandler("PLAYER_ENTERING_WORLD", omif.FireFullyLoadedEvent)
function omif.SlashCommand(args)
+104 -43
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local addonName, omi = ...
-- Profiler is only active if the optional Commander dependency is included
@@ -26,56 +26,117 @@ local Printf = Commander.Printf
local types = omi.GetModule("types")
local data = {}
local startTime
local stopTime
local functionTimers = {}
local functionNames = {}
Commander.RegisterCommand("omi-pstart", {
description="Start/reset the OmicronFrames profiler",
command=function()
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
end
PrintLn("OmicronFrames: start profiling")
ResetCPUUsage()
-- Permanently wrap all functions to do profiling
local function EnableFunctionProfiler()
if #functionTimers ~= 0 then
return
end
})
Commander.RegisterCommand("omi-pstop", {
description="Stop the OmicronFrames profiler",
command=function()
PrintLn("OmicronFrames: stop profiling")
UpdateAddOnCPUUsage()
local total = GetAddOnCPUUsage(addonName)
data = {{
name = "Total OmicronFrames time",
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
})
local GetTimePreciseSec = GetTimePreciseSec
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
T[fnName] = wrapped
end
end
end
table.sort(data, function(a, b) return a.time > b.time 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", {
description = "Stop the OmicronFrames profiler",
command = function()
if not startTime then
PrintLn("Profiling not started. Run omi-pstart first.")
return
end
PrintLn("OmicronFrames: stop profiling")
local profiler = C_CVar.GetCVar("scriptProfile") == "1"
data = {}
stopTime = GetTimePreciseSec()
if profiler then
UpdateAddOnCPUUsage()
local total = GetAddOnCPUUsage(addonName)
table.insert(data, {
name = "Total OmicronFrames time",
time = total,
})
end
for i, time in ipairs(functionTimers) do
local name = functionNames[i]
table.insert(data, {
name = name,
time = time * 1000,
})
end
table.sort(data, function(a, b)
return a.time < b.time
end)
end,
})
Commander.RegisterCommand("omi-pprint", {
description="Print the collected OmicronFrames profiling data",
command=function()
for _, item in ipairs(data) do
Printf("% 5.1f%% % 5fms %s\n", item.pct*100, item.time, item.name)
description = "Print the collected OmicronFrames profiling data",
command = function()
if not stopTime then
PrintLn("Profiling data not collected yet. Run omi-pstop first.")
return
end
end
local top = data[#data].time
local total = stopTime - startTime
for _, item in ipairs(data) do
local time = item.time
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,
})
+139 -26
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,9 +13,11 @@
-- 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/>.
-- 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
@@ -24,27 +26,119 @@ types.AuraList = AuraList
local statusLists = {
Immune = {
[642] = true, -- Divine Shield, Paladin
[186265] = true -- Aspect of the Turtle, Hunter
[642] = true, -- Divine Shield, Paladin
[186265] = true, -- Aspect of the Turtle, Hunter
[45438] = true, -- Ice Block
[31224] = true, -- Cloak of Shadows
[196555] = true, -- Netherwalk
},
Bomb = {
[642] = true, -- Divine Shield, Paladin
[186265] = true, -- Aspect of the Turtle, Hunter
[381615] = true, -- Raszageth, Static Charge
[377467] = true -- Raszageth, Fulminating Charge
[381615] = true, -- Raszageth, Static 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 = {
[642] = true, -- Divine Shield, Paladin
[186265] = true -- Aspect of the Turtle, Hunter
}
-- Jade Serpent Temple
[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
-- callback function with UnitAuraInfo table as argument
local function ForEachAuraSlots(unit, fn, continuationToken, ...)
local GetAuraDataBySlot = C_UnitAuras.GetAuraDataBySlot
local n = select('#', ...)
for i=1, n do
local n = select("#", ...)
for i = 1, n do
local slot = select(i, ...)
fn(GetAuraDataBySlot(unit, slot))
end
@@ -53,11 +147,11 @@ end
-- Helper function that goes over all aura slots for a given filter
local function ForEachAuraFiltered(unit, filter, fn)
local UnitAuraSlots = UnitAuraSlots
local UnitAuraSlots = C_UnitAuras.GetAuraSlots
local continuationToken = nil
repeat
continuationToken = ForEachAuraSlots(unit, fn, UnitAuraSlots(unit, filter, nil, continuationToken))
until (continuationToken == nil)
until continuationToken == nil
end
-- 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)
end
---@return AuraList
function AuraList.new(cls, ...)
--- I really dislike duplicating this everywhere but it makes
@@ -84,27 +177,40 @@ function AuraList:Init(unitframe)
self.statusCount = {}
self.auras = {} -- map AuraInstanceID to UnitAuraInfo table
self.triggers = {}
self.triggersBySpell = {}
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
@@ -179,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
@@ -213,8 +319,15 @@ function AuraList:RemoveAura(iid)
return statusChanged
end
-- FIXME: param wrong
---@param info UnitAuraInfo
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 statusChanged = false
for _, aura in ipairs(info.addedAuras or {}) do
+12 -4
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
@@ -70,17 +70,26 @@ end
--- Show the square indicator.
---@param data table
function BorderIndicator:Show(data)
if data.color then
self.color = data.color
end
local frame, stacks = self:GetFrame()
frame:Show()
end
function BorderIndicator:Update(data)
if data.color then
self:SetColor(data.color)
end
end
function BorderIndicator:GetFrame()
local frame, new = borderPool:Acquire()
if new then
frame.backdropInfo = {
edgeFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\pixel_edge",
edgeSize = 1,
insets = {left=0, right=0, top=0, bottom=0},
insets = { left = 0, right = 0, top = 0, bottom = 0 },
}
end
@@ -99,4 +108,3 @@ function BorderIndicator:Hide()
self.frame = nil
borderPool:Release(frame)
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
+5 -9
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
local AuraTrigger = types.AuraTrigger
@@ -26,14 +26,10 @@ local Indicator = types.CreateClass("Indicator")
types.Indicator = Indicator
--- Show the indicator and supply it with data
function Indicator:Show(data)
end
function Indicator:Show(data) end
--- Update the indicator with new data
function Indicator:Update(data)
end
function Indicator:Update(data) end
--- Hide the indicator
function Indicator:Hide()
end
function Indicator:Hide() end
+50 -6
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
@@ -45,7 +45,9 @@ end
---@param y number
---@param color number[]
---@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.frameParent = unitframe.overlays
self.size = size
@@ -54,6 +56,8 @@ function SquareIndicator:Init(unitframe, size, point, x, y, color, showStacks)
self.y = y
self.color = color
self.showStacks = showStacks
self.fadeTime = fadeTime
self.flashTime = flashTime
end
--- Set the color of the Square Indicator
@@ -61,7 +65,7 @@ end
function SquareIndicator:SetColor(color)
self.color = color
local frame = self.frame
if frame then
if frame then
frame:SetBackdropColor(unpack(color))
end
end
@@ -77,7 +81,18 @@ function SquareIndicator:Show(data)
stacks:SetText(numStacks)
stacks:Show()
end
frame:SetBackdropColor(unpack(self.color))
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))
end
frame:Show()
end
@@ -89,7 +104,7 @@ function SquareIndicator:GetFrame()
bgFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\square_white",
edgeFile = "Interface\\Addons\\OmicronFrames\\media\\textures\\pixel_edge",
edgeSize = 2,
insets = {left=0, right=0, top=0, bottom=0},
insets = { left = 0, right = 0, top = 0, bottom = 0 },
}
frame:ApplyBackdrop()
@@ -104,17 +119,45 @@ function SquareIndicator:GetFrame()
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 SquareIndicator:Update(data)
if data.color then
self:SetColor(data.color)
end
local numStacks = data.stacks or 0
if numStacks > 0 then
self.stacks:SetText(numStacks)
self.stacks:Show()
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
--- Hide the square indicator.
@@ -123,5 +166,6 @@ function SquareIndicator:Hide()
self.stacks:Hide()
self.frame = nil
self.stacks = nil
frame:SetScript("OnUpdate", nil)
squarePool:Release(frame)
end
+2 -2
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
---@class Object
+26 -16
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,15 +13,17 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
types.StatusBar = types.CreateClass("StatusBar")
local StatusBar = types.StatusBar
function StatusBar:Init(parent, width, height, level, top)
if top == nil then top = true end
function StatusBar:Init(parent, width, height, level, top, texture, bgtexture)
if top == nil then
top = true
end
level = level or 0
-- parent: the parent frame
self.parent = parent
@@ -30,7 +32,7 @@ function StatusBar:Init(parent, width, height, level, top)
local bar = CreateFrame("StatusBar", nil, parentFrame)
bar:SetFrameStrata("MEDIUM")
bar:SetFrameLevel(level)
if top then
bar:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", 0, 0)
else
@@ -38,16 +40,18 @@ function StatusBar:Init(parent, width, height, level, top)
end
bar:SetSize(width, height)
bar:SetStatusBarTexture("Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal")
bar:SetStatusBarTexture(texture)
bar:GetStatusBarTexture():SetHorizTile(false)
bar:GetStatusBarTexture():SetVertTile(false)
self.bar = bar
local bg = bar:CreateTexture(nil, "BACKGROUND")
bg:SetTexture("Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal")
bg:SetAllPoints(true)
self.bg = bg
if bgtexture ~= nil then
local bg = bar:CreateTexture(nil, "BACKGROUND")
bg:SetTexture(bgtexture)
bg:SetAllPoints(true)
self.bg = bg
end
self:SetRange(0, 1)
self:SetValue(1)
bar:Show()
@@ -61,6 +65,10 @@ function StatusBar:SetValue(value)
self.bar:SetValue(value)
end
function StatusBar:SetFillStyle(style)
self.bar:SetFillStyle(style)
end
function StatusBar:Show()
self.bar:Show()
end
@@ -70,13 +78,15 @@ function StatusBar:Hide()
end
function StatusBar:SetInterpolatedColor(start, stop, progress)
local r = start[1] + (stop[1] - start[1])*progress
local g = start[2] + (stop[2] - start[2])*progress
local b = start[3] + (stop[3] - start[3])*progress
local r = start[1] + (stop[1] - start[1]) * progress
local g = start[2] + (stop[2] - start[2]) * progress
local b = start[3] + (stop[3] - start[3]) * progress
self:SetColor(r, g, b)
end
function StatusBar:SetColor(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
+70 -20
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
---@class Trigger
@@ -21,25 +21,20 @@ 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)
types.AuraTrigger = AuraTrigger
---Creates a new AuraTrigger from a config description and attaches it to the
---correct data source
---@param unit UnitFrame
---@param config table
---@return AuraTrigger
function AuraTrigger.CreateFromConfig(unit, config)
local trigger = AuraTrigger:new(
config.spellId,
config.own,
config.requiredCount,
config.invert
)
local trigger =
AuraTrigger:new(config.spellIds, config.own, config.requiredCount, config.invert, config.defaultData)
unit.auras:AddTrigger(trigger)
return trigger
end
@@ -53,28 +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
function AuraTrigger:Init(spellId, own, requiredCount, invert)
Trigger.Init(self, invert)
self.spellId = spellId
---@param defaultData table | nil
function AuraTrigger:Init(spellIds, own, requiredCount, invert, defaultData)
Trigger.Init(self, invert, defaultData)
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, nil, 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
@@ -83,23 +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:SetState(self.count >= self.requiredCount, {stacks=aura.applications}, 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)
if self.active then
self.indicator:Update({stacks=after.applications})
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
@@ -108,6 +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:SetState(self.count >= self.requiredCount, nil, false)
local newData = self:BestAuraData(nil)
self:SetStateAndData(self.count >= self.requiredCount, newData)
end
+13 -9
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
@@ -26,7 +26,7 @@ local Trigger = types.Trigger
---indicator.
---@class MultiTrigger: Trigger
---@field active boolean
---@field invert boolean
---@field invert boolean
local MultiTrigger = types.CreateClass("MultiTrigger", Trigger)
types.MultiTrigger = MultiTrigger
@@ -73,7 +73,7 @@ function MultiTrigger:AddTrigger(trigger)
end,
Hide = function(_, data)
self:OnChildDeactivate(idx, trigger, data)
end
end,
})
end
@@ -92,7 +92,7 @@ function MultiTrigger:OnChildActivate(idx, trigger, data)
table.sort(activeChildren)
-- The highest priority active trigger has changed
if activeChild ~= activeChildren[1] then
self:SetState(true, data)
self:SetStateAndData(true, data)
end
end
@@ -113,8 +113,13 @@ function MultiTrigger:OnChildDeactivate(idx, trigger, data)
end
end
table.remove(activeChildren, found)
if activeChild ~= activeChildren[1] then
self:SetState(activeChildren[1] ~= nil, childrenData[1], true)
local newActiveChild = activeChildren[1]
if activeChild ~= newActiveChild then
if activeChildren[1] ~= nil then
self:SetStateAndData(true, childrenData[newActiveChild])
else
self:SetStateAndData(false, {})
end
end
end
@@ -131,6 +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
+7 -8
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
---@class Trigger
@@ -29,8 +29,7 @@ types.StatusTrigger = StatusTrigger
---@param unit UnitFrame
---@param config table
function StatusTrigger.CreateFromConfig(unit, config)
local trigger = StatusTrigger:new(config.status, config.requiredCount,
config.invert)
local trigger = StatusTrigger:new(config.status, config.requiredCount, config.invert, config.defaultData)
unit.auras:AddTrigger(trigger)
return trigger
end
@@ -45,10 +44,10 @@ end
--- Initialize a new AuraTrigger object
---@param status string The kind of status to trigger on
---@param requiredCount number | nil
---@param requiredCount number | nil
---@param invert boolean | nil
function StatusTrigger:Init(status, requiredCount, invert)
Trigger.Init(self, invert)
function StatusTrigger:Init(status, requiredCount, invert, defaultData)
Trigger.Init(self, invert, defaultData)
self.status = status
self.requiredCount = requiredCount or 1
self.invert = invert or false
@@ -59,5 +58,5 @@ end
-- status:
-- Must be a valid UnitAuraInfo
function StatusTrigger:UpdateStatus(count)
self:SetState(count >= self.requiredCount, nil, false)
self:SetStateAndData(count >= self.requiredCount, nil)
end
+43 -15
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,20 +13,21 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
--- Triggers objects provide a link between indicators and some data source. They
-- can pass data from the data source into the indicator. This is a supertype for
-- all triggers and should not be constructed on its own.
-- can pass data from the data source into the indicator. This is a supertype for
-- all triggers and should not be constructed on its own.
---@class Trigger: Object
---@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")
types.Trigger = Trigger
---@param unit UnitFrame
---@param config table
---@return Trigger
@@ -36,10 +37,27 @@ end
--- Initialize a new Trigger object
---@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
self.invert = 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
--- Set the target of the Trigger.
@@ -49,30 +67,40 @@ end
function Trigger:SetTarget(target)
self.indicator = target
if self.active then
target:Show()
target:Show(self.data)
end
end
---Set the state of the trigger. Will activate the indicator if needed.
---@param data table<string,any> | nil
---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, 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
if self.active ~= state then
if state then
self.indicator:Show(data)
self.indicator:Show(self.data)
else
self.indicator:Hide()
end
self.active = state
elseif doUpdate and self.active then
self.indicator:Update(data)
self.indicator:Update(self.data)
end
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, nil, false)
self:SetStateAndData(false, nil)
end
+4 -4
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omif = select(2, ...)
local types = omif.GetModule("types")
@@ -24,7 +24,7 @@ local function CreateClassRaw(name, parent)
c.__typeInfo = {
name = name,
class = c,
parent = parent
parent = parent,
}
c.__index = c
if parent ~= nil then
@@ -48,6 +48,6 @@ function types.IsDerivedFrom(a, b)
return true
end
current = current.__typeInfo.parent
until current == nil;
until current == nil
return false
end
+231 -67
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,10 +13,15 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
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
@@ -26,6 +31,9 @@ local AuraList = types.AuraList
---@class SquareIndicator
local SquareIndicator = types.SquareIndicator
---@class IconIndicator
local IconIndicator = types.IconIndicator
---@class SquareIndicator
local BorderIndicator = types.BorderIndicator
@@ -33,26 +41,42 @@ local BorderIndicator = types.BorderIndicator
local UnitFrame = types.CreateClass("UnitFrame")
types.UnitFrame = UnitFrame
local colors = {
hostile = {0.5, 0.0, 0.0},
neutral = {0.7, 0.7, 0.0},
healthy = {0, 0.1, 0},
high = {0, 1.0, 0},
mid = {1.0, 1.0, 0},
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}
hostile = { 0.5, 0.0, 0.0 },
neutral = { 0.7, 0.7, 0.0 },
healthy = { 0, 0.1, 0 },
high = { 0, 1.0, 0 },
mid = { 1.0, 1.0, 0 },
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 },
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 config table
function UnitFrame:Init(unit, config)
@@ -71,9 +95,25 @@ function UnitFrame:Init(unit, config)
self:SetMouseBinds(config.mouse)
self.hp = StatusBar:new(self, width, height, 0, true)
self.power = StatusBar:new(self, width, 6, 2, false)
local texture = "Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal"
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.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)
local overlays = CreateFrame("Frame", nil, secure)
@@ -86,6 +126,8 @@ function UnitFrame:Init(unit, config)
self:CreateTriggers(config.triggers)
self:InitDebuffs()
self:RegisterEvents()
self:Enable()
end
@@ -110,14 +152,23 @@ function UnitFrame:CreateIndicator(indicator)
indicator.x,
indicator.y,
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
return BorderIndicator:new(
self,
indicator.thickness,
indicator.color
)
return BorderIndicator:new(self, indicator.thickness, indicator.color)
else
error(string.format("Invalid Indicator kind `%s` requested", indicator.kind))
end
@@ -127,7 +178,7 @@ function UnitFrame:StartRangeTicker()
if self.rangeTicker then
return
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:UpdateRange()
end)
@@ -138,10 +189,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()
@@ -157,7 +206,7 @@ end
-- Returns
-- true if the unit is in range
-- false if the unit is not in range
-- nil if it could not be determined
-- nil if it could not be determined
function UnitFrame:IsInRange()
local unit = self.unit
local friendlySpell = self.rangeFriendly
@@ -165,12 +214,12 @@ function UnitFrame:IsInRange()
-- Prefer to use configured spells
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
return IsSpellInRange(enemySpell, unit) == 1
return C_Spell.IsSpellInRange(enemySpell, unit) == true
end
-- Fall back to raid/party only range check
-- Fall back to raid/party only range check
local inRange, checkedRange = UnitInRange(unit)
if checkedRange then
return inRange
@@ -181,7 +230,7 @@ end
function UnitFrame:UpdateRange()
local unit = self.unit
if UnitIsDeadOrGhost(unit) then
self.secureFrame:SetAlpha(1.0)
return
@@ -235,7 +284,7 @@ function UnitFrame:PrepareWheelBinds(bindings)
-- scroll wheel to the current unit frame button when the mouse enters and
-- to unbind it when the mouse leaves. When the keybind is triggered the
-- button will receive a click with a custom button name
-- Build a table with all lines of the scroll wheel + modifier combinations
-- we want to bind, then concat it with newlines into a full script
local unit = self.unit
@@ -246,14 +295,20 @@ function UnitFrame:PrepareWheelBinds(bindings)
if bind.button == "wheel-up" or bind.button == "wheel-down" then
local prefix = ModifiersToPrefix(bind.mods)
local button = bind.button == "wheel-up" and "MOUSEWHEELUP" or "MOUSEWHEELDOWN"
table.insert(bindScript,
string.format([[self:SetBindingClick(false, "%s%s", "OmicronSecureFrame%s", "%s%s")]],
prefix, button, unit, prefix, bind.button
)
table.insert(
bindScript,
string.format(
[[self:SetBindingClick(false, "%s%s", "OmicronSecureFrame%s", "%s%s")]],
prefix,
button,
unit,
prefix,
bind.button
)
)
end
end
local secure = self.secureFrame
local bindScript = table.concat(bindScript, "\n")
@@ -272,7 +327,7 @@ function UnitFrame:SetMouseBinds(binds)
secure:RegisterForClicks("AnyDown")
self:PrepareWheelBinds(binds)
for _, bind in ipairs(binds) do
local prefix = ModifiersToPrefix(bind.mods)
local button
@@ -295,7 +350,7 @@ function UnitFrame:SetMouseBinds(binds)
if bind.kind == "macro" then
self:SetMacroAction(button, bind.data)
elseif bind.kind == "spell" then
self:SetSpellAction(button, bind.data)
self:SetSpellAction(button, bind.data)
else
secure:SetAttribute(button, bind.kind)
end
@@ -339,10 +394,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
@@ -389,10 +443,13 @@ function UnitFrame:RegisterEvents()
end
end
secure:RegisterEvent("GROUP_ROSTER_UPDATE")
secure:RegisterUnitEvent("UNIT_AURA", unit)
secure:RegisterUnitEvent("UNIT_HEALTH", unit)
secure:RegisterUnitEvent("UNIT_MAXHEALTH", unit)
secure:RegisterUnitEvent("UNIT_NAME_UPDATE", unit)
secure:RegisterUnitEvent("UNIT_ABSORB_AMOUNT_CHANGED", unit)
secure:RegisterUnitEvent("UNIT_HEAL_ABSORB_AMOUNT_CHANGED", unit)
end
-- returns whether or not the unit guid has changed since the last call to this
@@ -413,6 +470,97 @@ function UnitFrame:HasUnitChanged()
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)
if self:HasUnitChanged() then
self:UpdateAll(true)
@@ -421,6 +569,8 @@ function UnitFrame:UNIT_AURA(unit, info)
if self.auras:Update(info) then
self:UpdateHealthColor()
end
self:UpdateDebuffs()
end
function UnitFrame:UNIT_HEALTH()
@@ -455,7 +605,7 @@ end
function UnitFrame:ROLE_CHANGED_INFORM(name, changer, old, new)
if UnitName(self.unit) == name then
self:UpdateRole(new)
self:UpdateRole(new)
end
end
@@ -519,9 +669,12 @@ function UnitFrame:UpdateAll(unitChanged)
end
self:UpdateHealth()
self:UpdateHealthColor()
self:UpdateAbsorb()
self:UpdateShield()
self:UpdateRange()
self:UpdateName()
self:UpdateRole() -- Also calls UpdatePower if power is visible
self:UpdateDebuffs()
end
function UnitFrame:UpdateHealth()
@@ -533,6 +686,32 @@ function UnitFrame:UpdateHealth()
self.hp:SetValue(val)
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()
local unit = self.unit
local val = UnitHealth(unit)
@@ -563,7 +742,7 @@ function UnitFrame:UpdateHealthColor()
elseif isFriend and pct >= 0.90 then
self.hp:SetColor(unpack(colors.healthy))
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)
else
local progress = pct / 0.75
@@ -580,25 +759,10 @@ function UnitFrame:UpdateName()
-- 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
-- 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)
else
self.name:SetTextColor(1, 1, 1)
end
self.name:SetText(UnitName(self.unit):sub(1, 5))
self.name:SetText(utf8_truncate((UnitName(self.unit)), 5))
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
]]--
+22 -21
View File
@@ -1,7 +1,7 @@
-- Copyright 2023 <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)
@@ -13,7 +13,7 @@
-- 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/>.
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
local omi = select(2, ...)
local types = omi.GetModule("types")
local UnitGroup = types.CreateClass("UnitGroup")
@@ -32,13 +32,14 @@ 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
-- players < non-players
local player = UnitIsPlayer(lunit)
if UnitIsPlayer(lunit) ~= UnitIsPlayer(runit) then
@@ -50,16 +51,16 @@ local function UnitFrameDefaultCompare(lhs, rhs)
if UnitIsUnit(lunit, "player") ~= UnitIsUnit(runit, "player") then
return UnitIsUnit(lunit, "player")
end
-- tank < healer < damage < ?
local lrole = RoleValue(UnitGroupRolesAssigned(lunit))
local rrole = RoleValue(UnitGroupRolesAssigned(runit))
if lrole ~= rrole then
return lrole < rrole
end
end
return lunit < runit
end
@@ -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)
@@ -121,14 +122,14 @@ function UnitGroup:Sort()
table.sort(self.units, UnitFrameDefaultCompare)
local left, top, width, height = self.left, self.top, self.width, self.height
local maxFrameIndex = #self.units
for y=0,5 do
for x=0,4 do
local num = y*5 + x + 1
for y = 0, 5 do
for x = 0, 4 do
local num = y * 5 + x + 1
if num > maxFrameIndex then
return
end
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