--[[ 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 {} -- Note: CleveRoids.lastComboPoints and CleveRoids.lastComboPointsTime are initialized in Init.lua -- Initialize SavedVariable for learned combo durations -- Structure: CleveRoids_ComboDurations[spellID][comboPoints] = duration CleveRoids_ComboDurations = CleveRoids_ComboDurations or {} -- Family membership without hardcoded rank lists. C_Spell.GetSpellName resolves -- ANY spellID from the client's Spell.dbc -- every rank (so no enumeration or -- spellbook scan) and TWoW's custom spells alike -- so "is this spellID a Rip?" -- is just a name match against one seed rank. seedNameCache memoizes the seed's -- localized name (locale-safe: derived from the ID, not a hardcoded string; a -- seed absent from the DBC, e.g. a TWoW spell on a stock client, resolves to nil -- and the set simply never matches -- which is correct, that spell can't be cast). local seedNameCache = {} local function SeedName(seedID) local n = seedNameCache[seedID] if n == nil then n = C_Spell.GetSpellName(seedID) or false seedNameCache[seedID] = n end return n or nil end -- Stand-in for a hardcoded {spellID=true,...} rank set: t[spellID] is true iff -- spellID is any rank of the family seeded by seedID. Same read shape as the old -- tables, so every membership consumer (`X and X[id]`) keeps working unchanged. -- Only valid for membership tests -- these are not iterable (pairs() sees empty). local function RankSet(seedID) return setmetatable({}, { __index = function(_, spellID) if type(spellID) ~= "number" then return nil end local name = SeedName(seedID) return (name and C_Spell.GetSpellName(spellID) == name) and true or nil end }) end -- 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 = 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 } -- Ferocious Bite spell IDs. A combo-point finisher, so the cast-time combo -- snapshot has to know it. CleveRoids.FerociousBiteSpellIDs = { [22557] = true, -- Rank 1 [22568] = true, -- Rank 2 [22827] = true, -- Rank 3 [22828] = true, -- Rank 4 [22829] = true, -- Rank 5 [31018] = true, -- Rank 6 } -- Combined table for all bleed spells that need immunity detection -- Used when checking if a cast bleed failed to apply (indicates bleed immunity) -- NOTE: These are the DEBUFF spell IDs (what appears on target), not cast spell IDs CleveRoids.BleedSpellIDs = { -- Rip (bleed DoT from Cat Form finisher) [1079] = true, -- Rank 1 [9492] = true, -- Rank 2 [9493] = true, -- Rank 3 [9752] = true, -- Rank 4 [9894] = true, -- Rank 5 [9896] = true, -- Rank 6 -- Rake (cast ID = debuff ID for Rake) [1822] = true, -- Rank 1 [1823] = true, -- Rank 2 [1824] = true, -- Rank 3 [9904] = true, -- Rank 4 -- Pounce Bleed (triggered spell IDs, different from cast IDs) [9007] = true, -- Rank 1 (triggered by Pounce 9005) [9824] = true, -- Rank 2 (triggered by Pounce 9823) [9826] = true, -- Rank 3 (triggered by Pounce 9827) } -- Mapping from Pounce cast spell IDs to their triggered Pounce Bleed spell IDs -- Used to track the correct debuff when UNIT_CASTEVENT fires for Pounce CleveRoids.PounceToBleedMapping = { [9005] = 9007, -- Pounce Rank 1 → Pounce Bleed Rank 1 [9823] = 9824, -- Pounce Rank 2 → Pounce Bleed Rank 2 [9827] = 9826, -- Pounce Rank 3 → Pounce Bleed Rank 3 } -- ============================================================================= -- SHAMAN: Molten Blast → Flame Shock Refresh (TWoW Custom) -- When Molten Blast hits, it refreshes Flame Shock duration on the target -- Detection: Monitor combat log for Molten Blast damage, then refresh Flame Shock -- ============================================================================= -- TWoW custom; seed resolves on the TWoW client (nil/never-matches on stock, -- where Molten Blast can't be cast anyway). CleveRoids.MoltenBlastSpellIDs = RankSet(36916) CleveRoids.FlameShockSpellIDs = { [8050] = true, -- Rank 1 [8052] = true, -- Rank 2 [8053] = true, -- Rank 3 [10447] = true, -- Rank 4 [10448] = true, -- Rank 5 [29228] = true, -- Rank 6 } -- ============================================================================= -- WARLOCK: Conflagrate → Immolate Duration Reduction -- When Conflagrate is cast, it reduces Immolate duration by 3 seconds -- ============================================================================= CleveRoids.ConflagrateSpellIDs = RankSet(17962) CleveRoids.ImmolateSpellIDs = { [348] = true, -- Rank 1 [707] = true, -- Rank 2 [1094] = true, -- Rank 3 [2941] = true, -- Rank 4 [11665] = true, -- Rank 5 [11667] = true, -- Rank 6 [11668] = true, -- Rank 7 [25309] = true, -- Rank 8 } -- ============================================================================= -- DRUID: Rake Debuff Cap Boss Whitelist -- These bosses are likely to hit the 48 debuff cap, causing Rake to get pushed off -- For mobs NOT in this list, we verify Rake is actually on the target before tracking -- Prevents false tracking when debuffs get pushed off at cap -- ============================================================================= CleveRoids.MobsThatBleed = { -- Karazhan Crypts / TWoW Custom ["0xF13000F1F3276A33"] = true, -- Keeper Gnarlmoon ["0xF13000F1FA276A32"] = true, -- Ley-Watcher Incantagos ["0xF13000EA3F276C05"] = true, -- King ["0xF13000EA31279058"] = true, -- Queen ["0xF13000EA43276C06"] = true, -- Bishop ["0xF13000EA44279044"] = true, -- Pawn ["0xF13000EA42276C07"] = true, -- Rook ["0xF13000EA4D05DA44"] = true, -- Sanv Tas'dal ["0xF13000EA57276C04"] = true, -- Kruul ["0xF130016C95276DAF"] = true, -- Mephistroth -- Naxxramas ["0xF130003E5401591A"] = true, -- Anub'Rekhan ["0xF130003E510159B0"] = true, -- Grand Widow Faerlina ["0xF130003E500159A3"] = true, -- Maexxna ["0xF130003EBD01598C"] = true, -- Razuvious ["0xF130003EBC01599F"] = true, -- Gothik ["0xF130003EBF015AB2"] = true, -- Zeliek ["0xF130003EBE015AB3"] = true, -- Mograine ["0xF130003EC1015AB1"] = true, -- Blaumeux ["0xF130003EC0015AB0"] = true, -- Thane ["0xF130003E52015824"] = true, -- Noth ["0xF130003E4001588D"] = true, -- Heigan ["0xF130003E8B0158A2"] = true, -- Loatheb ["0xF130003E9C0158EA"] = true, -- Patchwerk ["0xF130003E3B0158EF"] = true, -- Grobbulus ["0xF130003E3C0158F0"] = true, -- Gluth ["0xF130003E380159A0"] = true, -- Thaddius ["0xF130003E75015AB4"] = true, -- Sapphiron ["0xF130003E76015AED"] = true, -- Kel'Thuzad } -- Legacy name-based table for backwards compatibility 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 }, ["Rip"] = { base_duration = 10, increment = 2, all_ranks = true } } -- Function to get current combo points -- Note: In Vanilla WoW 1.12, GetComboPoints() takes no parameters -- (The "player", "target" syntax is from TBC/WotLK) function CleveRoids.GetComboPoints() return GetComboPoints() or 0 end -- Update stored combo points (call this frequently) function CleveRoids.UpdateComboPoints() local current = CleveRoids.GetComboPoints() if current > 0 then CleveRoids.lastComboPoints = current CleveRoids.lastComboPointsTime = GetTime() end 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 = CleveRoids.StripRank(spell) local checkName = CleveRoids.StripRank(spellName) 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 = CleveRoids.StripRank(spell) local checkName = CleveRoids.StripRank(spellName) 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 -- 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: Get learned duration for specific combo point count function CleveRoids.GetLearnedComboDuration(spellID, comboPoints) if not CleveRoids_ComboDurations then CleveRoids_ComboDurations = {} end 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 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 local baseDuration -- Check for learned duration first local learned = CleveRoids.GetLearnedComboDuration(spellID, comboPoints) if learned then baseDuration = learned else -- Fall back to formula baseDuration = data.base + (comboPoints - 1) * data.increment end -- Apply all duration modifiers (Nampower, talents, equipment, set bonuses) if CleveRoids.ApplyAllDurationModifiers then baseDuration = CleveRoids.ApplyAllDurationModifiers(spellID, baseDuration) else -- Fallback for load order (ApplyAllDurationModifiers not yet defined) if CleveRoids.ApplyTalentModifier then baseDuration = CleveRoids.ApplyTalentModifier(spellID, baseDuration) end if CleveRoids.ApplyEquipmentModifier then baseDuration = CleveRoids.ApplyEquipmentModifier(spellID, baseDuration) end end return baseDuration end -- Function to track combo points when casting (by spell name) function CleveRoids.TrackComboPointCast(spellName) if not CleveRoids.IsComboScalingSpell(spellName) then return end -- Get current combo points, but if they're 0 (already consumed), use last known value local comboPoints = CleveRoids.GetComboPoints() -- If combo points are 0, use lastComboPoints as fallback if comboPoints == 0 and CleveRoids.lastComboPoints > 0 then comboPoints = CleveRoids.lastComboPoints if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff9900CleveRoids:|r ComboTrack: Using lastComboPoints (%d) for %s", comboPoints, spellName) ) end end 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 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 end end -- Store the tracking data CleveRoids.ComboPointTracking[spellName] = { combo_points = comboPoints, duration = duration, cast_time = currentTime, 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 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 = currentTime -- 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 -- Get current combo points, but if they're 0 (already consumed), use last known value local comboPoints = CleveRoids.GetComboPoints() -- If combo points are 0, try multiple fallback sources if comboPoints == 0 then -- PRIMARY: Check SPELL_CAST_EVENT capture (most reliable - client-side pre-server) local pending = CleveRoids.pendingCasts and CleveRoids.pendingCasts[spellID] if pending and pending.comboPoints and pending.comboPoints > 0 then comboPoints = pending.comboPoints if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ff88[TrackComboByID]|r Using SPELL_CAST_EVENT CP: %d for spell ID %d", comboPoints, spellID) ) end -- Fallback: lastComboPoints (don't reset immediately - let it persist) elseif CleveRoids.lastComboPoints > 0 then comboPoints = CleveRoids.lastComboPoints -- Don't reset here - let it persist for multiple events if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff9900[TrackComboByID]|r Using lastComboPoints: %d for spell ID %d", comboPoints, spellID) ) end else -- Second, check if name-based tracking has recent data for this spell local spellName = C_Spell.GetSpellName(spellID) if spellName then -- Remove rank info for comparison local baseName = CleveRoids.StripRank(spellName) 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) 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 -- Reset lastComboPoints after successfully using it if comboPoints > 0 and comboPoints == CleveRoids.lastComboPoints then CleveRoids.lastComboPoints = 0 if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff888888[TrackComboByID]|r Reset lastComboPoints after use") ) end end return duration 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 -- DEBUG: Confirm ShowComboTracking is defined (disabled to reduce login spam) -- 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 global CastSpell IMMEDIATELY (before Extension system) if _G.CastSpell then local originalCastSpell = _G.CastSpell _G.CastSpell = function(id, bookType) -- Capture combo points IMMEDIATELY, before anything else local currentCP = CleveRoids.GetComboPoints() -- Get spell name for checking local spellName = GetSpellName(id, bookType) if currentCP and currentCP > 0 then CleveRoids.lastComboPoints = currentCP CleveRoids.lastComboPointsTime = GetTime() if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[CastSpell Hook]|r Captured %d CP before spell %d/%s (name:%s)", currentCP, id or 0, bookType or "nil", spellName or "nil") ) end end -- Check if it's a combo finisher and pre-populate tracking if spellName then local isCombo = CleveRoids.IsComboScalingSpell(spellName) if CleveRoids.debug and currentCP and currentCP > 0 then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff888888[CastSpell Debug]|r spell='%s' isCombo=%s CP=%d", spellName, tostring(isCombo), currentCP) ) end if isCombo and currentCP and currentCP > 0 then local duration = CleveRoids.CalculateComboScaledDuration(spellName, currentCP) if duration then local baseName = CleveRoids.StripRank(spellName) CleveRoids.ComboPointTracking[baseName] = { combo_points = currentCP, duration = duration, cast_time = GetTime(), target = UnitName("target") or "Unknown", confirmed = true } if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ff00[CastSpell Pre-Track]|r %s with %d CP, duration: %ds", baseName, currentCP, duration) ) end end end end return originalCastSpell(id, bookType) end -- DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ComboPointTracker: CastSpell hook installed!|r") end -- Hook global UseAction IMMEDIATELY (before Extension system) if _G.UseAction then local originalUseAction = _G.UseAction _G.UseAction = function(slot, target, button) -- Always capture combo points before action, in case it's a finisher local currentCP = CleveRoids.GetComboPoints() -- Inspect the action button to see what spell it is local macroName, actionType, actionID = GetActionText(slot) local spellName = nil if actionType == "SPELL" and actionID then spellName = C_Spell.GetSpellName(actionID) end if currentCP and currentCP > 0 then CleveRoids.lastComboPoints = currentCP CleveRoids.lastComboPointsTime = GetTime() -- Debug message removed to reduce spam -- If it's a combo finisher, pre-populate tracking if spellName and CleveRoids.IsComboScalingSpell(spellName) then local duration = CleveRoids.CalculateComboScaledDuration(spellName, currentCP) if duration then local baseName = CleveRoids.StripRank(spellName) CleveRoids.ComboPointTracking[baseName] = { combo_points = currentCP, duration = duration, cast_time = GetTime(), target = UnitName("target") or "Unknown", confirmed = true } if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ff00[UseAction Pre-Track]|r %s with %d CP, duration: %ds", baseName, currentCP, duration) ) end end end end -- Debug message removed to reduce spam return originalUseAction(slot, target, button) end -- DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ComboPointTracker: UseAction hook installed!|r") end -- Hook global CastSpellByName IMMEDIATELY (before Extension system) if CastSpellByName then local originalCastSpellByName = CastSpellByName CastSpellByName = function(spellName, onSelf) local currentCP = CleveRoids.GetComboPoints() -- Debug message removed to reduce spam if spellName and CleveRoids.IsComboScalingSpell(spellName) then if currentCP and currentCP > 0 then CleveRoids.lastComboPoints = currentCP CleveRoids.lastComboPointsTime = GetTime() -- Pre-populate tracking local duration = CleveRoids.CalculateComboScaledDuration(spellName, currentCP) if duration then local baseName = CleveRoids.StripRank(spellName) CleveRoids.ComboPointTracking[baseName] = { combo_points = currentCP, duration = duration, cast_time = GetTime(), target = UnitName("target") or "Unknown", confirmed = true } if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff8800[CastSpellByName Pre-Track]|r %s with %d CP, duration: %ds", baseName, currentCP, duration) ) end end end end return originalCastSpellByName(spellName, onSelf) end -- DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ComboPointTracker: CastSpellByName hook installed!|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 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") Extension.RegisterEvent("PLAYER_TARGET_CHANGED", "OnTargetChanged") Extension.RegisterUnitEvent("UNIT_AURA", "OnUnitAura", "target", "player") Extension.RegisterEvent("PLAYER_COMBO_POINTS", "OnComboPointsChanged") -- PERFORMANCE OPTIMIZATION: Removed OnUpdate polling for combo points -- Combo points are now tracked via PLAYER_COMBO_POINTS event + spell cast hooks -- This eliminates continuous polling while maintaining identical functionality -- Events that trigger combo point updates: -- - PLAYER_COMBO_POINTS (native event when CP change) -- - PLAYER_TARGET_CHANGED (target switch clears CP) -- - UNIT_AURA (for debuff applications) -- - CastSpellByName/QueueSpellByName hooks (pre-cast capture) -- Note: CastSpell, UseAction, and CastSpellByName hooks are set up immediately when file loads (see above) end -- Update combo points on relevant events function Extension.OnTargetChanged() CleveRoids.UpdateComboPoints() end -- Registered as a unit event for target and player, so arg1 is always one of -- those two -- no token check needed. function Extension.OnUnitAura() CleveRoids.UpdateComboPoints() end function Extension.OnComboPointsChanged() CleveRoids.UpdateComboPoints() 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 -- Capture current combo points BEFORE the spell cast local currentCP = CleveRoids.GetComboPoints() if currentCP and currentCP > 0 then CleveRoids.lastComboPoints = currentCP if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaaaff[Pre-Cast]|r Captured %d combo points before casting %s", currentCP, spellName) ) end end CleveRoids.TrackComboPointCast(spellName) -- 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 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 confirmed flag and the tracking tracking.confirmed = false 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 -- Capture current combo points BEFORE the spell cast local currentCP = CleveRoids.GetComboPoints() if currentCP and currentCP > 0 then CleveRoids.lastComboPoints = currentCP if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaaaff[Pre-Cast]|r Captured %d combo points before casting %s", currentCP, spellName) ) end end 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 -- Hook CleveRoids.CastSpell if it exists function Extension.CastSpell_Hook(spellName) if spellName and CleveRoids.IsComboScalingSpell(spellName) then -- Capture current combo points BEFORE the spell cast local currentCP = CleveRoids.GetComboPoints() if currentCP and currentCP > 0 then CleveRoids.lastComboPoints = currentCP if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaaaff[Pre-Cast]|r Captured %d combo points before casting %s", currentCP, spellName) ) end end 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 -- Integration with DoCast function (moved before Extension registration) 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 -- Capture current combo points BEFORE the spell cast local currentCP = CleveRoids.GetComboPoints() if currentCP and currentCP > 0 then CleveRoids.lastComboPoints = currentCP if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaaaff[Pre-Cast]|r Captured %d combo points before casting %s", currentCP, spellName) ) end end 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 end