Add files via upload
This commit is contained in:
@@ -1,15 +1,4 @@
|
||||
# Cursive - updated for octowow
|
||||
-Requires nampower https://github.com/Emyrk/nampower
|
||||
|
||||
-Added global cooldown bar can change size/color in settings
|
||||
|
||||
-Added ghost icons for DOTs can toggle on/off in settings
|
||||
|
||||
<img width="946" height="779" alt="image" src="https://github.com/user-attachments/assets/6d73aa87-e928-4a95-8562-17f8311a8d80" />
|
||||
<img width="476" height="523" alt="image" src="https://github.com/user-attachments/assets/328e1a25-2a98-4c23-81a3-0572fb56127c" />
|
||||
|
||||
|
||||
|
||||
# Cursive
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
|
||||
+130
@@ -18,6 +18,14 @@ Cursive:RegisterDefaults("profile", {
|
||||
showgcdbar = true,
|
||||
gcdbarheight = 3,
|
||||
gcdbarwidth = 200,
|
||||
gcdbarcolor = { r = 1, g = 0.85, b = 0.1 },
|
||||
showghostcurse = true,
|
||||
showghostcorruption = true,
|
||||
showghostimmolate = true,
|
||||
showghostsiphonlife = true,
|
||||
ghosticonalpha = 0.4,
|
||||
showallmissingghosts = false,
|
||||
flashonexpiring = true,
|
||||
showtargetindicator = true,
|
||||
showraidicons = true,
|
||||
showhealthbar = true,
|
||||
@@ -82,6 +90,19 @@ local function splitIgnoreString(str, delimiter)
|
||||
end
|
||||
|
||||
local barOptions = {
|
||||
["flashonexpiring"] = {
|
||||
type = "toggle",
|
||||
name = "Flash Icon On Expiring",
|
||||
desc = "Flash a curse/DoT icon when it's about to fall off (same threshold as the expiring sound)",
|
||||
order = 96,
|
||||
get = function()
|
||||
return Cursive.db.profile.flashonexpiring
|
||||
end,
|
||||
set = function(v)
|
||||
Cursive.db.profile.flashonexpiring = v
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end,
|
||||
},
|
||||
["invertbars"] = {
|
||||
type = "toggle",
|
||||
name = "Invert Bar Display",
|
||||
@@ -370,6 +391,90 @@ local barOptions = {
|
||||
},
|
||||
}
|
||||
|
||||
local ghostOptions = {
|
||||
["showghostcurse"] = {
|
||||
type = "toggle",
|
||||
name = "Show Ghost Curse Icon",
|
||||
desc = "Show a faded icon suggesting a curse to cast when the target has none",
|
||||
order = 1,
|
||||
get = function()
|
||||
return Cursive.db.profile.showghostcurse
|
||||
end,
|
||||
set = function(v)
|
||||
Cursive.db.profile.showghostcurse = v
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end,
|
||||
},
|
||||
["showghostcorruption"] = {
|
||||
type = "toggle",
|
||||
name = "Show Ghost Corruption",
|
||||
desc = "Show a faded icon suggesting Corruption when missing from the target",
|
||||
order = 2,
|
||||
get = function()
|
||||
return Cursive.db.profile.showghostcorruption
|
||||
end,
|
||||
set = function(v)
|
||||
Cursive.db.profile.showghostcorruption = v
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end,
|
||||
},
|
||||
["showghostimmolate"] = {
|
||||
type = "toggle",
|
||||
name = "Show Ghost Immolate",
|
||||
desc = "Show a faded icon suggesting Immolate when missing from the target",
|
||||
order = 3,
|
||||
get = function()
|
||||
return Cursive.db.profile.showghostimmolate
|
||||
end,
|
||||
set = function(v)
|
||||
Cursive.db.profile.showghostimmolate = v
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end,
|
||||
},
|
||||
["showghostsiphonlife"] = {
|
||||
type = "toggle",
|
||||
name = "Show Ghost Siphon Life",
|
||||
desc = "Show a faded icon suggesting Siphon Life when missing from the target",
|
||||
order = 4,
|
||||
get = function()
|
||||
return Cursive.db.profile.showghostsiphonlife
|
||||
end,
|
||||
set = function(v)
|
||||
Cursive.db.profile.showghostsiphonlife = v
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end,
|
||||
},
|
||||
["ghostalpha"] = {
|
||||
type = "range",
|
||||
name = "Ghost Icon Transparency",
|
||||
desc = "How faded the ghost icons appear (higher = more visible)",
|
||||
order = 5,
|
||||
min = 10,
|
||||
max = 100,
|
||||
step = 5,
|
||||
get = function()
|
||||
return math.floor(Cursive.db.profile.ghosticonalpha * 100 + 0.5)
|
||||
end,
|
||||
set = function(v)
|
||||
Cursive.db.profile.ghosticonalpha = v / 100
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end,
|
||||
},
|
||||
["showallmissingghosts"] = {
|
||||
type = "toggle",
|
||||
name = "Show All Missing (not just next)",
|
||||
desc = "Show a ghost icon for every missing curse/DoT at once, instead of just the next one in priority order",
|
||||
order = 6,
|
||||
get = function()
|
||||
return Cursive.db.profile.showallmissingghosts
|
||||
end,
|
||||
set = function(v)
|
||||
Cursive.db.profile.showallmissingghosts = v
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
local mobFilters = {
|
||||
["incombat"] = {
|
||||
type = "toggle",
|
||||
@@ -582,6 +687,7 @@ Cursive.cmdtable = {
|
||||
Cursive.db.profile.gcdbarheight = v
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end
|
||||
Cursive.ui.PreviewGcdBar()
|
||||
end,
|
||||
},
|
||||
["gcdbarwidth"] = {
|
||||
@@ -600,6 +706,23 @@ Cursive.cmdtable = {
|
||||
Cursive.db.profile.gcdbarwidth = v
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
end
|
||||
Cursive.ui.PreviewGcdBar()
|
||||
end,
|
||||
},
|
||||
["gcdbarcolor"] = {
|
||||
type = "color",
|
||||
name = "GCD Bar Color",
|
||||
desc = "Color of the GCD swing-timer bar (shows a preview fill while you adjust it)",
|
||||
order = 43,
|
||||
get = function()
|
||||
return Cursive.db.profile.gcdbarcolor.r, Cursive.db.profile.gcdbarcolor.g, Cursive.db.profile.gcdbarcolor.b
|
||||
end,
|
||||
set = function(r, g, b)
|
||||
Cursive.db.profile.gcdbarcolor.r = r
|
||||
Cursive.db.profile.gcdbarcolor.g = g
|
||||
Cursive.db.profile.gcdbarcolor.b = b
|
||||
Cursive.UpdateFramesFromConfig()
|
||||
Cursive.ui.PreviewGcdBar()
|
||||
end,
|
||||
},
|
||||
["clickthrough"] = {
|
||||
@@ -652,6 +775,13 @@ Cursive.cmdtable = {
|
||||
order = 13,
|
||||
args = barOptions
|
||||
},
|
||||
["ghosticons"] = {
|
||||
type = "group",
|
||||
name = "Ghost Icon Settings",
|
||||
desc = "Ghost Icon Settings",
|
||||
order = 14,
|
||||
args = ghostOptions
|
||||
},
|
||||
["filters"] = {
|
||||
type = "group",
|
||||
name = L["Mob filters"],
|
||||
|
||||
@@ -8,6 +8,9 @@ local utils = Cursive.utils
|
||||
local filter = Cursive.filter
|
||||
|
||||
local ui = CreateFrame("Frame", "CursiveUI", UIParent)
|
||||
-- Registry of currently-expiring curse icon frames that should flash.
|
||||
-- Populated/cleared during row updates, animated by a shared ticker below.
|
||||
ui.flashingCurseIcons = {}
|
||||
|
||||
ui.border = {
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
@@ -107,6 +110,7 @@ local function UpdateRootBarFrame()
|
||||
ui.rootBarFrame.gcdBar:SetHeight(config.gcdbarheight)
|
||||
ui.rootBarFrame.gcdBar:SetWidth(config.gcdbarwidth)
|
||||
ui.rootBarFrame.gcdBar:SetStatusBarTexture(config.bartexture)
|
||||
ui.rootBarFrame.gcdBar:SetStatusBarColor(config.gcdbarcolor.r, config.gcdbarcolor.g, config.gcdbarcolor.b, 0.9)
|
||||
ui.rootBarFrame.gcdBar.enabled = config.showgcdbar
|
||||
if not config.showgcdbar then
|
||||
ui.rootBarFrame.gcdBar:Hide()
|
||||
@@ -158,7 +162,7 @@ local function CreateRoot()
|
||||
gcdBar:SetWidth(Cursive.db.profile.gcdbarwidth)
|
||||
gcdBar:SetPoint("TOPLEFT", frame.caption, "BOTTOMLEFT", -8, -2)
|
||||
gcdBar:SetStatusBarTexture(Cursive.db.profile.bartexture)
|
||||
gcdBar:SetStatusBarColor(1, .85, .1, .9)
|
||||
gcdBar:SetStatusBarColor(Cursive.db.profile.gcdbarcolor.r, Cursive.db.profile.gcdbarcolor.g, Cursive.db.profile.gcdbarcolor.b, 0.9)
|
||||
gcdBar:SetMinMaxValues(0, 1)
|
||||
gcdBar:SetValue(0)
|
||||
gcdBar.enabled = Cursive.db.profile.showgcdbar
|
||||
@@ -496,6 +500,21 @@ local function CreateBarThirdSection(unitFrame, guid)
|
||||
curse:Hide()
|
||||
unitFrame["curse" .. i] = curse
|
||||
end
|
||||
|
||||
-- Ghost icons: faded icons showing missing curse/DoT(s) to cast on
|
||||
-- this target. One frame per possible slot (same count as real curse
|
||||
-- icons), so "show all missing" mode can display several at once.
|
||||
-- Repositioned dynamically on each refresh to slot into wherever the
|
||||
-- next real curse icon would actually go.
|
||||
unitFrame.ghostIcons = {}
|
||||
for i = 1, config.maxcurses do
|
||||
local ghost = thirdSection:CreateTexture(nil, "OVERLAY")
|
||||
ghost:SetWidth(config.curseiconsize)
|
||||
ghost:SetHeight(config.curseiconsize)
|
||||
ghost:SetAlpha(config.ghosticonalpha)
|
||||
ghost:Hide()
|
||||
unitFrame.ghostIcons[i] = ghost
|
||||
end
|
||||
end
|
||||
|
||||
local function CreateBar(row, col, guid)
|
||||
@@ -551,6 +570,70 @@ local function hasAnySpellId(guid, spellIds)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Priority list for the ghost icon: checks curse presence first (via
|
||||
-- Cursive's own tracked curse data), then DoTs in order, returning the
|
||||
-- icon path for whichever is missing first. Curse icon textures (CoE/CoS/
|
||||
-- CoR/CoW) are confirmed-correct from prior testing; the DoT textures and
|
||||
-- the Curse of Agony fallback below are reasonable but UNVERIFIED guesses
|
||||
-- -- worth checking in-game and reporting back if any look wrong.
|
||||
local GHOST_DOT_ICONS = {
|
||||
{ name = "Corruption", texture = "Spell_Shadow_AbominationExplosion", settingKey = "showghostcorruption" },
|
||||
{ name = "Immolate", texture = "Spell_Fire_Immolation", settingKey = "showghostimmolate" },
|
||||
{ name = "Siphon Life", texture = "Spell_Shadow_Requiem", settingKey = "showghostsiphonlife" },
|
||||
}
|
||||
local GHOST_CURSE_FALLBACK_ICON = "Interface\\Icons\\Spell_Shadow_CurseOfSargeras" -- Curse of Agony
|
||||
|
||||
local function GetGhostTextures(guid)
|
||||
local config = Cursive.db.profile
|
||||
local textures = {}
|
||||
|
||||
-- Curse check: specifically checks Curse of Agony via Cursive's own
|
||||
-- tested HasCurse API, rather than a custom table check. This matters
|
||||
-- for Malediction warlocks -- casting Elements/Shadow/Recklessness also
|
||||
-- applies Agony, so "is any curse present" isn't the right question;
|
||||
-- what matters is whether Agony itself has fallen off and needs
|
||||
-- refreshing, even if another curse is technically still ticking.
|
||||
-- malediction=0 here since we're already asking about Agony directly,
|
||||
-- not one of the curses that would normally redirect to it.
|
||||
if config.showghostcurse then
|
||||
local hasCurse = Cursive.curses:HasCurse(L["curse of agony"], guid, 0, 0)
|
||||
if not hasCurse then
|
||||
table.insert(textures, GHOST_CURSE_FALLBACK_ICON)
|
||||
if not config.showallmissingghosts then
|
||||
return textures
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- DoT check: direct debuff scan via SuperWoW's GUID-based UnitDebuff,
|
||||
-- since these aren't part of Cursive's own curse tracking. Each DoT
|
||||
-- checked individually against its own toggle, since not everyone
|
||||
-- runs all three (e.g. pure Affliction warlocks often skip Immolate).
|
||||
for _, dot in ipairs(GHOST_DOT_ICONS) do
|
||||
if config[dot.settingKey] then
|
||||
local found = false
|
||||
local i = 1
|
||||
while true do
|
||||
local tex = UnitDebuff(guid, i)
|
||||
if not tex then break end
|
||||
if string.find(tex, dot.texture, 1, true) then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
if not found then
|
||||
table.insert(textures, "Interface\\Icons\\" .. dot.texture)
|
||||
if not config.showallmissingghosts then
|
||||
return textures
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return textures -- empty if everything's up (or all checks disabled)
|
||||
end
|
||||
|
||||
local function GetSortedCurses(guidCurses)
|
||||
-- Collect keys
|
||||
local curseNames = {}
|
||||
@@ -632,6 +715,8 @@ local function DisplayGuid(guid)
|
||||
local curse = unitFrame["curse" .. i]
|
||||
curse:Hide()
|
||||
curse.timer:Hide()
|
||||
ui.flashingCurseIcons[curse] = nil
|
||||
curse:SetAlpha(1)
|
||||
end
|
||||
|
||||
local guidCurses = Cursive.curses.guids[guid]
|
||||
@@ -657,18 +742,63 @@ local function DisplayGuid(guid)
|
||||
curse.timer:Show()
|
||||
curse:Show()
|
||||
|
||||
if remaining < 1 then
|
||||
if remaining < 1 then
|
||||
if Cursive.curses:ShouldPlayExpiringSound(curseName, guid) then
|
||||
PlaySoundFile("Interface\\AddOns\\Cursive\\sounds\\expiring.mp3")
|
||||
end
|
||||
elseif Cursive.curses:HasRequestedExpiringSound(curseName, guid) then
|
||||
Cursive.curses:EnableExpiringSound(curseName, guid)
|
||||
else
|
||||
if Cursive.curses:HasRequestedExpiringSound(curseName, guid) then
|
||||
Cursive.curses:EnableExpiringSound(curseName, guid)
|
||||
end
|
||||
end
|
||||
|
||||
-- Flash check: independent, wider threshold (<=1 rather than <1)
|
||||
-- since remaining is an integer-quantized value that may jump
|
||||
-- straight from 2 to gone without ever reading a value strictly
|
||||
-- less than 1 for any meaningful duration.
|
||||
if Cursive.db.profile.flashonexpiring and remaining <= 1 then
|
||||
ui.flashingCurseIcons[curse] = true
|
||||
else
|
||||
ui.flashingCurseIcons[curse] = nil
|
||||
curse:SetAlpha(1)
|
||||
end
|
||||
|
||||
curseNumber = curseNumber + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local cfg = Cursive.db.profile
|
||||
|
||||
-- Hide all ghost slots first, matching the same pattern used for real
|
||||
-- curse icons above.
|
||||
for i = 1, cfg.maxcurses do
|
||||
if unitFrame.ghostIcons[i] then
|
||||
unitFrame.ghostIcons[i]:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
if cfg.showghostcurse or cfg.showghostcorruption or cfg.showghostimmolate or cfg.showghostsiphonlife then
|
||||
local ghostTextures = GetGhostTextures(guid)
|
||||
local slot = curseNumber
|
||||
for _, tex in ipairs(ghostTextures) do
|
||||
if slot > cfg.maxcurses then
|
||||
break
|
||||
end
|
||||
local ghostIcon = unitFrame.ghostIcons[slot]
|
||||
ghostIcon:ClearAllPoints()
|
||||
if cfg.invertbars then
|
||||
local rightOffset = slot * ui.padding + ((slot - 1) * cfg.curseiconsize)
|
||||
ghostIcon:SetPoint("RIGHT", unitFrame.thirdSection, "RIGHT", -rightOffset, 0)
|
||||
else
|
||||
ghostIcon:SetPoint("LEFT", unitFrame.thirdSection, "LEFT", slot * ui.padding + ((slot - 1) * cfg.curseiconsize), 0)
|
||||
end
|
||||
ghostIcon:SetTexture(tex)
|
||||
ghostIcon:Show()
|
||||
slot = slot + 1
|
||||
end
|
||||
end
|
||||
|
||||
unitFrame:Show()
|
||||
ui.numDisplayed = ui.numDisplayed + 1
|
||||
|
||||
@@ -903,12 +1033,33 @@ ui.StartGcdBar = function(duration)
|
||||
end
|
||||
|
||||
local gcdTicker = CreateFrame("Frame", "CursiveGcdTicker", UIParent)
|
||||
ui.gcdPreviewUntil = nil
|
||||
|
||||
-- Call this from any GCD bar setting's set() callback to show a temporary
|
||||
-- static preview fill, since the real bar normally only appears during an
|
||||
-- actual GCD -- this lets color/size changes be seen immediately.
|
||||
function ui.PreviewGcdBar()
|
||||
ui.gcdPreviewUntil = GetTime() + 4
|
||||
end
|
||||
|
||||
gcdTicker:SetScript("OnUpdate", function()
|
||||
if not ui.rootBarFrame or not ui.rootBarFrame.gcdBar then
|
||||
return
|
||||
end
|
||||
|
||||
local bar = ui.rootBarFrame.gcdBar
|
||||
|
||||
-- Preview mode: triggered directly by changing a GCD bar setting,
|
||||
-- rather than trying to detect whether the options menu is open.
|
||||
if ui.gcdPreviewUntil and GetTime() < ui.gcdPreviewUntil then
|
||||
bar:SetMinMaxValues(0, 1)
|
||||
bar:SetValue(0.6)
|
||||
if not bar:IsShown() then
|
||||
bar:Show()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if not bar.enabled or not ui.gcdBarStart then
|
||||
if bar:IsShown() then
|
||||
bar:Hide()
|
||||
@@ -931,4 +1082,25 @@ gcdTicker:SetScript("OnUpdate", function()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Flashes any curse icon currently registered as expiring, via a smooth
|
||||
-- alpha oscillation. Single shared ticker for all rows rather than one per
|
||||
-- icon, since the registry is normally small (just whatever's expiring).
|
||||
local flashTicker = CreateFrame("Frame", "CursiveFlashTicker", UIParent)
|
||||
local flashTime = 0
|
||||
flashTicker:SetScript("OnUpdate", function()
|
||||
local any = false
|
||||
for icon in pairs(ui.flashingCurseIcons) do
|
||||
any = true
|
||||
break
|
||||
end
|
||||
if not any then
|
||||
return
|
||||
end
|
||||
flashTime = flashTime + arg1 * 3
|
||||
local alpha = 0.35 + (0.65 * (0.5 + 0.5 * math.sin(flashTime)))
|
||||
for icon in pairs(ui.flashingCurseIcons) do
|
||||
icon:SetAlpha(alpha)
|
||||
end
|
||||
end)
|
||||
|
||||
Cursive.ui = ui
|
||||
|
||||
Reference in New Issue
Block a user