mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
53a1cf1441
Same shape as Carnage, opposite direction: Dark Harvest is a channel that makes the caster's DoTs on the target tick 30% faster, and the server never tells an observer the remaining durations changed. So this addon stamped dhStartTime on every tracked record when the channel began, dhEndTime when it stopped, and subtracted 30% of the elapsed window from each remaining time on read. ClassicAPI compresses the ticks in the DLL (src/turtle/DarkHarvest.cpp), so expirationTime already reflects it. Removed: GetDarkHarvestReduction, ApplyDarkHarvestStart, ApplyDarkHarvestEnd, GetTimeRemainingWithDarkHarvest, the channel-start capture on both the UNIT_CASTEVENT and nampower paths, the channel-stop finalizer in SPELLCAST_CHANNEL_STOP, the darkHarvestData state, and DarkHarvestSpellIDs. The darkHarvest flag in Extensions/CursiveCustomSpells.lua stays -- that is spell metadata this addon exports to Cursive, not tracking of our own.
917 lines
37 KiB
Lua
917 lines
37 KiB
Lua
--[[
|
|
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
|