performance updates, meleerange conditional checks if unit is alive

This commit is contained in:
Jrc13245
2026-03-11 19:57:33 -04:00
parent 8bc3dc76e1
commit efdcde3eca
8 changed files with 285 additions and 175 deletions
+8 -8
View File
@@ -260,8 +260,8 @@ function CleveRoids.IsComboScalingSpell(spellName)
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+%)", "")
local baseName = CleveRoids.StripRank(spell)
local checkName = CleveRoids.StripRank(spellName)
if baseName == checkName then
return true
end
@@ -283,8 +283,8 @@ function CleveRoids.GetComboScalingData(spellName)
-- 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+%)", "")
local baseName = CleveRoids.StripRank(spell)
local checkName = CleveRoids.StripRank(spellName)
if baseName == checkName then
return data
end
@@ -492,7 +492,7 @@ function CleveRoids.TrackComboPointCastByID(spellID, targetGUID)
local spellName = GetSpellRecField(spellID, "name")
if spellName then
-- Remove rank info for comparison
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
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
@@ -648,7 +648,7 @@ if _G.CastSpell then
if isCombo and currentCP and currentCP > 0 then
local duration = CleveRoids.CalculateComboScaledDuration(spellName, currentCP)
if duration then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
CleveRoids.ComboPointTracking[baseName] = {
combo_points = currentCP,
duration = duration,
@@ -695,7 +695,7 @@ if _G.UseAction then
if spellName and CleveRoids.IsComboScalingSpell(spellName) then
local duration = CleveRoids.CalculateComboScaledDuration(spellName, currentCP)
if duration then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
CleveRoids.ComboPointTracking[baseName] = {
combo_points = currentCP,
duration = duration,
@@ -733,7 +733,7 @@ if CastSpellByName then
-- Pre-populate tracking
local duration = CleveRoids.CalculateComboScaledDuration(spellName, currentCP)
if duration then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
CleveRoids.ComboPointTracking[baseName] = {
combo_points = currentCP,
duration = duration,
+3 -3
View File
@@ -101,7 +101,7 @@ local function GetCarnageOverride(effect)
for spellID, override in pairs(CleveRoids.carnageDurationOverrides) do
local spellName = GetSpellRecField(spellID, "name")
if spellName then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
if baseName == effect and override.timestamp and (GetTime() - override.timestamp) < 5 then
local timeleft = override.duration - (GetTime() - override.timestamp)
if timeleft < 0 then timeleft = 0 end
@@ -211,7 +211,7 @@ function Extension.HookPfUILibdebuff()
-- Get spell name for this ID
local spellName = GetSpellRecField(spellID, "name")
if spellName then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
if baseName == effect then
-- Same spell - check if still active
local remaining = rec.duration + rec.start - GetTime()
@@ -389,7 +389,7 @@ function Extension.SyncComboDurationToPfUI(guid, spellID, duration)
end
-- Remove rank from spell name to match pfUI's format
local effectName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local effectName = CleveRoids.StripRank(spellName)
-- Update pfUI's stored debuff duration
local pflib = pfUI.api.libdebuff
+27 -21
View File
@@ -107,7 +107,7 @@ local function BuildSpellNameCache()
for sid, _ in pairs(lib.personalDebuffs) do
local name = GetSpellRecField(sid, "name")
if name then
name = gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
name = CleveRoids.StripRank(name)
if not _spellNameToIDs[name] then
_spellNameToIDs[name] = {}
end
@@ -120,7 +120,7 @@ local function BuildSpellNameCache()
for sid, _ in pairs(lib.sharedDebuffs) do
local name = GetSpellRecField(sid, "name")
if name then
name = gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
name = CleveRoids.StripRank(name)
if not _spellNameToIDs[name] then
_spellNameToIDs[name] = {}
end
@@ -143,7 +143,7 @@ local function GetSpellIDsForName(spellName)
-- Strip rank from input spell name to match cache keys
-- This handles cases like "Faerie Fire (Feral)(Rank 4)" -> "Faerie Fire (Feral)"
if spellName then
spellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
spellName = CleveRoids.StripRank(spellName)
-- Convert underscores to spaces for matching (e.g., "Thunder_Clap" -> "Thunder Clap")
spellName = string.gsub(spellName, "_", " ")
end
@@ -619,14 +619,14 @@ local function Multi(t, func, conditionals, condition)
if conditionals and conditionals._groups and conditionals._groups[condition] then
local groups = conditionals._groups[condition]
-- All groups must pass (AND between groups)
-- PERFORMANCE: Index-based loop instead of ipairs() to avoid iterator closure
local numGroups = table.getn(groups)
-- PERFORMANCE: Use .n field (set at build time) instead of table.getn() O(n) scan
local numGroups = groups.n or table.getn(groups)
for gi = 1, numGroups do
local group = groups[gi]
local groupPassed = false
local groupOp = group.operator or "OR"
local values = group.values
local numValues = table.getn(values)
local numValues = group.n or table.getn(values)
if groupOp == "AND" then
-- AND within group: ALL values must match
@@ -696,14 +696,14 @@ local function NegatedMulti(t, func, conditionals, condition)
if conditionals and conditionals._groups and conditionals._groups[condition] then
local groups = conditionals._groups[condition]
-- All groups must pass (AND between groups)
-- PERFORMANCE: Index-based loop instead of ipairs() to avoid iterator closure
local numGroups = table.getn(groups)
-- PERFORMANCE: Use .n field (set at build time) instead of table.getn() O(n) scan
local numGroups = groups.n or table.getn(groups)
for gi = 1, numGroups do
local group = groups[gi]
local groupPassed = false
local groupOp = group.operator or "OR"
local values = group.values
local numValues = table.getn(values)
local numValues = group.n or table.getn(values)
-- FLIPPED from positive conditionals (De Morgan's law for intuitive behavior)
if groupOp == "AND" then
@@ -967,7 +967,7 @@ function CleveRoids.FindAllCasterAuraByName(targetGuid, searchName)
if not casters then
local searchLower = string.lower(searchName)
for spellName, c in pairs(targetData) do
local baseName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
local baseName = CleveRoids.StripRank(spellName)
if string.lower(baseName) == searchLower then
casters = c
break
@@ -1073,7 +1073,7 @@ local function OnAutoAttackOther(attackerGuid, targetGuid, totalDamage, hitInfo,
if CleveRoids.debug then
local spellName = GetSpellRecField and GetSpellRecField(spellID, "name") or "Unknown"
local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "") or "Unknown"
local baseName = CleveRoids.StripRank(spellName) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on melee hit - new duration: %ds",
baseName, spellID, rec.duration)
@@ -1083,7 +1083,7 @@ local function OnAutoAttackOther(attackerGuid, targetGuid, totalDamage, hitInfo,
-- Sync to pfUI if loaded (pre-7.6 only)
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
local spellName = GetSpellRecField and GetSpellRecField(spellID, "name") or nil
local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target")
local targetLevel = UnitLevel("target") or 0
if targetName and baseName then
@@ -1242,6 +1242,7 @@ local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAu
if spellId and durationMs and durationMs > 0 then
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
if spellName then
CleveRoids._allCasterAuraDirty = true
if not CleveRoids.AllCasterAuraTracking[targetGuid] then
CleveRoids.AllCasterAuraTracking[targetGuid] = {}
end
@@ -1324,6 +1325,9 @@ local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAu
end
-- Cleanup old aura tracking entries (3-level: targetGuid → spellName → casterGuid)
-- PERFORMANCE: Skip cleanup if nothing was written since last cleanup
if not CleveRoids._allCasterAuraDirty then return end
CleveRoids._allCasterAuraDirty = false
local tGuid, spellNames = next(CleveRoids.AllCasterAuraTracking)
while tGuid do
local nextTGuid = next(CleveRoids.AllCasterAuraTracking, tGuid)
@@ -1413,6 +1417,7 @@ autoAttackFrame:SetScript("OnEvent", function()
local playerGUID = CleveRoids.GetGUID("player")
local durSpellName = GetSpellRecField and GetSpellRecField(spellId, "name")
if playerGUID and durSpellName then
CleveRoids._allCasterAuraDirty = true
if not CleveRoids.AllCasterAuraTracking[playerGUID] then
CleveRoids.AllCasterAuraTracking[playerGUID] = {}
end
@@ -1712,7 +1717,7 @@ end
local function NormalizeSpellNameForComparison(spellName)
if not spellName then return nil end
-- Strip rank suffix: "Faerie Fire (Feral)(Rank 4)" -> "Faerie Fire (Feral)"
local normalized = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
local normalized = CleveRoids.StripRank(spellName)
-- Convert underscores to spaces and lowercase
normalized = string.lower(string.gsub(normalized, "_", " "))
return normalized
@@ -1798,7 +1803,7 @@ local function _get_debuff_timeleft(unitToken, auraName)
-- Strip rank suffix for consistent matching (e.g., "Rake(Rank 4)" -> "Rake")
if auraName then
auraName = string.gsub(auraName, "%s*%(%s*Rank%s+%d+%s*%)", "")
auraName = CleveRoids.StripRank(auraName)
-- Convert underscores to spaces for matching (e.g., "Thunder_Clap" -> "Thunder Clap")
auraName = string.gsub(auraName, "_", " ")
end
@@ -1811,7 +1816,7 @@ local function _get_debuff_timeleft(unitToken, auraName)
-- For overflow slots 17-48, nil means "regular buff filtered out", not "end of list"
if not effect and idx <= 16 then break end
-- Strip rank from effect name for comparison
local effectBase = effect and string.gsub(effect, "%s*%(%s*Rank%s+%d+%s*%)", "")
local effectBase = CleveRoids.StripRank(effect)
if effectBase and effectBase == auraName and timeleft and timeleft >= 0 then
return timeleft, duration
end
@@ -3546,7 +3551,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
-- Strip rank suffix for consistent matching (e.g., "Faerie Fire (Feral)(Rank 4)" -> "Faerie Fire (Feral)")
if args.name then
args.name = string.gsub(args.name, "%s*%(%s*Rank%s+%d+%s*%)", "")
args.name = CleveRoids.StripRank(args.name)
-- Convert underscores to spaces for matching (e.g., "Thunder_Clap" -> "Thunder Clap")
args.name = string.gsub(args.name, "_", " ")
end
@@ -4032,7 +4037,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
if not args.name then return false end
-- Strip rank suffix for consistent matching (e.g., "Faerie Fire (Feral)(Rank 4)" -> "Faerie Fire (Feral)")
args.name = string.gsub(args.name, "%s*%(%s*Rank%s+%d+%s*%)", "")
args.name = CleveRoids.StripRank(args.name)
-- Convert underscores to spaces for matching (e.g., "Thunder_Clap" -> "Thunder Clap")
args.name = string.gsub(args.name, "_", " ")
@@ -4159,7 +4164,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
if rec and rec.caster == "player" then
local n = GetSpellRecField and GetSpellRecField(sid, "name")
if n then
n = _string_gsub(n, "%s*%(%s*Rank%s+%d+%s*%)", "")
n = CleveRoids.StripRank(n)
if _string_lower(n) == cleanupNameLower then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
@@ -4246,7 +4251,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
if rec and rec.caster == "player" then
local n = GetSpellRecField and GetSpellRecField(sid, "name")
if n then
n = _string_gsub(n, "%s*%(%s*Rank%s+%d+%s*%)", "")
n = CleveRoids.StripRank(n)
if _string_lower(n) == fallbackNameLower then
local timeRemaining = rec.duration + rec.start - GetTime()
if timeRemaining > 0 then
@@ -4485,7 +4490,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
-- For overflow slots 17-48, nil means "regular buff filtered out", not "end of list"
if not effect and idx <= 16 then break end
-- Strip rank from effect name for comparison
local effectBase = effect and string.gsub(effect, "%s*%(%s*Rank%s+%d+%s*%)", "")
local effectBase = CleveRoids.StripRank(effect)
if effectBase and effectBase == args.name then
local shouldSkip = false
@@ -5019,7 +5024,7 @@ function CleveRoids.CheckSpellCast(unit, spell)
end
if castEntry.spellName then
-- Strip rank suffix for comparison: "Heal(Rank 4)" -> "Heal"
local baseName = string.gsub(castEntry.spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(castEntry.spellName)
if baseName == spell or castEntry.spellName == spell then
return true
end
@@ -5447,6 +5452,7 @@ end
--- @return boolean
function CleveRoids.IsUnitInMeleeRange(unit, cleaveRange)
if not UnitExists(unit) then return false end
if UnitIsDead(unit) then return false end
local threshold = cleaveRange and 5 or 2
+1 -1
View File
@@ -260,7 +260,7 @@ SlashCmdList.CAST = function(msg)
end
if duration and CleveRoids.ComboPointTracking then
-- Remove rank from spell name for pfUI compatibility
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
-- Populate name-based tracking BEFORE the spell is cast
-- This allows pfUI's AddEffect hook to find it
CleveRoids.ComboPointTracking[baseName] = {
+146 -114
View File
@@ -750,7 +750,7 @@ function CleveRoids.GetSpellCost(spellSlot, bookType)
reagent = nil
local name = GetSpellName(spellSlot, bookType)
if name then
name = string.gsub(name, "%s*%(%s*Rank%s+%d+%s*%)%s*$", "") -- strip "(Rank X)" only
name = CleveRoids.StripRank(name)
reagent = _ReagentBySpell[name]
end
end
@@ -1926,7 +1926,7 @@ function CleveRoids.ParseMsg(msg)
-- store the raw action for callers and strip trailing "(Rank X)" for comparisons
conditionals.action = action
action = string.gsub(action, "%s*%(%s*Rank%s+%d+%s*%)%s*$", "")
action = CleveRoids.StripRank(action)
-- IMPORTANT: if there's NO conditional block, return nil conditionals so
-- DoWithConditionals will hit the {macroName} execution branch.
@@ -1991,7 +1991,7 @@ function CleveRoids.ParseMsg(msg)
if not conditionals._groups then
conditionals._groups = {}
end
conditionals._groups[condition] = { { values = { conditionals.action }, operator = "OR" } }
conditionals._groups[condition] = { { values = { conditionals.action }, n = 1, operator = "OR" }, n = 1 }
end
else
-- existing code for when conditionals[condition] already exists
@@ -2011,9 +2011,12 @@ function CleveRoids.ParseMsg(msg)
conditionals._groups = {}
end
if not conditionals._groups[condition] then
conditionals._groups[condition] = {}
conditionals._groups[condition] = { n = 0 }
end
table.insert(conditionals._groups[condition], { values = { conditionals.action }, operator = "OR" })
local grp = conditionals._groups[condition]
local newEntry = { values = { conditionals.action }, n = 1, operator = "OR" }
grp.n = grp.n + 1
grp[grp.n] = newEntry
end
else
-- Has args. Ensure the key's value is a table and add new arguments.
@@ -2064,10 +2067,12 @@ function CleveRoids.ParseMsg(msg)
-- Create a new group for this conditional instance
-- Structure: { values = { ... }, operator = "OR" or "AND" }
if not conditionals._groups[condition] then
conditionals._groups[condition] = {}
conditionals._groups[condition] = { n = 0 }
end
local currentGroup = { values = {}, operator = operatorType }
table.insert(conditionals._groups[condition], currentGroup)
local currentGroup = { values = {}, n = 0, operator = operatorType }
local grp = conditionals._groups[condition]
grp.n = grp.n + 1
grp[grp.n] = currentGroup
-- Split args by the determined separator
for _, arg_item in CleveRoids.splitString(args, separator) do
@@ -2096,7 +2101,8 @@ function CleveRoids.ParseMsg(msg)
if not operator or not amount then
-- No operator found, treat as simple string argument
table.insert(conditionals[condition], processed_arg)
table.insert(currentGroup.values, processed_arg)
currentGroup.n = currentGroup.n + 1
currentGroup.values[currentGroup.n] = processed_arg
else
local name_to_use = (name and name ~= "") and name or conditionals.action
local final_amount_str, num_replacements = string.gsub(amount, "#", "")
@@ -2130,7 +2136,8 @@ function CleveRoids.ParseMsg(msg)
comparisons = comparisons -- Store all comparisons
}
table.insert(conditionals[condition], entry)
table.insert(currentGroup.values, entry)
currentGroup.n = currentGroup.n + 1
currentGroup.values[currentGroup.n] = entry
else
-- Fallback to single comparison if parsing failed
local entry = {
@@ -2140,7 +2147,8 @@ function CleveRoids.ParseMsg(msg)
checkStacks = should_check_stacks
}
table.insert(conditionals[condition], entry)
table.insert(currentGroup.values, entry)
currentGroup.n = currentGroup.n + 1
currentGroup.values[currentGroup.n] = entry
end
else
-- Normal single-comparison conditional (existing behavior)
@@ -2151,7 +2159,8 @@ function CleveRoids.ParseMsg(msg)
checkStacks = should_check_stacks
}
table.insert(conditionals[condition], entry)
table.insert(currentGroup.values, entry)
currentGroup.n = currentGroup.n + 1
currentGroup.values[currentGroup.n] = entry
end
end
end
@@ -3983,48 +3992,61 @@ CleveRoids.CLEANUP_INTERVAL = 5 -- Run cleanup every 5 seconds
local GetTime = GetTime
local UnitAffectingCombat = UnitAffectingCombat
local pairs = pairs
local next = next
local UnitPosition = UnitPosition
local IsAltKeyDown = IsAltKeyDown
local IsShiftKeyDown = IsShiftKeyDown
local IsControlKeyDown = IsControlKeyDown
-- PERFORMANCE: Module-level constants (avoid re-creation per frame)
local POS_TRACK_INTERVAL = 0.01 -- 10ms between position samples (100 Hz, matches MonkeySpeed)
local SEQUENCE_STALE_TIME = 300 -- 5 minutes
function CleveRoids.OnUpdate(self)
-- PERFORMANCE: Local alias for global namespace (saves ~80+ global lookups per frame)
local CR = CleveRoids
local CRM = CleveRoidMacros
-- Prevent SuperWoW API calls during shutdown (crash prevention)
if CleveRoids.isShuttingDown then return end
if CR.isShuttingDown then return end
-- Clear macro stop flags at the start of each frame
-- This ensures /stopmacro and /skipmacro only affect commands in the same frame (same macro execution)
-- Without SuperMacro, this is necessary because we can't hook into macro line execution
if CleveRoids.stopMacroFlag then
CleveRoids.stopMacroFlag = false
if CR.stopMacroFlag then
CR.stopMacroFlag = false
end
if CleveRoids.skipMacroFlag then
CleveRoids.skipMacroFlag = false
if CR.skipMacroFlag then
CR.skipMacroFlag = false
end
if CleveRoids.stopOnCastFlag then
CleveRoids.stopOnCastFlag = false
if CR.stopOnCastFlag then
CR.stopOnCastFlag = false
end
-- PERFORMANCE: Single GetTime() call per frame
local time = GetTime()
-- PERFORMANCE: Early exit if not ready (before any other checks)
if not CleveRoids.ready then
if not CR.ready then
-- Handle initialization timer only when not ready
if CleveRoids.initializationTimer and time >= CleveRoids.initializationTimer then
CleveRoids.initializationTimer = nil
CleveRoids.IndexItems()
if CR.initializationTimer and time >= CR.initializationTimer then
CR.initializationTimer = nil
CR.IndexItems()
-- FIX: Set ready=true BEFORE IndexActionBars so GetAction() works
-- IndexActionSlot calls GetAction() which has an early-exit if ready=false,
-- preventing TestForActiveAction from populating actions.active
CleveRoids.ready = true
CR.ready = true
-- FIX: Suppress action event handlers during initial indexing
-- IndexActionBars sends ACTIONBAR_SLOT_CHANGED for each slot which triggers
-- addon handlers (pfUI, Bongos). 120 rapid calls can cause issues.
CleveRoids._suppressActionHandlers = true
CleveRoids.IndexActionBars()
CleveRoids._suppressActionHandlers = false
CR._suppressActionHandlers = true
CR.IndexActionBars()
CR._suppressActionHandlers = false
CleveRoids.TestForAllActiveActions()
CleveRoids.lastUpdate = time
CR.TestForAllActiveActions()
CR.lastUpdate = time
-- FIX: Force refresh ALL Blizzard action buttons after initialization
-- Blizzard queries IsUsableAction during login before we're ready,
@@ -4033,8 +4055,8 @@ function CleveRoids.OnUpdate(self)
-- would trigger 120 rapid handler calls causing issues.
if not (pfUI and pfUI.bars) then
for slot = 1, 120 do
if CleveRoids.Actions[slot] then
CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
if CR.Actions[slot] then
CR.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
end
end
end
@@ -4048,17 +4070,16 @@ function CleveRoids.OnUpdate(self)
-- Track player position at ~100 Hz with 4-sample circular buffer for snappy movement detection.
-- Matches MonkeySpeed's 0.01s detection interval for similar responsiveness.
-- PERFORMANCE: Pre-allocated circular buffer eliminates ~100 table allocs/sec and table.remove shifting.
local posTrackInterval = 0.01 -- 10ms between position samples (100 Hz, matches MonkeySpeed)
local lastPosTime = CleveRoids._positionTrackTime or 0
local lastPosTime = CR._positionTrackTime or 0
if (time - lastPosTime) >= posTrackInterval then
CleveRoids._positionTrackTime = time
if (time - lastPosTime) >= POS_TRACK_INTERVAL then
CR._positionTrackTime = time
if UnitPosition then
local x, y = UnitPosition("player")
if x and y then
-- Initialize circular buffer if needed (pre-allocate 4 entry tables)
local history = CleveRoids._positionHistory
local history = CR._positionHistory
if not history then
history = {
{ x = 0, y = 0, time = 0 },
@@ -4066,14 +4087,15 @@ function CleveRoids.OnUpdate(self)
{ x = 0, y = 0, time = 0 },
{ x = 0, y = 0, time = 0 },
}
CleveRoids._positionHistory = history
CleveRoids._posHistoryIndex = 0
CleveRoids._posHistoryCount = 0
CR._positionHistory = history
CR._posHistoryIndex = 0
CR._posHistoryCount = 0
end
-- Advance circular index (1-based, wraps at 4)
local idx = math.mod(CleveRoids._posHistoryIndex, 4) + 1
CleveRoids._posHistoryIndex = idx
local idx = CR._posHistoryIndex + 1
if idx > 4 then idx = 1 end
CR._posHistoryIndex = idx
-- Reuse existing entry (zero allocation)
local entry = history[idx]
@@ -4082,69 +4104,70 @@ function CleveRoids.OnUpdate(self)
entry.time = time
-- Track how many samples we have (caps at 4)
if CleveRoids._posHistoryCount < 4 then
CleveRoids._posHistoryCount = CleveRoids._posHistoryCount + 1
if CR._posHistoryCount < 4 then
CR._posHistoryCount = CR._posHistoryCount + 1
end
-- Maintain legacy vars for any code that uses them directly
if CleveRoids._posHistoryCount >= 2 then
local prevIdx = math.mod((idx - 2 + 4), 4) + 1
CleveRoids._previousPlayerPos = history[prevIdx]
CleveRoids._currentPlayerPos = entry
if CR._posHistoryCount >= 2 then
local prevIdx = idx - 1
if prevIdx < 1 then prevIdx = 4 end
CR._previousPlayerPos = history[prevIdx]
CR._currentPlayerPos = entry
end
end
end
end
-- PERFORMANCE: Delayed WDB warmup after login (ensures GetItemInfo works after WDB clear)
if CleveRoids.wdbWarmupTime and time >= CleveRoids.wdbWarmupTime then
CleveRoids.wdbWarmupTime = nil
CleveRoids.DoWDBWarmup()
if CR.wdbWarmupTime and time >= CR.wdbWarmupTime then
CR.wdbWarmupTime = nil
CR.DoWDBWarmup()
end
-- PERFORMANCE: Cache refresh rate calculation (avoid per-frame division)
local refreshRate = CleveRoids.cachedRefreshRate
local refreshRate = CR.cachedRefreshRate
if not refreshRate then
refreshRate = 1 / (CleveRoidMacros.refresh or 5)
CleveRoids.cachedRefreshRate = refreshRate
refreshRate = 1 / (CRM.refresh or 5)
CR.cachedRefreshRate = refreshRate
end
-- PERFORMANCE: Throttle check FIRST - skip most work on non-throttled frames
local lastUpdate = CleveRoids.lastUpdate or 0
local bypassThrottle = CleveRoids.isActionUpdateQueued and CleveRoidMacros.realtime == 0
local lastUpdate = CR.lastUpdate or 0
local bypassThrottle = CR.isActionUpdateQueued and CRM.realtime == 0
local shouldUpdate = bypassThrottle or (time - lastUpdate) >= refreshRate
if not shouldUpdate then
return -- Early exit for non-throttled frames
end
CleveRoids.lastUpdate = time
CR.lastUpdate = time
-- Process deferred equipment index updates (for throttled UNIT_INVENTORY_CHANGED)
-- PERFORMANCE: Skip check entirely if no pending update
local pendingTime = CleveRoids.equipIndexPendingTime
local pendingTime = CR.equipIndexPendingTime
if pendingTime and not UnitAffectingCombat("player") then
if (time - (CleveRoids.lastEquipIndexTime or 0)) >= 0.2 then
CleveRoids.lastEquipIndexTime = time
CleveRoids.equipIndexPendingTime = nil
CleveRoids.lastItemIndexTime = time
CleveRoids.IndexItems()
CleveRoids.Actions = {}
CleveRoids.Macros = {}
CleveRoids.IndexActionBars()
if (time - (CR.lastEquipIndexTime or 0)) >= 0.2 then
CR.lastEquipIndexTime = time
CR.equipIndexPendingTime = nil
CR.lastItemIndexTime = time
CR.IndexItems()
CR.Actions = {}
CR.Macros = {}
CR.IndexActionBars()
if CleveRoidMacros.realtime == 0 then
CleveRoids.QueueActionUpdate()
if CRM.realtime == 0 then
CR.QueueActionUpdate()
end
end
end
-- PERFORMANCE: Check for expired reactive procs only if we have any
-- Use statically allocated removal buffer to avoid per-frame allocation
local reactiveProcs = CleveRoids.reactiveProcs
local reactiveProcs = CR.reactiveProcs
if reactiveProcs then
local hasExpiredProc = false
local toRemove = CleveRoids._procRemovalBuffer
local toRemove = CR._procRemovalBuffer
local removeCount = 0
-- PERFORMANCE: Use next() directly instead of pairs() to avoid iterator allocation
@@ -4166,67 +4189,67 @@ function CleveRoids.OnUpdate(self)
-- If any proc expired, immediately update all actions
if hasExpiredProc then
CleveRoids.TestForAllActiveActions()
CleveRoids.isActionUpdateQueued = false
CR.TestForAllActiveActions()
CR.isActionUpdateQueued = false
end
end
-- PERFORMANCE: Check for modifier key state changes (no events for these in vanilla WoW)
-- Only queue update if modifier state actually changed - avoids full TestForAllActiveActions
-- Nampower v2.41+ fires KEY_DOWN/KEY_UP for all keys including modifiers, so skip polling
if not CleveRoids.NampowerAPI.features.hasKeyEvents then
local altDown = IsAltKeyDown() and true or false
local shiftDown = IsShiftKeyDown() and true or false
local ctrlDown = IsControlKeyDown() and true or false
if not CR.NampowerAPI.features.hasKeyEvents then
local altDown = IsAltKeyDown()
local shiftDown = IsShiftKeyDown()
local ctrlDown = IsControlKeyDown()
if altDown ~= CleveRoids._lastAltDown or
shiftDown ~= CleveRoids._lastShiftDown or
ctrlDown ~= CleveRoids._lastCtrlDown then
CleveRoids._lastAltDown = altDown
CleveRoids._lastShiftDown = shiftDown
CleveRoids._lastCtrlDown = ctrlDown
if altDown ~= CR._lastAltDown or
shiftDown ~= CR._lastShiftDown or
ctrlDown ~= CR._lastCtrlDown then
CR._lastAltDown = altDown
CR._lastShiftDown = shiftDown
CR._lastCtrlDown = ctrlDown
-- Modifier changed - queue update in event-driven mode, or just mark for realtime
if CleveRoidMacros.realtime == 0 then
CleveRoids.isActionUpdateQueued = true
if CRM.realtime == 0 then
CR.isActionUpdateQueued = true
end
end
end
-- Check the saved variable to decide which update mode to use.
if CleveRoidMacros.realtime == 1 then
if CRM.realtime == 1 then
-- Realtime Mode: Force an update on every throttled tick for maximum responsiveness.
CleveRoids.TestForAllActiveActions()
CR.TestForAllActiveActions()
else
-- Event-Driven Mode (Default): Only update if a relevant game event has queued it.
if CleveRoids.isActionUpdateQueued then
if CleveRoids.debug then
if CR.isActionUpdateQueued then
if CR.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cffff00ff[OnUpdate]|r Processing queued action update")
end
CleveRoids.TestForAllActiveActions()
CleveRoids.isActionUpdateQueued = false -- Reset the flag after updating
if CleveRoids.debug then
CR.TestForAllActiveActions()
CR.isActionUpdateQueued = false -- Reset the flag after updating
if CR.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cffff00ff[OnUpdate]|r Action update complete, flag reset")
end
end
end
-- The rest of this function handles time-based logic that must always run.
if CleveRoids.CurrentSpell.autoAttackLock and (time - CleveRoids.autoAttackLockElapsed) > refreshRate then
CleveRoids.CurrentSpell.autoAttackLock = false
CleveRoids.autoAttackLockElapsed = nil
if CR.CurrentSpell.autoAttackLock and (time - CR.autoAttackLockElapsed) > refreshRate then
CR.CurrentSpell.autoAttackLock = false
CR.autoAttackLockElapsed = nil
end
-- PERFORMANCE: Use next() directly instead of pairs() to avoid iterator allocation
local Sequences = CleveRoids.Sequences
local Sequences = CR.Sequences
local seqKey, sequence = next(Sequences)
while seqKey do
if sequence.index > 1 and sequence.reset.secs and (time - (sequence.lastUpdate or 0)) >= sequence.reset.secs then
CleveRoids.ResetSequence(sequence)
CR.ResetSequence(sequence)
end
seqKey, sequence = next(Sequences, seqKey)
end
-- PERFORMANCE: Use next() directly instead of pairs() to avoid iterator allocation
local spell_tracking = CleveRoids.spell_tracking
local spell_tracking = CR.spell_tracking
local guid, cast = next(spell_tracking)
while guid do
local nextGuid = next(spell_tracking, guid) -- Get next before potential removal
@@ -4237,8 +4260,8 @@ function CleveRoids.OnUpdate(self)
end
-- Clean stale castTracking entries (standalone mode only, pfUI 7.6 manages its own)
if not CleveRoids.hasPfUI76 then
local ct = CleveRoids.castTracking
if not CR.hasPfUI76 then
local ct = CR.castTracking
local ctGuid, ctEntry = next(ct)
while ctGuid do
local nextCtGuid = next(ct, ctGuid)
@@ -4252,12 +4275,12 @@ function CleveRoids.OnUpdate(self)
-- PERFORMANCE OPTIMIZATION: Run memory cleanup less frequently (every 5 seconds instead of every frame)
-- This reduces CPU usage while maintaining effective memory management
if (time - CleveRoids.lastCleanupTime) >= CleveRoids.CLEANUP_INTERVAL then
CleveRoids.lastCleanupTime = time
if (time - CR.lastCleanupTime) >= CR.CLEANUP_INTERVAL then
CR.lastCleanupTime = time
-- MEMORY: Clean up carnageDurationOverrides older than 30 seconds
-- PERFORMANCE: Use next() directly instead of pairs() to avoid iterator allocation
local carnageOverrides = CleveRoids.carnageDurationOverrides
local carnageOverrides = CR.carnageDurationOverrides
if carnageOverrides then
local spellID, data = next(carnageOverrides)
while spellID do
@@ -4271,7 +4294,7 @@ function CleveRoids.OnUpdate(self)
-- MEMORY: Clean up old ComboPointTracking entries (older than 60 seconds)
-- PERFORMANCE: Use next() directly instead of pairs() to avoid iterator allocation
local comboTracking = CleveRoids.ComboPointTracking
local comboTracking = CR.ComboPointTracking
if comboTracking then
local trackName, data = next(comboTracking)
while trackName do
@@ -4284,25 +4307,34 @@ function CleveRoids.OnUpdate(self)
end
-- MEMORY: Clear spell name caches every 60 seconds (12 cleanup cycles)
CleveRoids._spellCacheCleanupCounter = (CleveRoids._spellCacheCleanupCounter or 0) + 1
if CleveRoids._spellCacheCleanupCounter >= 12 then
CleveRoids._spellCacheCleanupCounter = 0
if CleveRoids.ClearSpellNameCaches then
CleveRoids.ClearSpellNameCaches()
CR._spellCacheCleanupCounter = (CR._spellCacheCleanupCounter or 0) + 1
if CR._spellCacheCleanupCounter >= 12 then
CR._spellCacheCleanupCounter = 0
if CR.ClearSpellNameCaches then
CR.ClearSpellNameCaches()
end
if CR.ClearStripRankCache then
CR.ClearStripRankCache()
end
end
-- MEMORY: Clean up stale perTargetState entries in sequences (older than 5 minutes)
-- This prevents memory bloat from tracking many targets over time
local SEQUENCE_TARGET_STALE_TIME = 300 -- 5 minutes
for _, sequence in pairs(CleveRoids.Sequences) do
if sequence.perTargetState then
for guid, state in pairs(sequence.perTargetState) do
if state.lastUpdate and (time - state.lastUpdate) > SEQUENCE_TARGET_STALE_TIME then
sequence.perTargetState[guid] = nil
-- PERFORMANCE: Use next() instead of pairs() to avoid iterator closure allocation
local seqKey2, seq2 = next(CR.Sequences)
while seqKey2 do
if seq2.perTargetState then
local ptGuid, ptState = next(seq2.perTargetState)
while ptGuid do
local nextPtGuid = next(seq2.perTargetState, ptGuid)
if ptState.lastUpdate and (time - ptState.lastUpdate) > SEQUENCE_STALE_TIME then
seq2.perTargetState[ptGuid] = nil
end
ptGuid = nextPtGuid
ptState = nextPtGuid and seq2.perTargetState[nextPtGuid]
end
end
seqKey2, seq2 = next(CR.Sequences, seqKey2)
end
end
end
@@ -5240,7 +5272,7 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time
rec.start = GetTime()
local spellName = GetSpellRecField(spellID, "name")
local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "") or "Unknown"
local baseName = CleveRoids.StripRank(spellName) or "Unknown"
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
+1 -1
View File
@@ -122,7 +122,7 @@ local function CreateIconButton(parent, index, iconTable)
GameTooltip:SetOwner(btn, "ANCHOR_BOTTOMLEFT")
local spellName = GetSpellRecField and GetSpellRecField(data.spellId, "name") or ("Spell " .. data.spellId)
if spellName then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
GameTooltip:AddLine(baseName, 1, 1, 1)
end
if data.remaining and data.remaining > 0 then
+51 -2
View File
@@ -194,7 +194,25 @@
(aggressive/defensive/passive) including for infernal.
- CastSpell now handles spells not in DBC gracefully.
Current version: v3.3.0
Environmental Damage, Damage Shield, Dispel Events (v3.4+):
- ENVIRONMENTAL_DMG_SELF / ENVIRONMENTAL_DMG_OTHER - Environmental damage events
(exhaustion, drowning, fall, lava, slime, fire). Params: unitGuid, dmgType,
damage, absorb, resist. No CVar needed.
- DAMAGE_SHIELD_SELF / DAMAGE_SHIELD_OTHER - Damage shield events (Thorns, etc.).
Params: unitGuid, targetGuid, damage, spellSchool. No CVar needed.
- SPELL_DISPEL_BY_SELF / SPELL_DISPEL_BY_OTHER - Spell dispel events.
Params: casterGuid, targetGuid, spellId. No CVar needed.
GetSpellRangeData, PvP Unit Data Blocking (v4.0+):
- GetSpellRangeData(rangeIndex) - Returns minRange, maxRange, flags, name for a
SpellRange DBC entry. flags=0 is ranged, flags=1 is melee (Combat Range).
Use with GetSpellRecField(spellId, "rangeIndex") to look up a spell's range.
- BREAKING: GetUnitData/GetUnitField now return nil for PvP-flagged units you can
attack. BUFF_ADDED_OTHER/BUFF_REMOVED_OTHER no longer fire for those units.
Existing API wrappers (GetUnitHealth, etc.) fall back to standard WoW API.
- Hooks initialized later so _GUID events work even with old perf_boost.
Current version: v4.0.0
]]
local _G = _G or getfenv(0)
@@ -411,6 +429,16 @@ API.VERSION_REQUIREMENTS = {
-- v3.3+ - Chat bubble controls, demon autocast reaction state preservation
["ChatBubbleControls"] = { 3, 3, 0 }, -- NP_ChatBubbleDistance, NP_ChatBubblesWhisper, NP_ChatBubblesRaid, NP_ChatBubblesBattleground
["DemonAutocastReactions"] = { 3, 3, 0 }, -- NP_PreserveGreaterDemonAutocast now saves/restores reaction states including infernal
-- v3.4+ - Environmental damage, damage shield, dispel events
["EnvironmentalDmgEvents"] = { 3, 4, 0 }, -- ENVIRONMENTAL_DMG_SELF/OTHER events
["DamageShieldEvents"] = { 3, 4, 0 }, -- DAMAGE_SHIELD_SELF/OTHER events
["SpellDispelEvents"] = { 3, 4, 0 }, -- SPELL_DISPEL_BY_SELF/OTHER events
-- v4.0+ - GetSpellRangeData, PvP unit data blocking, GUID event init fix
["GetSpellRangeData"] = { 4, 0, 0, "GetSpellRangeData" },
["PvpUnitDataBlocking"] = { 4, 0, 0 }, -- GetUnitData/GetUnitField return nil for PvP-flagged attackable units; BUFF/DEBUFF_ADDED/REMOVED_OTHER suppressed
["GuidEventInitFix"] = { 4, 0, 0 }, -- _GUID events now work with old perf_boost (hooks initialized later)
}
-- Check if a specific feature is available
@@ -611,6 +639,16 @@ local function InitializeFeatures()
f.hasChatBubbleControls = API.HasFeature("ChatBubbleControls")
f.hasDemonAutocastReactions = API.HasFeature("DemonAutocastReactions")
-- v3.4+ Environmental damage, damage shield, dispel events
f.hasEnvironmentalDmgEvents = API.HasFeature("EnvironmentalDmgEvents")
f.hasDamageShieldEvents = API.HasFeature("DamageShieldEvents")
f.hasSpellDispelEvents = API.HasFeature("SpellDispelEvents")
-- v4.0+ GetSpellRangeData, PvP unit data blocking, GUID event init fix
f.hasGetSpellRangeData = API.HasFeature("GetSpellRangeData")
f.hasPvpUnitDataBlocking = API.HasFeature("PvpUnitDataBlocking")
f.hasGuidEventInitFix = API.HasFeature("GuidEventInitFix")
-- Runtime detection for enhanced spell functions (verify by testing)
if f.hasEnhancedSpellFunctions and GetSpellTexture then
local success, result = pcall(function()
@@ -1350,7 +1388,7 @@ function API.FindUnitAuraInfo(unitToken, searchSpellId, searchNameLower)
local name = GetSpellRecField and GetSpellRecField(auraId, "name")
if name then
-- Strip rank suffix for consistent matching
local baseName = string.gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
local baseName = CleveRoids.StripRank(name)
if string.lower(baseName) == searchNameLower then
matched = true
end
@@ -3422,6 +3460,17 @@ function API.IsAuraHidden(spellId)
return _G.IsAuraHidden(spellId)
end
-- Get spell range data from SpellRange DBC by rangeIndex (v4.0+)
-- Returns minRange, maxRange, flags, name or nil if unavailable/invalid
-- flags: 0 = ranged, 1 = melee (Combat Range)
-- Note: returned ranges are raw DBC values before combat reach adjustments
function API.GetSpellRangeData(rangeIndex)
if not API.features.hasGetSpellRangeData or not _G.GetSpellRangeData then
return nil
end
return _G.GetSpellRangeData(rangeIndex)
end
--------------------------------------------------------------------------------
-- HEALTH/POWER WRAPPERS (GetUnitField extended token support with fallback)
--------------------------------------------------------------------------------
+48 -25
View File
@@ -17,6 +17,29 @@ local tonumber = tonumber
local tostring = tostring
local GetTime = GetTime
--------------------------------------------------------------------------------
-- PERFORMANCE: Memoized rank-stripping function
-- Caches results to avoid repeated string.gsub + string allocation on every call.
-- Two patterns are used across the codebase; we handle both with one function.
-- Cache grows with unique spell names (~200-300 total), negligible memory.
--------------------------------------------------------------------------------
local _stripRankCache = {}
local _STRIP_RANK_CACHE_MAX = 512
function CleveRoids.StripRank(name)
if not name then return nil end
local cached = _stripRankCache[name]
if cached then return cached end
-- Handles both "Spell (Rank 4)" and "Spell(Rank 4)" with flexible whitespace
cached = gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
_stripRankCache[name] = cached
return cached
end
function CleveRoids.ClearStripRankCache()
_stripRankCache = {}
end
-- Spells with physical damage + resistable CC effect (weapon-dependent)
-- These spells deal physical damage that ALWAYS lands (unless dodged/parried/blocked),
-- but apply a CC effect that can be resisted independently.
@@ -2040,7 +2063,7 @@ function lib:GetSpellBaseName(spellID)
if not name then return nil end
-- Remove rank suffix
return string.gsub(name, "%s*%(Rank %d+%)", "")
return CleveRoids.StripRank(name)
end
-- Track rank refreshes for pfUI integration
@@ -2265,7 +2288,7 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)
local targetLevel = UnitLevel(guid) or UnitLevel("target") or 1
-- Strip rank from spell name for pfUI (it uses base names)
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
-- Also register the duration in pfUI's duration table
if pflib.debuffs then
@@ -2523,7 +2546,7 @@ local function SeedUnit(unit)
local spellName = GetSpellRecField(spellID, "name")
if spellName and pflib.AddEffect then
local targetLevel = UnitLevel(unit) or 1
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
if pflib.debuffs then
pflib.debuffs[baseName] = duration
end
@@ -2612,7 +2635,7 @@ local function SeedUnit(unit)
local spellName = GetSpellRecField(spellID, "name")
if spellName and pflib.AddEffect then
local targetLevel = UnitLevel(unit) or 1
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
if pflib.debuffs then
pflib.debuffs[baseName] = duration
end
@@ -2882,7 +2905,7 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
local pflib = pfUI.api.libdebuff
local ripSpellName = GetSpellRecField(ripSpellID, "name")
local baseName = ripSpellName and string.gsub(ripSpellName, "%s*%(Rank %d+%)", "") or "Rip"
local baseName = CleveRoids.StripRank(ripSpellName) or "Rip"
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
@@ -2995,7 +3018,7 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
local pflib = pfUI.api.libdebuff
local rakeSpellName = GetSpellRecField(rakeSpellID, "name")
local baseName = rakeSpellName and string.gsub(rakeSpellName, "%s*%(Rank %d+%)", "") or "Rake"
local baseName = CleveRoids.StripRank(rakeSpellName) or "Rake"
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
@@ -4204,7 +4227,7 @@ ev:SetScript("OnEvent", function()
local spellName = GetSpellRecField(spellID, "name")
if spellName and CleveRoids.ComboPointTracking then
-- Remove rank from spell name to match pfUI's format
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
CleveRoids.ComboPointTracking[baseName] = {
combo_points = comboPoints,
duration = duration,
@@ -5153,7 +5176,7 @@ ev:SetScript("OnEvent", function()
-- Name-based combo tracking for pfUI compatibility
if debuffComboPoints and debuffComboPoints > 0 and CleveRoids.ComboPointTracking then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
CleveRoids.ComboPointTracking[baseName] = {
combo_points = debuffComboPoints,
duration = debuffDuration,
@@ -5708,8 +5731,8 @@ evLearn:SetScript("OnEvent", function()
for biteSpellID, _ in pairs(CleveRoids.FerociousBiteSpellIDs) do
local biteName = GetSpellRecField(biteSpellID, "name")
if biteName then
biteName = string.gsub(biteName, "%s*%(%s*Rank%s+%d+%s*%)", "")
local messageSpellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
biteName = CleveRoids.StripRank(biteName)
local messageSpellName = CleveRoids.StripRank(spellName)
if lower(biteName) == lower(messageSpellName) then
isFerociousBite = true
break
@@ -5736,7 +5759,7 @@ evLearn:SetScript("OnEvent", function()
-- PERSONAL DEBUFFS: Cancel pending tracking if spell was dodged/parried/blocked
if lib.pendingPersonalDebuffs then
local messageSpellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
local messageSpellName = CleveRoids.StripRank(spellName)
local toRemove = {}
for i, pending in ipairs(lib.pendingPersonalDebuffs) do
@@ -5749,12 +5772,12 @@ evLearn:SetScript("OnEvent", function()
local matchesCast = false
if pendingSpellName then
pendingSpellName = string.gsub(pendingSpellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
pendingSpellName = CleveRoids.StripRank(pendingSpellName)
matchesTriggered = lower(pendingSpellName) == lower(messageSpellName)
end
if castSpellName then
castSpellName = string.gsub(castSpellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
castSpellName = CleveRoids.StripRank(castSpellName)
matchesCast = lower(castSpellName) == lower(messageSpellName)
end
@@ -5787,13 +5810,13 @@ evLearn:SetScript("OnEvent", function()
find(message, "resist") or find(message, "immune")
if isSpellFailure and lib.pendingCCDebuffs then
local messageSpellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
local messageSpellName = CleveRoids.StripRank(spellName)
local toRemove = {}
for i, pending in ipairs(lib.pendingCCDebuffs) do
local pendingSpellName = GetSpellRecField(pending.spellID, "name")
if pendingSpellName then
pendingSpellName = string.gsub(pendingSpellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
pendingSpellName = CleveRoids.StripRank(pendingSpellName)
if lower(pendingSpellName) == lower(messageSpellName) then
-- Found the pending CC that was avoided/resisted - cancel it (don't record immunity)
if CleveRoids.debug then
@@ -5820,7 +5843,7 @@ evLearn:SetScript("OnEvent", function()
-- SHARED DEBUFFS: Cancel pending tracking if spell was dodged/parried/blocked/missed/resisted/immune
if isSpellFailure and lib.pendingSharedDebuffs then
local messageSpellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
local messageSpellName = CleveRoids.StripRank(spellName)
local toRemove = {}
for i, pending in ipairs(lib.pendingSharedDebuffs) do
@@ -5832,12 +5855,12 @@ evLearn:SetScript("OnEvent", function()
local matchesCast = false
if pendingSpellName then
pendingSpellName = string.gsub(pendingSpellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
pendingSpellName = CleveRoids.StripRank(pendingSpellName)
matchesTracking = lower(pendingSpellName) == lower(messageSpellName)
end
if castSpellName then
castSpellName = string.gsub(castSpellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
castSpellName = CleveRoids.StripRank(castSpellName)
matchesCast = lower(castSpellName) == lower(messageSpellName)
end
@@ -5875,10 +5898,10 @@ evLearn:SetScript("OnEvent", function()
-- Get the spell name from the cast (strip rank)
local castSpellName = GetSpellRecField(castSpellID, "name")
if castSpellName then
castSpellName = string.gsub(castSpellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
castSpellName = CleveRoids.StripRank(castSpellName)
-- Strip rank from the message spell name for comparison
local messageSpellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "")
local messageSpellName = CleveRoids.StripRank(spellName)
-- Only remove if the spell names match (case-insensitive)
if lower(castSpellName) == lower(messageSpellName) then
@@ -5901,7 +5924,7 @@ evLearn:SetScript("OnEvent", function()
for sid, _ in pairs(lib.personalDebuffs) do
local name = GetSpellRecField(sid, "name")
if name then
name = string.gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
name = CleveRoids.StripRank(name)
if name == castSpellName then
table.insert(matchingSpellIDs, sid)
end
@@ -5912,7 +5935,7 @@ evLearn:SetScript("OnEvent", function()
for sid, _ in pairs(lib.sharedDebuffs) do
local name = GetSpellRecField(sid, "name")
if name then
name = string.gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
name = CleveRoids.StripRank(name)
if name == castSpellName then
table.insert(matchingSpellIDs, sid)
end
@@ -5988,7 +6011,7 @@ evLearn:SetScript("OnEvent", function()
local pflib = pfUI.api.libdebuff
local spellName = GetSpellRecField(flameShockID, "name")
if spellName and pflib.AddEffect then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local baseName = CleveRoids.StripRank(spellName)
local targetLevel = UnitLevel(targetGUID) or UnitLevel("target") or 1
pflib:AddEffect(targetName, targetLevel, baseName, rec.duration, "player")
end
@@ -6167,7 +6190,7 @@ evJudgement:SetScript("OnEvent", function()
local spellName = GetSpellRecField(spellID, "name")
if spellName and targetName then
local effectName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local effectName = CleveRoids.StripRank(spellName)
pfUI.api.libdebuff:AddEffect(targetName, targetLevel, effectName, rec.duration, "player")
if CleveRoids.debug then
@@ -9671,7 +9694,7 @@ local function HandleDebuffFade()
for spellID in pairs(lib.objects[targetGUID]) do
local name = GetSpellRecField(spellID, "name")
if name then
name = gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
name = CleveRoids.StripRank(name)
if name == spellName then
-- Learn duration if we have timing data
if lib.learnCastTimers[targetGUID] and