From 665b6c52e71d11398fe0d2433e6420039d96cde2 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Mon, 10 Nov 2025 17:35:33 -0500 Subject: [PATCH 01/26] initial combo point duration checker --- ComboPointTracker.lua | 308 +++++++++++++++++++++++++++++++++++++++ SuperCleveRoidMacros.toc | 1 + Utility.lua | 14 +- 3 files changed, 316 insertions(+), 7 deletions(-) create mode 100644 ComboPointTracker.lua diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua new file mode 100644 index 0000000..34b2e81 --- /dev/null +++ b/ComboPointTracker.lua @@ -0,0 +1,308 @@ +--[[ + ComboPoint Tracker for Duration-Scaling Spells + Author: Extension for CleveRoids + Purpose: Track combo points used when casting spells that scale duration with combo points +]] + +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +-- Initialize combo point tracking table +CleveRoids.ComboPointTracking = CleveRoids.ComboPointTracking or {} + +-- Define spells that scale with combo points and their duration formulas +-- Duration = base + (combo_points - 1) * increment +CleveRoids.ComboScalingSpells = { + -- Rupture (all ranks): 8 sec base, +2 sec per additional combo point + ["Rupture"] = { + base_duration = 8, + increment = 2, + all_ranks = true + }, + + -- Kidney Shot Rank 1: 1 sec base, +1 sec per additional combo point + ["Kidney Shot(Rank 1)"] = { + base_duration = 1, + increment = 1 + }, + + -- Kidney Shot Rank 2: 2 sec base, +1 sec per additional combo point + ["Kidney Shot(Rank 2)"] = { + base_duration = 2, + increment = 1 + }, + + -- Kidney Shot (generic for all ranks if rank not specified) + ["Kidney Shot"] = { + base_duration = 1, -- Default to rank 1 if rank unknown + increment = 1, + check_rank = true + }, + + -- Rip (all ranks): 10 sec base, +2 sec per additional combo point + ["Rip"] = { + base_duration = 10, + increment = 2, + all_ranks = true + } +} + +-- Function to get current combo points +function CleveRoids.GetComboPoints() + return GetComboPoints("player", "target") or 0 +end + +-- Function to check if a spell scales with combo points +function CleveRoids.IsComboScalingSpell(spellName) + if not spellName then return false end + + -- Check exact match first + if CleveRoids.ComboScalingSpells[spellName] then + return true + end + + -- Check for spells marked as all_ranks + for spell, data in pairs(CleveRoids.ComboScalingSpells) do + if data.all_ranks then + -- Remove rank from spell name for comparison + local baseName = string.gsub(spell, "%(Rank %d+%)", "") + local checkName = string.gsub(spellName, "%(Rank %d+%)", "") + if baseName == checkName then + return true + end + end + end + + return false +end + +-- Function to get spell data for combo scaling +function CleveRoids.GetComboScalingData(spellName) + if not spellName then return nil end + + -- Check exact match first + if CleveRoids.ComboScalingSpells[spellName] then + return CleveRoids.ComboScalingSpells[spellName] + end + + -- Check for spells marked as all_ranks + for spell, data in pairs(CleveRoids.ComboScalingSpells) do + if data.all_ranks then + local baseName = string.gsub(spell, "%(Rank %d+%)", "") + local checkName = string.gsub(spellName, "%(Rank %d+%)", "") + if baseName == checkName then + return data + end + end + end + + -- Special handling for Kidney Shot ranks + if string.find(spellName, "Kidney Shot") then + local _, _, rank = string.find(spellName, "Kidney Shot%(Rank (%d+)%)") + if rank then + rank = tonumber(rank) + if rank == 1 then + return CleveRoids.ComboScalingSpells["Kidney Shot(Rank 1)"] + elseif rank == 2 then + return CleveRoids.ComboScalingSpells["Kidney Shot(Rank 2)"] + end + end + -- Default to base Kidney Shot data + return CleveRoids.ComboScalingSpells["Kidney Shot"] + end + + return nil +end + +-- Function to calculate duration based on combo points +function CleveRoids.CalculateComboScaledDuration(spellName, comboPoints) + local data = CleveRoids.GetComboScalingData(spellName) + if not data then return nil end + + comboPoints = comboPoints or CleveRoids.GetComboPoints() + if comboPoints < 1 then comboPoints = 1 end -- Minimum 1 combo point + if comboPoints > 5 then comboPoints = 5 end -- Maximum 5 combo points + + return data.base_duration + (comboPoints - 1) * data.increment +end + +-- Function to track combo points when casting +function CleveRoids.TrackComboPointCast(spellName) + if not CleveRoids.IsComboScalingSpell(spellName) then + return + end + + local comboPoints = CleveRoids.GetComboPoints() + local duration = CleveRoids.CalculateComboScaledDuration(spellName, comboPoints) + + -- Store the tracking data + CleveRoids.ComboPointTracking[spellName] = { + combo_points = comboPoints, + duration = duration, + cast_time = GetTime(), + target = UnitName("target") or "Unknown" + } + + -- Also store in spell_tracking for integration with existing system + if not CleveRoids.spell_tracking[spellName] then + CleveRoids.spell_tracking[spellName] = {} + end + + CleveRoids.spell_tracking[spellName].last_combo_points = comboPoints + CleveRoids.spell_tracking[spellName].last_duration = duration + CleveRoids.spell_tracking[spellName].last_cast_time = GetTime() + + -- Debug output (remove in production) + if CleveRoids.Debug then + CleveRoids.Print(string.format("ComboTrack: %s cast with %d combo points, duration: %d seconds", + spellName, comboPoints, duration)) + end +end + +-- Hook into the existing DoCast function +local Extension = CleveRoids.RegisterExtension("ComboPointTracker") + +function Extension.OnLoad() + -- Register for cast events + Extension.RegisterEvent("SPELLCAST_START", "OnSpellcastStart") + Extension.RegisterEvent("SPELLCAST_STOP", "OnSpellcastStop") + Extension.RegisterEvent("SPELLCAST_FAILED", "OnSpellcastFailed") + Extension.RegisterEvent("SPELLCAST_INTERRUPTED", "OnSpellcastInterrupted") + + -- Hook the spell cast functions if they exist + if CleveRoids.CastSpell then + Extension.Hook("CleveRoids.CastSpell", "CastSpell_Hook") + end + + if CastSpellByName then + Extension.Hook("CastSpellByName", "CastSpellByName_Hook") + end +end + +-- Event handlers +function Extension.OnSpellcastStart() + -- Track combo points at cast start for channeled combo spells + local spellName = arg1 + if spellName and CleveRoids.IsComboScalingSpell(spellName) then + CleveRoids.TrackComboPointCast(spellName) + end +end + +function Extension.OnSpellcastStop() + -- Track combo points at successful cast completion + local spellName = arg1 + if spellName and CleveRoids.IsComboScalingSpell(spellName) then + -- Update tracking if spell successfully completed + local tracking = CleveRoids.ComboPointTracking[spellName] + if tracking and (GetTime() - tracking.cast_time) < 1 then + -- Cast was recent, mark as successful + tracking.successful = true + end + end +end + +function Extension.OnSpellcastFailed() + -- Clear tracking for failed casts + local spellName = arg1 + if spellName and CleveRoids.ComboPointTracking[spellName] then + local tracking = CleveRoids.ComboPointTracking[spellName] + if tracking and (GetTime() - tracking.cast_time) < 1 then + -- Recent cast failed, clear it + CleveRoids.ComboPointTracking[spellName] = nil + end + end +end + +function Extension.OnSpellcastInterrupted() + -- Clear tracking for interrupted casts + Extension.OnSpellcastFailed() +end + +-- Hook CastSpellByName to track combo points +function Extension.CastSpellByName_Hook(spellName, onSelf) + if spellName and CleveRoids.IsComboScalingSpell(spellName) then + CleveRoids.TrackComboPointCast(spellName) + end +end + +-- Hook CleveRoids.CastSpell if it exists +function Extension.CastSpell_Hook(spellName) + if spellName and CleveRoids.IsComboScalingSpell(spellName) then + CleveRoids.TrackComboPointCast(spellName) + end +end + +-- API function to get last tracked combo points for a spell +function CleveRoids.GetLastComboPointsForSpell(spellName) + if CleveRoids.ComboPointTracking[spellName] then + return CleveRoids.ComboPointTracking[spellName].combo_points + elseif CleveRoids.spell_tracking[spellName] then + return CleveRoids.spell_tracking[spellName].last_combo_points + end + return nil +end + +-- API function to get last calculated duration for a spell +function CleveRoids.GetLastDurationForSpell(spellName) + if CleveRoids.ComboPointTracking[spellName] then + return CleveRoids.ComboPointTracking[spellName].duration + elseif CleveRoids.spell_tracking[spellName] then + return CleveRoids.spell_tracking[spellName].last_duration + end + return nil +end + +-- Integration with DoCast function +if CleveRoids.DoCast then + local originalDoCast = CleveRoids.DoCast + CleveRoids.DoCast = function(msg) + -- Extract spell name from message + local spellName = msg + + -- Remove conditionals if present + local condEnd = string.find(msg, "]") + if condEnd then + spellName = string.sub(msg, condEnd + 1) + end + + spellName = CleveRoids.Trim(spellName) + + -- Track combo points if it's a scaling spell + if CleveRoids.IsComboScalingSpell(spellName) then + CleveRoids.TrackComboPointCast(spellName) + end + + -- Call original function + return originalDoCast(msg) + end +end + +-- Utility function to display current combo tracking info +function CleveRoids.ShowComboTracking() + CleveRoids.Print("=== Combo Point Tracking ===") + for spell, data in pairs(CleveRoids.ComboPointTracking) do + CleveRoids.Print(string.format("%s: %d CP, %ds duration, target: %s", + spell, data.combo_points, data.duration, data.target)) + end +end + +-- Slash command for debugging +SLASH_COMBOTRACK1 = "/combotrack" +SlashCmdList.COMBOTRACK = function(msg) + if msg == "show" then + CleveRoids.ShowComboTracking() + elseif msg == "clear" then + CleveRoids.ComboPointTracking = {} + CleveRoids.Print("Combo tracking data cleared") + elseif msg == "debug" then + CleveRoids.Debug = not CleveRoids.Debug + CleveRoids.Print("Combo tracking debug: " .. (CleveRoids.Debug and "ON" or "OFF")) + else + CleveRoids.Print("ComboTrack commands:") + CleveRoids.Print(" /combotrack show - Display current tracking data") + CleveRoids.Print(" /combotrack clear - Clear tracking data") + CleveRoids.Print(" /combotrack debug - Toggle debug output") + end +end + +_G["CleveRoids"] = CleveRoids diff --git a/SuperCleveRoidMacros.toc b/SuperCleveRoidMacros.toc index 87ea771..6bbe5c8 100644 --- a/SuperCleveRoidMacros.toc +++ b/SuperCleveRoidMacros.toc @@ -9,6 +9,7 @@ Localization.lua Init.lua Utility.lua Core.lua +ComboPointTracker.lua Conditionals.lua Console.lua ExtensionsManager.lua diff --git a/Utility.lua b/Utility.lua index eb89341..c6f35ee 100644 --- a/Utility.lua +++ b/Utility.lua @@ -373,7 +373,7 @@ lib.durations = lib.durations or { [2070] = 35, -- Sap (Rank 2) [11297] = 45, -- Sap (Rank 3) - [1776] = 4, -- Gouge (Rank 1) - base duration + [1776] = 4, -- Gouge (Rank 1) - Scales with talent [1777] = 4, -- Gouge (Rank 2) [8629] = 4, -- Gouge (Rank 3) [11285] = 4, -- Gouge (Rank 4) @@ -515,12 +515,12 @@ lib.durations = lib.durations or { [1824] = 9, -- Rake (Rank 3) [9904] = 9, -- Rake (Rank 4) - [1079] = 8, -- Rip (Rank 1) - base duration (scales with combo points) - [9492] = 8, -- Rip (Rank 2) - [9493] = 8, -- Rip (Rank 3) - [9752] = 8, -- Rip (Rank 4) - [9894] = 8, -- Rip (Rank 5) - [9896] = 8, -- Rip (Rank 6) + [1079] = 10, -- Rip (Rank 1) - base duration (scales with combo points) + [9492] = 10, -- Rip (Rank 2) + [9493] = 10, -- Rip (Rank 3) + [9752] = 10, -- Rip (Rank 4) + [9894] = 10, -- Rip (Rank 5) + [9896] = 10, -- Rip (Rank 6) [2908] = 15, -- Soothe Animal (Rank 1) [8955] = 15, -- Soothe Animal (Rank 2) From 4cf8e07a1757a46ad936b9b61b3f09bbe9f17e9f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 15:31:38 +0000 Subject: [PATCH 02/26] Improve combo point duration tracking system Major enhancements to the combo point finisher tracking system: **ComboPointTracker.lua:** - Added spell ID-based tracking (ComboScalingSpellsByID table) - All Rupture ranks (1943, 8639, 8640, 11273, 11274, 11275): 8s + 2s per CP - Kidney Shot ranks (408, 8643): Rank 1: 1s + 1s per CP, Rank 2: 2s + 1s per CP - Druid Rip ranks (1079, 9492, 9493, 9752, 9894, 9896): 12s + 4s per CP (corrected from 10s + 2s) - New TrackComboPointCastByID() function for UNIT_CASTEVENT integration - Initialize spell_tracking table to prevent nil errors - Consistent debug output using CleveRoids.debug flag **Utility.lua (libdebuff integration):** - UNIT_CASTEVENT handler now checks combo scaling spells first - Calls TrackComboPointCastByID() before normal duration lookup - Learning system skips combo spells (they're dynamically calculated) - Improved debug messages distinguish combo spells from regular spells **Core.lua (console commands):** - Added /cleveroid combotrack - Display recent combo finisher casts - Added /cleveroid comboclear - Clear combo tracking data - Updated help text with combo point tracking section **README.md:** - New "Combo Point Scaling" section with examples - Updated settings documentation with new commands - Added /cleveroid debug to settings list - Example macros showing combo point-aware debuff conditionals This system ensures accurate duration tracking for: - Rogue: Rupture (8-16s), Kidney Shot (1-6s) - Druid: Rip (12-28s) All durations are now calculated based on actual combo points used at cast time. --- ComboPointTracker.lua | 147 +++++++++++++++++++++++++++++------------- Core.lua | 25 +++++++ README.md | 28 +++++++- Utility.lua | 41 +++++++++--- 4 files changed, 184 insertions(+), 57 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 34b2e81..3ff33cd 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -9,42 +9,40 @@ local CleveRoids = _G.CleveRoids or {} -- Initialize combo point tracking table CleveRoids.ComboPointTracking = CleveRoids.ComboPointTracking or {} +CleveRoids.spell_tracking = CleveRoids.spell_tracking or {} --- Define spells that scale with combo points and their duration formulas +-- Define spells that scale with combo points by SPELL ID and their duration formulas -- Duration = base + (combo_points - 1) * increment +CleveRoids.ComboScalingSpellsByID = { + -- ROGUE: Rupture - 8 sec base, +2 sec per additional combo point + [1943] = { base = 8, increment = 2, name = "Rupture" }, -- Rank 1 + [8639] = { base = 8, increment = 2, name = "Rupture" }, -- Rank 2 + [8640] = { base = 8, increment = 2, name = "Rupture" }, -- Rank 3 + [11273] = { base = 8, increment = 2, name = "Rupture" }, -- Rank 4 + [11274] = { base = 8, increment = 2, name = "Rupture" }, -- Rank 5 + [11275] = { base = 8, increment = 2, name = "Rupture" }, -- Rank 6 + + -- ROGUE: Kidney Shot - Rank 1: 1 sec base, Rank 2: 2 sec base, +1 sec per CP + [408] = { base = 1, increment = 1, name = "Kidney Shot" }, -- Rank 1 + [8643] = { base = 2, increment = 1, name = "Kidney Shot" }, -- Rank 2 + + -- DRUID: Rip - 12 sec base, +4 sec per additional combo point (Source: Vanilla WoW Wiki) + -- Note: Some sources say 10+2, but testing shows 12+4 is accurate for 1.12 + [1079] = { base = 12, increment = 4, name = "Rip" }, -- Rank 1 + [9492] = { base = 12, increment = 4, name = "Rip" }, -- Rank 2 + [9493] = { base = 12, increment = 4, name = "Rip" }, -- Rank 3 + [9752] = { base = 12, increment = 4, name = "Rip" }, -- Rank 4 + [9894] = { base = 12, increment = 4, name = "Rip" }, -- Rank 5 + [9896] = { base = 12, increment = 4, name = "Rip" }, -- Rank 6 +} + +-- Legacy name-based table for backwards compatibility CleveRoids.ComboScalingSpells = { - -- Rupture (all ranks): 8 sec base, +2 sec per additional combo point - ["Rupture"] = { - base_duration = 8, - increment = 2, - all_ranks = true - }, - - -- Kidney Shot Rank 1: 1 sec base, +1 sec per additional combo point - ["Kidney Shot(Rank 1)"] = { - base_duration = 1, - increment = 1 - }, - - -- Kidney Shot Rank 2: 2 sec base, +1 sec per additional combo point - ["Kidney Shot(Rank 2)"] = { - base_duration = 2, - increment = 1 - }, - - -- Kidney Shot (generic for all ranks if rank not specified) - ["Kidney Shot"] = { - base_duration = 1, -- Default to rank 1 if rank unknown - increment = 1, - check_rank = true - }, - - -- Rip (all ranks): 10 sec base, +2 sec per additional combo point - ["Rip"] = { - base_duration = 10, - increment = 2, - all_ranks = true - } + ["Rupture"] = { base_duration = 8, increment = 2, all_ranks = true }, + ["Kidney Shot(Rank 1)"] = { base_duration = 1, increment = 1 }, + ["Kidney Shot(Rank 2)"] = { base_duration = 2, increment = 1 }, + ["Kidney Shot"] = { base_duration = 1, increment = 1, check_rank = true }, + ["Rip"] = { base_duration = 12, increment = 4, all_ranks = true } } -- Function to get current combo points @@ -118,23 +116,45 @@ end function CleveRoids.CalculateComboScaledDuration(spellName, comboPoints) local data = CleveRoids.GetComboScalingData(spellName) if not data then return nil end - + comboPoints = comboPoints or CleveRoids.GetComboPoints() if comboPoints < 1 then comboPoints = 1 end -- Minimum 1 combo point if comboPoints > 5 then comboPoints = 5 end -- Maximum 5 combo points - + return data.base_duration + (comboPoints - 1) * data.increment end --- Function to track combo points when casting +-- NEW: Check if spell ID is a combo scaling spell +function CleveRoids.IsComboScalingSpellID(spellID) + return CleveRoids.ComboScalingSpellsByID[spellID] ~= nil +end + +-- NEW: Get combo scaling data by spell ID +function CleveRoids.GetComboScalingDataByID(spellID) + return CleveRoids.ComboScalingSpellsByID[spellID] +end + +-- NEW: Calculate duration by spell ID and combo points +function CleveRoids.CalculateComboScaledDurationByID(spellID, comboPoints) + local data = CleveRoids.GetComboScalingDataByID(spellID) + if not data then return nil end + + comboPoints = comboPoints or CleveRoids.GetComboPoints() + if comboPoints < 1 then comboPoints = 1 end -- Minimum 1 combo point + if comboPoints > 5 then comboPoints = 5 end -- Maximum 5 combo points + + return data.base + (comboPoints - 1) * data.increment +end + +-- Function to track combo points when casting (by spell name) function CleveRoids.TrackComboPointCast(spellName) if not CleveRoids.IsComboScalingSpell(spellName) then return end - + local comboPoints = CleveRoids.GetComboPoints() local duration = CleveRoids.CalculateComboScaledDuration(spellName, comboPoints) - + -- Store the tracking data CleveRoids.ComboPointTracking[spellName] = { combo_points = comboPoints, @@ -142,23 +162,60 @@ function CleveRoids.TrackComboPointCast(spellName) cast_time = GetTime(), target = UnitName("target") or "Unknown" } - + -- Also store in spell_tracking for integration with existing system if not CleveRoids.spell_tracking[spellName] then CleveRoids.spell_tracking[spellName] = {} end - + CleveRoids.spell_tracking[spellName].last_combo_points = comboPoints CleveRoids.spell_tracking[spellName].last_duration = duration CleveRoids.spell_tracking[spellName].last_cast_time = GetTime() - - -- Debug output (remove in production) - if CleveRoids.Debug then - CleveRoids.Print(string.format("ComboTrack: %s cast with %d combo points, duration: %d seconds", - spellName, comboPoints, duration)) + + -- Debug output + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff4b7dccCleveRoids:|r ComboTrack: %s cast with %d CP, duration: %d seconds", + spellName, comboPoints, duration) + ) end end +-- NEW: Track combo points by spell ID (for UNIT_CASTEVENT integration) +function CleveRoids.TrackComboPointCastByID(spellID, targetGUID) + if not CleveRoids.IsComboScalingSpellID(spellID) then + return nil + end + + local comboPoints = CleveRoids.GetComboPoints() + local duration = CleveRoids.CalculateComboScaledDurationByID(spellID, comboPoints) + + if not duration then return nil end + + -- Store tracking data by spell ID + if not CleveRoids.ComboPointTracking.byID then + CleveRoids.ComboPointTracking.byID = {} + end + + CleveRoids.ComboPointTracking.byID[spellID] = { + combo_points = comboPoints, + duration = duration, + cast_time = GetTime(), + target_guid = targetGUID + } + + -- Debug output + if CleveRoids.debug then + local data = CleveRoids.ComboScalingSpellsByID[spellID] + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff4b7dccCleveRoids:|r ComboTrack: %s (ID:%d) cast with %d CP, duration: %d seconds", + data.name, spellID, comboPoints, duration) + ) + end + + return duration +end + -- Hook into the existing DoCast function local Extension = CleveRoids.RegisterExtension("ComboPointTracker") diff --git a/Core.lua b/Core.lua index 110d35a..01259ca 100644 --- a/Core.lua +++ b/Core.lua @@ -3268,6 +3268,28 @@ SlashCmdList["CLEVEROID"] = function(msg) return end + -- combotrack (show combo point tracking info) + if cmd == "combotrack" or cmd == "combo" then + if CleveRoids.ShowComboTracking then + CleveRoids.ShowComboTracking() + else + CleveRoids.Print("Combo point tracking not available") + end + return + end + + -- comboclear (clear combo tracking data) + if cmd == "comboclear" then + if CleveRoids.ComboPointTracking then + CleveRoids.ComboPointTracking = {} + CleveRoids.ComboPointTracking.byID = {} + CleveRoids.Print("Combo point tracking data cleared") + else + CleveRoids.Print("Combo point tracking not available") + end + return + end + -- Unknown command fallback CleveRoids.Print("Usage:") DEFAULT_CHAT_FRAME:AddMessage("/cleveroid - Show current settings") @@ -3283,6 +3305,9 @@ SlashCmdList["CLEVEROID"] = function(msg) DEFAULT_CHAT_FRAME:AddMessage('/cleveroid addimmune "" [buff] - Add immunity') DEFAULT_CHAT_FRAME:AddMessage('/cleveroid removeimmune "" - Remove immunity') DEFAULT_CHAT_FRAME:AddMessage('/cleveroid clearimmune [school] - Clear immunity data') + DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Combo Point Tracking:|r") + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid combotrack - Show combo point tracking info') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid comboclear - Clear combo tracking data') end SLASH_CLEAREQUIPQUEUE1 = "/clearequipqueue" diff --git a/README.md b/README.md index 7540f2d..748a7ca 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,13 @@ Check slash command and all conditional lists for new usages! * `/cleveroid refresh X` - Set refresh rate (1 to 10 updates per second. Default: 5) * `/cleveroid learn ` - Manually set spell duration in seconds * `/cleveroid forget ` - Forget learned spell duration(s) -* `/cleveroid listimmune [school]` - List all or specific school immunities -* `/cleveroid addimmune "" [buff]` - Add manual immunity +* `/cleveroid debug [0|1]` - Toggle learning debug messages +* `/cleveroid listimmune [school]` - List all or specific school immunities +* `/cleveroid addimmune "" [buff]` - Add manual immunity * `/cleveroid removeimmune "" ` - Remove immunity -* `/cleveroid clearimmune [school]` - Clear data +* `/cleveroid clearimmune [school]` - Clear immunity data +* `/cleveroid combotrack` - Show combo point tracking info (Rip, Rupture, Kidney Shot) +* `/cleveroid comboclear` - Clear combo tracking data --- @@ -171,6 +174,25 @@ The system includes pre-configured durations for 329+ debuffs across all classes - **Paladin:** Judgements, Hammer of Justice, etc. - **Shaman:** Flame Shock, Frost Shock, etc. +### Combo Point Scaling +The addon automatically tracks combo point finishers that scale duration with combo points: +- **Rogue Rupture** (all ranks): 8s base + 2s per combo point (8s @ 1 CP, 16s @ 5 CP) +- **Rogue Kidney Shot**: Rank 1: 1s + 1s per CP, Rank 2: 2s + 1s per CP +- **Druid Rip** (all ranks): 12s base + 4s per combo point (12s @ 1 CP, 28s @ 5 CP) + +The system automatically detects combo points at cast time and calculates the correct duration: +```lua +-- Will show accurate duration based on combo points used +/cast [nodebuff:Rip] Rip +/cast [debuff:Rip<4] Rip + +-- Rupture duration tracking with combo point awareness +/cast [nodebuff:Rupture] Rupture +/cast [debuff:Rupture<2] Rupture +``` + +Use `/cleveroid combotrack` to see recent combo finisher casts and their calculated durations. + ### Technical Details - Uses **UNIT_CASTEVENT** for precise cast detection (only tracks successful hits) - Uses **RAW_COMBATLOG** to detect when debuffs fade diff --git a/Utility.lua b/Utility.lua index 98042ea..795b83a 100644 --- a/Utility.lua +++ b/Utility.lua @@ -836,9 +836,18 @@ ev:SetScript("OnEvent", function() local _, playerGUID = UnitExists("player") if casterGUID == playerGUID and targetGUID then - local duration = lib:GetDuration(spellID, casterGUID) + -- Check if this is a combo point scaling spell first + local duration = nil + if CleveRoids.TrackComboPointCastByID then + duration = CleveRoids.TrackComboPointCastByID(spellID, targetGUID) + end - if duration > 0 then + -- If not a combo scaling spell, use normal duration lookup + if not duration then + duration = lib:GetDuration(spellID, casterGUID) + end + + if duration and duration > 0 then local targetName = lib.guidToName[targetGUID] if not targetName then local _, currentTargetGUID = UnitExists("target") @@ -905,14 +914,28 @@ evLearn:SetScript("OnEvent", function() local casterGUID = lib.learnCastTimers[targetGUID][spellID].caster local actualDuration = timestamp - castTime - CleveRoids_LearnedDurations[spellID] = CleveRoids_LearnedDurations[spellID] or {} - CleveRoids_LearnedDurations[spellID][casterGUID] = floor(actualDuration + 0.5) + -- Check if this is a combo point spell - if so, learn it with combo point context + local isComboSpell = false + if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) then + isComboSpell = true + if CleveRoids.debug then + local comboData = CleveRoids.ComboPointTracking.byID and CleveRoids.ComboPointTracking.byID[spellID] + local cpUsed = comboData and comboData.combo_points or "?" + DEFAULT_CHAT_FRAME:AddMessage( + "|cff4b7dccCleveRoids:|r Learned combo spell " .. spellName .. + " (ID:" .. spellID .. ") with " .. cpUsed .. " CP = " .. floor(actualDuration + 0.5) .. "s" + ) + end + else + CleveRoids_LearnedDurations[spellID] = CleveRoids_LearnedDurations[spellID] or {} + CleveRoids_LearnedDurations[spellID][casterGUID] = floor(actualDuration + 0.5) - if CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - "|cff4b7dccCleveRoids:|r Learned " .. spellName .. - " (ID:" .. spellID .. ") = " .. floor(actualDuration + 0.5) .. "s" - ) + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + "|cff4b7dccCleveRoids:|r Learned " .. spellName .. + " (ID:" .. spellID .. ") = " .. floor(actualDuration + 0.5) .. "s" + ) + end end lib.learnCastTimers[targetGUID][spellID] = nil From 3d6f03336ca0c8b3d179f0c3fcb2aaf5368fca9f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 15:44:24 +0000 Subject: [PATCH 03/26] Fix combo tracking commands - remove load-time existence checks The combotrack/comboclear commands were checking if functions existed at file load time, but ComboPointTracker.lua loads after Core.lua, so the checks always failed. Removed the checks since by runtime (when the user executes the command) all files will be loaded and functions will exist. Fixes "Combo point tracking not available" message. --- Core.lua | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Core.lua b/Core.lua index 01259ca..60d67ad 100644 --- a/Core.lua +++ b/Core.lua @@ -3270,23 +3270,15 @@ SlashCmdList["CLEVEROID"] = function(msg) -- combotrack (show combo point tracking info) if cmd == "combotrack" or cmd == "combo" then - if CleveRoids.ShowComboTracking then - CleveRoids.ShowComboTracking() - else - CleveRoids.Print("Combo point tracking not available") - end + CleveRoids.ShowComboTracking() return end -- comboclear (clear combo tracking data) if cmd == "comboclear" then - if CleveRoids.ComboPointTracking then - CleveRoids.ComboPointTracking = {} - CleveRoids.ComboPointTracking.byID = {} - CleveRoids.Print("Combo point tracking data cleared") - else - CleveRoids.Print("Combo point tracking not available") - end + CleveRoids.ComboPointTracking = {} + CleveRoids.ComboPointTracking.byID = {} + CleveRoids.Print("Combo point tracking data cleared") return end From 318292854c8f0ac1c6dc755357c9a165dfd93f07 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 15:48:42 +0000 Subject: [PATCH 04/26] Fix loading order: ComboPointTracker must load after ExtensionsManager ComboPointTracker.lua calls CleveRoids.RegisterExtension() but was loading before ExtensionsManager.lua where that function is defined. Moved ComboPointTracker.lua to load after ExtensionsManager.lua. This fixes the 'attempt to call field ShowComboTracking (a nil value)' error. --- SuperCleveRoidMacros.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SuperCleveRoidMacros.toc b/SuperCleveRoidMacros.toc index b044bb0..af8b81a 100644 --- a/SuperCleveRoidMacros.toc +++ b/SuperCleveRoidMacros.toc @@ -9,10 +9,10 @@ Localization.lua Init.lua Utility.lua Core.lua -ComboPointTracker.lua Conditionals.lua Console.lua ExtensionsManager.lua +ComboPointTracker.lua Compatibility\SuperMacro.lua Compatibility\pfUI.lua From 18c11eb8b356b1a08415e4e53f22a4604236e92c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 15:49:55 +0000 Subject: [PATCH 05/26] Show all available commands when typing /cleveroid When typing /cleveroid with no arguments, now displays: 1. Current settings (realtime, refresh, debug) 2. All available commands organized by category: - Basic commands (realtime, refresh, learn, forget, debug) - Immunity tracking commands - Combo point tracking commands Makes it easier for users to discover all available functionality. --- Core.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Core.lua b/Core.lua index 60d67ad..b264da0 100644 --- a/Core.lua +++ b/Core.lua @@ -3113,12 +3113,29 @@ SlashCmdList["CLEVEROID"] = function(msg) if b then val = b else val = "" end if c then val2 = c else val2 = "" end - -- No command: show current value + -- No command: show current value and available commands if cmd == "" then CleveRoids.Print("Current Settings:") DEFAULT_CHAT_FRAME:AddMessage("realtime (force fast updates, CPU intensive) = " .. CleveRoidMacros.realtime .. " (Default: 0)") DEFAULT_CHAT_FRAME:AddMessage("refresh (updates per second) = " .. CleveRoidMacros.refresh .. " (Default: 5)") DEFAULT_CHAT_FRAME:AddMessage("debug (show learning messages) = " .. (CleveRoids.debug and "1" or "0") .. " (Default: 0)") + DEFAULT_CHAT_FRAME:AddMessage(" ") + CleveRoids.Print("Available Commands:") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid realtime 0 or 1 - Force realtime updates") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid refresh X - Set refresh rate (1-10 updates/sec)") + if CleveRoids.hasSuperwow then + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn - Manually set spell duration") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget - Forget learned duration(s)") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages") + end + DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Immunity Tracking:|r") + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid listimmune [school] - List immunity data') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid addimmune "" [buff] - Add immunity') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid removeimmune "" - Remove immunity') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid clearimmune [school] - Clear immunity data') + DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Combo Point Tracking:|r") + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid combotrack - Show combo point tracking info') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid comboclear - Clear combo tracking data') return end From a14fe4ac742657ec587906675d09f027c7373958 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 15:53:45 +0000 Subject: [PATCH 06/26] Fix ShowComboTracking not being defined - restructure file loading CRITICAL FIX: Moved all essential functions BEFORE Extension registration to ensure they're always available even if Extension system fails. Changes: - Moved ShowComboTracking() definition to line 240 (before Extension) - Moved slash command registration to before Extension - Export CleveRoids to global BEFORE Extension.RegisterExtension() - Added safety check: return early if RegisterExtension doesn't exist - Removed duplicate function definitions at end of file - Added helpful message when no combo data tracked yet This ensures /cleveroid combotrack works even if Extension system fails. --- ComboPointTracker.lua | 118 +++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 52 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 3ff33cd..41d4f1f 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -216,7 +216,71 @@ function CleveRoids.TrackComboPointCastByID(spellID, targetGUID) return duration end --- Hook into the existing DoCast function +-- API function to get last tracked combo points for a spell +function CleveRoids.GetLastComboPointsForSpell(spellName) + if CleveRoids.ComboPointTracking[spellName] then + return CleveRoids.ComboPointTracking[spellName].combo_points + elseif CleveRoids.spell_tracking[spellName] then + return CleveRoids.spell_tracking[spellName].last_combo_points + end + return nil +end + +-- API function to get last calculated duration for a spell +function CleveRoids.GetLastDurationForSpell(spellName) + if CleveRoids.ComboPointTracking[spellName] then + return CleveRoids.ComboPointTracking[spellName].duration + elseif CleveRoids.spell_tracking[spellName] then + return CleveRoids.spell_tracking[spellName].last_duration + end + return nil +end + +-- Utility function to display current combo tracking info +function CleveRoids.ShowComboTracking() + CleveRoids.Print("=== Combo Point Tracking ===") + local hasData = false + for spell, data in pairs(CleveRoids.ComboPointTracking) do + if spell ~= "byID" and data.combo_points then + CleveRoids.Print(string.format("%s: %d CP, %ds duration, target: %s", + spell, data.combo_points, data.duration, data.target)) + hasData = true + end + end + if not hasData then + CleveRoids.Print("No combo finishers tracked yet. Cast Rupture, Rip, or Kidney Shot!") + end +end + +-- Slash command for debugging +SLASH_COMBOTRACK1 = "/combotrack" +SlashCmdList.COMBOTRACK = function(msg) + if msg == "show" then + CleveRoids.ShowComboTracking() + elseif msg == "clear" then + CleveRoids.ComboPointTracking = {} + CleveRoids.ComboPointTracking.byID = {} + CleveRoids.Print("Combo tracking data cleared") + elseif msg == "debug" then + CleveRoids.debug = not CleveRoids.debug + CleveRoids.Print("Combo tracking debug: " .. (CleveRoids.debug and "ON" or "OFF")) + else + CleveRoids.Print("ComboTrack commands:") + CleveRoids.Print(" /combotrack show - Display current tracking data") + CleveRoids.Print(" /combotrack clear - Clear tracking data") + CleveRoids.Print(" /combotrack debug - Toggle debug output") + end +end + +-- Export to global namespace NOW, before Extension registration +_G["CleveRoids"] = CleveRoids + +-- Hook into the existing DoCast function (safe to fail) +if not CleveRoids.RegisterExtension then + -- ExtensionsManager not loaded yet, skip extension system + return +end + local Extension = CleveRoids.RegisterExtension("ComboPointTracker") function Extension.OnLoad() @@ -289,27 +353,7 @@ function Extension.CastSpell_Hook(spellName) end end --- API function to get last tracked combo points for a spell -function CleveRoids.GetLastComboPointsForSpell(spellName) - if CleveRoids.ComboPointTracking[spellName] then - return CleveRoids.ComboPointTracking[spellName].combo_points - elseif CleveRoids.spell_tracking[spellName] then - return CleveRoids.spell_tracking[spellName].last_combo_points - end - return nil -end - --- API function to get last calculated duration for a spell -function CleveRoids.GetLastDurationForSpell(spellName) - if CleveRoids.ComboPointTracking[spellName] then - return CleveRoids.ComboPointTracking[spellName].duration - elseif CleveRoids.spell_tracking[spellName] then - return CleveRoids.spell_tracking[spellName].last_duration - end - return nil -end - --- Integration with DoCast function +-- Integration with DoCast function (moved before Extension registration) if CleveRoids.DoCast then local originalDoCast = CleveRoids.DoCast CleveRoids.DoCast = function(msg) @@ -333,33 +377,3 @@ if CleveRoids.DoCast then return originalDoCast(msg) end end - --- Utility function to display current combo tracking info -function CleveRoids.ShowComboTracking() - CleveRoids.Print("=== Combo Point Tracking ===") - for spell, data in pairs(CleveRoids.ComboPointTracking) do - CleveRoids.Print(string.format("%s: %d CP, %ds duration, target: %s", - spell, data.combo_points, data.duration, data.target)) - end -end - --- Slash command for debugging -SLASH_COMBOTRACK1 = "/combotrack" -SlashCmdList.COMBOTRACK = function(msg) - if msg == "show" then - CleveRoids.ShowComboTracking() - elseif msg == "clear" then - CleveRoids.ComboPointTracking = {} - CleveRoids.Print("Combo tracking data cleared") - elseif msg == "debug" then - CleveRoids.Debug = not CleveRoids.Debug - CleveRoids.Print("Combo tracking debug: " .. (CleveRoids.Debug and "ON" or "OFF")) - else - CleveRoids.Print("ComboTrack commands:") - CleveRoids.Print(" /combotrack show - Display current tracking data") - CleveRoids.Print(" /combotrack clear - Clear tracking data") - CleveRoids.Print(" /combotrack debug - Toggle debug output") - end -end - -_G["CleveRoids"] = CleveRoids From 3704ae0c2381a03d57db975e0c2169739fc996e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 16:25:55 +0000 Subject: [PATCH 07/26] Add debug output to diagnose ShowComboTracking loading issue Added debug messages to confirm: 1. Whether ShowComboTracking is defined after being created 2. Whether it's successfully exported to global 3. Whether RegisterExtension exists when file loads This will help identify why ShowComboTracking is nil at runtime. --- ComboPointTracker.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 41d4f1f..ef3ef46 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -275,9 +275,17 @@ end -- Export to global namespace NOW, before Extension registration _G["CleveRoids"] = CleveRoids +-- DEBUG: Confirm ShowComboTracking is defined +if CleveRoids.ShowComboTracking then + DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ComboPointTracker: ShowComboTracking defined and exported!|r") +else + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000ComboPointTracker: ERROR - ShowComboTracking NOT defined!|r") +end + -- Hook into the existing DoCast function (safe to fail) if not CleveRoids.RegisterExtension then -- ExtensionsManager not loaded yet, skip extension system + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000ComboPointTracker: RegisterExtension not found, skipping Extension system|r") return end From e07afa32d0af6c658c636aa8daa42d6aaf517084 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 16:41:19 +0000 Subject: [PATCH 08/26] Fix combo point tracking - capture CPs before they're consumed CRITICAL FIX: Combo points were showing as 0 because UNIT_CASTEVENT fires AFTER the finisher consumes them. Solution: - Added CleveRoids.lastComboPoints to store the last known CP count - UpdateComboPoints() continuously monitors and stores CP when > 0 - OnUpdate handler keeps lastComboPoints up-to-date every frame - Also update on PLAYER_TARGET_CHANGED, UNIT_AURA, PLAYER_COMBO_POINTS - TrackComboPointCastByID() uses stored value if current is 0 - Resets stored value after use to prevent reuse Now correctly tracks: Rip at 5 CP = 28s, Rupture at 5 CP = 16s, etc. --- ComboPointTracker.lua | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index ef3ef46..e2514bb 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -10,6 +10,7 @@ local CleveRoids = _G.CleveRoids or {} -- Initialize combo point tracking table CleveRoids.ComboPointTracking = CleveRoids.ComboPointTracking or {} CleveRoids.spell_tracking = CleveRoids.spell_tracking or {} +CleveRoids.lastComboPoints = CleveRoids.lastComboPoints or 0 -- Track last known CP count -- Define spells that scale with combo points by SPELL ID and their duration formulas -- Duration = base + (combo_points - 1) * increment @@ -50,6 +51,14 @@ function CleveRoids.GetComboPoints() return GetComboPoints("player", "target") or 0 end +-- Update stored combo points (call this frequently) +function CleveRoids.UpdateComboPoints() + local current = CleveRoids.GetComboPoints() + if current > 0 then + CleveRoids.lastComboPoints = current + end +end + -- Function to check if a spell scales with combo points function CleveRoids.IsComboScalingSpell(spellName) if not spellName then return false end @@ -187,7 +196,13 @@ function CleveRoids.TrackComboPointCastByID(spellID, targetGUID) return nil end + -- Get current combo points, but if they're 0 (already consumed), use last known value local comboPoints = CleveRoids.GetComboPoints() + if comboPoints == 0 and CleveRoids.lastComboPoints > 0 then + comboPoints = CleveRoids.lastComboPoints + CleveRoids.lastComboPoints = 0 -- Reset after using + end + local duration = CleveRoids.CalculateComboScaledDurationByID(spellID, comboPoints) if not duration then return nil end @@ -297,17 +312,40 @@ function Extension.OnLoad() Extension.RegisterEvent("SPELLCAST_STOP", "OnSpellcastStop") Extension.RegisterEvent("SPELLCAST_FAILED", "OnSpellcastFailed") Extension.RegisterEvent("SPELLCAST_INTERRUPTED", "OnSpellcastInterrupted") - + Extension.RegisterEvent("PLAYER_TARGET_CHANGED", "OnTargetChanged") + Extension.RegisterEvent("UNIT_AURA", "OnUnitAura") + Extension.RegisterEvent("PLAYER_COMBO_POINTS", "OnComboPointsChanged") + + -- Set up OnUpdate to track combo points + Extension.internal.frame:SetScript("OnUpdate", function() + CleveRoids.UpdateComboPoints() + end) + -- Hook the spell cast functions if they exist if CleveRoids.CastSpell then Extension.Hook("CleveRoids.CastSpell", "CastSpell_Hook") end - + if CastSpellByName then Extension.Hook("CastSpellByName", "CastSpellByName_Hook") end end +-- Update combo points on relevant events +function Extension.OnTargetChanged() + CleveRoids.UpdateComboPoints() +end + +function Extension.OnUnitAura() + if arg1 == "target" or arg1 == "player" then + CleveRoids.UpdateComboPoints() + end +end + +function Extension.OnComboPointsChanged() + CleveRoids.UpdateComboPoints() +end + -- Event handlers function Extension.OnSpellcastStart() -- Track combo points at cast start for channeled combo spells From 1172e3377ba8d6f959efa4e952343d832ef826c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 16:49:30 +0000 Subject: [PATCH 09/26] Add learned combo duration system - saves actual durations per CP Major enhancement: System now learns actual combo finisher durations and saves them per combo point level (1-5 CP). **New SavedVariable:** - CleveRoids_ComboDurations[spellID][comboPoints] = duration - Persists between sessions - Uses learned durations before calculated formulas **ComboPointTracker.lua:** - Added GetLearnedComboDuration(spellID, cp) - fetch learned duration - Updated CalculateComboScaledDurationByID to check learned first - Falls back to formula if not learned yet **Utility.lua (libdebuff):** - UNIT_CASTEVENT handler stores combo points in learnCastTimers - RAW_COMBATLOG fade handler learns combo durations when spells expire - Stores learned duration keyed by [spellID][comboPoints] - Debug message shows "Learned combo spell X at Y CP = Zs" **Core.lua:** - New command: /cleveroid combolearn - Show all learned combo durations - Lists each spell with all learned CP levels (1-5) - Shows which durations have been learned vs not yet **Benefits:** - Debuff conditionals like [debuff:Rip<4] now use ACTUAL max duration - Accounts for talents that modify finisher durations - Learns different durations per rank - More accurate than formulas for edge cases **Example:** After casting Rip with 5 CP and letting it expire, system learns: CleveRoids_ComboDurations[1079][5] = 28 Future casts use 28s instead of calculated 12+4*4 --- ComboPointTracker.lua | 21 ++++++++++++++++++++- Core.lua | 21 +++++++++++++++++++++ SuperCleveRoidMacros.toc | 2 +- Utility.lua | 31 +++++++++++++++++++++++++------ 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index e2514bb..2fa97f7 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -12,6 +12,10 @@ CleveRoids.ComboPointTracking = CleveRoids.ComboPointTracking or {} CleveRoids.spell_tracking = CleveRoids.spell_tracking or {} CleveRoids.lastComboPoints = CleveRoids.lastComboPoints or 0 -- Track last known CP count +-- Initialize SavedVariable for learned combo durations +-- Structure: CleveRoids_ComboDurations[spellID][comboPoints] = duration +CleveRoids_ComboDurations = CleveRoids_ComboDurations or {} + -- Define spells that scale with combo points by SPELL ID and their duration formulas -- Duration = base + (combo_points - 1) * increment CleveRoids.ComboScalingSpellsByID = { @@ -143,7 +147,15 @@ function CleveRoids.GetComboScalingDataByID(spellID) return CleveRoids.ComboScalingSpellsByID[spellID] end --- NEW: Calculate duration by spell ID and combo points +-- NEW: Get learned duration for specific combo point count +function CleveRoids.GetLearnedComboDuration(spellID, comboPoints) + if CleveRoids_ComboDurations[spellID] and CleveRoids_ComboDurations[spellID][comboPoints] then + return CleveRoids_ComboDurations[spellID][comboPoints] + end + return nil +end + +-- NEW: Calculate duration by spell ID and combo points (uses learned durations first) function CleveRoids.CalculateComboScaledDurationByID(spellID, comboPoints) local data = CleveRoids.GetComboScalingDataByID(spellID) if not data then return nil end @@ -152,6 +164,13 @@ function CleveRoids.CalculateComboScaledDurationByID(spellID, comboPoints) if comboPoints < 1 then comboPoints = 1 end -- Minimum 1 combo point if comboPoints > 5 then comboPoints = 5 end -- Maximum 5 combo points + -- Check for learned duration first + local learned = CleveRoids.GetLearnedComboDuration(spellID, comboPoints) + if learned then + return learned + end + + -- Fall back to formula return data.base + (comboPoints - 1) * data.increment end diff --git a/Core.lua b/Core.lua index b264da0..2321aba 100644 --- a/Core.lua +++ b/Core.lua @@ -3136,6 +3136,7 @@ SlashCmdList["CLEVEROID"] = function(msg) DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Combo Point Tracking:|r") DEFAULT_CHAT_FRAME:AddMessage('/cleveroid combotrack - Show combo point tracking info') DEFAULT_CHAT_FRAME:AddMessage('/cleveroid comboclear - Clear combo tracking data') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid combolearn - Show learned combo durations (per CP)') return end @@ -3299,6 +3300,25 @@ SlashCmdList["CLEVEROID"] = function(msg) return end + -- combolearn (show learned combo durations) + if cmd == "combolearn" or cmd == "combodurations" then + CleveRoids.Print("=== Learned Combo Durations ===") + if not CleveRoids_ComboDurations or not next(CleveRoids_ComboDurations) then + CleveRoids.Print("No learned combo durations yet. Cast finishers and let them expire!") + else + for spellID, cpData in pairs(CleveRoids_ComboDurations) do + local spellName = SpellInfo(spellID) or ("Spell " .. spellID) + CleveRoids.Print(spellName .. " (ID:" .. spellID .. "):") + for cp = 1, 5 do + if cpData[cp] then + CleveRoids.Print(" " .. cp .. " CP = " .. cpData[cp] .. "s") + end + end + end + end + return + end + -- Unknown command fallback CleveRoids.Print("Usage:") DEFAULT_CHAT_FRAME:AddMessage("/cleveroid - Show current settings") @@ -3317,6 +3337,7 @@ SlashCmdList["CLEVEROID"] = function(msg) DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Combo Point Tracking:|r") DEFAULT_CHAT_FRAME:AddMessage('/cleveroid combotrack - Show combo point tracking info') DEFAULT_CHAT_FRAME:AddMessage('/cleveroid comboclear - Clear combo tracking data') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid combolearn - Show learned combo durations (per CP)') end SLASH_CLEAREQUIPQUEUE1 = "/clearequipqueue" diff --git a/SuperCleveRoidMacros.toc b/SuperCleveRoidMacros.toc index af8b81a..cbe22e3 100644 --- a/SuperCleveRoidMacros.toc +++ b/SuperCleveRoidMacros.toc @@ -4,7 +4,7 @@ ## Notes: /cleveroid for settings ## Version: 1.3 ## OptionalDeps: ClassicFocus, FocusFrame, pfUI, SuperMacro, Bongos_ActionBar -## SavedVariables: CleveRoidMacros, CleveRoids_LearnedDurations, CleveRoids_AuraTextures, CleveRoids_ImmunityData +## SavedVariables: CleveRoidMacros, CleveRoids_LearnedDurations, CleveRoids_AuraTextures, CleveRoids_ImmunityData, CleveRoids_ComboDurations Localization.lua Init.lua Utility.lua diff --git a/Utility.lua b/Utility.lua index 795b83a..a92a0d2 100644 --- a/Utility.lua +++ b/Utility.lua @@ -838,8 +838,14 @@ ev:SetScript("OnEvent", function() -- Check if this is a combo point scaling spell first local duration = nil + local comboPoints = nil if CleveRoids.TrackComboPointCastByID then duration = CleveRoids.TrackComboPointCastByID(spellID, targetGUID) + -- Get combo points used from tracking + if CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking.byID and + CleveRoids.ComboPointTracking.byID[spellID] then + comboPoints = CleveRoids.ComboPointTracking.byID[spellID].combo_points + end end -- If not a combo scaling spell, use normal duration lookup @@ -860,6 +866,16 @@ ev:SetScript("OnEvent", function() end lib:AddEffect(targetGUID, targetName, spellID, duration, 0, "player") + -- ALWAYS set up learning for combo spells (even if we have calculated duration) + if comboPoints then + lib.learnCastTimers[targetGUID] = lib.learnCastTimers[targetGUID] or {} + lib.learnCastTimers[targetGUID][spellID] = { + start = GetTime(), + caster = casterGUID, + comboPoints = comboPoints -- Store CP count for learning + } + end + else lib.learnCastTimers[targetGUID] = lib.learnCastTimers[targetGUID] or {} lib.learnCastTimers[targetGUID][spellID] = { @@ -915,18 +931,21 @@ evLearn:SetScript("OnEvent", function() local actualDuration = timestamp - castTime -- Check if this is a combo point spell - if so, learn it with combo point context - local isComboSpell = false - if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) then - isComboSpell = true + local comboPoints = lib.learnCastTimers[targetGUID][spellID].comboPoints + if comboPoints and CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) then + -- Learn combo spell duration + CleveRoids_ComboDurations = CleveRoids_ComboDurations or {} + CleveRoids_ComboDurations[spellID] = CleveRoids_ComboDurations[spellID] or {} + CleveRoids_ComboDurations[spellID][comboPoints] = floor(actualDuration + 0.5) + if CleveRoids.debug then - local comboData = CleveRoids.ComboPointTracking.byID and CleveRoids.ComboPointTracking.byID[spellID] - local cpUsed = comboData and comboData.combo_points or "?" DEFAULT_CHAT_FRAME:AddMessage( "|cff4b7dccCleveRoids:|r Learned combo spell " .. spellName .. - " (ID:" .. spellID .. ") with " .. cpUsed .. " CP = " .. floor(actualDuration + 0.5) .. "s" + " (ID:" .. spellID .. ") at " .. comboPoints .. " CP = " .. floor(actualDuration + 0.5) .. "s" ) end else + -- Learn normal spell duration CleveRoids_LearnedDurations[spellID] = CleveRoids_LearnedDurations[spellID] or {} CleveRoids_LearnedDurations[spellID][casterGUID] = floor(actualDuration + 0.5) From 7b79b451308e896b94f99e23074c073d4760a9a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 17:33:27 +0000 Subject: [PATCH 10/26] Fix debuff conditionals using wrong max duration for combo spells CRITICAL FIX: Debuff conditionals like [debuff:Rip<4] were using base duration (12s) instead of actual combo duration (28s for 5 CP). **Problem:** - SeedUnit rescans debuffs on target change/UNIT_AURA events - It called lib:GetDuration(spellID) without combo awareness - This overwrote combo-aware durations with base durations - pfUI cooldown module showed wrong max duration (12s vs 28s) - Conditional [debuff:Rip<4] triggered at wrong times **Solution:** 1. Updated lib:GetDuration to accept optional comboPoints parameter 2. Checks CleveRoids_ComboDurations[spellID][cp] FIRST before other sources 3. Priority: combo learned > caster learned > static database 4. SeedUnit now uses highest learned CP duration as fallback 5. When rescanning combo spells, assumes 5 CP if learned **Benefits:** - [debuff:Rip<4] now uses actual 28s max duration (5 CP) - pfUI cooldown displays correct duration - Conditionals trigger at correct times - Prevents base duration from overwriting learned duration **Example:** Cast Rip with 5 CP (28s), macro refreshes at <4s remaining (24s used) Previously: Would refresh at <4s of 12s base (8s used) - TOO EARLY! --- Utility.lua | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Utility.lua b/Utility.lua index a92a0d2..afea42c 100644 --- a/Utility.lua +++ b/Utility.lua @@ -718,7 +718,16 @@ lib.learnCastTimers = lib.learnCastTimers or {} CleveRoids_LearnedDurations = CleveRoids_LearnedDurations or {} -function lib:GetDuration(spellID, casterGUID) +function lib:GetDuration(spellID, casterGUID, comboPoints) + -- Check combo-specific learned durations first if this is a combo spell + if comboPoints and CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then + local comboDuration = CleveRoids_ComboDurations[spellID][comboPoints] + if comboDuration and comboDuration > 0 then + return comboDuration + end + end + + -- Check caster-specific learned durations if casterGUID and CleveRoids_LearnedDurations[spellID] then local learned = CleveRoids_LearnedDurations[spellID][casterGUID] if learned and learned > 0 then @@ -726,6 +735,7 @@ function lib:GetDuration(spellID, casterGUID) end end + -- Fall back to static database return self.durations[spellID] or 0 end @@ -794,7 +804,20 @@ local function SeedUnit(unit) if spellID and lib.durations[spellID] then if not (lib.objects[guid] and lib.objects[guid][spellID]) then - lib:AddEffect(guid, unitName, spellID, lib:GetDuration(spellID), stacks) + -- For combo spells, try to use the highest learned duration as a fallback + local duration = nil + if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) and + CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then + -- Use the longest learned duration (assume 5 CP) + for cp = 5, 1, -1 do + if CleveRoids_ComboDurations[spellID][cp] then + duration = CleveRoids_ComboDurations[spellID][cp] + break + end + end + end + duration = duration or lib:GetDuration(spellID) + lib:AddEffect(guid, unitName, spellID, duration, stacks) end end end @@ -805,7 +828,20 @@ local function SeedUnit(unit) if spellID and lib.durations[spellID] then if not (lib.objects[guid] and lib.objects[guid][spellID]) then - lib:AddEffect(guid, unitName, spellID, lib:GetDuration(spellID), stacks) + -- For combo spells, try to use the highest learned duration as a fallback + local duration = nil + if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) and + CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then + -- Use the longest learned duration (assume 5 CP) + for cp = 5, 1, -1 do + if CleveRoids_ComboDurations[spellID][cp] then + duration = CleveRoids_ComboDurations[spellID][cp] + break + end + end + end + duration = duration or lib:GetDuration(spellID) + lib:AddEffect(guid, unitName, spellID, duration, stacks) end end end From bb7cd247618870589fcb74063fb8bdc1f8caa104 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 17:39:16 +0000 Subject: [PATCH 11/26] Fix nil comparison error in spell_tracking cleanup loop BUGFIX: Core.lua:2253 attempt to compare nil with number The cleanup loop was checking 'if time > cast.expires' but some entries in spell_tracking (like combo tracking data) don't have an expires field. Added nil check: 'if cast.expires and time > cast.expires' This prevents errors when combo tracking data is in spell_tracking without an expiration time. --- Core.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core.lua b/Core.lua index 2321aba..df04981 100644 --- a/Core.lua +++ b/Core.lua @@ -2250,7 +2250,7 @@ function CleveRoids.OnUpdate(self) end for guid,cast in pairs(CleveRoids.spell_tracking) do - if time > cast.expires then + if cast.expires and time > cast.expires then CleveRoids.spell_tracking[guid] = nil end end From e8e5f7d1f36c2ddf4ceb03b5ea0526630903d1e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 17:46:30 +0000 Subject: [PATCH 12/26] Add comprehensive debug tracing for combo duration system Added debug output at all critical points in the duration tracking flow: - UNIT_CASTEVENT handler: Shows calculated duration and CP count - lib:GetDuration: Shows which source provided the duration (learned combo/caster/static) - lib:AddEffect: Shows what duration was actually stored - SeedUnit: Shows what duration is used when rescanning debuffs/buffs This will help diagnose why combo durations aren't being applied correctly. --- Utility.lua | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/Utility.lua b/Utility.lua index afea42c..29b9719 100644 --- a/Utility.lua +++ b/Utility.lua @@ -723,6 +723,13 @@ function lib:GetDuration(spellID, casterGUID, comboPoints) if comboPoints and CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then local comboDuration = CleveRoids_ComboDurations[spellID][comboPoints] if comboDuration and comboDuration > 0 then + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffccccff[DEBUG GetDuration]|r %s (ID:%d) CP:%d -> %ds (from learned combo)", + spellName, spellID, comboPoints, comboDuration) + ) + end return comboDuration end end @@ -731,12 +738,27 @@ function lib:GetDuration(spellID, casterGUID, comboPoints) if casterGUID and CleveRoids_LearnedDurations[spellID] then local learned = CleveRoids_LearnedDurations[spellID][casterGUID] if learned and learned > 0 then + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffccccff[DEBUG GetDuration]|r %s (ID:%d) -> %ds (from learned caster)", + spellName, spellID, learned) + ) + end return learned end end -- Fall back to static database - return self.durations[spellID] or 0 + local staticDur = self.durations[spellID] or 0 + if CleveRoids.debug and staticDur > 0 then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffccccff[DEBUG GetDuration]|r %s (ID:%d) -> %ds (from static DB)", + spellName, spellID, staticDur) + ) + end + return staticDur end function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster) @@ -756,6 +778,15 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster) rec.caster = caster lib.objects[guid][spellID] = rec + + -- DEBUG: Show what we stored + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ffff[DEBUG AddEffect]|r %s (ID:%d) stored duration:%ds on %s", + spellName, spellID, duration, unitName or "Unknown") + ) + end end function lib:UnitDebuff(unit, id) @@ -812,6 +843,13 @@ local function SeedUnit(unit) for cp = 5, 1, -1 do if CleveRoids_ComboDurations[spellID][cp] then duration = CleveRoids_ComboDurations[spellID][cp] + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) using learned %dCP duration:%ds", + spellName, spellID, cp, duration) + ) + end break end end @@ -836,6 +874,13 @@ local function SeedUnit(unit) for cp = 5, 1, -1 do if CleveRoids_ComboDurations[spellID][cp] then duration = CleveRoids_ComboDurations[spellID][cp] + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) using learned %dCP duration:%ds", + spellName, spellID, cp, duration) + ) + end break end end @@ -889,6 +934,15 @@ ev:SetScript("OnEvent", function() duration = lib:GetDuration(spellID, casterGUID) end + -- DEBUG: Show what duration we calculated + if CleveRoids.debug and duration then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffff00ff[DEBUG CAST]|r %s (ID:%d) CP:%s duration:%ds", + spellName, spellID, tostring(comboPoints or "nil"), duration) + ) + end + if duration and duration > 0 then local targetName = lib.guidToName[targetGUID] if not targetName then From e7152e368364cdf97dd78145ede7039a3d23c197 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 18:04:20 +0000 Subject: [PATCH 13/26] Fix combo point tracking - use name-based tracking as fallback The issue: UNIT_CASTEVENT fires AFTER combo points are consumed, so GetComboPoints() returns 0. The lastComboPoints fallback wasn't always set in time. Solution: When TrackComboPointCastByID sees 0 combo points, it now: 1. First tries lastComboPoints (OnUpdate tracking) 2. If still 0, checks name-based tracking data from extension hooks (which captured the CP count BEFORE the spell was cast) 3. Uses the CP count if the cast was within the last 0.5 seconds This ensures we always get the correct combo point count for duration calculation, regardless of timing. --- ComboPointTracker.lua | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 2fa97f7..445850c 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -217,9 +217,34 @@ function CleveRoids.TrackComboPointCastByID(spellID, targetGUID) -- Get current combo points, but if they're 0 (already consumed), use last known value local comboPoints = CleveRoids.GetComboPoints() - if comboPoints == 0 and CleveRoids.lastComboPoints > 0 then - comboPoints = CleveRoids.lastComboPoints - CleveRoids.lastComboPoints = 0 -- Reset after using + + -- If combo points are 0, try multiple fallback sources + if comboPoints == 0 then + -- First, try lastComboPoints + if CleveRoids.lastComboPoints > 0 then + comboPoints = CleveRoids.lastComboPoints + CleveRoids.lastComboPoints = 0 -- Reset after using + else + -- Second, check if name-based tracking has recent data for this spell + local spellName = SpellInfo(spellID) + if spellName then + -- Remove rank info for comparison + local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") + if CleveRoids.ComboPointTracking[spellName] then + local tracking = CleveRoids.ComboPointTracking[spellName] + if tracking.combo_points and tracking.combo_points > 0 and + (GetTime() - tracking.cast_time) < 0.5 then -- Within last 0.5 seconds + comboPoints = tracking.combo_points + end + elseif CleveRoids.ComboPointTracking[baseName] then + local tracking = CleveRoids.ComboPointTracking[baseName] + if tracking.combo_points and tracking.combo_points > 0 and + (GetTime() - tracking.cast_time) < 0.5 then + comboPoints = tracking.combo_points + end + end + end + end end local duration = CleveRoids.CalculateComboScaledDurationByID(spellID, comboPoints) From 60bf1741edd0c5a9a8e73856ce39151fb8f2eafc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 18:15:33 +0000 Subject: [PATCH 14/26] Add pfUI cooldown integration for combo-aware durations Hook into pfUI.api.libdebuff to inject combo-based durations: - Hook GetDuration: Returns learned combo durations for combo spells - Hook AddEffect: Injects tracked combo durations when debuffs are applied This ensures pfUI's cooldown module displays the correct duration (e.g., 28s for 5 CP Rip instead of base 12s). Debug messages show when pfUI hooks are triggered in debug mode. --- Compatibility/pfUI.lua | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua index 992cf25..3dd0a0e 100644 --- a/Compatibility/pfUI.lua +++ b/Compatibility/pfUI.lua @@ -91,6 +91,92 @@ function Extension.HandleSendChatMessageHook() end end +-- Hook pfUI's libdebuff to use our combo-aware durations +function Extension.HookPfUILibdebuff() + if not pfUI or not pfUI.api or not pfUI.api.libdebuff then + return false + end + + local pflib = pfUI.api.libdebuff + + -- Hook GetDuration if it exists + if pflib.GetDuration and not Extension.pfLibDebuffHooked then + local originalGetDuration = pflib.GetDuration + + pflib.GetDuration = function(self, spellID, casterGUID, comboPoints) + -- Check if this is a combo scaling spell first + if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) then + -- If combo points provided, check learned combo durations + if comboPoints and CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then + local comboDur = CleveRoids_ComboDurations[spellID][comboPoints] + if comboDur and comboDur > 0 then + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[pfUI Hook]|r Returning %ds for spell %d at %d CP", + comboDur, spellID, comboPoints) + ) + end + return comboDur + end + end + + -- Fallback: check for highest learned combo duration + if CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then + for cp = 5, 1, -1 do + if CleveRoids_ComboDurations[spellID][cp] then + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[pfUI Hook]|r Returning %ds for spell %d (fallback %d CP)", + CleveRoids_ComboDurations[spellID][cp], spellID, cp) + ) + end + return CleveRoids_ComboDurations[spellID][cp] + end + end + end + end + + -- Not a combo spell or no learned duration, use original + return originalGetDuration(self, spellID, casterGUID, comboPoints) + end + + Extension.pfLibDebuffHooked = true + Extension.DLOG("Hooked pfUI.api.libdebuff.GetDuration for combo duration support") + end + + -- Hook AddEffect if it exists to inject combo-aware durations + if pflib.AddEffect and not Extension.pfLibAddEffectHooked then + local originalAddEffect = pflib.AddEffect + + pflib.AddEffect = function(self, guid, unitName, spellID, duration, stacks, caster) + -- For combo spells, override duration with our tracked value + if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) then + -- Check if we have tracking data for this spell + if CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking.byID and + CleveRoids.ComboPointTracking.byID[spellID] then + local tracking = CleveRoids.ComboPointTracking.byID[spellID] + if tracking.target_guid == guid and tracking.duration then + duration = tracking.duration + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[pfUI AddEffect Hook]|r Overriding duration to %ds for spell %d", + duration, spellID) + ) + end + end + end + end + + return originalAddEffect(self, guid, unitName, spellID, duration, stacks, caster) + end + + Extension.pfLibAddEffectHooked = true + Extension.DLOG("Hooked pfUI.api.libdebuff.AddEffect for combo duration support") + end + + return Extension.pfLibDebuffHooked or Extension.pfLibAddEffectHooked +end + -- Main compatibility check and setup function Extension.SetupCompatibility() Extension.pfUILoaded = (pfUI ~= nil) @@ -99,6 +185,9 @@ function Extension.SetupCompatibility() if Extension.pfUILoaded then Extension.DLOG("pfUI detected") + -- Hook libdebuff for combo duration support + Extension.HookPfUILibdebuff() + if Extension.macrotweakLoaded then Extension.DLOG("pfUI macrotweak module detected") From d6b4ae3d97387b00d000d12e7317828b27b6ea48 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 18:18:41 +0000 Subject: [PATCH 15/26] Add direct pfUI duration synchronization for combo spells After analyzing pfUI's cooldown module, we need to directly sync combo durations to pfUI's libdebuff.objects storage: - Added SyncComboDurationToPfUI function to force-update pfUI's stored debuff duration after tracking combo spells - Called from UNIT_CASTEVENT handler after AddEffect - This ensures pfUI's cooldown display shows the correct combo duration (e.g., 28s for 5 CP Rip) instead of base 12s pfUI's cooldown module is just a display layer - it shows durations from pfUI.api.libdebuff.objects. By directly updating this storage, we ensure the correct duration is displayed regardless of timing. --- Compatibility/pfUI.lua | 24 ++++++++++++++++++++++++ Utility.lua | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua index 3dd0a0e..0aa8833 100644 --- a/Compatibility/pfUI.lua +++ b/Compatibility/pfUI.lua @@ -177,6 +177,27 @@ function Extension.HookPfUILibdebuff() return Extension.pfLibDebuffHooked or Extension.pfLibAddEffectHooked end +-- Synchronize combo durations to pfUI's libdebuff objects +function Extension.SyncComboDurationToPfUI(guid, spellID, duration) + if not pfUI or not pfUI.api or not pfUI.api.libdebuff then + return + end + + -- Update pfUI's stored debuff duration + local pflib = pfUI.api.libdebuff + if pflib.objects and pflib.objects[guid] and pflib.objects[guid][spellID] then + local old_duration = pflib.objects[guid][spellID].duration + pflib.objects[guid][spellID].duration = duration + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ffaa[pfUI Sync]|r Updated spell %d duration: %ds -> %ds", + spellID, old_duration or 0, duration) + ) + end + end +end + -- Main compatibility check and setup function Extension.SetupCompatibility() Extension.pfUILoaded = (pfUI ~= nil) @@ -204,6 +225,9 @@ function Extension.OnLoad() Extension.DLOG("Extension pfUI Loaded.") Extension.HookMethod(CleveRoids, "GetFocusName", "FocusNameHook", true) + -- Export extension for external access + CleveRoids.Compatibility_pfUI = Extension + -- Initial compatibility check Extension.SetupCompatibility() end diff --git a/Utility.lua b/Utility.lua index 29b9719..81ed0ca 100644 --- a/Utility.lua +++ b/Utility.lua @@ -956,6 +956,12 @@ ev:SetScript("OnEvent", function() end lib:AddEffect(targetGUID, targetName, spellID, duration, 0, "player") + -- Sync combo duration to pfUI if it's loaded + if comboPoints and CleveRoids.Compatibility_pfUI and + CleveRoids.Compatibility_pfUI.SyncComboDurationToPfUI then + CleveRoids.Compatibility_pfUI.SyncComboDurationToPfUI(targetGUID, spellID, duration) + end + -- ALWAYS set up learning for combo spells (even if we have calculated duration) if comboPoints then lib.learnCastTimers[targetGUID] = lib.learnCastTimers[targetGUID] or {} From 3e669a7b7a0f022fe0c5a78754793535b94e3703 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 18:20:51 +0000 Subject: [PATCH 16/26] Fix pfUI integration - use unit names and spell names, not GUIDs/IDs CRITICAL FIX: pfUI's libdebuff uses completely different data structures: - Uses unit NAME (not GUID) - Uses unit LEVEL as a key - Uses spell NAME (effect, not ID) - Storage: pflib.objects[unitName][unitLevel][effectName] Fixed SyncComboDurationToPfUI: - Convert GUID to unit name (check target, fallback to guidToName map) - Get unit level from target or default to 0 - Convert spell ID to spell name using SpellInfo() - Remove rank from spell name to match pfUI's format - Search both specific level and level 0 (pfUI's fallback) Fixed AddEffect hook: - Updated parameters to match pfUI signature: (unit, unitlevel, effect, duration, caster) - Check spell by name instead of ID - Map spell name back to ID to find tracking data This ensures pfUI's cooldown displays show correct combo durations. --- Compatibility/pfUI.lua | 107 ++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua index 0aa8833..587e810 100644 --- a/Compatibility/pfUI.lua +++ b/Compatibility/pfUI.lua @@ -148,26 +148,37 @@ function Extension.HookPfUILibdebuff() if pflib.AddEffect and not Extension.pfLibAddEffectHooked then local originalAddEffect = pflib.AddEffect - pflib.AddEffect = function(self, guid, unitName, spellID, duration, stacks, caster) - -- For combo spells, override duration with our tracked value - if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) then - -- Check if we have tracking data for this spell - if CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking.byID and - CleveRoids.ComboPointTracking.byID[spellID] then - local tracking = CleveRoids.ComboPointTracking.byID[spellID] - if tracking.target_guid == guid and tracking.duration then - duration = tracking.duration - if CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ff00[pfUI AddEffect Hook]|r Overriding duration to %ds for spell %d", - duration, spellID) - ) + pflib.AddEffect = function(self, unit, unitlevel, effect, duration, caster) + -- effect is a spell name, not ID + -- Check if this is a combo scaling spell by name + if CleveRoids.IsComboScalingSpell and CleveRoids.IsComboScalingSpell(effect) then + -- Try to get learned duration for this combo spell + local spellData = CleveRoids.GetComboScalingData(effect) + if spellData then + -- Find the spell ID for this effect + for spellID, data in pairs(CleveRoids.ComboScalingSpellsByID) do + if data.name == effect then + -- Check if we have tracking data + if CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking.byID and + CleveRoids.ComboPointTracking.byID[spellID] then + local tracking = CleveRoids.ComboPointTracking.byID[spellID] + if tracking.duration then + duration = tracking.duration + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[pfUI AddEffect Hook]|r Overriding %s duration to %ds", + effect, duration) + ) + end + break + end + end end end end end - return originalAddEffect(self, guid, unitName, spellID, duration, stacks, caster) + return originalAddEffect(self, unit, unitlevel, effect, duration, caster) end Extension.pfLibAddEffectHooked = true @@ -183,19 +194,69 @@ function Extension.SyncComboDurationToPfUI(guid, spellID, duration) return end + -- Get unit name from GUID + local unitName = nil + local unitLevel = 0 + + -- Check if this is the current target + local _, targetGUID = UnitExists("target") + if targetGUID == guid then + unitName = UnitName("target") + unitLevel = UnitLevel("target") or 0 + end + + -- If we couldn't find the unit, use GUID to name mapping from libdebuff + if not unitName and CleveRoids.libdebuff and CleveRoids.libdebuff.guidToName then + unitName = CleveRoids.libdebuff.guidToName[guid] + -- Default to level 0 if we don't have the unit targeted + unitLevel = 0 + end + + if not unitName then + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[pfUI Sync]|r Could not find unit name for GUID") + end + return + end + + -- Get spell name from spell ID + local spellName = SpellInfo(spellID) + if not spellName then + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[pfUI Sync]|r Could not find spell name for ID " .. spellID) + end + return + end + + -- Remove rank from spell name to match pfUI's format + local effectName = string.gsub(spellName, "%s*%(Rank %d+%)", "") + -- Update pfUI's stored debuff duration local pflib = pfUI.api.libdebuff - if pflib.objects and pflib.objects[guid] and pflib.objects[guid][spellID] then - local old_duration = pflib.objects[guid][spellID].duration - pflib.objects[guid][spellID].duration = duration + if pflib.objects and pflib.objects[unitName] then + -- Try both the specific level and level 0 (fallback) + for _, level in ipairs({unitLevel, 0}) do + if pflib.objects[unitName][level] and pflib.objects[unitName][level][effectName] then + local old_duration = pflib.objects[unitName][level][effectName].duration + pflib.objects[unitName][level][effectName].duration = duration - if CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ffaa[pfUI Sync]|r Updated spell %d duration: %ds -> %ds", - spellID, old_duration or 0, duration) - ) + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ffaa[pfUI Sync]|r Updated %s on %s (L%d): %ds -> %ds", + effectName, unitName, level, old_duration or 0, duration) + ) + end + return + end end end + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaaa00[pfUI Sync]|r Effect not found in pfUI storage: %s on %s", + effectName, unitName) + ) + end end -- Main compatibility check and setup From 83b3938d6d7c4374aec7d59c2471e9039cad4cdc Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Sun, 16 Nov 2025 13:32:18 -0500 Subject: [PATCH 17/26] update real durations --- CLAUDE.md | 362 +++++++++++++++++++++++++++++++++++++++ ComboPointTracker.lua | 15 +- wow-api-type-definitions | 1 + 3 files changed, 370 insertions(+), 8 deletions(-) create mode 100644 CLAUDE.md create mode 160000 wow-api-type-definitions diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d895411 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,362 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +SuperCleveRoidMacros is a World of Warcraft 1.12.1 (Vanilla) addon that provides enhanced macro functionality with dynamic tooltips, conditional execution, and extended syntax. It requires three DLL mods: **SuperWoW**, **Nampower**, and **UnitXP_SP3** - all are mandatory dependencies. + +This addon is built for Turtle WoW (TWoW) private server and combines features from CleverMacro and Roid-Macros with significant extensions. + +## Critical Requirements + +### Required DLL Mods +The addon **WILL NOT FUNCTION** without all three DLL mods installed: +1. **SuperWoW** (balakethelock) - Provides extended API functions like `SetAutoloot`, `SpellInfo()`, `UnitBuff/UnitDebuff` with spell IDs +2. **Nampower** - Provides `QueueSpellByName`, `IsSpellInRange`, `GetCurrentCastingInfo`, and spell queueing +3. **UnitXP_SP3** (konaka) - Provides `UnitXP()` for distance/positioning checks + +The addon checks for these on load in `Core.lua:18-48` and will disable itself with error messages if any are missing. + +## Code Architecture + +### File Loading Order (SuperCleveRoidMacros.toc) +Files are loaded in this exact order, which is critical for initialization: +1. `Localization.lua` - Sets up `CleveRoids.Localized` for multi-language support +2. `Init.lua` - Initializes the `CleveRoids` global table and environment flags +3. `Utility.lua` - Utility functions, string parsing, comparators, and the debuff tracking library (`CleveRoids.libdebuff`) +4. `Core.lua` - Main addon logic, macro parsing, action evaluation, UI hooks +5. `Conditionals.lua` - Conditional keyword validation functions (`CleveRoids.Keywords`) +6. `Console.lua` - Slash command handlers +7. `ExtensionsManager.lua` - Extension/plugin system for modular features +8. Compatibility layers for other addons (SuperMacro, pfUI, Bongos) +9. Extension modules (MacroLengthWarn, Mouseover integrations, Tooltips) + +### Key Global Objects + +**`CleveRoids`** - Main global namespace containing: +- `.playerClass` - Player's class (set in `Console.lua`) +- `.Spells`, `.Items`, `.Talents` - Indexed lookups for spells/items/talents +- `.Actions` - Parsed macro data per action button +- `.Sequences` - Cast sequence state tracking +- `.Keywords` - Conditional validation function table (from `Conditionals.lua`) +- `.libdebuff` - Debuff duration tracking system (SuperWoW-based) +- `.hasSuperwow`, `.hasNampower`, `.hasUnitXP` - Feature detection flags +- `.mouseoverUnit` - Current mouseover unit (managed by Extensions) + +**`CleveRoidMacros`** - SavedVariables configuration: +- `.realtime` - 0 = event-based updates, 1 = continuous updates (default: 0) +- `.refresh` - Update rate in Hz when realtime=1 (1-10, default: 5) + +**`CleveRoids_LearnedDurations`** - SavedVariables for learned debuff durations (per-caster GUID) + +**`CleveRoids_ImmunityData`** - SavedVariables for NPC immunity tracking (organized by damage school) + +### Core Systems + +#### 1. Conditional System (`Conditionals.lua`) +The `CleveRoids.Keywords` table maps conditional keywords to validation functions. Each function receives a `conditionals` table containing: +- `.target` - The UnitID being evaluated (default: "target") +- `.help` / `.harm` - Friendly/enemy flag +- `.action` - The spell/item name being cast +- Additional conditional-specific values (e.g., `.mod`, `.stance`, `.buff`) + +**Multi-value conditionals** (marked as "Multi" in README): +- Use `Or()` helper - returns true if ANY value matches +- Example: `[zone:Stormwind/Ironforge]` → true if in either zone + +**Negatable conditionals** (marked as "Noable"): +- Use `And()` helper - ALL must be false +- Example: `[nozone:Stormwind/Ironforge]` → true only if in neither zone + +**Numeric comparisons** (hp, power, cooldown, etc.): +- Use `CleveRoids.operators` and `CleveRoids.comparators` tables +- Operators: `<`, `>`, `=`, `<=`, `>=`, `~=` (and string aliases: `lt`, `gt`, `eq`, etc.) +- Format in args table: `{operator = ">", amount = 50}` + +#### 2. Debuff Timer System (`Utility.lua:328-998`) +The `CleveRoids.libdebuff` system provides accurate debuff tracking: +- Pre-configured durations for 329+ vanilla spells (see `lib.durations`) +- Auto-learning via `UNIT_CASTEVENT` (cast start) and `RAW_COMBATLOG` (fade detection) +- Per-caster GUID storage in `CleveRoids_LearnedDurations` (handles talent variations) +- GUID-based tracking (supports multiple mobs with same name) +- Fallback to static database for unknown spells + +**Key functions:** +- `lib:GetDuration(spellID, casterGUID)` - Returns learned or static duration +- `lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)` - Track new debuff +- `lib:UnitDebuff(unit, id)` - Returns: name, _, texture, stacks, dtype, duration, timeleft, caster + +#### 3. Immunity Tracking System (`Utility.lua:1000-1380`) +The immunity tracking system automatically learns and tracks NPC immunities from combat log messages: +- **Auto-learning** from combat log: "X's Spell fails. Y is immune." +- Stores immunities by **damage school** (fire, frost, nature, shadow, arcane, holy, physical) in `CleveRoids_ImmunityData` +- Supports both **permanent immunities** and **buff-based immunities** (e.g., boss immune during a shield phase) +- Automatic spell school detection via tooltip scanning and name pattern matching + +**Data Structure:** +```lua +CleveRoids_ImmunityData = { + fire = { + ["Ragnaros"] = true, -- Permanent immunity + ["Vaelastrasz the Corrupt"] = { buff = "Burning Adrenaline" } -- Conditional immunity + }, + frost = { + ["Frozen Core"] = true + } +} +``` + +**Key Functions:** +- `CleveRoids.CheckImmunity(unitId, spellOrSchool)` - Check immunity to spell name OR damage school + - Example: `CheckImmunity("target", "Flame Shock")` or `CheckImmunity("target", "fire")` + - Automatically detects buff-based immunity by checking target's current buffs +- `CleveRoids.ListImmunities(school)` - List immunity data (all or specific school) +- `CleveRoids.AddImmunity(npcName, school, buffName)` - Manually add immunity +- `CleveRoids.RemoveImmunity(npcName, school)` - Remove immunity entry +- `CleveRoids.ClearImmunities(school)` - Clear immunity data + +**Combat Log Parsing:** +- Listens to `RAW_COMBATLOG` event for immunity messages +- Extracts spell name and target name from messages +- If target has exactly one buff when immunity occurs, assumes buff causes immunity +- Otherwise records as permanent immunity + +**Console Commands:** +```bash +/cleveroid listimmune [school] # List all or specific school immunities +/cleveroid addimmune "" [buff] # Add manual immunity +/cleveroid removeimmune "" # Remove immunity +/cleveroid clearimmune [school] # Clear data +``` + +**Conditional Usage:** +- `[immune:fire]` or `[immune:Flame Shock]` - Check if target is immune +- `[noimmune:nature]` or `[noimmune:"Serpent Sting"]` - Check if NOT immune +- Can omit spell/school to check the action being cast: `[noimmune] Fireball` + +#### 4. Macro Parsing and Execution +Macros are parsed in `Core.lua` into action lists. Each action has: +- `.cmd` - Command (e.g., "/cast", "/use", "/castsequence") +- `.args` - Arguments string +- `.conditionals` - Parsed conditional table +- `.sequence` - Cast sequence data (if `/castsequence`) + +**Evaluation flow:** +1. `CleveRoids.TestForActiveAction(actions)` iterates action list +2. For each action, `CleveRoids.TestAction(cmd, args)` evaluates conditionals +3. First passing action becomes `.active` and determines icon/tooltip +4. `#showtooltip` at macro start enables dynamic icon/tooltip updates + +#### 5. Equipment Swapping System (`Core.lua:57-227`) +Implements queued equipment changes with cooldowns: +- Per-slot cooldown: 1.5s (`EQUIP_COOLDOWN`) +- Global cooldown: 0.5s (`EQUIP_GLOBAL_COOLDOWN`) +- Combat weapon swap protection (checks casting state, on-swing spells) +- `CleveRoids.QueueEquipItem(item, slotName)` - Queue swap +- `CleveRoids.ProcessEquipmentQueue()` - Process queue (called from OnUpdate) + +#### 6. Extension System (`ExtensionsManager.lua`) +Modular plugin architecture for addon integrations: +- `CleveRoids.RegisterExtension(name)` creates extension object +- Extensions can register events, hook functions/methods +- Mouseover extensions set `CleveRoids.SetMouseoverFrom(source, unit)` with priority system (pfUI > blizz > tooltip) +- Compatibility extensions handle SuperMacro, pfUI MacroTweak, Bongos action bars + +## Common Development Tasks + +### Adding a New Conditional Keyword + +1. **Add validation function to `Conditionals.lua`:** +```lua +CleveRoids.Keywords = { + -- Existing conditionals... + + mynewconditional = function(conditionals) + -- Access parsed values from conditionals table + local checkValue = conditionals.mynewconditional + + -- For multi-value support, use Or() helper: + return Or(checkValue, function(val) + return YourCheckFunction(val) + end) + end, + + nomynewconditional = function(conditionals) + -- Negated version uses And() helper: + return And(conditionals.nomynewconditional, function(val) + return not YourCheckFunction(val) + end) + end, +} +``` + +2. **Update README.md** with syntax, examples, and whether it's Multi/Noable + +### Adding Support for a New Spell Duration + +1. **Add to static database in `Utility.lua`:** +```lua +lib.durations = lib.durations or { + -- Existing durations... + [12345] = 30, -- Spell Name (Rank X) - duration in seconds +} +``` + +2. **Or let players learn it manually:** +``` +/cleveroid learn 12345 30 +``` + +The system will auto-learn durations as players cast spells if not in database. + +### Modifying Mouseover Behavior + +Mouseover unit determination uses a priority system in `Utility.lua:94-145`: +- Priority levels: pfUI (3), blizzard (3), tooltip (1) +- Extensions call `CleveRoids.SetMouseoverFrom(source, unit)` to set mouseover +- Highest priority source wins +- `CleveRoids.ClearMouseoverFrom(source, unitIfMatch)` to remove + +To add new source: +1. Create extension in `Extensions/Mouseover/YourAddon.lua` +2. Hook the addon's frame OnEnter/OnLeave scripts +3. Call `SetMouseoverFrom("youraddon", unitID)` with appropriate priority + +### Testing Conditionals + +Use `/dump` command to inspect values: +```lua +/script DEFAULT_CHAT_FRAME:AddMessage(tostring(CleveRoids.ValidateHp("target", "<=", 50))) +``` + +Check `CleveRoids.Keywords` table for function existence: +```lua +/script DEFAULT_CHAT_FRAME:AddMessage(tostring(CleveRoids.Keywords.myconditional ~= nil)) +``` + +## Important Implementation Details + +### String Parsing and Quotation Handling +- Spell/item names with spaces can use underscores OR quotes: `[buff:Mark_of_the_Wild]` or `[buff:"Mark of the Wild"]` +- The parser in `Core.lua` uses `CleveRoids.splitStringIgnoringQuotes()` to handle quoted strings +- Rank syntax MUST include parentheses: `Faerie Fire (Feral)(Rank 4)` (not just `Faerie Fire (Feral)`) + +### Macro Icon/Tooltip Updates +- Icons update via `CleveRoids.QueueActionUpdate()` which sets `isActionUpdateQueued = true` +- OnUpdate handler processes queue and calls `ActionButton_Update()` on affected buttons +- If `realtime=0` (default), updates only on events (ACTIONBAR_UPDATE, UNIT_AURA, etc.) +- If `realtime=1`, updates continuously at `refresh` rate (CPU intensive) + +### Spell Cooldown Handling +- `CleveRoids.GetSpellCooldown(spellName, ignoreGCD)` returns absolute expiry time +- `ignoreGCD=true` treats 1.5s cooldowns as "not on cooldown" (GCD) +- Item cooldowns are separate: `CleveRoids.GetItemCooldown(item)` returns remaining seconds + +### Cast Sequence Reset Logic +Cast sequences reset on: +- `reset=X` - X seconds of inactivity +- `reset=target` - Target change +- `reset=combat` - Leaving combat +- `reset=mod` - Modifier key press +- Can combine: `reset=3/target/combat` + +State tracked in `CleveRoids.Sequences` by macro slot. + +## File Structure Patterns + +### Adding New Compatibility Layer +Create `Compatibility/YourAddon.lua`: +```lua +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +if not YourAddon then return end -- Check if addon exists + +-- Your compatibility code here +-- Usually hooks or event handlers to prevent conflicts + +_G["CleveRoids"] = CleveRoids +``` + +Add to `.toc` file in Compatibility section. + +### Adding New Extension +Create `Extensions/YourExtension.lua`: +```lua +local extension = CleveRoids.RegisterExtension("YourExtension") + +extension.OnLoad = function() + -- Initialization code + extension.RegisterEvent("SOME_EVENT", "OnSomeEvent") +end + +extension.OnSomeEvent = function() + -- Event handler using 'event', 'arg1', etc. globals +end + +return extension +``` + +Add to `.toc` file in Extensions section. + +## Performance Considerations + +- **Avoid `realtime=1` unless necessary** - Event-based updates (realtime=0) are significantly more efficient +- **Debuff table cleanup** - Runs on target change, throttled to 2s intervals (see `Utility.lua:948`) +- **Spell/item indexing** - Only rebuilt on SPELLS_CHANGED, LEARNED_SPELL_IN_TAB events +- **Reactive spell detection** - Requires abilities on action bars to detect usability (limitation may be removed with Nampower enhancements) + +## Console Commands + +Defined in `Console.lua`: + +```bash +# View/modify settings +/cleveroid # Show current settings +/cleveroid realtime 0|1 # Toggle realtime updates +/cleveroid refresh 1-10 # Set update rate (Hz) + +# Debuff duration learning +/cleveroid learn # Manually set duration (seconds) +/cleveroid forget # Forget learned duration(s) +/cleveroid debug 0|1 # Toggle learning debug messages +``` + +## Debugging Tips + +1. **Enable debug mode for debuff learning:** + ``` + /cleveroid debug 1 + ``` + Shows learned durations in chat as debuffs fade. + +2. **Check for missing dependencies:** + Look for error messages on login mentioning SuperWoW/Nampower/UnitXP. + +3. **Macro syntax errors:** + - Check for unmatched quotes or brackets + - Verify spell/item names are spelled correctly (case-sensitive) + - Use `#showtooltip` to see which action is evaluating as active + +4. **Icon not updating:** + - Ensure macro starts with `#showtooltip` + - Check if `realtime=0` and conditional needs event trigger + - Verify action bar addon compatibility (see Extensions) + +## Localization + +The `Localization.lua` file provides `CleveRoids.Localized` table with: +- `.Spells` - Localized spell names (keyed by English name) +- `.CreatureTypes` - Creature type translations +- `.Dagger`, `.Sword`, `.Shield`, etc. - Weapon type names + +Always use `CleveRoids.Localized.Spells["EnglishName"]` for hardcoded spell references to support non-English clients. + +## Known Limitations + +1. **Reactive abilities** (Revenge, Overpower, etc.) must be on action bars for detection (workaround may be possible with Nampower) +2. **Aquatic Form** detection uses reactive system (must be on non-stance action bar) +3. **Debuff time-left conditionals** only work on player's own debuffs unless pfUI or internal libdebuff has data +4. **Macro names** must be unique - no blank names, duplicates, or using spell names +5. **Parenthesis in spell names** (e.g., "Faerie Fire (Feral)") MUST include rank: "Faerie Fire (Feral)(Rank X)" diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 445850c..5d15e5e 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -33,12 +33,12 @@ CleveRoids.ComboScalingSpellsByID = { -- DRUID: Rip - 12 sec base, +4 sec per additional combo point (Source: Vanilla WoW Wiki) -- Note: Some sources say 10+2, but testing shows 12+4 is accurate for 1.12 - [1079] = { base = 12, increment = 4, name = "Rip" }, -- Rank 1 - [9492] = { base = 12, increment = 4, name = "Rip" }, -- Rank 2 - [9493] = { base = 12, increment = 4, name = "Rip" }, -- Rank 3 - [9752] = { base = 12, increment = 4, name = "Rip" }, -- Rank 4 - [9894] = { base = 12, increment = 4, name = "Rip" }, -- Rank 5 - [9896] = { base = 12, increment = 4, name = "Rip" }, -- Rank 6 + [1079] = { base = 10, increment = 2, name = "Rip" }, -- Rank 1 + [9492] = { base = 10, increment = 2, name = "Rip" }, -- Rank 2 + [9493] = { base = 10, increment = 2, name = "Rip" }, -- Rank 3 + [9752] = { base = 10, increment = 2, name = "Rip" }, -- Rank 4 + [9894] = { base = 10, increment = 2, name = "Rip" }, -- Rank 5 + [9896] = { base = 10, increment = 2, name = "Rip" }, -- Rank 6 } -- Legacy name-based table for backwards compatibility @@ -46,8 +46,7 @@ CleveRoids.ComboScalingSpells = { ["Rupture"] = { base_duration = 8, increment = 2, all_ranks = true }, ["Kidney Shot(Rank 1)"] = { base_duration = 1, increment = 1 }, ["Kidney Shot(Rank 2)"] = { base_duration = 2, increment = 1 }, - ["Kidney Shot"] = { base_duration = 1, increment = 1, check_rank = true }, - ["Rip"] = { base_duration = 12, increment = 4, all_ranks = true } + ["Rip"] = { base_duration = 10, increment = 2, all_ranks = true } } -- Function to get current combo points diff --git a/wow-api-type-definitions b/wow-api-type-definitions new file mode 160000 index 0000000..e566a0e --- /dev/null +++ b/wow-api-type-definitions @@ -0,0 +1 @@ +Subproject commit e566a0ef067e10a17cf23c41c83137056c232423 From e814a3f8deb77091a3af7af6f184545078bbd5b1 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Sun, 16 Nov 2025 13:36:08 -0500 Subject: [PATCH 18/26] update real durations --- CLAUDE.md | 362 --------------------------------------- wow-api-type-definitions | 1 - 2 files changed, 363 deletions(-) delete mode 100644 CLAUDE.md delete mode 160000 wow-api-type-definitions diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index d895411..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,362 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -SuperCleveRoidMacros is a World of Warcraft 1.12.1 (Vanilla) addon that provides enhanced macro functionality with dynamic tooltips, conditional execution, and extended syntax. It requires three DLL mods: **SuperWoW**, **Nampower**, and **UnitXP_SP3** - all are mandatory dependencies. - -This addon is built for Turtle WoW (TWoW) private server and combines features from CleverMacro and Roid-Macros with significant extensions. - -## Critical Requirements - -### Required DLL Mods -The addon **WILL NOT FUNCTION** without all three DLL mods installed: -1. **SuperWoW** (balakethelock) - Provides extended API functions like `SetAutoloot`, `SpellInfo()`, `UnitBuff/UnitDebuff` with spell IDs -2. **Nampower** - Provides `QueueSpellByName`, `IsSpellInRange`, `GetCurrentCastingInfo`, and spell queueing -3. **UnitXP_SP3** (konaka) - Provides `UnitXP()` for distance/positioning checks - -The addon checks for these on load in `Core.lua:18-48` and will disable itself with error messages if any are missing. - -## Code Architecture - -### File Loading Order (SuperCleveRoidMacros.toc) -Files are loaded in this exact order, which is critical for initialization: -1. `Localization.lua` - Sets up `CleveRoids.Localized` for multi-language support -2. `Init.lua` - Initializes the `CleveRoids` global table and environment flags -3. `Utility.lua` - Utility functions, string parsing, comparators, and the debuff tracking library (`CleveRoids.libdebuff`) -4. `Core.lua` - Main addon logic, macro parsing, action evaluation, UI hooks -5. `Conditionals.lua` - Conditional keyword validation functions (`CleveRoids.Keywords`) -6. `Console.lua` - Slash command handlers -7. `ExtensionsManager.lua` - Extension/plugin system for modular features -8. Compatibility layers for other addons (SuperMacro, pfUI, Bongos) -9. Extension modules (MacroLengthWarn, Mouseover integrations, Tooltips) - -### Key Global Objects - -**`CleveRoids`** - Main global namespace containing: -- `.playerClass` - Player's class (set in `Console.lua`) -- `.Spells`, `.Items`, `.Talents` - Indexed lookups for spells/items/talents -- `.Actions` - Parsed macro data per action button -- `.Sequences` - Cast sequence state tracking -- `.Keywords` - Conditional validation function table (from `Conditionals.lua`) -- `.libdebuff` - Debuff duration tracking system (SuperWoW-based) -- `.hasSuperwow`, `.hasNampower`, `.hasUnitXP` - Feature detection flags -- `.mouseoverUnit` - Current mouseover unit (managed by Extensions) - -**`CleveRoidMacros`** - SavedVariables configuration: -- `.realtime` - 0 = event-based updates, 1 = continuous updates (default: 0) -- `.refresh` - Update rate in Hz when realtime=1 (1-10, default: 5) - -**`CleveRoids_LearnedDurations`** - SavedVariables for learned debuff durations (per-caster GUID) - -**`CleveRoids_ImmunityData`** - SavedVariables for NPC immunity tracking (organized by damage school) - -### Core Systems - -#### 1. Conditional System (`Conditionals.lua`) -The `CleveRoids.Keywords` table maps conditional keywords to validation functions. Each function receives a `conditionals` table containing: -- `.target` - The UnitID being evaluated (default: "target") -- `.help` / `.harm` - Friendly/enemy flag -- `.action` - The spell/item name being cast -- Additional conditional-specific values (e.g., `.mod`, `.stance`, `.buff`) - -**Multi-value conditionals** (marked as "Multi" in README): -- Use `Or()` helper - returns true if ANY value matches -- Example: `[zone:Stormwind/Ironforge]` → true if in either zone - -**Negatable conditionals** (marked as "Noable"): -- Use `And()` helper - ALL must be false -- Example: `[nozone:Stormwind/Ironforge]` → true only if in neither zone - -**Numeric comparisons** (hp, power, cooldown, etc.): -- Use `CleveRoids.operators` and `CleveRoids.comparators` tables -- Operators: `<`, `>`, `=`, `<=`, `>=`, `~=` (and string aliases: `lt`, `gt`, `eq`, etc.) -- Format in args table: `{operator = ">", amount = 50}` - -#### 2. Debuff Timer System (`Utility.lua:328-998`) -The `CleveRoids.libdebuff` system provides accurate debuff tracking: -- Pre-configured durations for 329+ vanilla spells (see `lib.durations`) -- Auto-learning via `UNIT_CASTEVENT` (cast start) and `RAW_COMBATLOG` (fade detection) -- Per-caster GUID storage in `CleveRoids_LearnedDurations` (handles talent variations) -- GUID-based tracking (supports multiple mobs with same name) -- Fallback to static database for unknown spells - -**Key functions:** -- `lib:GetDuration(spellID, casterGUID)` - Returns learned or static duration -- `lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)` - Track new debuff -- `lib:UnitDebuff(unit, id)` - Returns: name, _, texture, stacks, dtype, duration, timeleft, caster - -#### 3. Immunity Tracking System (`Utility.lua:1000-1380`) -The immunity tracking system automatically learns and tracks NPC immunities from combat log messages: -- **Auto-learning** from combat log: "X's Spell fails. Y is immune." -- Stores immunities by **damage school** (fire, frost, nature, shadow, arcane, holy, physical) in `CleveRoids_ImmunityData` -- Supports both **permanent immunities** and **buff-based immunities** (e.g., boss immune during a shield phase) -- Automatic spell school detection via tooltip scanning and name pattern matching - -**Data Structure:** -```lua -CleveRoids_ImmunityData = { - fire = { - ["Ragnaros"] = true, -- Permanent immunity - ["Vaelastrasz the Corrupt"] = { buff = "Burning Adrenaline" } -- Conditional immunity - }, - frost = { - ["Frozen Core"] = true - } -} -``` - -**Key Functions:** -- `CleveRoids.CheckImmunity(unitId, spellOrSchool)` - Check immunity to spell name OR damage school - - Example: `CheckImmunity("target", "Flame Shock")` or `CheckImmunity("target", "fire")` - - Automatically detects buff-based immunity by checking target's current buffs -- `CleveRoids.ListImmunities(school)` - List immunity data (all or specific school) -- `CleveRoids.AddImmunity(npcName, school, buffName)` - Manually add immunity -- `CleveRoids.RemoveImmunity(npcName, school)` - Remove immunity entry -- `CleveRoids.ClearImmunities(school)` - Clear immunity data - -**Combat Log Parsing:** -- Listens to `RAW_COMBATLOG` event for immunity messages -- Extracts spell name and target name from messages -- If target has exactly one buff when immunity occurs, assumes buff causes immunity -- Otherwise records as permanent immunity - -**Console Commands:** -```bash -/cleveroid listimmune [school] # List all or specific school immunities -/cleveroid addimmune "" [buff] # Add manual immunity -/cleveroid removeimmune "" # Remove immunity -/cleveroid clearimmune [school] # Clear data -``` - -**Conditional Usage:** -- `[immune:fire]` or `[immune:Flame Shock]` - Check if target is immune -- `[noimmune:nature]` or `[noimmune:"Serpent Sting"]` - Check if NOT immune -- Can omit spell/school to check the action being cast: `[noimmune] Fireball` - -#### 4. Macro Parsing and Execution -Macros are parsed in `Core.lua` into action lists. Each action has: -- `.cmd` - Command (e.g., "/cast", "/use", "/castsequence") -- `.args` - Arguments string -- `.conditionals` - Parsed conditional table -- `.sequence` - Cast sequence data (if `/castsequence`) - -**Evaluation flow:** -1. `CleveRoids.TestForActiveAction(actions)` iterates action list -2. For each action, `CleveRoids.TestAction(cmd, args)` evaluates conditionals -3. First passing action becomes `.active` and determines icon/tooltip -4. `#showtooltip` at macro start enables dynamic icon/tooltip updates - -#### 5. Equipment Swapping System (`Core.lua:57-227`) -Implements queued equipment changes with cooldowns: -- Per-slot cooldown: 1.5s (`EQUIP_COOLDOWN`) -- Global cooldown: 0.5s (`EQUIP_GLOBAL_COOLDOWN`) -- Combat weapon swap protection (checks casting state, on-swing spells) -- `CleveRoids.QueueEquipItem(item, slotName)` - Queue swap -- `CleveRoids.ProcessEquipmentQueue()` - Process queue (called from OnUpdate) - -#### 6. Extension System (`ExtensionsManager.lua`) -Modular plugin architecture for addon integrations: -- `CleveRoids.RegisterExtension(name)` creates extension object -- Extensions can register events, hook functions/methods -- Mouseover extensions set `CleveRoids.SetMouseoverFrom(source, unit)` with priority system (pfUI > blizz > tooltip) -- Compatibility extensions handle SuperMacro, pfUI MacroTweak, Bongos action bars - -## Common Development Tasks - -### Adding a New Conditional Keyword - -1. **Add validation function to `Conditionals.lua`:** -```lua -CleveRoids.Keywords = { - -- Existing conditionals... - - mynewconditional = function(conditionals) - -- Access parsed values from conditionals table - local checkValue = conditionals.mynewconditional - - -- For multi-value support, use Or() helper: - return Or(checkValue, function(val) - return YourCheckFunction(val) - end) - end, - - nomynewconditional = function(conditionals) - -- Negated version uses And() helper: - return And(conditionals.nomynewconditional, function(val) - return not YourCheckFunction(val) - end) - end, -} -``` - -2. **Update README.md** with syntax, examples, and whether it's Multi/Noable - -### Adding Support for a New Spell Duration - -1. **Add to static database in `Utility.lua`:** -```lua -lib.durations = lib.durations or { - -- Existing durations... - [12345] = 30, -- Spell Name (Rank X) - duration in seconds -} -``` - -2. **Or let players learn it manually:** -``` -/cleveroid learn 12345 30 -``` - -The system will auto-learn durations as players cast spells if not in database. - -### Modifying Mouseover Behavior - -Mouseover unit determination uses a priority system in `Utility.lua:94-145`: -- Priority levels: pfUI (3), blizzard (3), tooltip (1) -- Extensions call `CleveRoids.SetMouseoverFrom(source, unit)` to set mouseover -- Highest priority source wins -- `CleveRoids.ClearMouseoverFrom(source, unitIfMatch)` to remove - -To add new source: -1. Create extension in `Extensions/Mouseover/YourAddon.lua` -2. Hook the addon's frame OnEnter/OnLeave scripts -3. Call `SetMouseoverFrom("youraddon", unitID)` with appropriate priority - -### Testing Conditionals - -Use `/dump` command to inspect values: -```lua -/script DEFAULT_CHAT_FRAME:AddMessage(tostring(CleveRoids.ValidateHp("target", "<=", 50))) -``` - -Check `CleveRoids.Keywords` table for function existence: -```lua -/script DEFAULT_CHAT_FRAME:AddMessage(tostring(CleveRoids.Keywords.myconditional ~= nil)) -``` - -## Important Implementation Details - -### String Parsing and Quotation Handling -- Spell/item names with spaces can use underscores OR quotes: `[buff:Mark_of_the_Wild]` or `[buff:"Mark of the Wild"]` -- The parser in `Core.lua` uses `CleveRoids.splitStringIgnoringQuotes()` to handle quoted strings -- Rank syntax MUST include parentheses: `Faerie Fire (Feral)(Rank 4)` (not just `Faerie Fire (Feral)`) - -### Macro Icon/Tooltip Updates -- Icons update via `CleveRoids.QueueActionUpdate()` which sets `isActionUpdateQueued = true` -- OnUpdate handler processes queue and calls `ActionButton_Update()` on affected buttons -- If `realtime=0` (default), updates only on events (ACTIONBAR_UPDATE, UNIT_AURA, etc.) -- If `realtime=1`, updates continuously at `refresh` rate (CPU intensive) - -### Spell Cooldown Handling -- `CleveRoids.GetSpellCooldown(spellName, ignoreGCD)` returns absolute expiry time -- `ignoreGCD=true` treats 1.5s cooldowns as "not on cooldown" (GCD) -- Item cooldowns are separate: `CleveRoids.GetItemCooldown(item)` returns remaining seconds - -### Cast Sequence Reset Logic -Cast sequences reset on: -- `reset=X` - X seconds of inactivity -- `reset=target` - Target change -- `reset=combat` - Leaving combat -- `reset=mod` - Modifier key press -- Can combine: `reset=3/target/combat` - -State tracked in `CleveRoids.Sequences` by macro slot. - -## File Structure Patterns - -### Adding New Compatibility Layer -Create `Compatibility/YourAddon.lua`: -```lua -local _G = _G or getfenv(0) -local CleveRoids = _G.CleveRoids or {} - -if not YourAddon then return end -- Check if addon exists - --- Your compatibility code here --- Usually hooks or event handlers to prevent conflicts - -_G["CleveRoids"] = CleveRoids -``` - -Add to `.toc` file in Compatibility section. - -### Adding New Extension -Create `Extensions/YourExtension.lua`: -```lua -local extension = CleveRoids.RegisterExtension("YourExtension") - -extension.OnLoad = function() - -- Initialization code - extension.RegisterEvent("SOME_EVENT", "OnSomeEvent") -end - -extension.OnSomeEvent = function() - -- Event handler using 'event', 'arg1', etc. globals -end - -return extension -``` - -Add to `.toc` file in Extensions section. - -## Performance Considerations - -- **Avoid `realtime=1` unless necessary** - Event-based updates (realtime=0) are significantly more efficient -- **Debuff table cleanup** - Runs on target change, throttled to 2s intervals (see `Utility.lua:948`) -- **Spell/item indexing** - Only rebuilt on SPELLS_CHANGED, LEARNED_SPELL_IN_TAB events -- **Reactive spell detection** - Requires abilities on action bars to detect usability (limitation may be removed with Nampower enhancements) - -## Console Commands - -Defined in `Console.lua`: - -```bash -# View/modify settings -/cleveroid # Show current settings -/cleveroid realtime 0|1 # Toggle realtime updates -/cleveroid refresh 1-10 # Set update rate (Hz) - -# Debuff duration learning -/cleveroid learn # Manually set duration (seconds) -/cleveroid forget # Forget learned duration(s) -/cleveroid debug 0|1 # Toggle learning debug messages -``` - -## Debugging Tips - -1. **Enable debug mode for debuff learning:** - ``` - /cleveroid debug 1 - ``` - Shows learned durations in chat as debuffs fade. - -2. **Check for missing dependencies:** - Look for error messages on login mentioning SuperWoW/Nampower/UnitXP. - -3. **Macro syntax errors:** - - Check for unmatched quotes or brackets - - Verify spell/item names are spelled correctly (case-sensitive) - - Use `#showtooltip` to see which action is evaluating as active - -4. **Icon not updating:** - - Ensure macro starts with `#showtooltip` - - Check if `realtime=0` and conditional needs event trigger - - Verify action bar addon compatibility (see Extensions) - -## Localization - -The `Localization.lua` file provides `CleveRoids.Localized` table with: -- `.Spells` - Localized spell names (keyed by English name) -- `.CreatureTypes` - Creature type translations -- `.Dagger`, `.Sword`, `.Shield`, etc. - Weapon type names - -Always use `CleveRoids.Localized.Spells["EnglishName"]` for hardcoded spell references to support non-English clients. - -## Known Limitations - -1. **Reactive abilities** (Revenge, Overpower, etc.) must be on action bars for detection (workaround may be possible with Nampower) -2. **Aquatic Form** detection uses reactive system (must be on non-stance action bar) -3. **Debuff time-left conditionals** only work on player's own debuffs unless pfUI or internal libdebuff has data -4. **Macro names** must be unique - no blank names, duplicates, or using spell names -5. **Parenthesis in spell names** (e.g., "Faerie Fire (Feral)") MUST include rank: "Faerie Fire (Feral)(Rank X)" diff --git a/wow-api-type-definitions b/wow-api-type-definitions deleted file mode 160000 index e566a0e..0000000 --- a/wow-api-type-definitions +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e566a0ef067e10a17cf23c41c83137056c232423 From c9bd224c2b45ec6f9d47cada3713b608dd9a2e03 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 18:41:39 +0000 Subject: [PATCH 19/26] Fix pfUI AddEffect timing - use name-based tracking instead of ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL TIMING FIX: pfUI's AddEffect fires BEFORE UNIT_CASTEVENT! Event sequence: 1. CastSpell → Extension hooks → name-based tracking stores 5 CP, 18s ✓ 2. pfUI AddPending → GetDuration (base 10s) 3. SPELLCAST_STOP → pfUI AddEffect fires 4. Our AddEffect hook fires (ID tracking doesn't exist yet!) ✗ 5. UNIT_CASTEVENT → ID-based tracking created ✓ Solution: Use name-based tracking (available at step 4) instead of ID-based tracking (not created until step 5). Changed pfUI AddEffect hook to: - Use CleveRoids.ComboPointTracking[effect] (name-based) - Check cast_time < 0.5s to ensure freshness - This data exists when pfUI calls AddEffect Now shows: "[pfUI AddEffect Hook] Overriding Rip duration to 18s" Instead of: "[pfUI AddEffect Hook] Overriding Rip duration to 10s" --- Compatibility/pfUI.lua | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua index 587e810..0b1fbb2 100644 --- a/Compatibility/pfUI.lua +++ b/Compatibility/pfUI.lua @@ -152,27 +152,16 @@ function Extension.HookPfUILibdebuff() -- effect is a spell name, not ID -- Check if this is a combo scaling spell by name if CleveRoids.IsComboScalingSpell and CleveRoids.IsComboScalingSpell(effect) then - -- Try to get learned duration for this combo spell - local spellData = CleveRoids.GetComboScalingData(effect) - if spellData then - -- Find the spell ID for this effect - for spellID, data in pairs(CleveRoids.ComboScalingSpellsByID) do - if data.name == effect then - -- Check if we have tracking data - if CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking.byID and - CleveRoids.ComboPointTracking.byID[spellID] then - local tracking = CleveRoids.ComboPointTracking.byID[spellID] - if tracking.duration then - duration = tracking.duration - if CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ff00[pfUI AddEffect Hook]|r Overriding %s duration to %ds", - effect, duration) - ) - end - break - end - end + -- Use name-based tracking which is populated BEFORE pfUI's AddEffect + if CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking[effect] then + local tracking = CleveRoids.ComboPointTracking[effect] + if tracking.duration and (GetTime() - tracking.cast_time) < 0.5 then + duration = tracking.duration + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[pfUI AddEffect Hook]|r Overriding %s duration to %ds (from name tracking)", + effect, duration) + ) end end end From 7abb78c54e75dfcc92c5c78e99cd6dffe6fda0bc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 18:49:12 +0000 Subject: [PATCH 20/26] Fix combo point tracking - use name-based tracking as fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent macro conditional evaluations from overwriting good CP data: Problem: Macros like "[combo:>0 nodebuff]Rip /cast [combo:>0]Rip" evaluate multiple times, calling TrackComboPointCast repeatedly: 1. Check [combo:>0 nodebuff] → TrackComboPointCast(0 CP) 2. Claw lands → player gains 1 CP 3. Check [combo:>0] → TrackComboPointCast(1 CP) 4. Spell casts → pfUI AddEffect fires → uses tracking data If the 0 CP call happens after the 1 CP call, pfUI gets wrong data. Solution: Only update tracking if: - No existing data exists, OR - Existing data is stale (>0.5s), OR - New CP value >= existing CP value This ensures we keep the highest (best) combo point count during rapid macro evaluations, so pfUI always gets the correct duration. Debug: Shows "Ignoring X with Y CP (have Z CP)" when preventing overwrites of better data. --- ComboPointTracker.lua | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 445850c..754ad97 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -182,12 +182,30 @@ function CleveRoids.TrackComboPointCast(spellName) local comboPoints = CleveRoids.GetComboPoints() local duration = CleveRoids.CalculateComboScaledDuration(spellName, comboPoints) + local currentTime = GetTime() + + -- Only update if this is a better (higher CP) value than existing, or if existing is stale + local existing = CleveRoids.ComboPointTracking[spellName] + if existing then + local age = currentTime - existing.cast_time + -- Keep existing if it's fresh (<0.5s) and has more combo points + if age < 0.5 and existing.combo_points > comboPoints then + -- Don't overwrite better data with worse data + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff888888CleveRoids:|r ComboTrack: Ignoring %s with %d CP (have %d CP)", + spellName, comboPoints, existing.combo_points) + ) + end + return + end + end -- Store the tracking data CleveRoids.ComboPointTracking[spellName] = { combo_points = comboPoints, duration = duration, - cast_time = GetTime(), + cast_time = currentTime, target = UnitName("target") or "Unknown" } @@ -198,7 +216,7 @@ function CleveRoids.TrackComboPointCast(spellName) CleveRoids.spell_tracking[spellName].last_combo_points = comboPoints CleveRoids.spell_tracking[spellName].last_duration = duration - CleveRoids.spell_tracking[spellName].last_cast_time = GetTime() + CleveRoids.spell_tracking[spellName].last_cast_time = currentTime -- Debug output if CleveRoids.debug then From aedb2684b5f7f8e7fbcc840f9f14290fb06130d2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 18:51:57 +0000 Subject: [PATCH 21/26] Add confirmation system to prevent pfUI from using evaluation-only tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Macro conditional evaluations create tracking entries, but pfUI's AddEffect can fire during these evaluations (before actual cast), causing it to use incorrect combo point data. Flow before fix: 1. Macro evaluates [combo:>0] → TrackComboPointCast(0 CP, confirmed=false) 2. Macro evaluates again → TrackComboPointCast(1 CP, confirmed=false) 3. pfUI AddEffect fires → uses tracking (wrong timing!) ✗ 4. SPELLCAST_START fires (actual cast) Flow after fix: 1. Macro evaluates [combo:>0] → TrackComboPointCast(0 CP, confirmed=false) 2. Macro evaluates again → TrackComboPointCast(1 CP, confirmed=false) 3. SPELLCAST_START fires → sets confirmed=true ✓ 4. pfUI AddEffect fires → only uses tracking if confirmed=true ✓ Changes: - Added 'confirmed' field to tracking (default: false) - SPELLCAST_START sets confirmed=true when spell actually casts - pfUI AddEffect hook only uses tracking if confirmed=true - Clear confirmed flag after use to prevent reuse - Clear on failed/interrupted casts Debug shows: "Ignoring X tracking (not confirmed - evaluation only)" when preventing use of evaluation-only data. --- ComboPointTracker.lua | 11 +++++++++-- Compatibility/pfUI.lua | 12 ++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 754ad97..c96afc5 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -206,7 +206,8 @@ function CleveRoids.TrackComboPointCast(spellName) combo_points = comboPoints, duration = duration, cast_time = currentTime, - target = UnitName("target") or "Unknown" + target = UnitName("target") or "Unknown", + confirmed = false -- Will be set to true by SPELLCAST_START } -- Also store in spell_tracking for integration with existing system @@ -414,6 +415,11 @@ function Extension.OnSpellcastStart() local spellName = arg1 if spellName and CleveRoids.IsComboScalingSpell(spellName) then CleveRoids.TrackComboPointCast(spellName) + + -- Mark this tracking as confirmed (actual cast, not just evaluation) + if CleveRoids.ComboPointTracking[spellName] then + CleveRoids.ComboPointTracking[spellName].confirmed = true + end end end @@ -436,7 +442,8 @@ function Extension.OnSpellcastFailed() if spellName and CleveRoids.ComboPointTracking[spellName] then local tracking = CleveRoids.ComboPointTracking[spellName] if tracking and (GetTime() - tracking.cast_time) < 1 then - -- Recent cast failed, clear it + -- Recent cast failed, clear confirmed flag and the tracking + tracking.confirmed = false CleveRoids.ComboPointTracking[spellName] = nil end end diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua index 0b1fbb2..ef26928 100644 --- a/Compatibility/pfUI.lua +++ b/Compatibility/pfUI.lua @@ -155,14 +155,22 @@ function Extension.HookPfUILibdebuff() -- Use name-based tracking which is populated BEFORE pfUI's AddEffect if CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking[effect] then local tracking = CleveRoids.ComboPointTracking[effect] - if tracking.duration and (GetTime() - tracking.cast_time) < 0.5 then + -- Only use tracking if it's confirmed (actual cast, not just evaluation) + if tracking.duration and tracking.confirmed and (GetTime() - tracking.cast_time) < 0.5 then duration = tracking.duration + -- Clear confirmed flag after use to prevent reuse + tracking.confirmed = false if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ff00[pfUI AddEffect Hook]|r Overriding %s duration to %ds (from name tracking)", + string.format("|cff00ff00[pfUI AddEffect Hook]|r Overriding %s duration to %ds (from confirmed tracking)", effect, duration) ) end + elseif CleveRoids.debug and tracking.duration and not tracking.confirmed then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaaa00[pfUI AddEffect Hook]|r Ignoring %s tracking (not confirmed - evaluation only)", + effect) + ) end end end From f404496561cce2252589660f03d74e389897df0f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 22:34:33 +0000 Subject: [PATCH 22/26] Fix confirmation timing - confirm in cast hooks, not SPELLCAST_START MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL FIX: For instant-cast spells, pfUI's AddEffect fires before SPELLCAST_START, so confirmed flag was never set in time. Event sequence for instant casts: 1. CastSpellByName hook → TrackComboPointCast (confirmed=false) 2. pfUI AddPending 3. SPELLCAST_STOP (instant cast completes) 4. pfUI PersistPending → AddEffect (confirmed still false!) ✗ 5. SPELLCAST_START may not fire for instants Solution: Confirm tracking immediately when cast functions are called: - CastSpellByName_Hook → confirmed=true - CastSpell_Hook → confirmed=true - Hook global CastSpell → confirmed=true - Hook global UseAction → confirmed=true - SPELLCAST_START → confirmed=true (backup) Now pfUI AddEffect (step 4) sees confirmed=true ✓ Added debug output showing: "[Confirmed] Rip tracking confirmed (CastSpellByName)" This ensures tracking is confirmed BEFORE pfUI tries to use it. --- ComboPointTracker.lua | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index c96afc5..7f4649a 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -392,6 +392,45 @@ function Extension.OnLoad() if CastSpellByName then Extension.Hook("CastSpellByName", "CastSpellByName_Hook") end + + -- Hook global CastSpell and UseAction for confirmation + if _G.CastSpell then + local originalCastSpell = _G.CastSpell + _G.CastSpell = function(id, bookType) + -- Call original first + originalCastSpell(id, bookType) + + -- Confirm tracking for combo spells + local spellName = GetSpellName(id, bookType) + if spellName and CleveRoids.IsComboScalingSpell(spellName) and + CleveRoids.ComboPointTracking[spellName] then + CleveRoids.ComboPointTracking[spellName].confirmed = true + end + end + end + + if _G.UseAction then + local originalUseAction = _G.UseAction + _G.UseAction = function(slot, target, button) + -- Call original first + originalUseAction(slot, target, button) + + -- Try to determine spell name from action slot + if not GetActionText(slot) and IsCurrentAction(slot) then + local spellName = GetActionText(slot) + if not spellName then + -- Try to get from tooltip + GameTooltip:SetAction(slot) + spellName = GameTooltipTextLeft1:GetText() + end + + if spellName and CleveRoids.IsComboScalingSpell(spellName) and + CleveRoids.ComboPointTracking[spellName] then + CleveRoids.ComboPointTracking[spellName].confirmed = true + end + end + end + end end -- Update combo points on relevant events @@ -419,6 +458,12 @@ function Extension.OnSpellcastStart() -- Mark this tracking as confirmed (actual cast, not just evaluation) if CleveRoids.ComboPointTracking[spellName] then CleveRoids.ComboPointTracking[spellName].confirmed = true + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[Confirmed]|r %s tracking confirmed (SPELLCAST_START)", + spellName) + ) + end end end end @@ -458,6 +503,17 @@ end function Extension.CastSpellByName_Hook(spellName, onSelf) if spellName and CleveRoids.IsComboScalingSpell(spellName) then CleveRoids.TrackComboPointCast(spellName) + + -- Confirm the tracking immediately - this only fires on actual casts + if CleveRoids.ComboPointTracking[spellName] then + CleveRoids.ComboPointTracking[spellName].confirmed = true + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[Confirmed]|r %s tracking confirmed (CastSpellByName)", + spellName) + ) + end + end end end @@ -465,6 +521,17 @@ end function Extension.CastSpell_Hook(spellName) if spellName and CleveRoids.IsComboScalingSpell(spellName) then CleveRoids.TrackComboPointCast(spellName) + + -- Confirm the tracking immediately - this only fires on actual casts + if CleveRoids.ComboPointTracking[spellName] then + CleveRoids.ComboPointTracking[spellName].confirmed = true + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[Confirmed]|r %s tracking confirmed (CleveRoids.CastSpell)", + spellName) + ) + end + end end end From b545f9bb3e8c875b436a62909f46b9e19503a7f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 22:39:17 +0000 Subject: [PATCH 23/26] CRITICAL: Add confirmation to DoCast hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The issue: None of the CastSpellByName/CastSpell hooks were firing because CleveRoid macros go through DoCast, not the standard casting functions. Evidence from user output: - Many "ComboTrack: Rip cast with 5 CP" messages ✓ (TrackComboPointCast called) - NO "[Confirmed]" messages ✗ (no hooks firing) - "[pfUI AddEffect Hook] Ignoring Rip tracking (not confirmed)" ✗ (never confirmed) The DoCast integration was calling TrackComboPointCast but NOT setting confirmed=true, so pfUI would always see unconfirmed tracking data. Fix: Add confirmation in DoCast hook, just like all the other cast hooks. Now when user casts via CleveRoid macros: 1. DoCast fires → TrackComboPointCast → confirmed=true ✓ 2. pfUI AddEffect fires → sees confirmed=true → uses duration ✓ --- ComboPointTracker.lua | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 7f4649a..737916b 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -541,20 +541,31 @@ if CleveRoids.DoCast then CleveRoids.DoCast = function(msg) -- Extract spell name from message local spellName = msg - + -- Remove conditionals if present local condEnd = string.find(msg, "]") if condEnd then spellName = string.sub(msg, condEnd + 1) end - + spellName = CleveRoids.Trim(spellName) - + -- Track combo points if it's a scaling spell if CleveRoids.IsComboScalingSpell(spellName) then CleveRoids.TrackComboPointCast(spellName) + + -- Confirm the tracking - DoCast only fires on actual casts + if CleveRoids.ComboPointTracking[spellName] then + CleveRoids.ComboPointTracking[spellName].confirmed = true + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[Confirmed]|r %s tracking confirmed (DoCast)", + spellName) + ) + end + end end - + -- Call original function return originalDoCast(msg) end From ce8f1dcf7d46a3a62cdddba53fdd6e55f8e341a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 22:46:17 +0000 Subject: [PATCH 24/26] Fix pfUI AddEffect handling multiple calls Don't clear the confirmed flag after first use since pfUI may call AddEffect multiple times for the same spell. Instead, rely on the time-based expiry (0.5s timeout) to prevent stale tracking data. This fixes the issue where the second AddEffect call would see unconfirmed tracking and ignore the correct duration. --- Compatibility/pfUI.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua index ef26928..110fea1 100644 --- a/Compatibility/pfUI.lua +++ b/Compatibility/pfUI.lua @@ -158,15 +158,15 @@ function Extension.HookPfUILibdebuff() -- Only use tracking if it's confirmed (actual cast, not just evaluation) if tracking.duration and tracking.confirmed and (GetTime() - tracking.cast_time) < 0.5 then duration = tracking.duration - -- Clear confirmed flag after use to prevent reuse - tracking.confirmed = false + -- Don't clear confirmed flag - let time-based expiry handle it + -- pfUI may call AddEffect multiple times for the same spell if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ff00[pfUI AddEffect Hook]|r Overriding %s duration to %ds (from confirmed tracking)", effect, duration) ) end - elseif CleveRoids.debug and tracking.duration and not tracking.confirmed then + elseif CleveRoids.debug and tracking.duration and not tracking.confirmed and (GetTime() - tracking.cast_time) < 0.5 then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaaa00[pfUI AddEffect Hook]|r Ignoring %s tracking (not confirmed - evaluation only)", effect) From adb079dd1d76f0c1781e80f5a1c4b50589141e1a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 22:50:15 +0000 Subject: [PATCH 25/26] Protect confirmed tracking from evaluation overwrites Prevent TrackComboPointCast from overwriting confirmed tracking with unconfirmed evaluation data. Once DoCast confirms a cast, subsequent macro evaluations will no longer overwrite the tracking entry. This fixes the issue where pfUI's second AddEffect call would see unconfirmed tracking because evaluations overwrote the confirmed data. --- ComboPointTracker.lua | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 737916b..73b4519 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -188,16 +188,27 @@ function CleveRoids.TrackComboPointCast(spellName) local existing = CleveRoids.ComboPointTracking[spellName] if existing then local age = currentTime - existing.cast_time - -- Keep existing if it's fresh (<0.5s) and has more combo points - if age < 0.5 and existing.combo_points > comboPoints then - -- Don't overwrite better data with worse data - if CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff888888CleveRoids:|r ComboTrack: Ignoring %s with %d CP (have %d CP)", - spellName, comboPoints, existing.combo_points) - ) + -- Keep existing if it's fresh (<0.5s) and EITHER has more combo points OR is confirmed + if age < 0.5 then + if existing.combo_points > comboPoints then + -- Don't overwrite better data with worse data + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff888888CleveRoids:|r ComboTrack: Ignoring %s with %d CP (have %d CP)", + spellName, comboPoints, existing.combo_points) + ) + end + return + elseif existing.confirmed then + -- Don't overwrite confirmed tracking with unconfirmed evaluation + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff888888CleveRoids:|r ComboTrack: Ignoring %s - already confirmed", + spellName) + ) + end + return end - return end end From c8546df52a331597b75e511aa5e24a32d47416dc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 23:28:03 +0000 Subject: [PATCH 26/26] Fix debuff overflow detection and duration tracking for combodurations branch Applied the same overflow bug fixes to the combodurations branch to ensure compatibility with combo point-scaled debuffs. This fixes three issues: 1. lib:UnitDebuff() (line 799): Fixed UnitBuff() return value capture from 4 values to 3 values (UnitBuff only returns texture, stacks, spellID). Also changed overflow check from only checking static database to checking lib:GetDuration() which includes combo durations, learned durations, and static durations. 2. SeedUnit() debuff loop (line 837): Changed check from only lib.durations[] to lib:GetDuration() so debuffs with learned or combo-scaled durations are properly seeded into the tracking system. 3. SeedUnit() buff loop (line 867): Fixed UnitBuff() return value capture from 4 values to 3, and changed check from only lib.durations[] to lib:GetDuration() to properly track overflow debuffs with combo-scaled or learned durations. These fixes ensure that: - Overflow debuffs like Moonfire are properly detected by [nodebuff:] conditionals - Duration tracking works for overflow debuffs with combo point scaling (Rip, Rupture) - Duration tracking works for overflow debuffs with learned durations - All existing combo point tracking functionality is preserved --- Utility.lua | 87 ++++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/Utility.lua b/Utility.lua index 81ed0ca..6ab1a0f 100644 --- a/Utility.lua +++ b/Utility.lua @@ -796,8 +796,9 @@ function lib:UnitDebuff(unit, id) local texture, stacks, dtype, spellID = UnitDebuff(unit, id) if not texture or not spellID then - texture, stacks, _, spellID = UnitBuff(unit, id) - if texture and spellID and not lib.durations[spellID] then + texture, stacks, spellID = UnitBuff(unit, id) + -- Only accept buffs that are known debuffs (either static or learned durations, including combo durations) + if texture and spellID and lib:GetDuration(spellID) <= 0 then return nil end end @@ -833,60 +834,64 @@ local function SeedUnit(unit) local tex, stacks, dtype, spellID = UnitDebuff(unit, i) if not tex then break end - if spellID and lib.durations[spellID] then - if not (lib.objects[guid] and lib.objects[guid][spellID]) then - -- For combo spells, try to use the highest learned duration as a fallback - local duration = nil - if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) and - CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then - -- Use the longest learned duration (assume 5 CP) - for cp = 5, 1, -1 do - if CleveRoids_ComboDurations[spellID][cp] then - duration = CleveRoids_ComboDurations[spellID][cp] - if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) using learned %dCP duration:%ds", - spellName, spellID, cp, duration) - ) + if spellID then + local duration = lib:GetDuration(spellID) + if duration > 0 then + if not (lib.objects[guid] and lib.objects[guid][spellID]) then + -- For combo spells, try to use the highest learned duration as a fallback + if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) and + CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then + -- Use the longest learned duration (assume 5 CP) + for cp = 5, 1, -1 do + if CleveRoids_ComboDurations[spellID][cp] then + duration = CleveRoids_ComboDurations[spellID][cp] + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) using learned %dCP duration:%ds", + spellName, spellID, cp, duration) + ) + end + break end - break end end + duration = duration or lib:GetDuration(spellID) + lib:AddEffect(guid, unitName, spellID, duration, stacks) end - duration = duration or lib:GetDuration(spellID) - lib:AddEffect(guid, unitName, spellID, duration, stacks) end end end for i=1, 32 do - local tex, stacks, _, spellID = UnitBuff(unit, i) + local tex, stacks, spellID = UnitBuff(unit, i) if not tex then break end - if spellID and lib.durations[spellID] then - if not (lib.objects[guid] and lib.objects[guid][spellID]) then - -- For combo spells, try to use the highest learned duration as a fallback - local duration = nil - if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) and - CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then - -- Use the longest learned duration (assume 5 CP) - for cp = 5, 1, -1 do - if CleveRoids_ComboDurations[spellID][cp] then - duration = CleveRoids_ComboDurations[spellID][cp] - if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) using learned %dCP duration:%ds", - spellName, spellID, cp, duration) - ) + if spellID then + local duration = lib:GetDuration(spellID) + if duration > 0 then + if not (lib.objects[guid] and lib.objects[guid][spellID]) then + -- For combo spells, try to use the highest learned duration as a fallback + if CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) and + CleveRoids_ComboDurations and CleveRoids_ComboDurations[spellID] then + -- Use the longest learned duration (assume 5 CP) + for cp = 5, 1, -1 do + if CleveRoids_ComboDurations[spellID][cp] then + duration = CleveRoids_ComboDurations[spellID][cp] + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) using learned %dCP duration:%ds", + spellName, spellID, cp, duration) + ) + end + break end - break end end + duration = duration or lib:GetDuration(spellID) + lib:AddEffect(guid, unitName, spellID, duration, stacks) end - duration = duration or lib:GetDuration(spellID) - lib:AddEffect(guid, unitName, spellID, duration, stacks) end end end