Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 173 additions & 21 deletions EllesmereUICooldownManager/EllesmereUICooldownManager.lua
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,15 @@ local DEFAULTS = {
-- Off by default: opt-in, and off means the gate in StartNativeGlow
-- is a single boolean test that never fires.
glowsOnlyInCombat = false,
-- Rotation Assist uses Blizzard's native highlight until the user
-- explicitly opts into a custom style.
rotationAssistStyle = "blizzard",
rotationAssistColorMode = "default",
rotationAssistColorR = 1,
rotationAssistColorG = 0,
rotationAssistColorB = 0,
rotationAssistThickness = 3,
rotationAssistOutset = 1,
-- The 3 default bars (match Blizzard CDM)
bars = {
{
Expand Down Expand Up @@ -9937,21 +9946,36 @@ end

-------------------------------------------------------------------------------
-- Rotation Helper Integration (Blizzard C_AssistedCombat)
-- Highlights the currently suggested spell on its CDM icon using Blizzard's
-- native ActionBarButtonAssistedCombatHighlightTemplate -- same shine as the
-- stock action bars. Gated purely by Blizzard's "assistedCombatHighlight"
-- CVar; we don't carry a second toggle of our own.
-- Highlights the currently suggested spell on its CDM icon. The default is
-- Blizzard's ActionBarButtonAssistedCombatHighlightTemplate; profile settings
-- can opt into a custom glow or a static solid border.
-------------------------------------------------------------------------------
do
ns._rotationGlowedIcons = {}
ns._rotationHookInstalled = false
ns._rotationInCombat = false

local ROT_GLOW_RATIO = 0.33
local ROT_STYLE_TO_GLOW = {
pixel = 1,
shape = 2,
button = 3,
autocast = 4,
gcd = 5,
modern = 6,
classic = 7,
}
local ROT_RESTRICTED_STYLE = { [1] = 7, [2] = 6, [3] = 6, [4] = 6 }

local function _rotConfig()
local p = ECME.db and ECME.db.profile
return p and p.cdmBars
end

local function _rotCVarOn()
-- User can force-hide via our own toggle, overriding Blizzard's CVar
local p = ECME.db and ECME.db.profile
if p and p.cdmBars and p.cdmBars.hideRotationHelper then return false end
local cfg = _rotConfig()
if cfg and cfg.hideRotationHelper then return false end
return GetCVarBool and GetCVarBool("assistedCombatHighlight")
end

Expand All @@ -9969,17 +9993,128 @@ local function _rotCreateHighlight(icon)
return hf
end

local function _rotCreateCustom(icon)
local overlay = CreateFrame("Frame", nil, icon)
overlay:SetFrameLevel(icon:GetFrameLevel() + 15)
overlay:SetAlpha(0)
return overlay
end

local function _rotSolidBorder(overlay, thickness, r, g, b)
local t = overlay._rotSolid
if not t then
t = {}
for i = 1, 4 do
t[i] = overlay:CreateTexture(nil, "OVERLAY", nil, 7)
end
t[1]:SetPoint("TOPLEFT"); t[1]:SetPoint("TOPRIGHT")
t[2]:SetPoint("BOTTOMLEFT"); t[2]:SetPoint("BOTTOMRIGHT")
t[3]:SetPoint("TOPLEFT"); t[3]:SetPoint("BOTTOMLEFT")
t[4]:SetPoint("TOPRIGHT"); t[4]:SetPoint("BOTTOMRIGHT")
overlay._rotSolid = t
end
local px = SnapForScale(thickness)
t[1]:SetHeight(px); t[2]:SetHeight(px)
t[3]:SetWidth(px); t[4]:SetWidth(px)
for i = 1, 4 do
t[i]:SetColorTexture(r, g, b, 1)
t[i]:Show()
end
end

local function _rotHideSolid(overlay)
local t = overlay and overlay._rotSolid
if not t then return end
for i = 1, 4 do t[i]:Hide() end
end

local function _rotResolveColor(cfg)
local mode = cfg and cfg.rotationAssistColorMode or "default"
if mode == "class" then
local c = EllesmereUI.GetClassColor and EllesmereUI.GetClassColor(EllesmereUI._playerClass)
if c then return c.r, c.g, c.b end
elseif mode == "custom" then
return cfg.rotationAssistColorR or 1,
cfg.rotationAssistColorG or 0,
cfg.rotationAssistColorB or 0
end
return 1.0, 0.788, 0.137
end

local function _rotHide(icon)
local rfc = icon and _ecmeFC[icon]
local hf = rfc and rfc.rotationHighlight
if not hf then return end
if hf.Flipbook and hf.Flipbook.Anim then hf.Flipbook.Anim:Stop() end
hf:Hide()
if hf then
if hf.Flipbook and hf.Flipbook.Anim then hf.Flipbook.Anim:Stop() end
hf:Hide()
end
local custom = rfc and rfc.rotationCustomHighlight
if custom then
StopNativeGlow(custom)
_rotHideSolid(custom)
custom._rotCfgKey = nil
end
end

local function _rotShow(icon)
if not icon then return end
local rfc = FC(icon)
local cfg = _rotConfig()
local style = cfg and cfg.rotationAssistStyle or "blizzard"
if style ~= "solid" and not ROT_STYLE_TO_GLOW[style] then style = "blizzard" end

if style ~= "blizzard" then
local hf0 = rfc.rotationHighlight
if hf0 then
if hf0.Flipbook and hf0.Flipbook.Anim then hf0.Flipbook.Anim:Stop() end
hf0:Hide()
end
local overlay = rfc.rotationCustomHighlight
if not overlay then
overlay = _rotCreateCustom(icon)
rfc.rotationCustomHighlight = overlay
end
local outset = cfg.rotationAssistOutset or 1
if outset < 0 then outset = 0 elseif outset > 12 then outset = 12 end
overlay:ClearAllPoints()
overlay:SetPoint("TOPLEFT", icon, "TOPLEFT", -outset, outset)
overlay:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", outset, -outset)
overlay:SetFrameLevel(icon:GetFrameLevel() + 15)
local thickness = cfg.rotationAssistThickness or 3
if thickness < 1 then thickness = 1 elseif thickness > 8 then thickness = 8 end
local cr, cg, cb = _rotResolveColor(cfg)
local glowStyle = ROT_STYLE_TO_GLOW[style]
if glowStyle and rfc.isReplacementBuff then
glowStyle = ROT_RESTRICTED_STYLE[glowStyle] or glowStyle
end
local cfgKey = table.concat({ style, cr, cg, cb, thickness, outset, glowStyle or 0 }, ":")
if overlay._rotCfgKey ~= cfgKey or not overlay._glowActive then
overlay._rotCfgKey = cfgKey
if style == "solid" then
StopNativeGlow(overlay)
_rotSolidBorder(overlay, thickness, cr, cg, cb)
overlay._glowActive = true
overlay:SetAlpha(1)
else
_rotHideSolid(overlay)
local w = (icon:GetWidth() or 36) + outset * 2
local h = (icon:GetHeight() or 36) + outset * 2
StartNativeGlow(overlay, glowStyle, cr, cg, cb, {
th = thickness,
width = w,
height = h,
})
end
end
return
end

local custom = rfc.rotationCustomHighlight
if custom then
StopNativeGlow(custom)
_rotHideSolid(custom)
custom._rotCfgKey = nil
end
local hf = rfc.rotationHighlight
if not hf then
hf = _rotCreateHighlight(icon)
Expand All @@ -10002,7 +10137,7 @@ local function _rotShow(icon)
end
end

local function UpdateRotationHighlights()
local function UpdateRotationHighlightsNow()
if not _rotCVarOn() then
for icon in pairs(ns._rotationGlowedIcons) do
_rotHide(icon)
Expand All @@ -10012,6 +10147,8 @@ local function UpdateRotationHighlights()
end

local suggestedSpell = C_AssistedCombat and C_AssistedCombat.GetNextCastSpell and C_AssistedCombat.GetNextCastSpell()
if type(suggestedSpell) ~= "number"
or (issecretvalue and issecretvalue(suggestedSpell)) then suggestedSpell = nil end

local newSet = {}
if suggestedSpell then
Expand All @@ -10020,16 +10157,23 @@ local function UpdateRotationHighlights()
-- directions so "icon=base, suggested=override" and "icon=override, suggested=base" both match. Strictly a superset of exact-id match, so anything that highlighted before still does.
local GetBaseSpell = C_Spell and C_Spell.GetBaseSpell
local suggestedBase = (GetBaseSpell and GetBaseSpell(suggestedSpell)) or suggestedSpell
if type(suggestedBase) ~= "number"
or (issecretvalue and issecretvalue(suggestedBase)) then suggestedBase = suggestedSpell end
for _, icons in pairs(cdmBarIcons) do
for _, icon in ipairs(icons) do
local ifc = _ecmeFC[icon]
local sid = ifc and ifc.spellID
if type(sid) ~= "number" or (issecretvalue and issecretvalue(sid)) then sid = nil end
if sid and icon:IsShown() then
-- Direct/base match first (cheap); only resolve the icon's own base if those
-- miss, to keep the common path light. sid > 0: item/trinket icons store -itemID/-13/-14, which must not be fed to GetBaseSpell.
local match = (sid == suggestedSpell) or (sid == suggestedBase)
if not match and GetBaseSpell and sid > 0 then
match = GetBaseSpell(sid) == suggestedBase
local sidBase = GetBaseSpell(sid)
if type(sidBase) == "number"
and not (issecretvalue and issecretvalue(sidBase)) then
match = sidBase == suggestedBase
end
end
if match then
_rotShow(icon)
Expand All @@ -10045,16 +10189,23 @@ local function UpdateRotationHighlights()
end
ns._rotationGlowedIcons = newSet
end
ns.UpdateRotationHighlights = UpdateRotationHighlights

-- One-frame defer after a bar rebuild: icon frames may have just been recycled or re-shown, so we want to re-run the match after the layout settles (dirty-frame pattern).
-- Coalesce Blizzard's overlapping assisted-combat callbacks and bar rebuilds
-- into one pass on the next frame. A suggestion change normally fires both an
-- EventRegistry callback and UpdateAllAssistedHighlightFramesForSpell; running
-- the full icon scan for each notification needlessly doubles the hot path.
local _rotDirty = CreateFrame("Frame")
_rotDirty:Hide()
_rotDirty:SetScript("OnUpdate", function(self)
self:Hide()
UpdateRotationHighlights()
UpdateRotationHighlightsNow()
end)

local function QueueRotationHighlightUpdate()
_rotDirty:Show()
end
ns.UpdateRotationHighlights = QueueRotationHighlightUpdate

local function _rotSyncCombat()
local inCombat = InCombatLockdown() or UnitAffectingCombat("player")
ns._rotationInCombat = inCombat and true or false
Expand All @@ -10072,34 +10223,35 @@ local function _rotSyncCombat()
end
ns._syncRotationCombatState = _rotSyncCombat

local function InstallRotationHook()
function ns.InstallRotationHook()
if ns._rotationHookInstalled then return end
ns._rotationHookInstalled = true

_rotSyncCombat()

if EventRegistry and EventRegistry.RegisterCallback then
EventRegistry:RegisterCallback("AssistedCombatManager.OnAssistedHighlightSpellChange", function()
UpdateRotationHighlights()
QueueRotationHighlightUpdate()
end, "ECME_CDM_RotationHelper")
-- Clear highlights if the user flips Blizzard's CVar off at runtime.
EventRegistry:RegisterCallback("AssistedCombatManager.OnSetUseAssistedHighlight", function()
UpdateRotationHighlights()
QueueRotationHighlightUpdate()
end, "ECME_CDM_RotationHelper_CVar")
end

if AssistedCombatManager and AssistedCombatManager.UpdateAllAssistedHighlightFramesForSpell then
hooksecurefunc(AssistedCombatManager, "UpdateAllAssistedHighlightFramesForSpell", function()
UpdateRotationHighlights()
QueueRotationHighlightUpdate()
end)
end

-- Re-run after bar rebuilds so the shine follows icon recycling.
if ns.CollectAndReanchor then
hooksecurefunc(ns, "CollectAndReanchor", function() _rotDirty:Show() end)
hooksecurefunc(ns, "CollectAndReanchor", QueueRotationHighlightUpdate)
end

UpdateRotationHighlights()
QueueRotationHighlightUpdate()
end
end

-- Show Item Count "Out of Combat" mode: re-run the icon restyle for bars using it whenever combat starts or ends (the gate inside the restyle reads the event-tracked combat flag). No-ops instantly when no bar uses the mode.
Expand Down Expand Up @@ -10643,7 +10795,7 @@ eventFrame:SetScript("OnEvent", function(_, event, unit, updateInfo, arg3)
end
-- Install rotation helper hook after CDM frames have been built
C_Timer.After(1, function()
InstallRotationHook()
ns.InstallRotationHook()
end)
-- Safety: re-apply visibility after loading screen settles. Two passes to catch both fast and late viewer pool rebuilds.
C_Timer.After(1.5, _CDMApplyVisibility)
Expand Down
Loading