mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
fix debuff timer checks, nodebuff existence checks with ranked spells, and split type spells having both types immune checked for cast, improve readme
This commit is contained in:
+22
-3
@@ -714,6 +714,11 @@ local function _get_debuff_timeleft(unitToken, auraName)
|
||||
-- Defensive: verify libdebuff is a table before accessing properties
|
||||
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
|
||||
|
||||
-- Strip rank suffix for consistent matching (e.g., "Rake(Rank 4)" -> "Rake")
|
||||
if auraName then
|
||||
auraName = string.gsub(auraName, "%s*%(%s*Rank%s+%d+%s*%)", "")
|
||||
end
|
||||
|
||||
-- SuperWoW path: GUID-based lookup
|
||||
-- SuperWoW debuff slots: 1-16 are regular debuffs, 17-48 overflow to buff slots 1-32
|
||||
if CleveRoids.hasSuperwow and lib then
|
||||
@@ -727,7 +732,9 @@ local function _get_debuff_timeleft(unitToken, auraName)
|
||||
-- Only break for slots 1-16 (regular debuffs are dense)
|
||||
-- For overflow slots 17-48, nil means "regular buff filtered out", not "end of list"
|
||||
if not effect and i <= 16 then break end
|
||||
if effect and effect == auraName and timeleft and timeleft >= 0 then
|
||||
-- Strip rank from effect name for comparison
|
||||
local effectBase = effect and string.gsub(effect, "%s*%(%s*Rank%s+%d+%s*%)", "")
|
||||
if effectBase and effectBase == auraName and timeleft and timeleft >= 0 then
|
||||
return timeleft, duration
|
||||
end
|
||||
end
|
||||
@@ -741,7 +748,9 @@ local function _get_debuff_timeleft(unitToken, auraName)
|
||||
-- Only break for slots 1-16 (regular debuffs are dense)
|
||||
-- For overflow slots 17-48, nil means "regular buff filtered out", not "end of list"
|
||||
if not effect and idx <= 16 then break end
|
||||
if effect and effect == auraName and timeleft and timeleft >= 0 then
|
||||
-- Strip rank from effect name for comparison
|
||||
local effectBase = effect and string.gsub(effect, "%s*%(%s*Rank%s+%d+%s*%)", "")
|
||||
if effectBase and effectBase == auraName and timeleft and timeleft >= 0 then
|
||||
return timeleft, duration
|
||||
end
|
||||
end
|
||||
@@ -1812,6 +1821,11 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
|
||||
local stacks, remaining
|
||||
local i = isPlayer and 0 or 1
|
||||
|
||||
-- Strip rank suffix for consistent matching (e.g., "Faerie Fire (Feral)(Rank 4)" -> "Faerie Fire (Feral)")
|
||||
if args.name then
|
||||
args.name = string.gsub(args.name, "%s*%(%s*Rank%s+%d+%s*%)", "")
|
||||
end
|
||||
|
||||
-- PERFORMANCE: Cache lowercased search name to avoid repeated string.lower calls
|
||||
local searchName = args.name and _string_lower(args.name)
|
||||
|
||||
@@ -1926,6 +1940,9 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
|
||||
end
|
||||
if not args.name then return false end
|
||||
|
||||
-- Strip rank suffix for consistent matching (e.g., "Faerie Fire (Feral)(Rank 4)" -> "Faerie Fire (Feral)")
|
||||
args.name = string.gsub(args.name, "%s*%(%s*Rank%s+%d+%s*%)", "")
|
||||
|
||||
local found = false
|
||||
local texture, stacks, spellID, remaining
|
||||
local i
|
||||
@@ -2209,7 +2226,9 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
|
||||
-- Only break for slots 1-16 (regular debuffs are dense)
|
||||
-- For overflow slots 17-48, nil means "regular buff filtered out", not "end of list"
|
||||
if not effect and idx <= 16 then break end
|
||||
if effect and effect == args.name then
|
||||
-- Strip rank from effect name for comparison
|
||||
local effectBase = effect and string.gsub(effect, "%s*%(%s*Rank%s+%d+%s*%)", "")
|
||||
if effectBase and effectBase == args.name then
|
||||
local shouldSkip = false
|
||||
|
||||
-- Auto-detect: If args.mine not specified and this is a personal debuff, only match player casts
|
||||
|
||||
@@ -162,13 +162,16 @@ All conditionals support negation with `no` prefix (e.g., `[nocombat]`, `[nobuff
|
||||
|-------------|---------|-------------|
|
||||
| `cc` | `[cc]` `[cc:stun/fear]` | Target has CC effect |
|
||||
| `mycc` | `[mycc]` `[mycc:silence]` | Player has CC effect |
|
||||
| `immune` | `[immune:fire]` `[immune:stun]` | School/CC immunity |
|
||||
| `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:** stun, fear, root, snare/slow, sleep, charm, polymorph, banish, horror, disorient, silence, disarm, daze, freeze, shackle
|
||||
|
||||
**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
|
||||
|
||||
### Addon Integrations
|
||||
| Conditional | Addon | Example | Description |
|
||||
|-------------|-------|---------|-------------|
|
||||
@@ -308,6 +311,156 @@ Automatically adjusts tracked durations for talents (Imp. Gouge, Taste for Blood
|
||||
|
||||
---
|
||||
|
||||
## Immunity Tracking Guide
|
||||
|
||||
The addon auto-learns NPC immunities from combat. When a spell fails with "immune", the addon remembers it for that NPC.
|
||||
|
||||
### Using `[noimmune]`
|
||||
|
||||
| Usage | What it checks |
|
||||
|-------|----------------|
|
||||
| `[noimmune]` | Auto-detects spell's school from action |
|
||||
| `[noimmune:fire]` | Fire immunity specifically |
|
||||
| `[noimmune:bleed]` | Bleed immunity specifically |
|
||||
| `[noimmune:stun]` | Stun CC immunity |
|
||||
|
||||
### Common Examples
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Split Damage Spells (Rake, Pounce, Garrote)
|
||||
|
||||
These spells have an initial physical hit + a bleed DoT. The addon **automatically checks BOTH immunities**:
|
||||
|
||||
```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
|
||||
|
||||
-- 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
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings
|
||||
|
||||
```lua
|
||||
|
||||
+103
-8
@@ -4981,6 +4981,26 @@ local spellSchoolCache = {}
|
||||
local function GetSpellSchool(spellName, spellID)
|
||||
if not spellName and not spellID then return nil end
|
||||
|
||||
-- Get base name early for split damage spell check
|
||||
local baseName = nil
|
||||
if spellName then
|
||||
baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
|
||||
elseif spellID and SpellInfo then
|
||||
local fullName = SpellInfo(spellID)
|
||||
if fullName then
|
||||
baseName = string.gsub(fullName, "%s*%(.-%)%s*$", "")
|
||||
end
|
||||
end
|
||||
|
||||
-- PRIORITY 0 (HIGHEST): Split damage spells ALWAYS use their debuff school for immunity checks
|
||||
-- This prevents the initial physical hit from being learned and masking bleed immunity.
|
||||
-- Example: Rake's initial hit is physical, but the DoT is bleed. On bleed-immune targets,
|
||||
-- the DoT never ticks, so spellSchoolMapping stays "physical" - which would miss bleed immunity!
|
||||
if baseName and SPLIT_DAMAGE_SPELLS[baseName] then
|
||||
local school = SPLIT_DAMAGE_SPELLS[baseName].debuff
|
||||
return school
|
||||
end
|
||||
|
||||
-- PRIORITY 1: Use learned school from Nampower damage events (most accurate)
|
||||
if spellID and CleveRoids.spellSchoolMapping[spellID] then
|
||||
return CleveRoids.spellSchoolMapping[spellID]
|
||||
@@ -5003,20 +5023,17 @@ local function GetSpellSchool(spellName, spellID)
|
||||
|
||||
if not spellName then return nil end
|
||||
|
||||
-- Remove rank information for cache consistency
|
||||
local baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
|
||||
-- Remove rank information for cache consistency (may already be set from above)
|
||||
if not baseName then
|
||||
baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
|
||||
end
|
||||
|
||||
-- PRIORITY 3: Check cache
|
||||
if spellSchoolCache[baseName] then
|
||||
return spellSchoolCache[baseName]
|
||||
end
|
||||
|
||||
-- PRIORITY 4: Check if this is a split damage spell (return debuff school by default)
|
||||
if SPLIT_DAMAGE_SPELLS[baseName] then
|
||||
local school = SPLIT_DAMAGE_SPELLS[baseName].debuff
|
||||
spellSchoolCache[baseName] = school
|
||||
return school
|
||||
end
|
||||
-- PRIORITY 4: SPLIT_DAMAGE_SPELLS already checked at Priority 0
|
||||
|
||||
-- PRIORITY 5: Check known non-damaging spells
|
||||
-- These won't be learned via damage events and need explicit mapping
|
||||
@@ -5939,6 +5956,84 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool)
|
||||
else
|
||||
-- Input is a spell name, need to determine its school
|
||||
checkSpellName = spellOrSchool -- Save the spell name for unknown school check
|
||||
|
||||
-- Get base name for split damage spell check
|
||||
local baseName = string.gsub(spellOrSchool, "%s*%(.-%)%s*$", "")
|
||||
|
||||
-- SPLIT DAMAGE SPELLS: Check BOTH initial and debuff schools
|
||||
-- If target is immune to EITHER component, skip the spell
|
||||
if SPLIT_DAMAGE_SPELLS[baseName] then
|
||||
local splitData = SPLIT_DAMAGE_SPELLS[baseName]
|
||||
local initialSchool = splitData.initial
|
||||
local debuffSchool = splitData.debuff
|
||||
|
||||
-- Check initial school immunity (e.g., physical for Rake's initial hit)
|
||||
local initialImmune = false
|
||||
if initialSchool and CleveRoids_ImmunityData[initialSchool] then
|
||||
local initialImmunityData = CleveRoids_ImmunityData[initialSchool][targetName]
|
||||
if initialImmunityData == true then
|
||||
initialImmune = true
|
||||
elseif type(initialImmunityData) == "table" and not initialImmunityData.buff then
|
||||
initialImmune = true
|
||||
elseif type(initialImmunityData) == "table" and initialImmunityData.buff then
|
||||
-- Check if target has the immunity-granting buff
|
||||
if CleveRoids.hasSuperwow then
|
||||
for i = 1, 32 do
|
||||
local texture, stacks, spellID = UnitBuff(unitId, i)
|
||||
if not texture then break end
|
||||
if spellID then
|
||||
local buffName = SpellInfo(spellID)
|
||||
if buffName and buffName == initialImmunityData.buff then
|
||||
initialImmune = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check debuff school immunity (e.g., bleed for Rake's DoT)
|
||||
local debuffImmune = false
|
||||
if debuffSchool and CleveRoids_ImmunityData[debuffSchool] then
|
||||
local debuffImmunityData = CleveRoids_ImmunityData[debuffSchool][targetName]
|
||||
if debuffImmunityData == true then
|
||||
debuffImmune = true
|
||||
elseif type(debuffImmunityData) == "table" and not debuffImmunityData.buff then
|
||||
debuffImmune = true
|
||||
elseif type(debuffImmunityData) == "table" and debuffImmunityData.buff then
|
||||
-- Check if target has the immunity-granting buff
|
||||
if CleveRoids.hasSuperwow then
|
||||
for i = 1, 32 do
|
||||
local texture, stacks, spellID = UnitBuff(unitId, i)
|
||||
if not texture then break end
|
||||
if spellID then
|
||||
local buffName = SpellInfo(spellID)
|
||||
if buffName and buffName == debuffImmunityData.buff then
|
||||
debuffImmune = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Return true if immune to EITHER component
|
||||
if initialImmune or debuffImmune then
|
||||
if CleveRoids.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
string.format("|cffff6600[Immunity Check]|r %s on %s: initial(%s)=%s, debuff(%s)=%s",
|
||||
baseName, targetName,
|
||||
initialSchool, initialImmune and "IMMUNE" or "ok",
|
||||
debuffSchool, debuffImmune and "IMMUNE" or "ok")
|
||||
)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
school = GetSpellSchool(spellOrSchool)
|
||||
if not school then
|
||||
school = "unknown" -- If we can't determine school, check unknown category
|
||||
|
||||
Reference in New Issue
Block a user