mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
+12
-2
@@ -467,8 +467,18 @@ function CleveRoids.TrackComboPointCastByID(spellID, targetGUID)
|
||||
|
||||
-- If combo points are 0, try multiple fallback sources
|
||||
if comboPoints == 0 then
|
||||
-- First, try lastComboPoints (don't reset immediately - let it persist)
|
||||
if CleveRoids.lastComboPoints > 0 then
|
||||
-- PRIMARY: Check SPELL_CAST_EVENT capture (most reliable - client-side pre-server)
|
||||
local pending = CleveRoids.pendingCasts and CleveRoids.pendingCasts[spellID]
|
||||
if pending and pending.comboPoints and pending.comboPoints > 0 then
|
||||
comboPoints = pending.comboPoints
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
string.format("|cff00ff88[TrackComboByID]|r Using SPELL_CAST_EVENT CP: %d for spell ID %d",
|
||||
comboPoints, spellID)
|
||||
)
|
||||
end
|
||||
-- Fallback: lastComboPoints (don't reset immediately - let it persist)
|
||||
elseif CleveRoids.lastComboPoints > 0 then
|
||||
comboPoints = CleveRoids.lastComboPoints
|
||||
-- Don't reset here - let it persist for multiple events
|
||||
if CleveRoids.debug then
|
||||
|
||||
+15
-8
@@ -126,9 +126,14 @@ function Extension.HookPfUILibdebuff()
|
||||
-- If so, pfUI handles combo durations and Carnage internally - we only override on mismatch
|
||||
local hasPfUI76 = CleveRoids.hasPfUI76 or (pfUI.libdebuff_slot_ownership ~= nil)
|
||||
|
||||
-- pfUI 7.6+ handles all durations internally - no hooks needed
|
||||
if hasPfUI76 then
|
||||
Extension.DLOG("Skipped all libdebuff hooks (pfUI 7.6+ handles internally)")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Hook GetDuration if it exists
|
||||
-- pfUI's GetDuration signature: function(effect, rank) where effect is spell NAME
|
||||
-- pfUI 7.6+ already handles combo durations via GetStoredComboPoints(), so only override on mismatch
|
||||
if pflib.GetDuration and not Extension.pfLibDebuffHooked then
|
||||
local originalGetDuration = pflib.GetDuration
|
||||
|
||||
@@ -171,10 +176,8 @@ function Extension.HookPfUILibdebuff()
|
||||
Extension.DLOG("Hooked pfUI.api.libdebuff.GetDuration (mismatch-only mode)")
|
||||
end
|
||||
|
||||
-- Hook AddEffect if it exists
|
||||
-- NOTE: In pfUI 7.6+, AddEffect is LEGACY (rarely called - events handle tracking)
|
||||
-- We only hook for pre-7.6 compatibility and rank checking fallback
|
||||
if pflib.AddEffect and not Extension.pfLibAddEffectHooked and not hasPfUI76 then
|
||||
-- Hook AddEffect if it exists (pre-7.6 only - 7.6+ returns early above)
|
||||
if pflib.AddEffect and not Extension.pfLibAddEffectHooked then
|
||||
local originalAddEffect = pflib.AddEffect
|
||||
|
||||
pflib.AddEffect = function(self, unit, unitlevel, effect, duration, caster)
|
||||
@@ -258,9 +261,7 @@ function Extension.HookPfUILibdebuff()
|
||||
end
|
||||
|
||||
Extension.pfLibAddEffectHooked = true
|
||||
Extension.DLOG("Hooked pfUI.api.libdebuff.AddEffect (pre-7.6 legacy mode)")
|
||||
elseif hasPfUI76 then
|
||||
Extension.DLOG("Skipped AddEffect hook (pfUI 7.6+ handles internally)")
|
||||
Extension.DLOG("Hooked pfUI.api.libdebuff.AddEffect")
|
||||
end
|
||||
|
||||
-- Hook UnitDebuff to return Carnage override duration to display code
|
||||
@@ -343,11 +344,17 @@ function Extension.HookPfUILibdebuff()
|
||||
end
|
||||
|
||||
-- Synchronize combo durations to pfUI's libdebuff objects
|
||||
-- NOTE: pfUI 7.6+ handles combo durations internally - skip sync entirely
|
||||
function Extension.SyncComboDurationToPfUI(guid, spellID, duration)
|
||||
if not pfUI or not pfUI.api or not pfUI.api.libdebuff then
|
||||
return
|
||||
end
|
||||
|
||||
-- pfUI 7.6+ handles all durations internally
|
||||
if CleveRoids.hasPfUI76 or (pfUI.libdebuff_slot_ownership ~= nil) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Get unit name from GUID
|
||||
local unitName = nil
|
||||
local unitLevel = 0
|
||||
|
||||
+93
-26
@@ -835,6 +835,23 @@ function CleveRoids.FindAllCasterAuraByName(targetGuid, searchName)
|
||||
if not targetData then return nil, nil end
|
||||
|
||||
local now = GetTime()
|
||||
|
||||
-- Direct spell ID lookup: O(1) hash table access instead of iterating all entries
|
||||
local searchID = tonumber(searchName)
|
||||
if searchID then
|
||||
local auraData = targetData[searchID]
|
||||
if auraData and auraData.start and auraData.duration then
|
||||
local remaining = auraData.duration + auraData.start - now
|
||||
if remaining > 0 then
|
||||
return remaining, auraData.casterGuid
|
||||
else
|
||||
targetData[searchID] = nil
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- Name-based search (existing behavior)
|
||||
local searchLower = string.lower(searchName)
|
||||
|
||||
for spellId, auraData in pairs(targetData) do
|
||||
@@ -867,10 +884,16 @@ local HITINFO_GLANCING = 16384 -- 0x4000
|
||||
local HITINFO_CRUSHING = 32768 -- 0x8000
|
||||
local HITINFO_LEFTSWING = 4 -- 0x4 (Off-hand attack)
|
||||
|
||||
-- VictimState values
|
||||
-- VictimState values (from AUTO_ATTACK / SPELL_GO events)
|
||||
local VICTIMSTATE_UNAFFECTED = 0 -- Generic miss (seen with HITINFO_MISS)
|
||||
local VICTIMSTATE_NORMAL = 1 -- Hit landed
|
||||
local VICTIMSTATE_DODGE = 2
|
||||
local VICTIMSTATE_PARRY = 3
|
||||
local VICTIMSTATE_INTERRUPT = 4
|
||||
local VICTIMSTATE_BLOCKS = 5
|
||||
local VICTIMSTATE_EVADES = 6
|
||||
local VICTIMSTATE_IS_IMMUNE = 7
|
||||
local VICTIMSTATE_DEFLECTS = 8
|
||||
|
||||
-- Aura cap status bitfield
|
||||
local AURA_CAP_BUFF_FULL = 1
|
||||
@@ -2991,7 +3014,9 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
|
||||
end
|
||||
|
||||
-- PERFORMANCE: Cache lowercased search name to avoid repeated string.lower calls
|
||||
local searchName = args.name and _string_lower(args.name)
|
||||
-- Support spell ID matching: [mybuff:17941] matches by spell ID instead of name
|
||||
local searchID = args.name and tonumber(args.name)
|
||||
local searchName = not searchID and args.name and _string_lower(args.name) or nil
|
||||
|
||||
-- Primary search: BUFFS if isbuff==true, DEBUFFS if isbuff==false
|
||||
while true do
|
||||
@@ -3014,12 +3039,20 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
|
||||
|
||||
if not texture then break end
|
||||
|
||||
if current_spellID and searchName then
|
||||
-- PERFORMANCE: Use cached lowercase spell name lookup
|
||||
local lowerName = GetLowercaseSpellName(current_spellID)
|
||||
if lowerName and lowerName == searchName then
|
||||
found = true
|
||||
break
|
||||
if current_spellID then
|
||||
if searchID then
|
||||
-- Spell ID matching: [mybuff:17941]
|
||||
if current_spellID == searchID then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
elseif searchName then
|
||||
-- PERFORMANCE: Use cached lowercase spell name lookup
|
||||
local lowerName = GetLowercaseSpellName(current_spellID)
|
||||
if lowerName and lowerName == searchName then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3027,7 +3060,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
|
||||
end
|
||||
|
||||
-- Overflow handling: when searching DEBUFFS on non-players, also scan BUFFS
|
||||
if not isbuff and not isPlayer and not found and searchName then
|
||||
if not isbuff and not isPlayer and not found and (searchID or searchName) then
|
||||
i = 1
|
||||
while true do
|
||||
local texture
|
||||
@@ -3038,11 +3071,18 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
|
||||
if not texture then break end
|
||||
|
||||
if current_spellID then
|
||||
-- PERFORMANCE: Use cached lowercase spell name lookup
|
||||
local lowerName = GetLowercaseSpellName(current_spellID)
|
||||
if lowerName and lowerName == searchName then
|
||||
found = true
|
||||
break
|
||||
if searchID then
|
||||
if current_spellID == searchID then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
elseif searchName then
|
||||
-- PERFORMANCE: Use cached lowercase spell name lookup
|
||||
local lowerName = GetLowercaseSpellName(current_spellID)
|
||||
if lowerName and lowerName == searchName then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3148,12 +3188,15 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
|
||||
-- Convert underscores to spaces for matching (e.g., "Thunder_Clap" -> "Thunder Clap")
|
||||
args.name = string.gsub(args.name, "_", " ")
|
||||
|
||||
-- Support spell ID matching: [debuff:9904] matches by spell ID instead of name
|
||||
local searchID = tonumber(args.name)
|
||||
|
||||
local found = false
|
||||
local texture, stacks, spellID, remaining
|
||||
local i
|
||||
|
||||
-- PERFORMANCE: For non-SuperWoW, early return if no texture registered
|
||||
if not CleveRoids.hasSuperwow and not CleveRoids.auraTextures[args.name] then
|
||||
if not searchID and not CleveRoids.hasSuperwow and not CleveRoids.auraTextures[args.name] then
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -3170,7 +3213,13 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
|
||||
if not guid then return false end
|
||||
|
||||
-- PERFORMANCE: Use cached spell name -> ID mapping instead of iterating every call
|
||||
local matchingSpellIDs = GetSpellIDsForName(args.name) or {}
|
||||
-- For spell ID input, use the ID directly instead of name->ID lookup
|
||||
local matchingSpellIDs
|
||||
if searchID then
|
||||
matchingSpellIDs = {searchID}
|
||||
else
|
||||
matchingSpellIDs = GetSpellIDsForName(args.name) or {}
|
||||
end
|
||||
|
||||
-- Check tracking table for ANY rank of this spell: Did player cast this? Is timer valid?
|
||||
if matchingSpellIDs and table.getn(matchingSpellIDs) > 0 then
|
||||
@@ -3266,9 +3315,14 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
|
||||
if not tex then break end
|
||||
|
||||
if debuffSpellID then
|
||||
-- PERFORMANCE: Use cached spell name lookup
|
||||
local baseName, fullName = GetSpellNames(debuffSpellID)
|
||||
if baseName and (baseName == args.name or fullName == args.name) then
|
||||
local matched = false
|
||||
if searchID then
|
||||
matched = (debuffSpellID == searchID)
|
||||
else
|
||||
local baseName, fullName = GetSpellNames(debuffSpellID)
|
||||
matched = baseName and (baseName == args.name or fullName == args.name)
|
||||
end
|
||||
if matched then
|
||||
-- IMPORTANT: Only use fallback for SHARED debuffs
|
||||
-- Personal debuffs must come from tracking table (caster check)
|
||||
local isShared = lib and lib.IsPersonalDebuff and lib:IsPersonalDebuff(debuffSpellID) == false
|
||||
@@ -3292,9 +3346,14 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
|
||||
if not tex then break end
|
||||
|
||||
if buffSpellID then
|
||||
-- PERFORMANCE: Use cached spell name lookup
|
||||
local baseName, fullName = GetSpellNames(buffSpellID)
|
||||
if baseName and (baseName == args.name or fullName == args.name) then
|
||||
local matched = false
|
||||
if searchID then
|
||||
matched = (buffSpellID == searchID)
|
||||
else
|
||||
local baseName, fullName = GetSpellNames(buffSpellID)
|
||||
matched = baseName and (baseName == args.name or fullName == args.name)
|
||||
end
|
||||
if matched then
|
||||
-- IMPORTANT: Only use fallback for SHARED debuffs
|
||||
local isShared = lib and lib.IsPersonalDebuff and lib:IsPersonalDebuff(buffSpellID) == false
|
||||
if isShared then
|
||||
@@ -3318,8 +3377,12 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
|
||||
texture, stacks, spellID, remaining = CleveRoids.GetPlayerAura(i, false)
|
||||
if not texture then break end
|
||||
|
||||
if CleveRoids.hasSuperwow then
|
||||
-- PERFORMANCE: Use cached spell name lookup
|
||||
if searchID then
|
||||
if spellID == searchID then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
elseif CleveRoids.hasSuperwow then
|
||||
local baseName, fullName = GetSpellNames(spellID)
|
||||
if baseName and (baseName == args.name or fullName == args.name) then
|
||||
found = true
|
||||
@@ -3339,8 +3402,12 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
|
||||
texture, stacks, spellID, remaining = CleveRoids.GetPlayerAura(i, true)
|
||||
if not texture then break end
|
||||
|
||||
if CleveRoids.hasSuperwow then
|
||||
-- PERFORMANCE: Use cached spell name lookup
|
||||
if searchID then
|
||||
if spellID == searchID then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
elseif CleveRoids.hasSuperwow then
|
||||
local baseName, fullName = GetSpellNames(spellID)
|
||||
if baseName and (baseName == args.name or fullName == args.name) then
|
||||
found = true
|
||||
|
||||
@@ -4894,13 +4894,12 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time
|
||||
)
|
||||
end
|
||||
|
||||
-- Also sync to pfUI if it's loaded
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
-- Also sync to pfUI if it's loaded (pre-7.6 only)
|
||||
if not CleveRoids.hasPfUI76 and 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
|
||||
@@ -5533,6 +5532,8 @@ function CleveRoids.Frame:SPELL_CAST_EVENT()
|
||||
if event == "SPELL_CAST_EVENT" then
|
||||
local success = arg1
|
||||
local spellId = arg2
|
||||
local castType = arg3
|
||||
local targetGuid = arg4
|
||||
|
||||
-- BUGFIX: Update casting state on spell cast events (for [casting] conditional)
|
||||
if CleveRoids.UpdateCastingState then
|
||||
@@ -5542,6 +5543,8 @@ function CleveRoids.Frame:SPELL_CAST_EVENT()
|
||||
if success == 1 then
|
||||
CleveRoids.lastCastSpell = {
|
||||
spellId = spellId,
|
||||
castType = castType,
|
||||
targetGuid = targetGuid,
|
||||
timestamp = GetTime()
|
||||
}
|
||||
if SpellInfo then
|
||||
@@ -5550,6 +5553,47 @@ function CleveRoids.Frame:SPELL_CAST_EVENT()
|
||||
CleveRoids.lastCastSpell.spellName = name
|
||||
end
|
||||
end
|
||||
|
||||
-- Track pending cast for SPELL_GO correlation (reactive ability detection)
|
||||
-- Keyed by spellId so concurrent casts don't overwrite each other
|
||||
CleveRoids.pendingCasts = CleveRoids.pendingCasts or {}
|
||||
|
||||
-- Clean up consumed entries older than 5 seconds (lightweight, runs per-cast)
|
||||
local cleanupTime = GetTime() - 5
|
||||
for id, entry in pairs(CleveRoids.pendingCasts) do
|
||||
if entry.consumed and entry.consumedAt and entry.consumedAt < cleanupTime then
|
||||
CleveRoids.pendingCasts[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
CleveRoids.pendingCasts[spellId] = {
|
||||
castType = castType,
|
||||
targetGuid = targetGuid,
|
||||
timestamp = GetTime(),
|
||||
comboPoints = nil,
|
||||
}
|
||||
|
||||
-- Capture combo points NOW (before server consumes them)
|
||||
-- SPELL_CAST_EVENT fires client-side, so CP are guaranteed available
|
||||
if (CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellId)) or
|
||||
(CleveRoids.FerociousBiteSpellIDs and CleveRoids.FerociousBiteSpellIDs[spellId]) then
|
||||
local cp = CleveRoids.GetComboPoints and CleveRoids.GetComboPoints() or 0
|
||||
if cp > 0 then
|
||||
CleveRoids.pendingCasts[spellId].comboPoints = cp
|
||||
if CleveRoids.debug then
|
||||
local castSpellName = SpellInfo and SpellInfo(spellId) or "Unknown"
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
string.format("|cff00ff88[SPELL_CAST_EVENT]|r Captured %d CP for %s (ID:%d)",
|
||||
cp, castSpellName, spellId)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Cast failed - clear pending entry for this spell
|
||||
if CleveRoids.pendingCasts then
|
||||
CleveRoids.pendingCasts[spellId] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,14 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna
|
||||
1. Download and extract to `Interface/AddOns/SuperCleveRoidMacros`
|
||||
2. **Recommended pfUI:** Use [jrc13245/pfUI](https://github.com/jrc13245/pfUI) for full compatibility with macro spell scanning and action bar features
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Unique macro names required (no blanks, duplicates, or spell names)
|
||||
- Reactive abilities must be on action bars for detection
|
||||
- Debuff time-left conditionals only work on own debuffs unless pfUI libdebuff or Cursive has data
|
||||
- HealComm requires MarcelineVQ's [LunaUnitFrames](https://github.com/MarcelineVQ/LunaUnitFrames) for SuperWoW compatibility
|
||||
- Macro line length: 261 characters max (MacroLengthWarn extension prevents crashes)
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
@@ -24,7 +32,6 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna
|
||||
#showtooltip
|
||||
/cast [mod:alt] Frostbolt; [mod:ctrl] Fire Blast; Blink
|
||||
```
|
||||
|
||||
- **Conditionals** in `[]` brackets, space or comma separated
|
||||
- **Arguments** use colon: `[mod:alt]`, `[hp:>50]`
|
||||
- **Negation** with `no` prefix: `[nobuff]`, `[nomod:alt]`
|
||||
@@ -32,17 +39,27 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna
|
||||
- **Spell names** with spaces: `"Mark of the Wild"` or `Mark_of_the_Wild`
|
||||
|
||||
### Multi-Value Logic
|
||||
|
||||
| Syntax | Logic | Example | Meaning |
|
||||
|--------|-------|---------|---------|
|
||||
| `[buff:X/Y]` | OR | `[buff:Renew/Rejuvenation]` | Has Renew **or** Rejuvenation |
|
||||
| `[buff:X&Y]` | AND | `[buff:Fortitude&Mark_of_the_Wild]` | Has Fort **and** MotW |
|
||||
| `[nobuff:X/Y]` | NOR | `[nobuff:Renew/Rejuvenation]` | Missing **both** Renew **and** Rejuvenation |
|
||||
| `[nobuff:X&Y]` | NAND | `[nobuff:Fortitude&Mark_of_the_Wild]` | Missing **at least one** of Fort or MotW |
|
||||
|
||||
> **De Morgan's Law:** Negation flips the operator. `no` + `/`(OR) → AND (must lack all). `no` + `&`(AND) → OR (must lack at least one).
|
||||
|
||||
**Multiple instances = AND between groups:**
|
||||
```lua
|
||||
[buff:X/Y] -- X OR Y (has either)
|
||||
[buff:X&Y] -- X AND Y (has both)
|
||||
[nobuff:X/Y] -- NOT X AND NOT Y (missing both) - operators flip for negation
|
||||
[mybuff:X/Y mybuff:Z] -- (has X OR Y) AND (has Z)
|
||||
```
|
||||
|
||||
### Comparisons
|
||||
```lua
|
||||
[hp:>50] -- Health above 50%
|
||||
[buff:"Name"<5] -- Less than 5 seconds remaining
|
||||
[debuff:"Name">#3] -- 3+ stacks (use ># for stacks)
|
||||
[hp:>50] -- Health above 50%
|
||||
[hp:>20&<50] -- Health between 20% and 50%
|
||||
[buff:"Name"<5] -- Less than 5 seconds remaining
|
||||
[debuff:"Name">#3] -- 3+ stacks (use # for stacks)
|
||||
```
|
||||
|
||||
### Special Prefixes
|
||||
@@ -54,112 +71,213 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna
|
||||
|
||||
---
|
||||
|
||||
## Slash Commands
|
||||
|
||||
| Command | Cond | Example | Description |
|
||||
|---------|:----:|---------|-------------|
|
||||
| `/cast` | ✅ | `/cast [mod:alt] Frostbolt` | Cast spell |
|
||||
| `/use` | ✅ | `/use [hp:<30] Healing Potion` | Use item by name/ID/slot |
|
||||
| `/equip` | ✅ | `/equip [nocombat] Fishing Pole` | Equip item |
|
||||
| `/target` | ✅ | `/target [harm,nodead]` | Target with UnitXP 3D scanning |
|
||||
| `/castsequence` | ✅ | `/castsequence reset=3/target Spell1, Spell2` | Cast spells in sequence |
|
||||
| `/castpet` | ✅ | `/castpet [combat] Claw` | Cast pet spell |
|
||||
| `/startattack` | ✅ | `/startattack [harm]` | Start auto-attack |
|
||||
| `/stopattack` | ✅ | `/stopattack [noothertag]` | Stop auto-attack |
|
||||
| `/stopcasting` | ✅ | `/stopcasting [mycc:silence]` | Stop casting |
|
||||
| `/unqueue` | ✅ | `/unqueue [mod:shift]` | Cancel queued spell (Nampower) |
|
||||
| `/cleartarget` | ✅ | `/cleartarget [dead]` | Clear target |
|
||||
| `/cancelaura` | ✅ | `/cancelaura [combat] Ice Block` | Cancel buff (alias: `/unbuff`) |
|
||||
| `/unshift` | ✅ | `/unshift [myhp:<30]` | Cancel shapeshift form |
|
||||
| `/stopmacro` | ✅ | `/stopmacro [nocombat]` | Stop ALL macro execution |
|
||||
| `/skipmacro` | ✅ | `/skipmacro [nogroup]` | Stop current submacro only |
|
||||
| `/firstaction` | ✅ | `/firstaction [group]` | Priority: stop on first successful `/cast` |
|
||||
| `/nofirstaction` | ✅ | `/nofirstaction` | Re-enable multi-queue after `/firstaction` |
|
||||
| `/quickheal` | ✅ | `/quickheal [combat]` | Smart heal (alias: `/qh`, requires QuickHeal) |
|
||||
| `/petattack` | ✅ | `/petattack [harm]` | Pet attack |
|
||||
| `/petfollow` | ✅ | `/petfollow [nocombat]` | Pet follow |
|
||||
| `/petwait` | ✅ | `/petwait [mod:ctrl]` | Pet stay |
|
||||
| `/petpassive` | ✅ | `/petpassive` | Pet passive mode |
|
||||
| `/petdefensive` | ✅ | `/petdefensive` | Pet defensive mode |
|
||||
| `/petaggressive` | ✅ | `/petaggressive [group]` | Pet aggressive mode |
|
||||
| `/equipmh` | ✅ | `/equipmh [nocombat] Dagger` | Equip to main hand |
|
||||
| `/equipoh` | ✅ | `/equipoh [nocombat] Dagger` | Equip to off hand |
|
||||
| `/equip11`-`14` | ✅ | `/equip13 [combat] Trinket` | Equip to ring/trinket slot |
|
||||
| `/applymain` | ✅ | `/applymain [nomhimbue] Instant Poison` | Apply poison/oil to main hand |
|
||||
| `/applyoff` | ✅ | `/applyoff [noohimbue] Crippling Poison` | Apply poison/oil to off hand |
|
||||
| `/retarget` | — | `/retarget` | Clear invalid target, target nearest enemy |
|
||||
| `/runmacro` | — | `/runmacro MyMacro` | Execute macro by name |
|
||||
| `/rl` | — | `/rl` | Reload UI |
|
||||
| `/clearequipqueue` | — | `/clearequipqueue` | Clear equipment swap queue |
|
||||
| `/equipqueuestatus` | — | `/equipqueuestatus` | Show equipment queue status |
|
||||
|
||||
### Priority-Based Macro Evaluation
|
||||
|
||||
By default, all `/cast` lines evaluate and may queue spells with Nampower. `/firstaction` enables "first successful cast wins" mode.
|
||||
|
||||
```lua
|
||||
/firstaction
|
||||
/cast [myrawpower:>48] Shred -- Priority: only one of these
|
||||
/cast [myrawpower:>40] Claw
|
||||
/nofirstaction
|
||||
/cast Tiger's Fury -- Always evaluates alongside
|
||||
/startattack
|
||||
```
|
||||
|
||||
`/firstaction [cond]` accepts conditionals. Child macros (via `{MacroName}`) inherit priority state.
|
||||
|
||||
### UnitXP 3D Enemy Scanning
|
||||
|
||||
`/target` with any conditionals automatically uses UnitXP 3D scanning to find enemies in line of sight. Exception: `[help]` without `[harm]` (friendly-only). If no match is found, your original target is preserved.
|
||||
|
||||
```lua
|
||||
/target [name:Onyxia] -- Scan for exact name
|
||||
/target [nodead,harm] -- Scan for living enemies
|
||||
/target [cc:stun] -- Scan for stunned enemies
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conditionals Reference
|
||||
|
||||
All conditionals support negation with `no` prefix (e.g., `[nocombat]`, `[nobuff]`, `[nohelp]`). Some also have semantic opposites: `help`/`harm`, `isplayer`/`isnpc`, `alive`/`dead`, `inrange`/`outrange`.
|
||||
All conditionals support negation with `no` prefix unless noted. Some have semantic opposites: `help`/`harm`, `alive`/`dead`, `isplayer`/`isnpc`, `inrange`/`outrange`.
|
||||
|
||||
### Modifiers & Player State
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `mod` | `[mod:alt/ctrl/shift]` | Modifier key pressed |
|
||||
| `combat` | `[combat]` `[combat:target]` | In combat (player or unit) |
|
||||
| `form/stance` | `[form:1]` `[stance:2]` | Shapeshift/stance index |
|
||||
| `stealth` | `[stealth]` | In stealth (Rogue/Druid) |
|
||||
| `group` | `[group]` `[group:party/raid]` | Player in group type |
|
||||
| `resting` | `[resting]` | In rest area |
|
||||
| `swimming` | `[swimming]` | Can use aquatic form |
|
||||
| `moving` | `[moving]` `[moving:>100]` | Moving / speed % (MonkeySpeed) |
|
||||
| `zone` | `[zone:"Ironforge"]` | Current zone name |
|
||||
**Table columns:**
|
||||
- **Description** — what the conditional checks
|
||||
- **No** — supports `no` prefix negation
|
||||
- **Multi** — supports multi-value OR (`/`), AND (`&`), or comparison operators (`>`, `<`, `>=`, `<=`, `=`, `~=`)
|
||||
- **NP** — minimum Nampower version required
|
||||
- **Addon** — addon required or recommended
|
||||
|
||||
### Resources
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `myhp` | `[myhp:<30]` | Player HP % |
|
||||
| `myrawhp` | `[myrawhp:>1000]` | Player raw HP value |
|
||||
| `myhplost` | `[myhplost:>500]` | Player HP lost (max - current) |
|
||||
| `mypower` | `[mypower:>50]` | Player mana/rage/energy % |
|
||||
| `myrawpower` | `[myrawpower:>500]` | Player raw power value |
|
||||
| `mypowerlost` | `[mypowerlost:>200]` | Player power lost |
|
||||
| `druidmana` | `[druidmana:>=500]` | Druid mana while shapeshifted |
|
||||
| `combo` | `[combo:>=4]` | Combo points |
|
||||
| `stat` | `[stat:agi>100]` `[stat:ap>1000]` | Player stats (see below) |
|
||||
### Player Conditionals (@player)
|
||||
|
||||
**Stat types:** `str`, `agi`, `stam`, `int`, `spi`, `ap`, `rap`, `healing`, `armor`, `defense`, `arcane_power`, `fire_power`, `frost_power`, `nature_power`, `shadow_power`, `arcane_res`, `fire_res`, `frost_res`, `nature_res`, `shadow_res`
|
||||
These always evaluate against the player. Cannot be redirected with `@unit`.
|
||||
|
||||
### Buffs & Debuffs
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `mybuff` | `[mybuff:"Name"<5]` | Player has buff (with time check) |
|
||||
| `mydebuff` | `[mydebuff:"Name"]` | Player has debuff |
|
||||
| `mybuffcount` | `[mybuffcount:>15]` `[nomybuffcount:>28]` | Player buff slot count (32 max) |
|
||||
| `buff` | `[buff:"Name">#3]` | Target has buff (with stacks) |
|
||||
| `debuff` | `[debuff:"Sunder">20]` | Target has debuff (with time) |
|
||||
| `cursive` | `[cursive:Rake<3]` | Cursive addon tracking (GUID-based) |
|
||||
| Conditional | Description | No | Multi | Example | NP | Addon |
|
||||
|-------------|-------------|:--:|:-----:|---------|:--:|-------|
|
||||
| ***State & Modifiers*** | | | | | | |
|
||||
| `mod` | Modifier key pressed | ✅ | ✅ | `[mod:alt/ctrl]` | | |
|
||||
| `combat` | In combat (player default, or unit) | ✅ | ✅ | `[combat]` `[combat:target]` | | |
|
||||
| `form` / `stance` | Shapeshift form or stance index | ✅ | ✅ | `[form:1/3]` `[stance:2]` | | |
|
||||
| `stealth` | In stealth (Rogue/Druid) | ✅ | — | `[stealth]` | | |
|
||||
| `group` | Player is in group type | ✅ | ✅ | `[group:party/raid]` | | |
|
||||
| `resting` | In rest area | ✅ | — | `[resting]` | | |
|
||||
| `swimming` | Swimming | ✅ | — | `[swimming]` | | |
|
||||
| `moving` | Moving / speed % | ✅ | ✅ | `[moving]` `[moving:>100&<200]` | | MonkeySpeed (speed %) |
|
||||
| `zone` | Current zone name | ✅ | ✅ | `[zone:Ironforge/Stormwind]` | | |
|
||||
| ***Resources*** | | | | | | |
|
||||
| `myhp` | Player HP % | — | ✅ | `[myhp:<30]` `[myhp:>20&<50]` | | |
|
||||
| `myrawhp` | Player raw HP value | — | ✅ | `[myrawhp:>1000]` | | |
|
||||
| `myhplost` | Player HP deficit (max - current) | — | ✅ | `[myhplost:>500]` | | |
|
||||
| `mypower` | Player mana/rage/energy % | — | ✅ | `[mypower:>50]` | | |
|
||||
| `myrawpower` | Player raw power value | — | ✅ | `[myrawpower:>=40]` | | |
|
||||
| `mypowerlost` | Player power deficit | — | ✅ | `[mypowerlost:>200]` | | |
|
||||
| `druidmana` | Druid mana while shapeshifted | — | ✅ | `[druidmana:>=500]` | | |
|
||||
| `combo` | Combo points | ✅ | ✅ | `[combo:>=4]` | | |
|
||||
| `stat` | Player stat value | ✅ | ✅ | `[stat:agi>100]` `[stat:ap>1000]` | | |
|
||||
| `mylevel` | Player level | — | ✅ | `[mylevel:=60]` | | |
|
||||
| ***Player Auras*** | | | | | | |
|
||||
| `mybuff` | Player has buff (with time/stacks) | ✅ | ✅ | `[mybuff:Thorns<5]` `[nomybuff:MotW/GotW]` | | |
|
||||
| `mydebuff` | Player has debuff (with time/stacks) | ✅ | ✅ | `[mydebuff:Curse_of_Agony]` | | |
|
||||
| `mybuffcount` | Player buff slot count (32 max) | ✅ | ✅ | `[mybuffcount:>15]` `[nomybuffcount:>28]` | | |
|
||||
| `mybuffcapped` | Player buff bar full (32) | ✅ | — | `[nomybuffcapped]` | v2.20 | |
|
||||
| `mydebuffcapped` | Player debuff bar full (16) | ✅ | — | `[nomydebuffcapped]` | v2.20 | |
|
||||
| `mycc` | Player has CC effect | ✅ | ✅ | `[mycc:stun/silence]` `[nomycc]` | | |
|
||||
| ***Spells & Cooldowns*** | | | | | | |
|
||||
| `cooldown` | Spell/item CD remaining (ignores GCD) | ✅ | ✅ | `[cooldown:Sprint<5]` | | |
|
||||
| `cdgcd` | Spell/item CD remaining (includes GCD) | ✅ | ✅ | `[cdgcd:Sprint>0]` | | |
|
||||
| `gcd` | GCD is active / time remaining | ✅ | ✅ | `[gcd]` `[gcd:<1]` | | |
|
||||
| `usable` | Spell/item is usable now | ✅ | ✅ | `[usable:Overpower]` | | |
|
||||
| `reactive` | Reactive ability available (on bar) | ✅ | ✅ | `[reactive:Overpower]` | | |
|
||||
| `known` | Spell/talent known (with rank) | ✅ | ✅ | `[known:Berserk]` `[known:Frostbolt>#2]` | | |
|
||||
| `selfcasting` | Player is casting or channeling | ✅ | ✅ | `[selfcasting]` `[noselfcasting:Hearthstone]` | | |
|
||||
| `checkcasting` | True if NOT casting (specific spell) | — | ✅ | `[checkcasting]` `[checkcasting:Frostbolt]` | | |
|
||||
| `channeled` | Player is channeling | ✅ | ✅ | `[channeled]` `[channeled:Arcane_Missiles]` | | |
|
||||
| `checkchanneled` | True if NOT channeling (specific spell) | — | ✅ | `[checkchanneled]` `[checkchanneled:Evocation]` | | |
|
||||
| `spellcasttime` | Spell cast time from tooltip (real-time) | ✅ | ✅ | `[spellcasttime:>1.5]` `[spellcasttime:Frostbolt<2]` | | |
|
||||
| `queuedspell` | Spell is queued | ✅ | ✅ | `[queuedspell]` `[queuedspell:Fireball]` | v2.12 | |
|
||||
| `onswingpending` | On-swing spell pending | ✅ | — | `[onswingpending]` | v2.12 | |
|
||||
| ***Equipment*** | | | | | | |
|
||||
| `equipped` | Item or weapon type equipped | ✅ | ✅ | `[equipped:Daggers/Swords]` | | |
|
||||
| `mhimbue` | Main hand has temporary imbue | ✅ | ✅ | `[mhimbue:Instant_Poison<300]` `[mhimbue:>#5]` | | |
|
||||
| `ohimbue` | Off hand has temporary imbue | ✅ | ✅ | `[noohimbue]` `[ohimbue:Crippling_Poison]` | | |
|
||||
| `pet` | Has active pet (with family) | ✅ | ✅ | `[pet]` `[pet:Cat/Wolf]` | | |
|
||||
| ***Melee Tracking*** | | | | | | |
|
||||
| `lastswing` | Player melee swing type/timing | ✅ | ✅ | `[lastswing:dodge]` `[lastswing:<2]` | v2.24 | |
|
||||
| `incominghit` | Incoming attack type/timing | ✅ | ✅ | `[incominghit:crushing]` `[noincominghit:crit]` | v2.24 | |
|
||||
| `swingtimer` | Swing timer % elapsed | ✅ | ✅ | `[swingtimer:>80]` | | SP_SwingTimer |
|
||||
| `stimer` | Alias for swingtimer | ✅ | ✅ | `[stimer:<15]` | | SP_SwingTimer |
|
||||
| `slamclip` / `noslamclip` | Slam now will/won't clip auto-attack | ✅ | — | `[noslamclip]` | | SP_SwingTimer |
|
||||
| `nextslamclip` / `nonextslamclip` | Instant now will/won't cause next Slam to clip | ✅ | — | `[nonextslamclip]` | | SP_SwingTimer |
|
||||
| ***Target Existence*** | | | | | | |
|
||||
| `hastarget` | Player has a target | — | — | `[hastarget]` | | |
|
||||
| `notarget` | Player has no target | — | — | `[notarget]` | | |
|
||||
|
||||
### Cooldowns & Casting
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `cooldown` | `[cooldown:"Spell"<5]` | CD remaining (ignores GCD) |
|
||||
| `cdgcd` | `[cdgcd:"Spell">0]` | CD remaining (includes GCD) |
|
||||
| `gcd` | `[gcd]` `[gcd:<1]` | GCD is active / remaining time |
|
||||
| `usable` | `[usable:"Spell"]` | Spell/item is usable |
|
||||
| `reactive` | `[reactive:Overpower]` | Reactive ability available |
|
||||
| `known` | `[known:"Spell">#2]` | Spell/talent known (with rank) |
|
||||
| `channeled` | `[channeled]` | Currently channeling |
|
||||
| `channeltime` | `[channeltime:<0.5]` | Channel time remaining (seconds) |
|
||||
| `selfcasting` | `[selfcasting]` | Player is casting/channeling |
|
||||
| `casttime` | `[casttime:<0.5]` | Player cast time remaining |
|
||||
| `checkcasting` | `[checkcasting]` `[checkcasting:Frostbolt]` | NOT casting (specific spell) |
|
||||
| `checkchanneled` | `[checkchanneled]` | NOT channeling (specific spell) |
|
||||
| `queuedspell` | `[queuedspell]` `[queuedspell:Fireball]` | Spell queued (Nampower) |
|
||||
| `onswingpending` | `[onswingpending]` | On-swing spell pending |
|
||||
### Target Conditionals (default @target)
|
||||
|
||||
### Target Checks
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `exists` | `[@mouseover,exists]` | Unit exists |
|
||||
| `alive/dead` | `[alive]` `[dead]` | Alive or dead |
|
||||
| `help/harm` | `[help]` `[harm]` | Friendly or hostile |
|
||||
| `hp` | `[hp:<20]` `[hp:>30&<70]` | Target HP % |
|
||||
| `rawhp` | `[rawhp:>5000]` | Target raw HP value |
|
||||
| `hplost` | `[hplost:>1000]` | Target HP lost |
|
||||
| `power` | `[power:<30]` | Target power % |
|
||||
| `rawpower` | `[rawpower:>500]` | Target raw power value |
|
||||
| `powerlost` | `[powerlost:>100]` | Target power lost |
|
||||
| `powertype` | `[powertype:mana/rage/energy]` | Target's power type |
|
||||
| `level` | `[level:>60]` `[mylevel:=60]` | Unit level (skull = 63) |
|
||||
| `class` | `[class:Warrior/Priest]` | Target class (players only) |
|
||||
| `type` | `[type:Undead/Beast]` | Creature type |
|
||||
| `isplayer` | `[isplayer]` | Target is a player |
|
||||
| `isnpc` | `[isnpc]` | Target is an NPC |
|
||||
| `targeting` | `[targeting:player]` `[targeting:tank]` | Unit targeting you / any tank |
|
||||
| `istank` | `[istank]` `[@focus,istank]` | Unit is marked as tank (pfUI) |
|
||||
| `casting` | `[casting:"Spell"]` | Unit casting spell |
|
||||
| `party` | `[party]` `[party:focus]` | Unit in your party |
|
||||
| `raid` | `[raid]` `[raid:mouseover]` | Unit in your raid |
|
||||
| `member` | `[member]` | Target in party OR raid |
|
||||
| `hastarget` | `[hastarget]` | Player has a target |
|
||||
| `notarget` | `[notarget]` | Player has no target |
|
||||
| `pet` | `[pet]` `[pet:Cat/Wolf]` | Has pet (with family) |
|
||||
| `name` | `[name:Onyxia]` | Exact name match (case-insensitive) |
|
||||
| `tag` | `[tag]` | Target is tapped (by anyone) |
|
||||
| `notag` | `[notag]` | Target is not tapped |
|
||||
| `mytag` | `[mytag]` | Target is tapped by you |
|
||||
| `nomytag` | `[nomytag]` | Target is not tapped by you |
|
||||
| `othertag` | `[othertag]` | Target is tapped by someone else |
|
||||
| `noothertag` | `[noothertag]` | Not tapped by others (yours or unclaimed) |
|
||||
These default to checking the current target. Most can be redirected with `@unit` (e.g., `[@focus,hp:<30]`, `[@mouseover,exists]`).
|
||||
|
||||
### Range & Position
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `distance` | `[distance:<40]` | Distance in yards (UnitXP) |
|
||||
| `inrange` | `[inrange:"Spell"]` `[inrange:Spell>N]` | In spell range / count in range |
|
||||
| `outrange` | `[outrange:"Spell"]` `[outrange:Spell>N]` | Out of spell range / count out |
|
||||
| `meleerange` | `[meleerange]` `[meleerange:>N]` | In melee range / count in melee |
|
||||
| `behind` | `[behind]` `[behind:>N]` | Behind target / count behind |
|
||||
| `insight` | `[insight]` `[insight:>N]` | In line of sight / count in LoS |
|
||||
| Conditional | Description | No | Multi | Example | NP | Addon |
|
||||
|-------------|-------------|:--:|:-----:|---------|:--:|-------|
|
||||
| ***Existence & Disposition*** | | | | | | |
|
||||
| `exists` | Unit exists | ✅ | — | `[@mouseover,exists]` | | |
|
||||
| `alive` | Unit is alive | ✅ | — | `[alive]` | | |
|
||||
| `dead` | Unit is dead | ✅ | — | `[dead]` | | |
|
||||
| `help` | Unit is friendly | ✅ | — | `[help]` | | |
|
||||
| `harm` | Unit is hostile | ✅ | — | `[harm]` | | |
|
||||
| `isplayer` | Unit is a player | ✅ | — | `[isplayer]` | | |
|
||||
| `isnpc` | Unit is an NPC | ✅ | — | `[isnpc]` | | |
|
||||
| ***Resources*** | | | | | | |
|
||||
| `hp` | Target HP % | — | ✅ | `[hp:<20]` `[hp:>30&<70]` | | |
|
||||
| `rawhp` | Target raw HP value | — | ✅ | `[rawhp:>5000]` | | |
|
||||
| `hplost` | Target HP deficit (max - current) | — | ✅ | `[hplost:>1000]` | | |
|
||||
| `power` | Target mana/rage/energy % | — | ✅ | `[power:<30]` | | |
|
||||
| `rawpower` | Target raw power value | — | ✅ | `[rawpower:>500]` | | |
|
||||
| `powerlost` | Target power deficit | — | ✅ | `[powerlost:>100]` | | |
|
||||
| `powertype` | Target power type | ✅ | ✅ | `[powertype:mana/rage]` | | |
|
||||
| `level` | Target level (skull = 63) | — | ✅ | `[level:>60]` | | |
|
||||
| ***Classification*** | | | | | | |
|
||||
| `class` | Target class (players only) | ✅ | ✅ | `[class:Warrior/Priest]` | | |
|
||||
| `type` | Creature type | ✅ | ✅ | `[type:Undead/Beast]` | | |
|
||||
| `name` | Exact name match (case-insensitive) | ✅ | ✅ | `[name:Onyxia]` `[noname:Critter/Dummy]` | | |
|
||||
| ***Target Auras*** | | | | | | |
|
||||
| `buff` | Target has buff (with time/stacks) | ✅ | ✅ | `[buff:Shield>#3]` `[nobuff:Renew/Rejuv]` | | |
|
||||
| `debuff` | Target has debuff (with time/stacks) | ✅ | ✅ | `[debuff:Moonfire<4]` `[nodebuff:Rake]` | | |
|
||||
| `buffcapped` | Target buff bar full (32) | ✅ | — | `[nobuffcapped]` | v2.20 | |
|
||||
| `debuffcapped` | Target debuff bar full | ✅ | — | `[nodebuffcapped]` | v2.20 | |
|
||||
| `cc` | Target has CC effect | ✅ | ✅ | `[cc:stun/fear]` `[nocc:polymorph]` | | |
|
||||
| `cursive` | GUID-based debuff tracking (time remaining) | ✅ | ✅ | `[cursive:Rake<3]` `[nocursive:Rip]` | | Cursive |
|
||||
| ***Casting*** | | | | | | |
|
||||
| `casting` | Target is casting (specific spell) | ✅ | ✅ | `[casting]` `[casting:Heal]` | | |
|
||||
| `casttime` | Target cast time remaining (seconds) | ✅ | ✅ | `[casttime:<0.5]` | | |
|
||||
| `channeltime` | Target channel time remaining (seconds) | ✅ | ✅ | `[channeltime:<0.5]` | | |
|
||||
| ***Relationship*** | | | | | | |
|
||||
| `party` | Unit is in your party | ✅ | ✅ | `[party]` `[party:focus]` | | |
|
||||
| `raid` | Unit is in your raid | ✅ | ✅ | `[raid]` `[raid:mouseover]` | | |
|
||||
| `member` | Target is in party or raid | ✅ | — | `[member]` | | |
|
||||
| `targeting` | Unit is targeting you or a tank | ✅ | ✅ | `[targeting:player]` `[notargeting:tank]` | | pfUI (for tank) |
|
||||
| `istank` | Unit is marked as tank | ✅ | — | `[istank]` `[@focus,istank]` | | pfUI |
|
||||
| `tag` | Target is tapped by anyone | ✅ | — | `[tag]` `[notag]` | | |
|
||||
| `mytag` | Target is tapped by you | ✅ | — | `[mytag]` `[nomytag]` | | |
|
||||
| `othertag` | Target is tapped by someone else | ✅ | — | `[othertag]` `[noothertag]` | | |
|
||||
| ***Range & Position*** | | | | | | |
|
||||
| `distance` | Distance in yards | ✅ | ✅ | `[distance:<40]` | | UnitXP_SP3 |
|
||||
| `meleerange` | In melee range / count in melee | ✅ | ✅ | `[meleerange]` `[meleerange:>1]` | | UnitXP_SP3 |
|
||||
| `inrange` | In spell range / count in range | ✅ | ✅ | `[inrange:Charge]` `[inrange:Multi-Shot>1]` | | UnitXP_SP3 (count) |
|
||||
| `outrange` | Out of spell range / count out | ✅ | ✅ | `[outrange:Charge]` `[outrange:Charge>0]` | | UnitXP_SP3 (count) |
|
||||
| `insight` | In line of sight / count in LoS | ✅ | ✅ | `[insight]` `[insight:>0]` | | UnitXP_SP3 |
|
||||
| `behind` | Behind target / count behind | ✅ | ✅ | `[behind]` `[behind:>=2]` | | UnitXP_SP3 |
|
||||
| `multiscan` | Scan enemies by priority, soft-cast | — | ✅ | `[multiscan:nearest]` `[multiscan:skull]` | | UnitXP_SP3 |
|
||||
| ***Immunity*** | | | | | | |
|
||||
| `immune` | Target is immune (school or CC type) | ✅ | ✅ | `[noimmune:fire]` `[noimmune:stun]` | | |
|
||||
| `resisted` | Last spell was resisted | ✅ | ✅ | `[resisted]` `[resisted:full/partial]` | | |
|
||||
| ***Addon Integrations*** | | | | | | |
|
||||
| `threat` | Threat % on target (100 = pull) | ✅ | ✅ | `[threat:>80]` | | TWThreat |
|
||||
| `ttk` | Time to kill target (seconds) | ✅ | ✅ | `[ttk:<10]` | | TimeToKill |
|
||||
| `tte` | Time to execute threshold (seconds) | ✅ | ✅ | `[tte:<5]` | | TimeToKill |
|
||||
|
||||
**Multi-Unit Count Mode:** Add operator + number to count enemies matching the condition (requires UnitXP). Operators: `>`, `<`, `>=`, `<=`, `=`, `~=`
|
||||
### Multi-Unit Count Mode
|
||||
|
||||
Range and position conditionals (`meleerange`, `behind`, `insight`, `inrange`, `outrange`) support counting enemies when given an operator + number. Requires UnitXP_SP3. Operators: `>`, `<`, `>=`, `<=`, `=`, `~=`
|
||||
|
||||
```lua
|
||||
/cast [meleerange:>1] Whirlwind -- AoE if 2+ enemies in melee
|
||||
@@ -168,126 +286,53 @@ All conditionals support negation with `no` prefix (e.g., `[nocombat]`, `[nobuff
|
||||
/cast [insight:>0] Arcane Explosion -- AoE if any enemy in LoS
|
||||
```
|
||||
|
||||
### Equipment
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `equipped` | `[equipped:Daggers]` | Item/type equipped |
|
||||
| `mhimbue` | `[mhimbue]` `[mhimbue:Instant_Poison]` | Main-hand has temporary imbue |
|
||||
| `ohimbue` | `[ohimbue]` `[ohimbue:Crippling_Poison]` | Off-hand has temporary imbue |
|
||||
### Multiscan (Target Scanning)
|
||||
|
||||
Scans enemies and soft-casts without changing your target. Requires UnitXP_SP3. Scanned targets must be in combat with player (exceptions: current target and `@unit` specified in macro).
|
||||
|
||||
| Priority | Description |
|
||||
|----------|-------------|
|
||||
| `nearest` | Closest enemy |
|
||||
| `farthest` | Farthest enemy |
|
||||
| `highesthp` / `lowesthp` | Highest/lowest HP % |
|
||||
| `highestrawhp` / `lowestrawhp` | Highest/lowest raw HP |
|
||||
| `markorder` | First mark in kill order (skull→cross→square→moon→triangle→diamond→circle→star) |
|
||||
| `skull` `cross` `square` `moon` `triangle` `diamond` `circle` `star` | Specific raid mark |
|
||||
|
||||
**Imbue Conditionals (Poisons, Oils, Sharpening Stones):**
|
||||
```lua
|
||||
[mhimbue] -- Has any temporary imbue
|
||||
[mhimbue:Instant_Poison] -- Has specific imbue (tooltip match)
|
||||
[mhimbue:<300] -- Imbue expires in < 300 seconds (5 min)
|
||||
[mhimbue:Instant_Poison<300] -- Specific imbue with time check
|
||||
[mhimbue:>#5] -- Has > 5 charges remaining
|
||||
[nomhimbue] -- No temporary imbue (apply needed)
|
||||
/cast [multiscan:nearest,nodebuff:Rake] Rake
|
||||
/cast [multiscan:skull,harm] Eviscerate
|
||||
/cast [multiscan:nearest,notargeting:tank,harm] Taunt
|
||||
```
|
||||
|
||||
**Note:** Temporary imbues are detected via tooltip time/charge markers, filtering out permanent enchants like Crusader.
|
||||
### Reference Tables
|
||||
|
||||
### CC & Immunity
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `cc` | `[cc]` `[cc:stun/fear]` | Target has CC effect |
|
||||
| `mycc` | `[mycc]` `[mycc:silence]` | Player has CC effect |
|
||||
| `immune` | `[immune:fire]` `[immune:stun]` | Target IS immune (skip cast) |
|
||||
| `noimmune` | `[noimmune]` `[noimmune:bleed]` | Target NOT immune (allow cast) |
|
||||
| `resisted` | `[resisted]` `[resisted:full/partial]` | Last spell was resisted |
|
||||
**CC Types** (for `[cc]`, `[mycc]`, `[immune]`):
|
||||
|
||||
**CC Types:** stun, fear, root, snare/slow, sleep, charm, polymorph, banish, horror, disorient, silence, disarm, daze, freeze, shackle
|
||||
| Type | Examples |
|
||||
|------|----------|
|
||||
| `stun` | Cheap Shot, Kidney Shot, HoJ, Bash, Gouge, Sap |
|
||||
| `fear` | Fear, Psychic Scream, Howl of Terror |
|
||||
| `root` | Entangling Roots, Frost Nova |
|
||||
| `snare` / `slow` | Hamstring, Wing Clip, Crippling Poison |
|
||||
| `sleep` | Hibernate, Wyvern Sting |
|
||||
| `charm` | Mind Control, Seduction |
|
||||
| `polymorph` | Polymorph (all variants) |
|
||||
| `banish` | Banish |
|
||||
| `horror` | Death Coil, Intimidating Shout |
|
||||
| `disorient` | Scatter Shot, Blind |
|
||||
| `silence` | Silence, Counterspell |
|
||||
| `disarm` | Disarm |
|
||||
| `daze` | Daze effects |
|
||||
| `freeze` | Freeze effects |
|
||||
| `shackle` | Shackle Undead |
|
||||
|
||||
**Loss-of-control** (checked by bare `[cc]`): stun, fear, sleep, charm, polymorph, banish, horror, freeze, disorient, shackle
|
||||
|
||||
**Damage Schools:** physical, fire, frost, nature, shadow, arcane, holy, bleed
|
||||
**Damage Schools** (for `[immune]`): physical, fire, frost, nature, shadow, arcane, holy, bleed
|
||||
|
||||
### Addon Integrations
|
||||
| Conditional | Addon | Example | Description |
|
||||
|-------------|-------|---------|-------------|
|
||||
| `swingtimer` | SP_SwingTimer | `[swingtimer:<15]` | Swing % elapsed |
|
||||
| `stimer` | SP_SwingTimer | `[stimer:>80]` | Alias for swingtimer |
|
||||
| `threat` | TWThreat | `[threat:>80]` | Threat % (100=pull) |
|
||||
| `ttk` | TimeToKill | `[ttk:<10]` | Time to kill (seconds) |
|
||||
| `tte` | TimeToKill | `[tte:<5]` | Time to execute (20% HP) |
|
||||
| `cursive` | Cursive | `[cursive:Rake>3]` | GUID debuff tracking |
|
||||
| `moving` | MonkeySpeed | `[moving:>100]` | Speed % (100=normal run) |
|
||||
| `targeting:tank` | pfUI | `[targeting:tank]` | Target is attacking any tank |
|
||||
| `istank` | pfUI | `[istank]` | Target is marked as tank |
|
||||
**Swing/Hit Types** (for `[lastswing]`, `[incominghit]`):
|
||||
|
||||
### pfUI Tank Integration
|
||||
|
||||
Identify loose mobs in trash pulls by checking if enemies are targeting designated tanks. Works with pfUI's tank marking systems.
|
||||
|
||||
**Tank Conditionals:**
|
||||
| Conditional | Description |
|
||||
|-------------|-------------|
|
||||
| `[targeting:tank]` | Target IS attacking any player marked as tank |
|
||||
| `[notargeting:tank]` | Target is NOT attacking any tank (loose mob!) |
|
||||
| `[istank]` | Target unit IS marked as tank |
|
||||
| `[noistank]` | Target unit is NOT marked as tank |
|
||||
|
||||
**Setting Up Tanks in pfUI:**
|
||||
|
||||
1. **Nameplate Off-Tank Names** (recommended): `/pfui` → Nameplates → Off-Tank Names
|
||||
- Add tank names separated by `#`: `#TankName1#TankName2#TankName3`
|
||||
- These names will show different colored nameplates AND work with `[targeting:tank]`
|
||||
|
||||
2. **Raid Frame Toggle**: Right-click a player in raid frames → "Toggle as Tank"
|
||||
- Only works when raid frames are visible
|
||||
|
||||
**Example Macros:**
|
||||
```lua
|
||||
-- Pick up loose mobs (not targeting any tank)
|
||||
/cast [multiscan:nearest,notargeting:tank,harm] Taunt
|
||||
|
||||
-- Only taunt if mob is targeting you (tank)
|
||||
/cast [targeting:player] Sunder Armor
|
||||
|
||||
-- Assist tanks - attack what they're tanking
|
||||
/cast [multiscan:nearest,targeting:tank,harm] Sunder Armor
|
||||
|
||||
-- Emergency taunt on loose mob hitting a healer
|
||||
/cast [notargeting:tank,notargeting:player] Taunt
|
||||
```
|
||||
|
||||
**Debug Command:**
|
||||
```
|
||||
/cleveroid tankdebug
|
||||
```
|
||||
Shows all marked tanks, current target info, and conditional results.
|
||||
|
||||
### Warrior Slam Conditionals
|
||||
For optimizing Slam rotations without clipping auto-attacks:
|
||||
|
||||
| Conditional | Description |
|
||||
|-------------|-------------|
|
||||
| `noslamclip` | True if Slam NOW won't clip auto-attack |
|
||||
| `slamclip` | True if Slam NOW WILL clip auto-attack |
|
||||
| `nonextslamclip` | True if instant NOW won't cause NEXT Slam to clip |
|
||||
| `nextslamclip` | True if instant NOW WILL cause NEXT Slam to clip |
|
||||
|
||||
```lua
|
||||
/cast [noslamclip] Slam
|
||||
/cast [slamclip] Heroic Strike -- Use HS when past slam window
|
||||
/cast [nonextslamclip] Bloodthirst
|
||||
```
|
||||
|
||||
### Auto-Attack Conditionals (Nampower v2.24+)
|
||||
|
||||
Track player melee swings and incoming attacks. Requires `NP_EnableAutoAttackEvents=1` CVar.
|
||||
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `lastswing` | `[lastswing]` | Any melee swing in last 5 seconds |
|
||||
| `lastswing:type` | `[lastswing:crit]` | Last swing was specific type |
|
||||
| `lastswing:<N` | `[lastswing:<2]` | Last swing was < N seconds ago |
|
||||
| `nolastswing:type` | `[nolastswing:miss/dodge]` | Last swing was NOT type (AND logic) |
|
||||
| `incominghit` | `[incominghit]` | Any incoming hit in last 5 seconds |
|
||||
| `incominghit:type` | `[incominghit:crushing]` | Last incoming hit was type |
|
||||
| `noincominghit:type` | `[noincominghit:crushing]` | Last hit was NOT type |
|
||||
|
||||
**Swing/Hit Types:**
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `crit` | Critical hit |
|
||||
@@ -301,165 +346,11 @@ Track player melee swings and incoming attacks. Requires `NP_EnableAutoAttackEve
|
||||
| `mainhand` / `mh` | Main-hand swing (lastswing only) |
|
||||
| `hit` | Successful hit (not miss/dodge/parry) |
|
||||
|
||||
```lua
|
||||
/cast [lastswing:dodge] Overpower -- Overpower after enemy dodged
|
||||
/use [incominghit:crushing] Last Stand -- Emergency CD after crushing blow
|
||||
/cast [lastswing:crit] Execute -- Execute after crit proc
|
||||
```
|
||||
**Stat Types** (for `[stat]`): `str`, `agi`, `stam`, `int`, `spi`, `ap`, `rap`, `healing`, `armor`, `defense`, `arcane_power`, `fire_power`, `frost_power`, `nature_power`, `shadow_power`, `arcane_res`, `fire_res`, `frost_res`, `nature_res`, `shadow_res`
|
||||
|
||||
### Aura Cap Conditionals (Nampower v2.20+)
|
||||
**Aura Capacity:** Player: 32 buffs, 16 debuffs. NPCs: 16 debuff slots + 32 overflow = 48 total.
|
||||
|
||||
Track buff/debuff bar capacity. Requires `NP_EnableAuraCastEvents=1` CVar for accurate tracking.
|
||||
|
||||
| Conditional | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| `mybuffcapped` | `[mybuffcapped]` | Player has 32 buffs (full) |
|
||||
| `nomybuffcapped` | `[nomybuffcapped]` | Player has buff room |
|
||||
| `mydebuffcapped` | `[mydebuffcapped]` | Player has 16 debuffs (full) |
|
||||
| `nomydebuffcapped` | `[nomydebuffcapped]` | Player has debuff room |
|
||||
| `buffcapped` | `[buffcapped]` | Target buff bar is full (32) |
|
||||
| `nobuffcapped` | `[nobuffcapped]` | Target has buff room |
|
||||
| `debuffcapped` | `[debuffcapped]` | Target debuff bar is full |
|
||||
| `nodebuffcapped` | `[nodebuffcapped]` | Target has debuff room |
|
||||
|
||||
**Aura Capacity:**
|
||||
- **Player:** 32 buff slots, 16 debuff slots
|
||||
- **NPCs:** 16 debuff slots + 32 overflow = 48 total visual debuff capacity
|
||||
|
||||
```lua
|
||||
/cast [nodebuffcapped,nocursive:Rake] Rake -- Only DoT if room on target
|
||||
/cast [nomybuffcapped] Mark of the Wild -- Only buff self if room
|
||||
/cast [@focus,nodebuffcapped] Corruption -- Only DoT focus if room
|
||||
```
|
||||
|
||||
### Multiscan (Target Scanning)
|
||||
Scans enemies and soft-casts without changing your target. Requires UnitXP_SP3.
|
||||
```lua
|
||||
/cast [multiscan:nearest,nodebuff:Rake] Rake
|
||||
/cast [multiscan:skull,harm] Eviscerate
|
||||
/cast [multiscan:markorder] Sinister Strike
|
||||
/cast [multiscan:highesthp,noimmune:stun] Cheap Shot
|
||||
/cast [multiscan:nearest,notargeting:tank,harm] Taunt -- Pick up loose mobs (pfUI)
|
||||
```
|
||||
|
||||
**Priorities:**
|
||||
| Priority | Description |
|
||||
|----------|-------------|
|
||||
| `nearest` | Closest enemy |
|
||||
| `farthest` | Farthest enemy |
|
||||
| `highesthp` | Highest HP % |
|
||||
| `lowesthp` | Lowest HP % |
|
||||
| `highestrawhp` | Highest raw HP |
|
||||
| `lowestrawhp` | Lowest raw HP |
|
||||
| `markorder` | First mark in kill order (skull→cross→square→moon→triangle→diamond→circle→star) |
|
||||
| `skull`, `cross`, `square`, `moon`, `triangle`, `diamond`, `circle`, `star` | Specific raid mark |
|
||||
|
||||
**Note:** Scanned targets must be in combat with player, except current target and `@unit` specified in macro.
|
||||
|
||||
---
|
||||
|
||||
## Slash Commands
|
||||
|
||||
### Commands with Conditional Support
|
||||
|
||||
These commands accept `[conditionals]` and use UnitXP 3D enemy scanning when applicable.
|
||||
|
||||
| Command | Conditionals | UnitXP Scan | Description |
|
||||
|---------|:------------:|:-----------:|-------------|
|
||||
| `/cast [cond] Spell` | ✅ | — | Cast spell with conditionals |
|
||||
| `/castpet [cond] Spell` | ✅ | — | Cast pet spell |
|
||||
| `/use [cond] Item` | ✅ | — | Use item by name/ID/slot |
|
||||
| `/equip [cond] Item` | ✅ | — | Equip item (same as /use) |
|
||||
| `/target [cond]` | ✅ | ✅ | Target with conditionals + enemy scan |
|
||||
| `/startattack [cond]` | ✅ | — | Start auto-attack if conditions met |
|
||||
| `/stopattack [cond]` | ✅ | — | Stop auto-attack if conditions met |
|
||||
| `/stopcasting [cond]` | ✅ | — | Stop casting if conditions met |
|
||||
| `/unqueue [cond]` | ✅ | — | Clear spell queue if conditions met |
|
||||
| `/cleartarget [cond]` | ✅ | — | Clear target if conditions met |
|
||||
| `/cancelaura [cond] Name` | ✅ | — | Cancel buff if conditions met |
|
||||
| `/quickheal [cond]` | ✅ | — | Smart heal (requires QuickHeal) |
|
||||
| `/stopmacro [cond]` | ✅ | — | Stop ALL macro execution (including parent macros) |
|
||||
| `/skipmacro [cond]` | ✅ | — | Stop current submacro only, parent continues |
|
||||
| `/firstaction [cond]` | ✅ | — | Stop on first successful `/cast` or `/use` (priority mode) |
|
||||
| `/nofirstaction [cond]` | ✅ | — | Re-enable multi-queue after `/firstaction` |
|
||||
| `/petattack [cond]` | ✅ | — | Pet attack with conditionals |
|
||||
| `/petfollow [cond]` | ✅ | — | Pet follow with conditionals |
|
||||
| `/petwait [cond]` | ✅ | — | Pet stay with conditionals |
|
||||
| `/petpassive [cond]` | ✅ | — | Pet passive with conditionals |
|
||||
| `/petdefensive [cond]` | ✅ | — | Pet defensive with conditionals |
|
||||
| `/petaggressive [cond]` | ✅ | — | Pet aggressive with conditionals |
|
||||
| `/castsequence` | ✅ | — | Sequence with reset conditionals |
|
||||
| `/equipmh [cond] Item` | ✅ | — | Equip to main hand |
|
||||
| `/equipoh [cond] Item` | ✅ | — | Equip to off hand |
|
||||
| `/equip11` - `/equip14 [cond]` | ✅ | — | Equip to slot (rings/trinkets) |
|
||||
| `/unshift [cond]` | ✅ | — | Cancel shapeshift if conditions met |
|
||||
| `/applymain [cond] Item` | ✅ | — | Apply poison/oil to main hand |
|
||||
| `/applyoff [cond] Item` | ✅ | — | Apply poison/oil to off hand |
|
||||
|
||||
#### Priority-Based Macro Evaluation (`/firstaction` & `/nofirstaction`)
|
||||
|
||||
By default, all `/cast` lines in a macro are evaluated and may queue spells with Nampower. Use `/firstaction` to enable "first successful cast wins" behavior - once a `/cast` or `/use` succeeds, the macro stops evaluating subsequent lines. Use `/nofirstaction` to re-enable normal multi-queue behavior.
|
||||
|
||||
```lua
|
||||
-- WITHOUT /firstaction: Both Shred AND Claw may queue if conditions are met
|
||||
/cast [myrawpower:>48] Shred
|
||||
/cast [myrawpower:>40] Claw
|
||||
|
||||
-- WITH /firstaction: Only Shred casts if energy >= 48, Claw is skipped
|
||||
/firstaction
|
||||
/cast [myrawpower:>48] Shred
|
||||
/cast [myrawpower:>40] Claw
|
||||
|
||||
-- Mixed mode: priority section + always-queue section
|
||||
/firstaction
|
||||
/cast [myrawpower:>48] Shred -- Priority: only one of these
|
||||
/cast [myrawpower:>40] Claw
|
||||
/nofirstaction
|
||||
/cast Tiger's Fury -- Always evaluates (can queue alongside)
|
||||
/startattack
|
||||
|
||||
-- With conditionals
|
||||
/firstaction [group] -- Priority mode only in groups
|
||||
/cast [myrawpower:>48] Shred
|
||||
/cast [myrawpower:>40] Claw
|
||||
```
|
||||
|
||||
**Child Macro Behavior:** `/firstaction` and `/nofirstaction` in child macros (called via `{MacroName}`) affect subsequent lines in the parent macro. Think of the child's lines as being "inlined" at the call site.
|
||||
|
||||
**Comparison:**
|
||||
- `/stopmacro [cond]` - Stop if condition is true (regardless of cast success)
|
||||
- `/skipmacro [cond]` - Stop current submacro only
|
||||
- `/firstaction [cond]` - Stop on first **successful** cast/use (priority mode)
|
||||
- `/nofirstaction [cond]` - Re-enable multi-queue behavior after `/firstaction`
|
||||
|
||||
### Commands without Conditional Support
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `/retarget` | Clear invalid target, target nearest enemy |
|
||||
| `/runmacro Name` | Execute macro by name (use `{MacroName}` in `/cast` for conditionals) |
|
||||
| `/rl` | Reload UI |
|
||||
|
||||
### UnitXP 3D Enemy Scanning
|
||||
|
||||
`/target` with any conditionals automatically uses UnitXP 3D scanning to find enemies in line of sight, even without nameplates visible. The only exception is `[help]` without `[harm]` (friendly-only targeting).
|
||||
|
||||
```lua
|
||||
/target [name:Onyxia] -- Scans for exact name match
|
||||
/target [nodead,harm] -- Scans for living enemies
|
||||
/target [hp:<30] -- Scans for low HP enemies
|
||||
/target [cc:stun] -- Scans for stunned enemies
|
||||
|
||||
-- Kara 40 Mage Incantagos example --
|
||||
/target [name:Red_Affinity]
|
||||
/cast [name:Red_Affinity] Fireball
|
||||
/target [name:Blue_Affinity]
|
||||
/cast [name:Blue_Affinity] Frostbolt
|
||||
/target [name:Mana_Affinity]
|
||||
/cast [name:Mana_Affinity] Arcane Missiles
|
||||
```
|
||||
|
||||
If no matching target is found, your original target is preserved.
|
||||
**Speed Reference** (for `[moving:>N]`): 0 = still, 100 = run, 160 = mount, 200 = epic mount.
|
||||
|
||||
---
|
||||
|
||||
@@ -473,6 +364,12 @@ Auto-learns debuff durations from your casts. 335+ spells pre-configured.
|
||||
/cast Wrath -- Filler
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Existence checks (`[nodebuff]`, `[debuff]`) detect ANY debuff on target
|
||||
- Time-remaining checks (`[debuff:X<5]`) use internal tracking from your casts
|
||||
- Shared debuffs (Sunder, Faerie Fire) are detected from any source
|
||||
- [Cursive](https://github.com/pepopo978/Cursive) provides GUID-based tracking with better accuracy (handles target switching, pending casts, debuff cap)
|
||||
|
||||
### Combo Point Tracking
|
||||
Tracks finisher durations (Rip, Rupture, Kidney Shot) accounting for combo points spent.
|
||||
|
||||
@@ -487,9 +384,9 @@ Automatically adjusts tracked durations for talents (Imp. Gouge, Taste for Blood
|
||||
|
||||
---
|
||||
|
||||
## Immunity Tracking Guide
|
||||
## Immunity Tracking
|
||||
|
||||
The addon auto-learns NPC immunities from combat. When a spell fails with "immune", the addon remembers it for that NPC.
|
||||
Auto-learns NPC immunities from combat. When a spell fails with "immune", the addon remembers it for that NPC.
|
||||
|
||||
### Using `[noimmune]`
|
||||
|
||||
@@ -500,172 +397,21 @@ The addon auto-learns NPC immunities from combat. When a spell fails with "immun
|
||||
| `[noimmune:bleed]` | Bleed immunity specifically |
|
||||
| `[noimmune:stun]` | Stun CC immunity |
|
||||
|
||||
### Common Examples
|
||||
**Split damage spells** (Rake, Pounce, Garrote) have initial physical hit + bleed DoT. `[noimmune]` automatically checks **both** components.
|
||||
|
||||
```lua
|
||||
-- Fire Mage: Skip immune targets
|
||||
/cast [noimmune] Fireball
|
||||
|
||||
-- Warlock: Check shadow immunity
|
||||
/cast [noimmune:shadow] Corruption
|
||||
|
||||
-- Rogue: Skip stun-immune targets
|
||||
/cast [noimmune:stun] Cheap Shot
|
||||
/cast Sinister Strike
|
||||
|
||||
-- Druid: Check fear immunity for Hibernate
|
||||
/cast [noimmune:sleep,type:Beast/Dragonkin] Hibernate
|
||||
/cast [noimmune] Rake -- Checks physical AND bleed
|
||||
/cast [noimmune:stun] Cheap Shot -- Checks stun immunity
|
||||
/cast [noimmune:shadow] Corruption -- Checks shadow immunity
|
||||
```
|
||||
|
||||
### Split Damage Spells (Rake, Pounce, Garrote)
|
||||
|
||||
These spells have an initial physical hit + a bleed DoT. The addon **automatically checks BOTH immunities**:
|
||||
|
||||
### Manual Immunity Commands
|
||||
```lua
|
||||
-- Checks both physical AND bleed immunity
|
||||
/cast [noimmune] Rake
|
||||
|
||||
-- With time check using Cursive
|
||||
/cast [noimmune, cursive:Rake<1.5] Rake
|
||||
|
||||
-- Multi-DoT rotation
|
||||
/cast [noimmune, nocursive:Rake] Rake
|
||||
/cast [noimmune, nocursive:Rip, combo:>=4] Rip
|
||||
```
|
||||
|
||||
`[noimmune]` for these spells returns false (skip cast) if target is immune to **either** component. You can also check specific schools:
|
||||
- `[noimmune:physical]` - only checks physical immunity
|
||||
- `[noimmune:bleed]` - only checks bleed immunity
|
||||
|
||||
### Pure Bleed Spells (Rip, Rupture, Rend)
|
||||
|
||||
These are pure bleeds with no initial hit:
|
||||
|
||||
```lua
|
||||
-- Rip (pure bleed)
|
||||
/cast [noimmune, combo:>=4] Rip
|
||||
|
||||
-- Rupture (pure bleed)
|
||||
/cast [noimmune, combo:>=4] Rupture
|
||||
```
|
||||
|
||||
### Manually Adding Immunities
|
||||
|
||||
If you know an NPC is immune before fighting:
|
||||
```lua
|
||||
/cleveroid addimmune "Boss Name" bleed -- Permanent bleed immunity
|
||||
/cleveroid addimmune "Boss Name" fire "Shield" -- Fire immune only when buffed
|
||||
/cleveroid addccimmune "Boss Name" stun -- Permanent stun immunity
|
||||
```
|
||||
|
||||
### Viewing Learned Immunities
|
||||
|
||||
```lua
|
||||
/cleveroid listimmune -- All school immunities
|
||||
/cleveroid listimmune bleed -- Only bleed immunities
|
||||
/cleveroid listccimmune -- All CC immunities
|
||||
/cleveroid listccimmune stun -- Only stun immunities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debuff Tracking Guide
|
||||
|
||||
### Built-in Tracking (`[debuff]`)
|
||||
|
||||
The addon checks debuffs on the target:
|
||||
|
||||
```lua
|
||||
-- Basic: Only cast if target doesn't have Moonfire
|
||||
/cast [nodebuff:Moonfire] Moonfire
|
||||
|
||||
-- Time check: Refresh when < 4 seconds left
|
||||
/cast [debuff:Moonfire<4] Moonfire
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Existence checks (`[nodebuff]`, `[debuff]`) detect ANY debuff on target
|
||||
- Time-remaining checks (`[debuff:X<5]`) use internal tracking from your casts
|
||||
- Shared debuffs (Sunder, Faerie Fire) are detected from any source
|
||||
|
||||
### Cursive Integration (`[cursive]`)
|
||||
|
||||
[Cursive](https://github.com/avitasia/Cursive) provides more accurate GUID-based tracking:
|
||||
|
||||
```lua
|
||||
-- Check if target has Rake (any time remaining)
|
||||
/cast [nocursive:Rake] Rake
|
||||
|
||||
-- Refresh when < 3 seconds remaining
|
||||
/cast [cursive:Rake<3] Rake
|
||||
|
||||
-- Complex: Only if missing OR about to expire
|
||||
/cast [nocursive:Rake>1.5] Rake
|
||||
```
|
||||
|
||||
**Advantages of Cursive:**
|
||||
- GUID-based (survives target switching)
|
||||
- Tracks pending casts
|
||||
- Works at debuff cap
|
||||
- More accurate timing
|
||||
|
||||
### MonkeySpeed Integration (`[moving]`)
|
||||
|
||||
[MonkeySpeed](https://github.com/jrc13245/MonkeySpeed) provides accurate movement speed detection via SuperWoW's `UnitPosition` API:
|
||||
|
||||
```lua
|
||||
-- Only cast if standing still
|
||||
/cast [nomoving] Aimed Shot
|
||||
|
||||
-- Cast instant ability while moving
|
||||
/cast [moving] Arcane Shot
|
||||
|
||||
-- Check speed percentage (100 = normal run speed)
|
||||
/cast [moving:>100] Sprint -- Already faster than normal
|
||||
/cast [moving:<50] Escape Artist -- Currently slowed
|
||||
|
||||
-- Combined with other conditionals
|
||||
/cast [nomoving, harm] Aimed Shot
|
||||
/cast [moving, harm] Arcane Shot
|
||||
```
|
||||
|
||||
**Speed Reference:**
|
||||
| Speed | Description |
|
||||
|-------|-------------|
|
||||
| `0` | Standing still |
|
||||
| `100` | Normal run speed |
|
||||
| `160` | Level 40 mount |
|
||||
| `200` | Epic mount |
|
||||
|
||||
**Notes:**
|
||||
- Basic `[moving]`/`[nomoving]` works without MonkeySpeed (position fallback)
|
||||
- Speed comparisons (`[moving:>100]`) require MonkeySpeed addon
|
||||
- Speed values vary with buffs/debuffs affecting movement
|
||||
|
||||
### Recommended DoT Macro Patterns
|
||||
|
||||
**Druid Feral (with Cursive):**
|
||||
```lua
|
||||
#showtooltip
|
||||
/cast [noimmune, nocursive:Rake] Rake
|
||||
```
|
||||
|
||||
**Druid Feral (without Cursive):**
|
||||
```lua
|
||||
#showtooltip
|
||||
/cast [noimmune, nodebuff] Rake
|
||||
```
|
||||
|
||||
**Warlock Multi-DoT:**
|
||||
```lua
|
||||
#showtooltip
|
||||
/cast [noimmune:shadow, nodebuff] Corruption
|
||||
```
|
||||
|
||||
**Rogue Rupture:**
|
||||
```lua
|
||||
#showtooltip
|
||||
/cast [noimmune, nodebuff, combo:>=4] Rupture
|
||||
/cleveroid addimmune "Boss Name" bleed -- Permanent immunity
|
||||
/cleveroid addimmune "Boss Name" fire "Shield" -- Conditional (while buffed)
|
||||
/cleveroid addccimmune "Boss Name" stun -- CC immunity
|
||||
/cleveroid listimmune [school] -- View school immunities
|
||||
/cleveroid listccimmune [type] -- View CC immunities
|
||||
```
|
||||
|
||||
---
|
||||
@@ -673,25 +419,15 @@ The addon checks debuffs on the target:
|
||||
## Settings
|
||||
|
||||
```lua
|
||||
/cleveroid -- View settings
|
||||
/cleveroid realtime 0|1 -- Instant updates (more CPU)
|
||||
/cleveroid refresh 1-10 -- Update rate in Hz
|
||||
/cleveroid debug 0|1 -- Debug messages
|
||||
/cleveroid learn <id> <dur> -- Manually set spell duration
|
||||
/cleveroid forget <id|all> -- Forget duration(s)
|
||||
```
|
||||
|
||||
### Immunity Commands
|
||||
```lua
|
||||
/cleveroid listimmune [school]
|
||||
/cleveroid addimmune "NPC" school [buff]
|
||||
/cleveroid listccimmune [type]
|
||||
/cleveroid addccimmune "NPC" type [buff]
|
||||
```
|
||||
|
||||
### Combo Tracking
|
||||
```lua
|
||||
/combotrack show|clear|debug
|
||||
/cleveroid -- View settings
|
||||
/cleveroid realtime 0|1 -- Instant tooltip updates (more CPU)
|
||||
/cleveroid refresh 1-10 -- Tooltip update rate in Hz
|
||||
/cleveroid debug 0|1 -- Debug messages
|
||||
/cleveroid learn <spellID> <dur> -- Manually set spell duration
|
||||
/cleveroid forget <spellID|all> -- Forget learned duration(s)
|
||||
/cleveroid debuffdebug [spell] -- Debug debuff tracking on target
|
||||
/cleveroid tankdebug -- Debug pfUI tank conditionals
|
||||
/combotrack show|clear|debug -- Combo point tracking
|
||||
```
|
||||
|
||||
---
|
||||
@@ -702,22 +438,14 @@ The addon checks debuffs on the target:
|
||||
|
||||
**Action Bars:** Blizzard, [pfUI](https://github.com/jrc13245/pfUI), Bongos, Discord Action Bars
|
||||
|
||||
**Integrations:** SP_SwingTimer, TWThreat, TimeToKill, QuickHeal, Cursive (with mouseover support for DoT timer bars), ClassicFocus, SuperMacro, MonkeySpeed
|
||||
**Integrations:** [SP_SwingTimer](https://github.com/jrc13245/SP_SwingTimer), [TWThreat](https://github.com/MarcelineVQ/TWThreat), [TimeToKill](https://github.com/jrc13245/TimeToKill), [QuickHeal](https://github.com/jrc13245/QuickHeal), [Cursive](https://github.com/pepopo978/Cursive), [ClassicFocus](https://github.com/wtfcolt/Addons-for-Vanilla-1.12.1-CFM/tree/master/ClassicFocus), [SuperMacro](https://github.com/jrc13245/SuperMacro-turtle-SuperWoW), [MonkeySpeed](https://github.com/jrc13245/MonkeySpeed)
|
||||
|
||||
> **Note:** For pfUI users, the [jrc13245/pfUI fork](https://github.com/jrc13245/pfUI) includes native SuperCleveRoidMacros integration for proper cooldown, icon, and tooltip display on conditional macros.
|
||||
|
||||
---
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Unique macro names required (no blanks, duplicates, or spell names)
|
||||
- Reactive abilities must be on action bars for detection
|
||||
- HealComm requires MarcelineVQ's [LunaUnitFrames](https://github.com/MarcelineVQ/LunaUnitFrames) for SuperWoW compatibility
|
||||
|
||||
---
|
||||
|
||||
## Credits
|
||||
|
||||
Based on [CleverMacro](https://github.com/DanielAdolfsson/CleverMacro) and [Roid-Macros](https://github.com/DennisWG/Roid-Macros). Cursive integration by Avitasia.
|
||||
Based on [CleverMacro](https://github.com/DanielAdolfsson/CleverMacro) and [Roid-Macros](https://github.com/DennisWG/Roid-Macros).
|
||||
|
||||
MIT License
|
||||
|
||||
+290
-58
@@ -1972,7 +1972,7 @@ function lib:ShouldApplyDebuffRank(targetGUID, newSpellID)
|
||||
end
|
||||
|
||||
-- Also clean up pfUI's tracking to prevent it from showing old ranks
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
|
||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
|
||||
local pflib = pfUI.api.libdebuff
|
||||
|
||||
if pflib.objects and pflib.objects[targetName] then
|
||||
@@ -2084,10 +2084,9 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)
|
||||
|
||||
lib.objects[guid][spellID] = rec
|
||||
|
||||
-- PFUI INTEGRATION: Inject all tracked debuffs into pfUI's libdebuff
|
||||
-- This allows pfUI to show accurate timers for ALL tracked debuffs (player + other casters)
|
||||
-- For shared debuffs like Sunder Armor, we want pfUI to show timers regardless of who cast it
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName then
|
||||
-- PFUI INTEGRATION: Inject all tracked debuffs into pfUI's libdebuff (pre-7.6 only)
|
||||
-- pfUI 7.6+ handles all duration tracking internally via GetUnitField
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName and not CleveRoids.hasPfUI76 then
|
||||
local pflib = pfUI.api.libdebuff
|
||||
local spellName = SpellInfo(spellID)
|
||||
|
||||
@@ -2351,8 +2350,8 @@ local function SeedUnit(unit)
|
||||
existing.start = GetTime()
|
||||
existing.duration = duration
|
||||
|
||||
-- PFUI INTEGRATION: Inject refreshed timer into pfUI
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName then
|
||||
-- PFUI INTEGRATION: Inject refreshed timer into pfUI (pre-7.6 only)
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName and not CleveRoids.hasPfUI76 then
|
||||
local pflib = pfUI.api.libdebuff
|
||||
local spellName = SpellInfo(spellID)
|
||||
if spellName and pflib.AddEffect then
|
||||
@@ -2440,8 +2439,8 @@ local function SeedUnit(unit)
|
||||
existing.start = GetTime()
|
||||
existing.duration = duration
|
||||
|
||||
-- PFUI INTEGRATION: Inject refreshed timer into pfUI
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName then
|
||||
-- PFUI INTEGRATION: Inject refreshed timer into pfUI (pre-7.6 only)
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName and not CleveRoids.hasPfUI76 then
|
||||
local pflib = pfUI.api.libdebuff
|
||||
local spellName = SpellInfo(spellID)
|
||||
if spellName and pflib.AddEffect then
|
||||
@@ -2714,7 +2713,7 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
|
||||
|
||||
-- DON'T call pfUI's AddEffect - just update the existing entry directly
|
||||
-- pfUI will pick up the new duration through our GetDuration/UnitDebuff hooks
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
local pflib = pfUI.api.libdebuff
|
||||
local ripSpellName = SpellInfo(ripSpellID)
|
||||
local baseName = ripSpellName and string.gsub(ripSpellName, "%s*%(Rank %d+%)", "") or "Rip"
|
||||
@@ -2827,7 +2826,7 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
|
||||
|
||||
-- DON'T call pfUI's AddEffect - just update the existing entry directly
|
||||
-- pfUI will pick up the new duration through our GetDuration/UnitDebuff hooks
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
local pflib = pfUI.api.libdebuff
|
||||
local rakeSpellName = SpellInfo(rakeSpellID)
|
||||
local baseName = rakeSpellName and string.gsub(rakeSpellName, "%s*%(Rank %d+%)", "") or "Rake"
|
||||
@@ -3280,7 +3279,17 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
-- Check after delay: 0.4s for hidden CC (no visible debuff), 0.2s for normal CC
|
||||
-- Hidden CC (e.g., Pounce stun) needs longer delay to wait for "afflicted by" messages
|
||||
local verifyDelay = pending.isHiddenCC and 0.4 or 0.2
|
||||
if elapsed >= verifyDelay then
|
||||
|
||||
-- Drop stale entries (>2s) without recording immunity - inconclusive due to severe lag
|
||||
if elapsed > 2.0 then
|
||||
if debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
_string_format("|cffff6600[CC Stale]|r Dropping stale CC entry for %s on %s (%.1fs old)",
|
||||
pending.ccType or "CC", pending.targetName or "Unknown", elapsed)
|
||||
)
|
||||
end
|
||||
-- Don't add to newPendingList - silently discard
|
||||
elseif elapsed >= verifyDelay then
|
||||
local ccVerified = false
|
||||
local totalDebuffs = 0
|
||||
|
||||
@@ -3290,10 +3299,24 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
ccVerified = true
|
||||
end
|
||||
|
||||
-- SPELL_GO early-exit: Use definitive hit/miss data when available
|
||||
-- This is faster and more reliable than debuff scanning, and handles debuff cap correctly
|
||||
if not ccVerified and pending.spellGoHit then
|
||||
ccVerified = true
|
||||
if debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
_string_format("|cff00ff00[CC Verified via SPELL_GO]|r %s landed on %s - skipping debuff scan",
|
||||
pending.ccType or "CC", pending.targetName or "Unknown")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- CC IMMUNITY VERIFICATION: Check if CC effect actually landed
|
||||
-- Uses hybrid approach: direct spell ID match OR mechanic-based validation
|
||||
-- (CC debuff IDs often differ from cast IDs, e.g., Pounce cast ≠ Pounce Stun debuff)
|
||||
if not ccVerified and hasSuperwow and pending.targetGUID then
|
||||
-- Guard: skip debuff scanning if SPELL_GO already determined outcome
|
||||
if not ccVerified and not pending.spellGoHit and not pending.spellGoMissed
|
||||
and hasSuperwow and pending.targetGUID then
|
||||
-- Skip verification if target is dead (debuffs are removed on death)
|
||||
if _UnitIsDead(pending.targetGUID) then
|
||||
ccVerified = true -- Assume CC landed, can't verify on dead target
|
||||
@@ -3350,6 +3373,13 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
end
|
||||
end
|
||||
|
||||
if not ccVerified and pending.spellGoMissed and debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
_string_format("|cffff6600[CC Missed via SPELL_GO]|r %s missed on %s - checking immunity",
|
||||
pending.ccType or "CC", pending.targetName or "Unknown")
|
||||
)
|
||||
end
|
||||
|
||||
-- If CC didn't land, check if it's immunity or debuff cap
|
||||
if not ccVerified and not _UnitIsDead(pending.targetGUID) then
|
||||
-- totalDebuffs already counted above, reuse it
|
||||
@@ -3392,7 +3422,8 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
|
||||
-- SPLIT CC SPELLS: Skip immunity recording for spells with physical damage + resistable CC
|
||||
-- (e.g., Master Strike) - physical damage lands but CC can be resisted independently
|
||||
local isSplitCCSpell = SPLIT_CC_SPELLS[pending.spellID]
|
||||
-- spellGoHit: SPELL_GO confirmed hit, so CC was resisted independently (not true immunity)
|
||||
local isSplitCCSpell = SPLIT_CC_SPELLS[pending.spellID] or pending.spellGoHit
|
||||
if isSplitCCSpell then
|
||||
if debug then
|
||||
local spellNameDebug = pending.spellName or (_SpellInfo(pending.spellID) or "Unknown")
|
||||
@@ -3485,13 +3516,36 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
)
|
||||
end
|
||||
|
||||
-- Check after 0.2s delay (enough time for server sync)
|
||||
if elapsed >= 0.2 then
|
||||
-- Drop stale entries (>2s) without recording immunity - inconclusive due to severe lag
|
||||
if elapsed > 2.0 then
|
||||
if debug then
|
||||
local spellNameDebug = pending.spellID and _SpellInfo(pending.spellID) or "Unknown"
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
_string_format("|cffff6600[Shared Stale]|r Dropping stale shared entry for %s on %s (%.1fs old)",
|
||||
spellNameDebug, pending.targetName or "Unknown", elapsed)
|
||||
)
|
||||
end
|
||||
-- Don't add to newPendingList - silently discard
|
||||
elseif elapsed >= 0.2 then
|
||||
local debuffVerified = false
|
||||
local totalDebuffs = 0
|
||||
|
||||
-- SPELL_GO early-exit: Use definitive hit/miss data when available
|
||||
if pending.spellGoHit then
|
||||
debuffVerified = true
|
||||
if debug then
|
||||
local spellNameDebug = _SpellInfo(pending.spellID) or "Shared Debuff"
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
_string_format("|cff00ff00[Shared Verified via SPELL_GO]|r %s landed on %s - skipping debuff scan",
|
||||
spellNameDebug, pending.targetName or "Unknown")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- Skip verification if target is dead (debuffs are removed on death)
|
||||
if hasSuperwow and pending.targetGUID then
|
||||
-- Guard: skip debuff scanning if SPELL_GO already determined outcome
|
||||
if not debuffVerified and not pending.spellGoHit and not pending.spellGoMissed
|
||||
and hasSuperwow and pending.targetGUID then
|
||||
if _UnitIsDead(pending.targetGUID) then
|
||||
-- Target died - can't verify immunity, assume debuff landed
|
||||
debuffVerified = true
|
||||
@@ -3545,7 +3599,8 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
|
||||
-- SPLIT CC SPELLS: Skip immunity recording for spells with physical damage + resistable CC
|
||||
-- (e.g., Master Strike) - the physical damage lands but CC can be resisted independently
|
||||
local isSplitCCSpell = SPLIT_CC_SPELLS[pending.spellID] or SPLIT_CC_SPELLS[pending.castSpellID]
|
||||
-- spellGoHit: SPELL_GO confirmed hit, so debuff was resisted independently (not true immunity)
|
||||
local isSplitCCSpell = SPLIT_CC_SPELLS[pending.spellID] or SPLIT_CC_SPELLS[pending.castSpellID] or pending.spellGoHit
|
||||
if isSplitCCSpell then
|
||||
if debug then
|
||||
local spellNameDebug = _SpellInfo(pending.castSpellID or pending.spellID) or "Unknown"
|
||||
@@ -3954,9 +4009,9 @@ ev:SetScript("OnEvent", function()
|
||||
)
|
||||
end
|
||||
|
||||
-- CRITICAL: Update pfUI's duration database directly
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and pfUI.api.libdebuff.debuffs then
|
||||
-- pfUI stores durations by spell name in its debuffs table
|
||||
-- Update pfUI's duration database directly (pre-7.6 only)
|
||||
-- pfUI 7.6+ handles combo durations internally via GetStoredComboPoints()
|
||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff and pfUI.api.libdebuff.debuffs then
|
||||
pfUI.api.libdebuff.debuffs[baseName] = duration
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
@@ -4389,6 +4444,34 @@ ev:SetScript("OnEvent", function()
|
||||
local _, playerGUID = UnitExists("player")
|
||||
local isOurs = (casterGuid == playerGUID)
|
||||
|
||||
-- Annotate pending CC/shared debuffs with SPELL_GO hit/miss outcome
|
||||
-- This data is consumed by the OnUpdate verification loop for early-exit paths
|
||||
if isOurs then
|
||||
-- Annotate pendingCCDebuffs
|
||||
for _, pending in ipairs(lib.pendingCCDebuffs) do
|
||||
if pending and pending.spellID == spellId and pending.targetGUID == targetGuid then
|
||||
if numHit > 0 then
|
||||
pending.spellGoHit = true
|
||||
elseif numMissed > 0 then
|
||||
pending.spellGoMissed = true
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Annotate pendingSharedDebuffs (match against both castSpellID and spellID)
|
||||
for _, pending in ipairs(lib.pendingSharedDebuffs) do
|
||||
if pending and pending.targetGUID == targetGuid
|
||||
and (pending.castSpellID == spellId or pending.spellID == spellId) then
|
||||
if numHit > 0 then
|
||||
pending.spellGoHit = true
|
||||
elseif numMissed > 0 then
|
||||
pending.spellGoMissed = true
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Spell missed - clear pending, mark as failed, track for immunity detection
|
||||
if numHit == 0 and numMissed > 0 then
|
||||
if lib.pendingCasts[targetGuid] then
|
||||
@@ -4482,14 +4565,26 @@ ev:SetScript("OnEvent", function()
|
||||
-- time AURA_CAST fires. Store in pendingCasts for AURA_CAST to use.
|
||||
if isOurs and lib.combopointAbilities and lib.combopointAbilities[spellName] then
|
||||
-- Capture combo points from multiple sources
|
||||
local comboPoints = CleveRoids.GetComboPoints and CleveRoids.GetComboPoints() or 0
|
||||
-- Priority: SPELL_CAST_EVENT (client-side, pre-server) > GetComboPoints > lastComboPoints > ComboPointTracking
|
||||
local comboPoints = 0
|
||||
|
||||
-- If already consumed, use lastComboPoints fallback
|
||||
-- PRIMARY: Check SPELL_CAST_EVENT capture (most reliable - captured before server consumed CP)
|
||||
local pending = CleveRoids.pendingCasts and CleveRoids.pendingCasts[spellId]
|
||||
if pending and pending.comboPoints and pending.comboPoints > 0 then
|
||||
comboPoints = pending.comboPoints
|
||||
end
|
||||
|
||||
-- Fallback: Try GetComboPoints (may still be available)
|
||||
if comboPoints == 0 then
|
||||
comboPoints = CleveRoids.GetComboPoints and CleveRoids.GetComboPoints() or 0
|
||||
end
|
||||
|
||||
-- Fallback: lastComboPoints (from pre-cast hooks or UNIT_CASTEVENT START)
|
||||
if comboPoints == 0 and CleveRoids.lastComboPoints and CleveRoids.lastComboPoints > 0 then
|
||||
comboPoints = CleveRoids.lastComboPoints
|
||||
end
|
||||
|
||||
-- Also check ComboPointTracking for recent data (from /cast hook pre-population)
|
||||
-- Fallback: ComboPointTracking for recent data (from /cast hook pre-population)
|
||||
if comboPoints == 0 and CleveRoids.ComboPointTracking then
|
||||
local tracking = CleveRoids.ComboPointTracking[spellName]
|
||||
if tracking and tracking.combo_points and tracking.combo_points > 0 then
|
||||
@@ -4551,24 +4646,35 @@ ev:SetScript("OnEvent", function()
|
||||
-- Handle combo point abilities for our casts
|
||||
if isOurs and lib.combopointAbilities[spellName] then
|
||||
-- Priority order for combo points:
|
||||
-- 1. pendingCasts (captured by SPELL_GO - most reliable)
|
||||
-- 2. CleveRoids.lastComboPoints (from /cast hook pre-population)
|
||||
-- 3. GetComboPoints() (might be 0 if already consumed)
|
||||
-- 1. SPELL_CAST_EVENT capture (client-side, before server consumes CP)
|
||||
-- 2. pendingCasts from SPELL_GO (captured on spell landing)
|
||||
-- 3. CleveRoids.lastComboPoints (from pre-cast hooks)
|
||||
-- 4. GetComboPoints() (might be 0 if already consumed)
|
||||
local comboPoints = 0
|
||||
local comboSource = "none"
|
||||
|
||||
-- Check pendingCasts first (populated by SPELL_GO)
|
||||
if lib.pendingCasts[targetGuid] and lib.pendingCasts[targetGuid][spellName] then
|
||||
-- PRIMARY: Check SPELL_CAST_EVENT capture (most reliable - client-side pre-server)
|
||||
local castPending = CleveRoids.pendingCasts and CleveRoids.pendingCasts[spellId]
|
||||
if castPending and castPending.comboPoints and castPending.comboPoints > 0 then
|
||||
comboPoints = castPending.comboPoints
|
||||
comboSource = "SPELL_CAST_EVENT"
|
||||
end
|
||||
|
||||
-- Fallback: Check lib.pendingCasts (populated by SPELL_GO)
|
||||
if comboPoints == 0 and lib.pendingCasts[targetGuid] and lib.pendingCasts[targetGuid][spellName] then
|
||||
local pending = lib.pendingCasts[targetGuid][spellName]
|
||||
local age = GetTime() - (pending.capturedAt or 0)
|
||||
if pending.comboPoints and pending.comboPoints > 0 and age < 2.0 then
|
||||
comboPoints = pending.comboPoints
|
||||
comboSource = "SPELL_GO"
|
||||
-- Clean up after use
|
||||
lib.pendingCasts[targetGuid][spellName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Clean up lib.pendingCasts after reading (regardless of which source won)
|
||||
if lib.pendingCasts[targetGuid] and lib.pendingCasts[targetGuid][spellName] then
|
||||
lib.pendingCasts[targetGuid][spellName] = nil
|
||||
end
|
||||
|
||||
-- Fallback to lastComboPoints
|
||||
if comboPoints == 0 and CleveRoids.lastComboPoints and CleveRoids.lastComboPoints > 0 then
|
||||
comboPoints = CleveRoids.lastComboPoints
|
||||
@@ -5096,7 +5202,7 @@ evLearn:SetScript("OnEvent", function()
|
||||
|
||||
-- Sync refresh to pfUI
|
||||
local targetName = lib.guidToName[targetGUID]
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
|
||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
|
||||
local pflib = pfUI.api.libdebuff
|
||||
local spellName = SpellInfo(flameShockID)
|
||||
if spellName and pflib.AddEffect then
|
||||
@@ -5262,17 +5368,14 @@ evJudgement:SetScript("OnEvent", function()
|
||||
)
|
||||
end
|
||||
|
||||
-- Also sync to pfUI if it's loaded
|
||||
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
-- Also sync to pfUI if it's loaded (pre-7.6 only)
|
||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||
local targetName = lib.guidToName[targetGUID] or UnitName("target")
|
||||
local targetLevel = UnitLevel("target") or 0
|
||||
local spellName = SpellInfo(spellID)
|
||||
|
||||
if spellName and targetName then
|
||||
-- Remove rank from spell name to match pfUI's format
|
||||
local effectName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
|
||||
|
||||
-- Refresh in pfUI's tracking
|
||||
pfUI.api.libdebuff:AddEffect(targetName, targetLevel, effectName, rec.duration, "player")
|
||||
|
||||
if CleveRoids.debug then
|
||||
@@ -7896,9 +7999,10 @@ function CleveRoids.ParseReactiveCombatLog()
|
||||
local _, targetGUID = UnitExists("target")
|
||||
|
||||
-- Check each reactive ability's trigger patterns
|
||||
-- NOTE: Overpower (enemy_dodge) ALWAYS uses combat log text parsing, even when SPELL_GO
|
||||
-- events are available. SPELL_GO only provides hit/miss COUNTS, not miss TYPES, so it
|
||||
-- cannot distinguish dodge (Overpower proc) from parry/block/resist (no Overpower proc).
|
||||
for spellName, config in pairs(reactivePatterns) do
|
||||
-- Check if this is a known reactive spell (skip hasSpell check - spellbook may not be indexed yet)
|
||||
-- If player uses [reactive:SpellName], they have the ability
|
||||
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells[spellName] then
|
||||
for _, pattern in ipairs(config.patterns) do
|
||||
if strfind(message, pattern) then
|
||||
@@ -7908,7 +8012,6 @@ function CleveRoids.ParseReactiveCombatLog()
|
||||
CleveRoids.SetReactiveProc(spellName, duration, guid)
|
||||
|
||||
-- For Overpower (enemy dodge), also update LastSwing so [lastswing:dodge] works
|
||||
-- This handles ability dodges that AUTO_ATTACK events don't capture
|
||||
if config.type == "enemy_dodge" and CleveRoids.LastSwing then
|
||||
CleveRoids.LastSwing.timestamp = GetTime()
|
||||
CleveRoids.LastSwing.victimState = VICTIMSTATE_DODGE_REACTIVE
|
||||
@@ -7964,10 +8067,19 @@ end
|
||||
-- Uses native events for dodge/parry/block detection when available.
|
||||
-- Falls back to combat log parsing for older Nampower versions.
|
||||
|
||||
-- VictimState constants from NampowerAPI (copied for performance)
|
||||
-- VictimState constants (from AUTO_ATTACK / SPELL_GO events)
|
||||
local VICTIMSTATE_UNAFFECTED = 0 -- Generic miss (seen with HITINFO_MISS)
|
||||
local VICTIMSTATE_NORMAL = 1 -- Hit landed
|
||||
local VICTIMSTATE_DODGE = 2
|
||||
local VICTIMSTATE_PARRY = 3
|
||||
local VICTIMSTATE_INTERRUPT = 4
|
||||
local VICTIMSTATE_BLOCKS = 5
|
||||
local VICTIMSTATE_EVADES = 6
|
||||
local VICTIMSTATE_IS_IMMUNE = 7
|
||||
local VICTIMSTATE_DEFLECTS = 8
|
||||
|
||||
-- HitInfo constant for SPELL_GO miss tracking
|
||||
local HITINFO_MISS = 16 -- 0x10
|
||||
|
||||
-- Track if we're using Nampower events (set during initialization)
|
||||
CleveRoids.usingNampowerAutoAttack = false
|
||||
@@ -8081,12 +8193,44 @@ if GetNampowerVersion then
|
||||
end
|
||||
end
|
||||
|
||||
-- Always register combat log events as fallback (or primary if no Nampower)
|
||||
reactiveFrame:RegisterEvent("RAW_COMBATLOG")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS")
|
||||
-- Check for Nampower v2.25+ SPELL_GO events (LastSwing tracking for yellow attack misses)
|
||||
-- NOTE: SPELL_GO only provides hit/miss counts, not miss types, so Overpower still needs
|
||||
-- combat log text parsing. SPELL_GO is used as a gate to avoid parsing combat log on every
|
||||
-- successful spell hit - only parse when SPELL_GO reports a miss.
|
||||
local hasSpellGoEvents = false
|
||||
if GetNampowerVersion then
|
||||
local npMajor, npMinor = GetNampowerVersion()
|
||||
if npMajor > 2 or (npMajor == 2 and npMinor >= 25) then
|
||||
hasSpellGoEvents = true
|
||||
reactiveFrame:RegisterEvent("SPELL_GO_SELF")
|
||||
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nampower]|r Using SPELL_GO events for yellow attack LastSwing tracking (v2.25+)")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Register combat log events for reactive abilities
|
||||
-- Outgoing dodge detection (Overpower) uses combat log text parsing because SPELL_GO only
|
||||
-- provides hit/miss counts, not miss types (dodge vs parry vs block vs resist). However,
|
||||
-- with SPELL_GO available, CHAT_MSG_SPELL_SELF_DAMAGE parsing is gated behind a miss window
|
||||
-- so we skip parsing on every successful spell hit (see OnEvent handler below).
|
||||
-- AUTO_ATTACK events handle white swing avoidance; combat log handles yellow ability dodges.
|
||||
if hasAutoAttackEvents then
|
||||
-- Incoming avoidance (Riposte/Revenge)
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS")
|
||||
-- Outgoing dodge detection (Overpower) - gated by SPELL_GO miss window when available
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
else
|
||||
-- Full combat log fallback
|
||||
reactiveFrame:RegisterEvent("RAW_COMBATLOG")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES")
|
||||
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS")
|
||||
end
|
||||
|
||||
reactiveFrame:SetScript("OnEvent", function()
|
||||
-- ========================================================================
|
||||
@@ -8122,18 +8266,106 @@ reactiveFrame:SetScript("OnEvent", function()
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- COMBAT LOG PARSING FOR REACTIVE ABILITIES
|
||||
-- NAMPOWER v2.25+ SPELL_GO EVENTS (yellow attack LastSwing tracking)
|
||||
-- ========================================================================
|
||||
-- NOTE: This must ALWAYS run, even when Nampower AUTO_ATTACK events are available.
|
||||
-- AUTO_ATTACK events only fire for white (auto-attack) swings, NOT for yellow
|
||||
-- (ability) damage like Mortal Strike, Heroic Strike, etc. being dodged/parried.
|
||||
-- Combat log parsing catches ability dodges that AUTO_ATTACK misses.
|
||||
-- The AUTO_ATTACK handlers above already `return` early, so no double-processing.
|
||||
if event == "RAW_COMBATLOG" or
|
||||
event == "CHAT_MSG_COMBAT_SELF_MISSES" or
|
||||
event == "CHAT_MSG_SPELL_SELF_DAMAGE" or
|
||||
event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES" or
|
||||
event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS" then
|
||||
-- Two-step: SPELL_CAST_EVENT (success=1) → SPELL_GO_SELF (hit/miss outcome)
|
||||
-- Updates LastSwing state for yellow ability misses. Overpower detection still uses
|
||||
-- combat log text parsing (SPELL_GO only has hit/miss counts, not types like dodge).
|
||||
-- SPELL_GO_SELF params: itemId, spellId, casterGuid, targetGuid, castFlags, numTargetsHit, numTargetsMissed
|
||||
if event == "SPELL_GO_SELF" then
|
||||
local spellId = arg2
|
||||
local targetGuid = arg4
|
||||
local numHit = arg6 or 0
|
||||
local numMissed = arg7 or 0
|
||||
|
||||
-- Step 1: Validate against SPELL_CAST_EVENT (confirms player-initiated, successful cast)
|
||||
local pending = CleveRoids.pendingCasts and CleveRoids.pendingCasts[spellId]
|
||||
if not pending or pending.consumed then
|
||||
return -- No matching SPELL_CAST_EVENT with success=1, or already processed
|
||||
end
|
||||
|
||||
-- Mark pending cast as consumed with timestamp instead of deleting immediately.
|
||||
-- Other handlers (libdebuff SPELL_GO, AURA_CAST, ComboPointTracker) on different
|
||||
-- frames may still need to read this data. Stale entries are cleaned up on next
|
||||
-- SPELL_CAST_EVENT for the same spellId (overwritten) or expire naturally.
|
||||
pending.consumed = true
|
||||
pending.consumedAt = GetTime()
|
||||
|
||||
-- Skip channels and targeting spells (not melee/ranged attacks)
|
||||
-- CastType: NORMAL=1, NON_GCD=2, ON_SWING=3, CHANNEL=4, TARGETING=5, TARGETING_NON_GCD=6
|
||||
if pending.castType and pending.castType >= 4 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Step 2: Check hit/miss outcome for LastSwing tracking
|
||||
-- NOTE: SPELL_GO only provides hit/miss COUNTS, not miss TYPES (dodge vs parry vs
|
||||
-- block vs resist). Overpower detection is handled by combat log text parsing which
|
||||
-- can distinguish "dodged" from other avoidance types. We only update LastSwing here
|
||||
-- as a generic miss indicator (combat log overwrites with specific type if dodge).
|
||||
if numMissed >= 1 and numHit == 0 then
|
||||
-- Use current target GUID if SPELL_GO targetGuid is empty
|
||||
local procTarget = targetGuid
|
||||
if not procTarget or procTarget == "0x0000000000000000" then
|
||||
procTarget = pending.targetGuid
|
||||
if not procTarget or procTarget == "0x0000000000000000" then
|
||||
local _, currentTargetGUID = UnitExists("target")
|
||||
procTarget = currentTargetGUID
|
||||
end
|
||||
end
|
||||
|
||||
-- Update LastSwing for yellow miss (victimState unknown from SPELL_GO, use UNAFFECTED)
|
||||
-- Don't overwrite AUTO_ATTACK data from the same frame (on-swing abilities fire both)
|
||||
-- Combat log handler will overwrite with specific type (dodge/parry/etc.) if available
|
||||
if CleveRoids.LastSwing and CleveRoids.LastSwing.timestamp ~= GetTime() then
|
||||
CleveRoids.LastSwing.timestamp = GetTime()
|
||||
CleveRoids.LastSwing.hitInfo = HITINFO_MISS
|
||||
CleveRoids.LastSwing.victimState = VICTIMSTATE_UNAFFECTED
|
||||
CleveRoids.LastSwing.damage = 0
|
||||
CleveRoids.LastSwing.blockedAmount = 0
|
||||
CleveRoids.LastSwing.absorbAmount = 0
|
||||
CleveRoids.LastSwing.resistAmount = 0
|
||||
CleveRoids.LastSwing.targetGuid = procTarget
|
||||
end
|
||||
|
||||
-- Gate combat log parsing: set miss window so CHAT_MSG_SPELL_SELF_DAMAGE
|
||||
-- only calls ParseReactiveCombatLog when we know a yellow miss occurred.
|
||||
-- This avoids parsing every successful spell hit message.
|
||||
CleveRoids._yellowMissWindow = GetTime()
|
||||
|
||||
if CleveRoids.debug then
|
||||
local spellName = SpellInfo and SpellInfo(spellId) or tostring(spellId)
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
string.format("|cff00ff00[SPELL_GO]|r Yellow miss detected - %s (hit=%d, miss=%d, castType=%s) - deferring to combat log for type",
|
||||
spellName, numHit, numMissed, tostring(pending.castType))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- COMBAT LOG TEXT PARSING FOR REACTIVE ABILITIES
|
||||
-- ========================================================================
|
||||
-- Handles: Overpower (outgoing dodge), Riposte (incoming parry), Revenge (incoming avoid).
|
||||
-- SPELL_GO provides hit/miss counts but not types, so combat log is still needed to
|
||||
-- distinguish dodge (Overpower) from parry/block/resist. But we only parse when SPELL_GO
|
||||
-- told us a miss occurred, avoiding overhead on every successful spell hit.
|
||||
if event == "CHAT_MSG_SPELL_SELF_DAMAGE" then
|
||||
-- Yellow ability avoidance (e.g., "Your Mortal Strike was dodged by Target")
|
||||
-- With SPELL_GO events: only parse when a yellow miss was recently detected.
|
||||
-- Without SPELL_GO: always parse (fallback for older Nampower).
|
||||
if hasSpellGoEvents then
|
||||
local missWindow = CleveRoids._yellowMissWindow
|
||||
if not missWindow or (GetTime() - missWindow) > 1.0 then
|
||||
return -- No recent yellow miss from SPELL_GO, skip parsing
|
||||
end
|
||||
end
|
||||
CleveRoids.ParseReactiveCombatLog()
|
||||
elseif event == "RAW_COMBATLOG" or
|
||||
event == "CHAT_MSG_COMBAT_SELF_MISSES" or
|
||||
event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES" or
|
||||
event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS" then
|
||||
CleveRoids.ParseReactiveCombatLog()
|
||||
end
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user