mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
performance updates, meleerange conditional checks if unit is alive
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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] = {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user