mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
fix macro icons, enhance libdebuff further with nampower, code cleanup, improve compatibility with supermacro
This commit is contained in:
@@ -29,6 +29,11 @@ do
|
||||
petaggressive=true, petdefensive=true, petwait=true,
|
||||
-- healing (requires QuickHeal addon)
|
||||
quickheal=true, qh=true,
|
||||
-- additional commands that were missing
|
||||
castpet=true, cleartarget=true,
|
||||
applymain=true, applyoff=true,
|
||||
-- slot-specific equip commands
|
||||
["equip11"]=true, ["equip12"]=true, ["equip13"]=true, ["equip14"]=true,
|
||||
}
|
||||
|
||||
-- alias map (if CRM only registers /cancelaura, but /unbuff appears)
|
||||
@@ -66,6 +71,12 @@ do
|
||||
-- SuperMacro calls RunLine(line) one line at a time; handle first arg.
|
||||
local text = arg and arg[1]
|
||||
|
||||
-- FIX: Skip comment lines (SuperMacro uses -- for comments)
|
||||
-- Let SuperMacro handle these to avoid any processing
|
||||
if type(text) == "string" and string.find(text, "^%s*%-%-") then
|
||||
return orig_RunLine(text)
|
||||
end
|
||||
|
||||
-- IMPORTANT: Check for /nofirstaction BEFORE the stop flag check
|
||||
-- This allows /nofirstaction to clear the stopMacroFlag set by /firstaction
|
||||
if type(text) == "string" then
|
||||
@@ -109,7 +120,8 @@ do
|
||||
-- nothing to do; pass through to SM
|
||||
else
|
||||
-- 2) generic /cmd forwarding (lets CRM handlers parse [])
|
||||
local _, _, raw, msg = string.find(text, "^/(%S+)%s*(.*)$")
|
||||
-- FIX: Added %s* to handle leading whitespace (like /castsequence pattern above)
|
||||
local _, _, raw, msg = string.find(text, "^%s*/(%S+)%s*(.*)$")
|
||||
if raw then
|
||||
local cmd = string.lower(raw)
|
||||
-- alias map: /unbuff → /cancelaura
|
||||
@@ -148,9 +160,10 @@ do
|
||||
CRM.Hooks.SuperMacro_RunMacro = orig_SuperMacro_RunMacro
|
||||
|
||||
local function hooked_RunMacro(index)
|
||||
-- Clear macro stop flags at macro start
|
||||
-- Clear ALL macro stop flags at macro start (including firstaction flag)
|
||||
CRM.stopMacroFlag = false
|
||||
CRM.skipMacroFlag = false
|
||||
CRM.stopOnCastFlag = false -- FIX: Also clear firstaction flag
|
||||
return orig_SuperMacro_RunMacro(index)
|
||||
end
|
||||
|
||||
@@ -166,9 +179,10 @@ do
|
||||
CRM.Hooks.RunSuperMacro = orig_RunSuperMacro
|
||||
|
||||
_G.RunSuperMacro = function(index)
|
||||
-- Clear macro stop flags at macro start
|
||||
-- Clear ALL macro stop flags at macro start (including firstaction flag)
|
||||
CRM.stopMacroFlag = false
|
||||
CRM.skipMacroFlag = false
|
||||
CRM.stopOnCastFlag = false -- FIX: Also clear firstaction flag
|
||||
return orig_RunSuperMacro(index)
|
||||
end
|
||||
end
|
||||
|
||||
+111
-1
@@ -1259,6 +1259,97 @@ local function PFUI_HasLibDebuff()
|
||||
and type(pfUI.api.libdebuff.UnitDebuff) == "function"
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- PENDING DEBUFF CAST DETECTION
|
||||
-- ============================================================================
|
||||
-- Prevents double-application when spamming [nodebuff] macros with spell queue.
|
||||
-- If we're currently casting or have queued a spell that applies this debuff,
|
||||
-- treat the debuff as "pending" so [nodebuff] returns false.
|
||||
|
||||
-- Helper: Normalize spell name for comparison (strip rank, lowercase)
|
||||
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*%)", "")
|
||||
-- Convert underscores to spaces and lowercase
|
||||
normalized = string.lower(string.gsub(normalized, "_", " "))
|
||||
return normalized
|
||||
end
|
||||
|
||||
-- Check if player is currently casting or has queued a spell that would apply this debuff
|
||||
-- This prevents [nodebuff] from returning true while the cast is in-flight
|
||||
-- @param spellName string - The debuff spell name to check
|
||||
-- @param targetUnit string - The target unit (to verify we're casting AT this target)
|
||||
-- @return boolean - True if this debuff is pending (being cast/queued to this target)
|
||||
local function IsPendingDebuffCast(spellName, targetUnit)
|
||||
if not spellName then return false end
|
||||
|
||||
local normalizedCheck = NormalizeSpellNameForComparison(spellName)
|
||||
if not normalizedCheck then return false end
|
||||
|
||||
-- Get target GUID for verification (only count as pending if casting AT this target)
|
||||
local targetGuid = nil
|
||||
if targetUnit and UnitExists(targetUnit) then
|
||||
local _, guid = UnitExists(targetUnit)
|
||||
targetGuid = guid
|
||||
end
|
||||
|
||||
-- Check if currently CASTING this spell
|
||||
-- CleveRoids.CurrentSpell tracks the spell being cast with time-based duration
|
||||
if CleveRoids.CurrentSpell and CleveRoids.CurrentSpell.type == "cast" then
|
||||
local castingName = CleveRoids.CurrentSpell.spellName
|
||||
if castingName then
|
||||
local normalizedCasting = NormalizeSpellNameForComparison(castingName)
|
||||
if normalizedCasting == normalizedCheck then
|
||||
-- Verify cast is still in progress (not finished)
|
||||
if CleveRoids.castStartTime and CleveRoids.castDuration then
|
||||
local remaining = CleveRoids.castDuration - (GetTime() - CleveRoids.castStartTime)
|
||||
if remaining > 0.1 then
|
||||
-- Cast is in-flight, debuff is pending
|
||||
return true
|
||||
end
|
||||
else
|
||||
-- No timing info but spell type is "cast" - assume pending
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if this spell is QUEUED via Nampower
|
||||
-- CleveRoids.queuedSpell tracks the spell waiting to fire after GCD/cast
|
||||
if CleveRoids.queuedSpell and CleveRoids.queuedSpell.spellName then
|
||||
local queuedName = CleveRoids.queuedSpell.spellName
|
||||
local normalizedQueued = NormalizeSpellNameForComparison(queuedName)
|
||||
if normalizedQueued == normalizedCheck then
|
||||
-- Spell is queued, debuff is pending
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Check pfUI's pending debuff tracking (uses SPELL_GO events for accuracy)
|
||||
-- pfUI.libdebuff_pending[guid][spellName] = true when cast is in-flight
|
||||
-- Cleared on SPELL_GO miss/resist, so this is very accurate
|
||||
if pfUI and pfUI.libdebuff_pending and targetGuid then
|
||||
local pendingForTarget = pfUI.libdebuff_pending[targetGuid]
|
||||
if pendingForTarget then
|
||||
-- pfUI stores by spell name (may or may not have rank)
|
||||
-- Check both the exact name and stripped name
|
||||
for pendingSpell, _ in pairs(pendingForTarget) do
|
||||
local normalizedPending = NormalizeSpellNameForComparison(pendingSpell)
|
||||
if normalizedPending == normalizedCheck then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Expose for use in nodebuff conditional
|
||||
CleveRoids.IsPendingDebuffCast = IsPendingDebuffCast
|
||||
|
||||
-- Helper: Get debuff time-left (seconds) from CleveRoids.libdebuff only
|
||||
local function _get_debuff_timeleft(unitToken, auraName)
|
||||
-- Defensive: verify libdebuff is a table before accessing properties
|
||||
@@ -4792,6 +4883,16 @@ CleveRoids.Keywords = {
|
||||
|
||||
nodebuff = function(conditionals)
|
||||
return NegatedMulti(conditionals.nodebuff, function(v)
|
||||
-- Extract spell name from args (could be string or table with name/operator/amount)
|
||||
local spellName = type(v) == "table" and v.name or v
|
||||
|
||||
-- Check if this debuff is PENDING (being cast or queued)
|
||||
-- This prevents double-application when spamming macros with spell queue
|
||||
if CleveRoids.IsPendingDebuffCast(spellName, conditionals.target) then
|
||||
return false -- Treat as if debuff exists (nodebuff returns false)
|
||||
end
|
||||
|
||||
-- Debuff not pending, check if it actually exists on target
|
||||
return not CleveRoids.ValidateUnitDebuff(conditionals.target, v)
|
||||
end, conditionals, "nodebuff")
|
||||
end,
|
||||
@@ -6495,11 +6596,20 @@ CleveRoids.Keywords = {
|
||||
|
||||
-- Negated spell name form
|
||||
return NegatedMulti(conditionals.nocursive, function(args)
|
||||
-- Extract spell name from args
|
||||
local spellName = type(args) == "string" and args or (type(args) == "table" and args.name)
|
||||
|
||||
-- Check if this debuff is PENDING (being cast or queued)
|
||||
-- This prevents double-application when spamming macros with spell queue
|
||||
if spellName and CleveRoids.IsPendingDebuffCast(spellName, target) then
|
||||
return false -- Treat as if debuff exists (nocursive returns false)
|
||||
end
|
||||
|
||||
if type(args) == "string" then
|
||||
-- Simple spell name check: [nocursive:Rake] = true if Rake is missing
|
||||
return not CleveRoids.ValidateCursiveDebuff(target, args, nil, nil)
|
||||
elseif type(args) == "table" then
|
||||
local spellName = args.name
|
||||
spellName = args.name
|
||||
|
||||
-- Handle multi-comparison negation
|
||||
if args.comparisons and type(args.comparisons) == "table" then
|
||||
|
||||
@@ -100,6 +100,13 @@ local StartAttack = function(msg)
|
||||
CleveRoids.CurrentSpell.autoAttackLock = true
|
||||
CleveRoids.autoAttackLockElapsed = GetTime()
|
||||
AttackTarget()
|
||||
-- FIX: Immediately set autoAttack flag so subsequent macro lines know attack started
|
||||
-- Don't wait for PLAYER_ENTER_COMBAT event which has a delay
|
||||
CleveRoids.CurrentSpell.autoAttack = true
|
||||
-- FIX: Queue icon update so action bars reflect the new state
|
||||
if CleveRoidMacros and CleveRoidMacros.realtime == 0 then
|
||||
CleveRoids.QueueActionUpdate()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -757,14 +757,24 @@ function CleveRoids.TestForActiveAction(actions)
|
||||
local newSequence = nil
|
||||
local firstUnconditional = nil
|
||||
|
||||
-- Only use tooltip-only branch when #showtooltip exists but action list is empty
|
||||
-- (e.g., bare "#showtooltip" with no /cast commands). When explicitTooltip is true,
|
||||
-- the spell was added to actions.list with proper cmd/args, so use the ELSE branch.
|
||||
if actions.tooltip and table.getn(actions.list) == 0 then
|
||||
if CleveRoids.TestAction(actions.cmd or "", actions.args or "") then
|
||||
-- Use tooltip-only branch when:
|
||||
-- 1. #showtooltip has an explicit argument (explicitTooltip = true), OR
|
||||
-- 2. #showtooltip exists but action list is empty (bare "#showtooltip")
|
||||
-- This ensures explicit tooltips always show their icon/usability correctly.
|
||||
if actions.tooltip and (actions.explicitTooltip or table.getn(actions.list) == 0) then
|
||||
-- For explicit tooltips, directly use the tooltip as active action
|
||||
-- (actions.cmd/args are empty, so TestAction would fail)
|
||||
if actions.explicitTooltip then
|
||||
hasActive = true
|
||||
newActiveAction = actions.tooltip
|
||||
-- Resolve nested macro references for #showtooltip propagation
|
||||
elseif CleveRoids.TestAction(actions.cmd or "", actions.args or "") then
|
||||
-- Bare #showtooltip (no arg) - validate with TestAction
|
||||
hasActive = true
|
||||
newActiveAction = actions.tooltip
|
||||
end
|
||||
|
||||
-- Resolve nested macro references for #showtooltip propagation
|
||||
if hasActive and newActiveAction then
|
||||
local macroName = CleveRoids.GetMacroNameFromAction(actions.tooltip.action)
|
||||
if macroName then
|
||||
local resolved = CleveRoids.ResolveNestedMacroActive(actions.tooltip, 0)
|
||||
@@ -795,6 +805,15 @@ function CleveRoids.TestForActiveAction(actions)
|
||||
if action.sequence then
|
||||
newSequence = action.sequence
|
||||
newActiveAction = CleveRoids.GetCurrentSequenceAction(newSequence)
|
||||
-- Check if current step is null/pass/noop - if so, skip this sequence for icon
|
||||
if newActiveAction and newActiveAction.action then
|
||||
local stepLower = string.lower(CleveRoids.Trim(newActiveAction.action))
|
||||
if stepLower == "null" or stepLower == "pass" or stepLower == "noop" then
|
||||
hasActive = false
|
||||
newActiveAction = nil
|
||||
newSequence = nil
|
||||
end
|
||||
end
|
||||
if not newActiveAction then hasActive = false end
|
||||
else
|
||||
newActiveAction = action
|
||||
@@ -1659,6 +1678,7 @@ function CleveRoids.ParseSequence(text)
|
||||
lastUpdate = 0,
|
||||
args = args,
|
||||
list = {},
|
||||
perTargetState = {}, -- [guid] = {index = N, lastUpdate = T} for reset=target
|
||||
}
|
||||
|
||||
-- fill reset rules: seconds or flags (target/combat/alt/ctrl/shift)
|
||||
@@ -2077,14 +2097,41 @@ function CleveRoids.GetCurrentSequenceAction(sequence)
|
||||
end
|
||||
|
||||
function CleveRoids.ResetSequence(sequence)
|
||||
-- Save the GUID before clearing it
|
||||
local targetGuid = sequence.lastTargetGuid
|
||||
|
||||
sequence.index = 1
|
||||
sequence.lastTargetGuid = nil -- Clear stored GUID so next use captures fresh target
|
||||
|
||||
-- Also reset per-target state for current target if tracking
|
||||
if sequence.reset and sequence.reset.target and targetGuid then
|
||||
if sequence.perTargetState then
|
||||
sequence.perTargetState[targetGuid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CleveRoids.AdvanceSequence(sequence)
|
||||
if sequence.index < table.getn(sequence.list) then
|
||||
-- Not at the end yet, just advance normally
|
||||
local oldIndex = sequence.index
|
||||
sequence.index = sequence.index + 1
|
||||
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ff00[Sequence Advanced]|r %d -> %d (of %d)",
|
||||
oldIndex, sequence.index, table.getn(sequence.list)
|
||||
))
|
||||
end
|
||||
|
||||
-- Sync per-target state if tracking by target
|
||||
if sequence.reset and sequence.reset.target and sequence.lastTargetGuid then
|
||||
sequence.perTargetState = sequence.perTargetState or {}
|
||||
sequence.perTargetState[sequence.lastTargetGuid] = {
|
||||
index = sequence.index,
|
||||
lastUpdate = GetTime(),
|
||||
}
|
||||
end
|
||||
else
|
||||
-- At the end of sequence - check if we should auto-reset or stay at last step
|
||||
local hasNonModifierReset = false
|
||||
@@ -2703,6 +2750,13 @@ local function _startAttackAction()
|
||||
CleveRoids.CurrentSpell.autoAttackLock = true
|
||||
CleveRoids.autoAttackLockElapsed = GetTime()
|
||||
AttackTarget()
|
||||
-- FIX: Immediately set autoAttack flag so subsequent macro lines know attack started
|
||||
-- Don't wait for PLAYER_ENTER_COMBAT event which has a delay
|
||||
CleveRoids.CurrentSpell.autoAttack = true
|
||||
-- FIX: Queue icon update so action bars reflect the new state
|
||||
if CleveRoidMacros and CleveRoidMacros.realtime == 0 then
|
||||
CleveRoids.QueueActionUpdate()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3484,6 +3538,28 @@ function CleveRoids.DoCastSequence(sequence)
|
||||
local active = CleveRoids.GetCurrentSequenceAction(sequence)
|
||||
if not (active and active.action) then return end
|
||||
|
||||
-- Debug: Show sequence state
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ffff[Sequence]|r Step %d/%d: '%s'",
|
||||
sequence.index, table.getn(sequence.list), active.action
|
||||
))
|
||||
end
|
||||
|
||||
-- Check for "null" step - does nothing, allows macro to fall through
|
||||
-- Usage: /castsequence reset=target Sunder Armor, null
|
||||
local actionLower = string.lower(CleveRoids.Trim(active.action))
|
||||
if actionLower == "null" or actionLower == "pass" or actionLower == "noop" then
|
||||
-- Update sequence state (so per-target tracking works)
|
||||
sequence.status = 0
|
||||
sequence.lastUpdate = GetTime()
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[Sequence]|r Hit null step, falling through")
|
||||
end
|
||||
-- Don't cast anything, just return - allows macro to continue to next line
|
||||
return
|
||||
end
|
||||
|
||||
sequence.status = 0
|
||||
sequence.lastUpdate = GetTime()
|
||||
sequence.expires = 0
|
||||
@@ -3814,6 +3890,19 @@ function CleveRoids.OnUpdate(self)
|
||||
CleveRoids.ClearSpellNameCaches()
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3938,15 +4027,33 @@ function ActionHasRange(slot)
|
||||
if not slot then return nil end
|
||||
local actions = CleveRoids.GetAction(slot)
|
||||
-- Only override for our macros with #showtooltip
|
||||
if actions and actions.tooltip and actions.active then
|
||||
if actions.active.inRange ~= -1 then
|
||||
return 1 -- Has range check with valid data
|
||||
else
|
||||
-- For channeled spells (inRange == -1), try proxy slot lookup
|
||||
local spellName = actions.active.spell and actions.active.spell.name
|
||||
local proxySlot = spellName and CleveRoids.GetProxyActionSlot(spellName)
|
||||
if proxySlot then
|
||||
return CleveRoids.Hooks.ActionHasRange(proxySlot)
|
||||
if actions and actions.tooltip then
|
||||
-- If we have an active action with valid range data, use it
|
||||
if actions.active then
|
||||
if actions.active.inRange ~= -1 then
|
||||
return 1 -- Has range check with valid data
|
||||
else
|
||||
-- For channeled spells (inRange == -1), try proxy slot lookup
|
||||
local spellName = actions.active.spell and actions.active.spell.name
|
||||
local proxySlot = spellName and CleveRoids.GetProxyActionSlot(spellName)
|
||||
if proxySlot then
|
||||
return CleveRoids.Hooks.ActionHasRange(proxySlot)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- FIX: Even without actions.active, if the macro has potential spell actions,
|
||||
-- return 1 to ensure pfUI calls IsActionInRange (which will do proper checks)
|
||||
-- This fixes Super macros where actions.active might not be populated yet
|
||||
if actions.tooltip.spell or actions.tooltip.type == "spell" then
|
||||
return 1 -- Tooltip is a spell - has range
|
||||
end
|
||||
-- Check if any action in the list is a spell
|
||||
if actions.list then
|
||||
for i = 1, table.getn(actions.list) do
|
||||
local action = actions.list[i]
|
||||
if action and (action.spell or action.type == "spell") then
|
||||
return 1 -- Has at least one spell action
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3959,12 +4066,40 @@ function IsActionInRange(slot, unit)
|
||||
if not slot then return nil end
|
||||
local actions = CleveRoids.GetAction(slot)
|
||||
-- Only override for our macros with #showtooltip
|
||||
if actions and actions.tooltip and actions.active and actions.active.type == "spell" then
|
||||
if actions.active.inRange ~= -1 then
|
||||
return actions.active.inRange
|
||||
else
|
||||
-- For channeled spells (inRange == -1), try proxy slot lookup
|
||||
local spellName = actions.active.spell and actions.active.spell.name
|
||||
if actions and actions.tooltip then
|
||||
-- If we have an active spell action, use its range data
|
||||
if actions.active and actions.active.type == "spell" then
|
||||
if actions.active.inRange ~= -1 then
|
||||
return actions.active.inRange
|
||||
else
|
||||
-- For channeled spells (inRange == -1), try proxy slot lookup
|
||||
local spellName = actions.active.spell and actions.active.spell.name
|
||||
local proxySlot = spellName and CleveRoids.GetProxyActionSlot(spellName)
|
||||
if proxySlot then
|
||||
return CleveRoids.Hooks.IsActionInRange(proxySlot, unit)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- FIX: If no active action but macro has tooltip spell, check its range directly
|
||||
-- This ensures range coloring works even when actions.active hasn't been populated
|
||||
if not actions.active and actions.tooltip.spell then
|
||||
local spell = actions.tooltip.spell
|
||||
local spellName = spell.name
|
||||
if spellName and IsSpellInRange then
|
||||
local targetUnit = unit or "target"
|
||||
if UnitExists(targetUnit) then
|
||||
-- FIX: Wrap in pcall - spell might not be in spellbook (items, other class spells, etc.)
|
||||
local ok, result = pcall(IsSpellInRange, spellName, targetUnit)
|
||||
if ok then
|
||||
if result == 0 then
|
||||
return 0 -- Out of range
|
||||
elseif result == 1 then
|
||||
return 1 -- In range
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Try proxy slot as fallback
|
||||
local proxySlot = spellName and CleveRoids.GetProxyActionSlot(spellName)
|
||||
if proxySlot then
|
||||
return CleveRoids.Hooks.IsActionInRange(proxySlot, unit)
|
||||
@@ -3989,7 +4124,35 @@ function IsUsableAction(slot, unit)
|
||||
-- We have an active action - return its usable state
|
||||
return actions.active.usable, actions.active.oom
|
||||
else
|
||||
-- This is our macro but no action is active (all conditionals failed)
|
||||
-- FIX: If no active action but tooltip has a spell, check its usability directly
|
||||
-- This ensures usability coloring works even when actions.active hasn't been populated
|
||||
if actions.tooltip.spell then
|
||||
local spell = actions.tooltip.spell
|
||||
local spellName = spell.name
|
||||
if spellName and IsSpellUsable then
|
||||
-- FIX: Wrap in pcall - spell might not be in spellbook (items, other class spells, etc.)
|
||||
local ok, usable, notEnoughMana = pcall(IsSpellUsable, spellName)
|
||||
if ok then
|
||||
if usable == 1 then
|
||||
return 1, nil -- Usable
|
||||
elseif notEnoughMana == 1 then
|
||||
return nil, 1 -- Out of mana
|
||||
else
|
||||
return nil, nil -- Not usable (wrong stance, etc.)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Fallback: check if spell has a cost and compare to current mana
|
||||
if spell.cost and spell.cost > 0 then
|
||||
local currentMana = UnitMana("player")
|
||||
if currentMana < spell.cost then
|
||||
return nil, 1 -- Out of mana
|
||||
end
|
||||
end
|
||||
-- Default to usable if we can't determine otherwise
|
||||
return 1, nil
|
||||
end
|
||||
-- No tooltip spell - this macro's conditionals all failed
|
||||
-- Return nil to make the icon dark
|
||||
return nil, nil
|
||||
end
|
||||
@@ -4605,45 +4768,40 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time
|
||||
if target and lib and lib.objects then
|
||||
local normalizedTarget = CleveRoids.NormalizeGUID(target)
|
||||
if normalizedTarget and lib.objects[normalizedTarget] then
|
||||
-- Refresh all active Judgements on the target (pfUI-style: by name, not just ID)
|
||||
-- Refresh all active Judgements on the target
|
||||
for spellID, rec in pairs(lib.objects[normalizedTarget]) do
|
||||
if rec.start and rec.duration then
|
||||
local spellName = SpellInfo(spellID)
|
||||
if spellName then
|
||||
-- Remove rank to get base name
|
||||
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
|
||||
-- Check if this is a judgement by spell ID
|
||||
if lib.judgementSpells and lib.judgementSpells[spellID] and rec.start and rec.duration then
|
||||
-- Only refresh if the Judgement is still active and was cast by player
|
||||
local remaining = rec.duration + rec.start - GetTime()
|
||||
if remaining > 0 and rec.caster == "player" then
|
||||
-- Refresh the Judgement by updating the start time
|
||||
rec.start = GetTime()
|
||||
|
||||
-- Check if this is a judgement by name (pfUI approach)
|
||||
if lib.judgementNames and lib.judgementNames[baseName] then
|
||||
-- Only refresh if the Judgement is still active and was cast by player
|
||||
local remaining = rec.duration + rec.start - GetTime()
|
||||
if remaining > 0 and rec.caster == "player" then
|
||||
-- Refresh the Judgement by updating the start time
|
||||
rec.start = GetTime()
|
||||
local spellName = SpellInfo(spellID)
|
||||
local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "") or "Unknown"
|
||||
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on %s hit - new duration: %ds",
|
||||
baseName, spellID, action, rec.duration)
|
||||
)
|
||||
end
|
||||
|
||||
-- Also sync to pfUI if it's loaded
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target")
|
||||
local targetLevel = UnitLevel("target") or 0
|
||||
|
||||
if targetName then
|
||||
-- Refresh in pfUI's tracking (by name)
|
||||
pfUI.api.libdebuff:AddEffect(targetName, targetLevel, baseName, rec.duration, "player")
|
||||
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on %s hit - new duration: %ds",
|
||||
baseName, spellID, action, rec.duration)
|
||||
string.format("|cff00ffaa[pfUI Judgement Refresh]|r Synced %s refresh to pfUI", baseName)
|
||||
)
|
||||
end
|
||||
|
||||
-- Also sync to pfUI if it's loaded
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target")
|
||||
local targetLevel = UnitLevel("target") or 0
|
||||
|
||||
if targetName then
|
||||
-- Refresh in pfUI's tracking (by name)
|
||||
pfUI.api.libdebuff:AddEffect(targetName, targetLevel, baseName, rec.duration, "player")
|
||||
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
string.format("|cff00ffaa[pfUI Judgement Refresh]|r Synced %s refresh to pfUI", baseName)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4983,19 +5141,43 @@ function CleveRoids.Frame:PLAYER_TARGET_CHANGED()
|
||||
-- Clear resist state when target changes
|
||||
CleveRoids.ClearResistState()
|
||||
|
||||
-- Reset any sequence with reset=target that has progressed past the first step
|
||||
-- Only reset if the target GUID actually changed (not just re-targeting same mob)
|
||||
-- Handle sequences with reset=target - save/restore per-target state
|
||||
-- Instead of resetting, we remember each target's progress in the sequence
|
||||
local currentGuid = nil
|
||||
if UnitExists("target") then
|
||||
local _, guid = UnitExists("target")
|
||||
currentGuid = guid
|
||||
end
|
||||
|
||||
for _, sequence in pairs(CleveRoids.Sequences) do
|
||||
if sequence.index > 1 and sequence.reset and sequence.reset.target then
|
||||
-- Compare with stored GUID - reset only if different
|
||||
-- If no target now, currentGuid is nil, which differs from stored GUID
|
||||
if sequence.lastTargetGuid ~= currentGuid then
|
||||
CleveRoids.ResetSequence(sequence)
|
||||
if sequence.reset and sequence.reset.target then
|
||||
local oldGuid = sequence.lastTargetGuid
|
||||
|
||||
-- Only process if target actually changed
|
||||
if oldGuid ~= currentGuid then
|
||||
-- Save current state for the old target (if we had one and progressed)
|
||||
if oldGuid and sequence.index > 1 then
|
||||
sequence.perTargetState = sequence.perTargetState or {}
|
||||
sequence.perTargetState[oldGuid] = {
|
||||
index = sequence.index,
|
||||
lastUpdate = sequence.lastUpdate,
|
||||
}
|
||||
end
|
||||
|
||||
-- Restore state for the new target (or start fresh)
|
||||
if currentGuid and sequence.perTargetState and sequence.perTargetState[currentGuid] then
|
||||
-- Restore saved state for this target
|
||||
local saved = sequence.perTargetState[currentGuid]
|
||||
sequence.index = saved.index
|
||||
sequence.lastUpdate = saved.lastUpdate
|
||||
else
|
||||
-- New target we haven't seen - start at step 1
|
||||
sequence.index = 1
|
||||
sequence.status = 0
|
||||
end
|
||||
|
||||
-- Update the current target GUID
|
||||
sequence.lastTargetGuid = currentGuid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+73
-1
@@ -44,11 +44,21 @@
|
||||
- Detailed hit info: damage, crit/glancing/crushing, dodge/parry/block
|
||||
- Requires NP_EnableAutoAttackEvents=1 CVar to enable
|
||||
|
||||
Spell Start/Go Events (v2.25+):
|
||||
- SPELL_START_SELF / SPELL_START_OTHER - Server notifies cast time spell begun
|
||||
- SPELL_GO_SELF / SPELL_GO_OTHER - Server notifies spell completed casting
|
||||
- Requires NP_EnableSpellStartEvents=1 and NP_EnableSpellGoEvents=1 CVars
|
||||
|
||||
Spell Heal/Energize Events (v2.26+):
|
||||
- SPELL_HEAL_BY_SELF / SPELL_HEAL_BY_OTHER / SPELL_HEAL_ON_SELF
|
||||
- SPELL_ENERGIZE_BY_SELF / SPELL_ENERGIZE_BY_OTHER / SPELL_ENERGIZE_ON_SELF
|
||||
- Requires NP_EnableSpellHealEvents=1 and NP_EnableSpellEnergizeEvents=1 CVars
|
||||
|
||||
Settings Integration:
|
||||
- Reads from NampowerSettings addon when available
|
||||
- Falls back to CVars when addon not present
|
||||
|
||||
Current version: v2.24.0
|
||||
Current version: v2.26.0
|
||||
]]
|
||||
|
||||
local _G = _G or getfenv(0)
|
||||
@@ -171,6 +181,14 @@ API.VERSION_REQUIREMENTS = {
|
||||
|
||||
-- v2.24+ - Auto-attack events
|
||||
["AutoAttackEvents"] = { 2, 24, 0 }, -- AUTO_ATTACK_SELF/OTHER events
|
||||
|
||||
-- v2.25+ - Spell start/go events (improved granularity over UNIT_CASTEVENT)
|
||||
["SpellStartEvents"] = { 2, 25, 0 }, -- SPELL_START_SELF/OTHER events
|
||||
["SpellGoEvents"] = { 2, 25, 0 }, -- SPELL_GO_SELF/OTHER events
|
||||
|
||||
-- v2.26+ - Spell heal/energize events
|
||||
["SpellHealEvents"] = { 2, 26, 0 }, -- SPELL_HEAL_BY_SELF/OTHER/ON_SELF events
|
||||
["SpellEnergizeEvents"] = { 2, 26, 0 }, -- SPELL_ENERGIZE_BY_SELF/OTHER/ON_SELF events
|
||||
}
|
||||
|
||||
-- Check if a specific feature is available
|
||||
@@ -277,6 +295,14 @@ local function InitializeFeatures()
|
||||
-- v2.24+ Auto-attack events
|
||||
f.hasAutoAttackEvents = API.HasFeature("AutoAttackEvents")
|
||||
|
||||
-- v2.25+ Spell start/go events
|
||||
f.hasSpellStartEvents = API.HasFeature("SpellStartEvents")
|
||||
f.hasSpellGoEvents = API.HasFeature("SpellGoEvents")
|
||||
|
||||
-- v2.26+ Spell heal/energize events
|
||||
f.hasSpellHealEvents = API.HasFeature("SpellHealEvents")
|
||||
f.hasSpellEnergizeEvents = API.HasFeature("SpellEnergizeEvents")
|
||||
|
||||
-- Runtime detection for enhanced spell functions (verify by testing)
|
||||
if f.hasEnhancedSpellFunctions and GetSpellTexture then
|
||||
local success, result = pcall(function()
|
||||
@@ -336,6 +362,12 @@ API.defaultSettings = {
|
||||
NP_EnableAuraCastEvents = "0", -- Enable AURA_CAST_ON_SELF/OTHER events
|
||||
-- v2.24+ CVars
|
||||
NP_EnableAutoAttackEvents = "0", -- Enable AUTO_ATTACK_SELF/OTHER events
|
||||
-- v2.25+ CVars
|
||||
NP_EnableSpellStartEvents = "0", -- Enable SPELL_START_SELF/OTHER events
|
||||
NP_EnableSpellGoEvents = "0", -- Enable SPELL_GO_SELF/OTHER events
|
||||
-- v2.26+ CVars
|
||||
NP_EnableSpellHealEvents = "0", -- Enable SPELL_HEAL_BY_SELF/OTHER/ON_SELF events
|
||||
NP_EnableSpellEnergizeEvents = "0", -- Enable SPELL_ENERGIZE_BY_SELF/OTHER/ON_SELF events
|
||||
}
|
||||
|
||||
-- Get a Nampower setting value
|
||||
@@ -1922,6 +1954,46 @@ API.VICTIMSTATE = {
|
||||
DEFLECTS = 8,
|
||||
}
|
||||
|
||||
-- Spell start/go event names (v2.25+)
|
||||
-- These provide improved granularity over UNIT_CASTEVENT for spell cast tracking
|
||||
-- SPELL_START: Server notifies a spell with cast time has begun
|
||||
-- SPELL_GO: Server notifies a spell has completed casting (projectile launched, instant landed)
|
||||
API.SPELL_START_EVENTS = {
|
||||
"SPELL_START_SELF", -- Player starts casting (requires NP_EnableSpellStartEvents=1)
|
||||
"SPELL_START_OTHER", -- Other unit starts casting
|
||||
}
|
||||
|
||||
API.SPELL_GO_EVENTS = {
|
||||
"SPELL_GO_SELF", -- Player's spell completed/fired (requires NP_EnableSpellGoEvents=1)
|
||||
"SPELL_GO_OTHER", -- Other unit's spell completed/fired
|
||||
}
|
||||
|
||||
-- Spell heal event names (v2.26+, requires NP_EnableSpellHealEvents=1)
|
||||
-- Parameters: targetGuid, casterGuid, spellId, amount, isCritical, isPeriodic
|
||||
API.SPELL_HEAL_EVENTS = {
|
||||
"SPELL_HEAL_BY_SELF", -- Player healed a target
|
||||
"SPELL_HEAL_BY_OTHER", -- Other unit healed a target
|
||||
"SPELL_HEAL_ON_SELF", -- Player was healed
|
||||
}
|
||||
|
||||
-- Spell energize event names (v2.26+, requires NP_EnableSpellEnergizeEvents=1)
|
||||
-- Parameters: targetGuid, casterGuid, spellId, powerType, amount, isPeriodic
|
||||
-- powerType: 0=mana, 1=rage, 2=focus, 3=energy, 4=happiness
|
||||
API.SPELL_ENERGIZE_EVENTS = {
|
||||
"SPELL_ENERGIZE_BY_SELF", -- Player energized a target
|
||||
"SPELL_ENERGIZE_BY_OTHER", -- Other unit energized a target
|
||||
"SPELL_ENERGIZE_ON_SELF", -- Player was energized
|
||||
}
|
||||
|
||||
-- Power type constants (for SPELL_ENERGIZE events)
|
||||
API.POWER_TYPE = {
|
||||
MANA = 0,
|
||||
RAGE = 1,
|
||||
FOCUS = 2,
|
||||
ENERGY = 3,
|
||||
HAPPINESS = 4,
|
||||
}
|
||||
|
||||
-- Unit events (v2.20+)
|
||||
API.UNIT_EVENTS = {
|
||||
"UNIT_DIED", -- Parameters: guid
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Author: DWG, _brain, Torio, yani9o, Avitasia
|
||||
## Title: [Super]CleveRoidMacros
|
||||
## Notes: /cleveroid for settings
|
||||
## Version: 1.8
|
||||
## Version: 1.9
|
||||
## OptionalDeps: ClassicFocus, FocusFrame, pfUI, SuperMacro, Bongos_ActionBar, Cursive, XPerl, LunaUnitFrames, DragonflightReloaded, -Dragonflight3
|
||||
## SavedVariables: CleveRoidMacros, CleveRoids_LearnedDurations, CleveRoids_AuraTextures, CleveRoids_ImmunityData, CleveRoids_ComboDurations, CleveRoids_SpellSchools
|
||||
Localization.lua
|
||||
|
||||
+1494
-259
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user