Skip to content

Commit a88c878

Browse files
authored
feat: innervate cooldown, spell reordering, and dead-state toggle (#12)
* feat: innervate cooldown, spell reordering, and dead-state toggle - Add Innervate (180s) to Druid cooldowns in Data.lua - Right-click any tracker row to toggle dead state: desaturates icon and greys out the card to indicate the player has died; right-click again to restore (battle rez). Timer continues running. /cdt reset clears all dead states. - Spell reordering via Settings panel: each ability row now has ▲/▼ arrow buttons to move it up or down in the tracker. Order is persisted in the new CooldownTrackerDB.spellOrder SavedVariable. A Reset Order button restores the default sequence. * fix: sort comparator strict ordering and stale spellOrder pruning - Guard sort comparator against comparing an element with itself (a.id == b.id returns false immediately), fixing potential 'invalid order function' runtime error in Lua 5.1 when spellOrder is empty. - Prune stale spell IDs from spellOrder during BuildExpandedCooldowns so ghost entries from renamed/removed spells can never silently consume swap positions. * fix: replace cramped scroll-bar arrow textures with UIPanelButton arrows Reorder buttons were 22x15px scroll-bar textures with overlapping anchors inside a 42px row. Replaced with UIPanelButtonTemplate buttons (24x18px each, 2px gap) anchored correctly so they stack cleanly without overlap. Switched arrow glyphs to unicode ▲/▼ which are far more legible at this size. * fix: use ASCII glyphs and center reorder buttons vertically in row FRIZQT__.TTF lacks the Unicode geometric arrow codepoints, causing them to render as boxes. Replaced with ASCII ^ and v which the font supports. Anchored both buttons from the row RIGHT with +/-10 Y offsets so the 18+2+18px pair is centered in the 42px row instead of sitting at the top. * fix: use native WoW arrow textures for reorder buttons Replaced mismatched font glyphs (^ vs v render at different heights in FRIZQT) with inline |T...|t texture escapes pointing at the same Arrow-Up and Arrow-Down assets Blizzard uses in their own UI. Both arrows render at 12x12px so they are guaranteed identical in size. * fix: center arrow textures in reorder buttons using child overlay Inline |T|t text can't be offset, so asymmetric padding in the native arrow textures made the up arrow hug the bottom and down arrow hug the top. Replaced with explicit child OVERLAY textures anchored at CENTER with +/-1 Y nudges to counteract the texture's internal whitespace. * fix: increase down arrow Y nudge to -3 for visual centering * fix: grey out timer and name text when player is marked dead ApplyDeadVisuals now sets timerLabel, nameLabel, and classLabel to grey (0.5, 0.5, 0.5) alongside the icon/strip/bg. ClearDeadVisuals restores nameLabel to white and classLabel to its class colour. timerLabel is restored implicitly by the UpdateRow tick which rewrites it each frame. * fix: preserve active timers across RebuildUI calls Removing CT.activeTimers = {} from RebuildUI() was wiping all running timers every time a settings change (column count, class count, spell visibility, reorder) triggered a rebuild. Timers are now only cleared explicitly by the /cdt reset slash command, which is the only caller that actually intends to reset them. Orphaned entries from ID changes (e.g. count 1→2) are harmless -- UpdateRow ignores keys with no matching row. * fix: use grey color codes in timer text for dead-state rows SetTextColor() is overridden by embedded |cff...|r escape sequences in the text string. UpdateRow now reads CT.deadStates each tick and injects |cff808080 instead of the normal green/orange codes, so the grey sticks regardless of timer state. Works whether dead is marked before or after the timer starts since UpdateRow fires every frame. * docs: update README for innervate, reorder, and dead-state features * fix: prune stale spellOrder IDs in MoveSpell and drop RebuildUI timer flash - MoveSpell now rebuilds the order array by removing any IDs not in CT.COOLDOWNS before searching for the swap position, matching the knownIds pruning already present in BuildExpandedCooldowns. Previously a ghost entry could silently consume a swap slot. - RebuildUI no longer unconditionally overwrites timerLabel with 'Ready'. UpdateRow handles the correct text (respecting active timers and dead state) on the next OnUpdate tick, eliminating the one-frame green flash when reordering while a timer is running.
1 parent 8ec6425 commit a88c878

6 files changed

Lines changed: 269 additions & 15 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Also update `## Version:` in `CooldownTracker.toc` to match the tag before taggi
6464
| `playSoundOnReady` | boolean | `true` | Play alert sound on cooldown expiry |
6565
| `frameLocked` | boolean | `false` | Lock window position (disable drag) |
6666
| `point`, `relPoint`, `x`, `y` | mixed | nil | Saved window position |
67+
| `spellOrder` | table | `{}` | Ordered array of spell `id` strings defining tracker display order; empty = default |
6768
3. **Slash Commands:** Register slash commands via the `SlashCmdList` table. Handle arguments cleanly.
6869
4. **No Third-Party Libraries:** The addon intentionally does not use Ace3 or other framework libraries to remain lightweight. Rely on the standard WoW API.
6970

Core.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ eventFrame:SetScript("OnEvent", function(_, event, name)
2626
CooldownTrackerDB = CooldownTrackerDB or {}
2727
CooldownTrackerDB.classCounts = CooldownTrackerDB.classCounts or {}
2828
CooldownTrackerDB.disabledSpells = CooldownTrackerDB.disabledSpells or {}
29+
CooldownTrackerDB.spellOrder = CooldownTrackerDB.spellOrder or {}
2930
if CooldownTrackerDB.playSoundOnReady == nil then
3031
CooldownTrackerDB.playSoundOnReady = true
3132
end
@@ -53,6 +54,8 @@ SlashCmdList["COOLDOWNTRACKER"] = function(msg)
5354
for _, cd in ipairs(CT.expandedCooldowns) do
5455
CT.activeTimers[cd.id] = nil
5556
end
57+
CT.deadStates = {}
58+
CT:UpdateAllRows()
5659
print("|cffaaddff[CooldownTracker]|r All timers reset.")
5760
elseif cmd == "settings" then
5861
CT:OpenSettings()

Data.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ CT.COOLDOWNS = {
3535
icon = "Interface\\Icons\\spell_nature_tranquility",
3636
r = 1.0, g = 0.49, b = 0.04,
3737
},
38+
{
39+
id = "druid_innervate",
40+
class = "Druid",
41+
name = "Innervate",
42+
duration = 180,
43+
defaultDuration = 180,
44+
icon = "Interface\\Icons\\spell_nature_lightning",
45+
r = 1.0, g = 0.49, b = 0.04,
46+
},
3847

3948
-- -------------------------------------------------------------------------
4049
-- Paladin (class colour: pink)

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ Because Midnight restricts addons from reading real-time combat data, this addon
77
## Features
88

99
- Click anywhere on a spell row to start or reset its timer
10+
- Right-click a spell row to mark a player as dead (row goes grey); right-click again to restore (battle rez)
1011
- Countdown display (`M:SS`) with colour-coded progress bar (green → yellow → red)
1112
- Audible alert when a cooldown becomes ready (toggleable)
1213
- Grid or vertical layout with configurable column count (1–9)
14+
- Reorderable spells — use ▲/▼ arrows in the settings panel to set any display order
1315
- Per-spell visibility toggles — hide abilities you're not tracking
1416
- Lockable window — prevent accidental dragging mid-raid
1517
- Draggable window with position saved between sessions
1618
- In-game settings panel (Escape → Options → AddOns → Healer Cooldown Tracker)
1719
- Tooltips showing cooldown details on hover
20+
- Tracks Druid cooldowns: Convoke the Spirits, Tranquility, Innervate
1821

1922
## Installation
2023

@@ -42,6 +45,7 @@ Clone the repo and symlink (or copy) the folder directly into your AddOns direct
4245

4346
- Click any spell row to start the cooldown timer
4447
- Click the same row again to reset a running timer
48+
- Right-click any spell row to mark the player as **dead** (row goes grey, timer keeps running); right-click again to restore (battle rez)
4549
- Drag the title bar to reposition; position saves on drag-stop
4650
- Click the **lock icon** (top-right of title bar) to lock/unlock the window position
4751

@@ -53,6 +57,7 @@ Open via **Escape → Options → AddOns → Healer Cooldown Tracker**:
5357
- **Play sound when cooldown is ready** — toggle the audible alert
5458
- **Class Roster** — set how many of each class are in the raid; abilities duplicate per player (up to 5)
5559
- **Show checkboxes** — hide individual spells from the tracker
60+
- **Spell order** — use ▲/▼ arrows next to each spell to reorder them; Reset Order restores the default sequence
5661
- **Cooldown durations** — override any ability's cooldown in seconds; revert with the Default button
5762

5863
## Adding More Cooldowns

Settings.lua

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,61 @@ local function ApplyCustomDurations()
2929
end
3030
end
3131

32+
-- Ensures spellOrder is fully populated from the default CT.COOLDOWNS order,
33+
-- then moves the spell with the given id by `delta` positions (-1 = up, +1 = down).
34+
-- Refreshes the tracker and updates the provided order-indicator labels table.
35+
local function MoveSpell(id, delta, labels)
36+
local order = CooldownTrackerDB.spellOrder
37+
if not order then return end
38+
39+
-- Populate from defaults if empty.
40+
if #order == 0 then
41+
for _, cd in ipairs(CT.COOLDOWNS) do
42+
table.insert(order, cd.id)
43+
end
44+
end
45+
46+
-- Build known-ID set, then rebuild order: remove stale IDs and append any missing ones.
47+
local knownIds = {}
48+
for _, cd in ipairs(CT.COOLDOWNS) do knownIds[cd.id] = true end
49+
50+
local pruned = {}
51+
for _, sid in ipairs(order) do
52+
if knownIds[sid] then table.insert(pruned, sid) end
53+
end
54+
for _, cd in ipairs(CT.COOLDOWNS) do
55+
local found = false
56+
for _, sid in ipairs(pruned) do
57+
if sid == cd.id then found = true; break end
58+
end
59+
if not found then table.insert(pruned, cd.id) end
60+
end
61+
order = pruned
62+
CooldownTrackerDB.spellOrder = order
63+
64+
local pos = nil
65+
for i, sid in ipairs(order) do
66+
if sid == id then pos = i; break end
67+
end
68+
if not pos then return end
69+
70+
local newPos = pos + delta
71+
if newPos < 1 or newPos > #order then return end
72+
order[pos], order[newPos] = order[newPos], order[pos]
73+
CooldownTrackerDB.spellOrder = order
74+
75+
if CT.RebuildUI then CT:RebuildUI() end
76+
77+
-- Refresh position indicators in the settings panel.
78+
if labels then
79+
local posMap = {}
80+
for i, sid in ipairs(order) do posMap[sid] = i end
81+
for spellId, lbl in pairs(labels) do
82+
lbl:SetText(tostring(posMap[spellId] or ""))
83+
end
84+
end
85+
end
86+
3287
-- ---------------------------------------------------------------------------
3388
-- Panel construction
3489
-- ---------------------------------------------------------------------------
@@ -239,10 +294,32 @@ local function CreateSettingsPanel()
239294
divider:SetWidth(CONTENT_WIDTH)
240295
divider:SetColorTexture(0.3, 0.3, 0.4, 0.6)
241296

242-
-- editBoxes and RefreshAllEditBoxes must be declared here so the OnShow
243-
-- closure below can reference them (Lua requires locals before use).
297+
-- editBoxes, spellCheckboxes, and orderLabels must be declared here so the
298+
-- OnShow closure below can reference them (Lua requires locals before use).
244299
local editBoxes = {}
245300
local spellCheckboxes = {}
301+
local orderLabels = {}
302+
303+
local function GetEffectiveOrderPos()
304+
local order = CooldownTrackerDB.spellOrder or {}
305+
local posMap = {}
306+
if #order == 0 then
307+
for i, cd in ipairs(CT.COOLDOWNS) do posMap[cd.id] = i end
308+
else
309+
for i, sid in ipairs(order) do posMap[sid] = i end
310+
end
311+
return posMap
312+
end
313+
314+
local function RefreshOrderIndicators()
315+
C_Timer.After(0, function()
316+
local posMap = GetEffectiveOrderPos()
317+
for spellId, lbl in pairs(orderLabels) do
318+
lbl:SetText(tostring(posMap[spellId] or ""))
319+
end
320+
end)
321+
end
322+
246323
local function RefreshAllEditBoxes()
247324
C_Timer.After(0, function()
248325
for _, cd in ipairs(CT.COOLDOWNS) do
@@ -269,6 +346,7 @@ local function CreateSettingsPanel()
269346
cb:SetChecked(not disabled[spellId])
270347
end
271348
RefreshAllEditBoxes()
349+
RefreshOrderIndicators()
272350
end)
273351
end)
274352

@@ -412,6 +490,52 @@ local function CreateSettingsPanel()
412490
GameTooltip:Show()
413491
end)
414492
editBox:SetScript("OnLeave", function() GameTooltip:Hide() end)
493+
494+
-- ----- Reorder up/down buttons ----------------------------------------
495+
local upBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
496+
upBtn:SetSize(24, 18)
497+
upBtn:SetPoint("RIGHT", row, "RIGHT", -14, 10)
498+
upBtn:SetText("")
499+
local upArrow = upBtn:CreateTexture(nil, "OVERLAY")
500+
upArrow:SetSize(12, 12)
501+
upArrow:SetPoint("CENTER", 0, 1)
502+
upArrow:SetTexture("Interface\\Buttons\\Arrow-Up-Up")
503+
upBtn:SetScript("OnClick", function()
504+
MoveSpell(cd.id, -1, orderLabels)
505+
end)
506+
upBtn:SetScript("OnEnter", function()
507+
GameTooltip:SetOwner(upBtn, "ANCHOR_RIGHT")
508+
GameTooltip:SetText("Move Up")
509+
GameTooltip:AddLine("Move this spell earlier in the tracker.", 0.8, 0.8, 0.8)
510+
GameTooltip:Show()
511+
end)
512+
upBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
513+
514+
local downBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
515+
downBtn:SetSize(24, 18)
516+
downBtn:SetPoint("RIGHT", row, "RIGHT", -14, -10)
517+
downBtn:SetText("")
518+
local downArrow = downBtn:CreateTexture(nil, "OVERLAY")
519+
downArrow:SetSize(12, 12)
520+
downArrow:SetPoint("CENTER", 0, -3)
521+
downArrow:SetTexture("Interface\\Buttons\\Arrow-Down-Up")
522+
downBtn:SetScript("OnClick", function()
523+
MoveSpell(cd.id, 1, orderLabels)
524+
end)
525+
downBtn:SetScript("OnEnter", function()
526+
GameTooltip:SetOwner(downBtn, "ANCHOR_RIGHT")
527+
GameTooltip:SetText("Move Down")
528+
GameTooltip:AddLine("Move this spell later in the tracker.", 0.8, 0.8, 0.8)
529+
GameTooltip:Show()
530+
end)
531+
downBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
532+
533+
local orderLabel = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
534+
orderLabel:SetSize(18, 14)
535+
orderLabel:SetPoint("RIGHT", upBtn, "LEFT", -2, 0)
536+
orderLabel:SetJustifyH("CENTER")
537+
orderLabel:SetTextColor(0.5, 0.5, 0.5)
538+
orderLabels[cd.id] = orderLabel
415539
end
416540

417541
-- ----- Reset All button -------------------------------------------------
@@ -430,6 +554,25 @@ local function CreateSettingsPanel()
430554
print("|cffaaddff[CooldownTracker]|r All durations reset to defaults.")
431555
end)
432556

557+
-- ----- Reset Order button -----------------------------------------------
558+
local resetOrderBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
559+
resetOrderBtn:SetSize(100, 26)
560+
resetOrderBtn:SetPoint("LEFT", resetAllBtn, "RIGHT", 8, 0)
561+
resetOrderBtn:SetText("Reset Order")
562+
resetOrderBtn:SetScript("OnClick", function()
563+
CooldownTrackerDB.spellOrder = {}
564+
if CT.RebuildUI then CT:RebuildUI() end
565+
RefreshOrderIndicators()
566+
print("|cffaaddff[CooldownTracker]|r Spell order reset to defaults.")
567+
end)
568+
resetOrderBtn:SetScript("OnEnter", function()
569+
GameTooltip:SetOwner(resetOrderBtn, "ANCHOR_TOP")
570+
GameTooltip:SetText("Reset Spell Order")
571+
GameTooltip:AddLine("Restores the default display order for all spells.", 0.8, 0.8, 0.8)
572+
GameTooltip:Show()
573+
end)
574+
resetOrderBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
575+
433576
return panel
434577
end
435578

0 commit comments

Comments
 (0)