diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 8219c67..94e1737 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -489,7 +489,7 @@ function CleveRoids.TrackComboPointCastByID(spellID, targetGUID) end else -- Second, check if name-based tracking has recent data for this spell - local spellName = SpellInfo(spellID) + local spellName = GetSpellRecField(spellID, "name") if spellName then -- Remove rank info for comparison local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") @@ -683,7 +683,7 @@ if _G.UseAction then local spellName = nil if actionType == "SPELL" and actionID then - spellName = SpellInfo(actionID) + spellName = GetSpellRecField(actionID, "name") end if currentCP and currentCP > 0 then diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua index 91d276c..a7caead 100644 --- a/Compatibility/pfUI.lua +++ b/Compatibility/pfUI.lua @@ -99,7 +99,7 @@ local function GetCarnageOverride(effect) end for spellID, override in pairs(CleveRoids.carnageDurationOverrides) do - local spellName = SpellInfo(spellID) + local spellName = GetSpellRecField(spellID, "name") if spellName then local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") if baseName == effect and override.timestamp and (GetTime() - override.timestamp) < 5 then @@ -190,8 +190,7 @@ function Extension.HookPfUILibdebuff() -- Check if this is the current target if UnitName("target") == unit then - local _, guid = UnitExists("target") - unitGUID = CleveRoids.NormalizeGUID(guid) + unitGUID = CleveRoids.GetGUID("target") end -- If we couldn't match to current target, check guidToName mapping @@ -210,7 +209,7 @@ function Extension.HookPfUILibdebuff() for spellID, rec in pairs(CleveRoids.libdebuff.objects[unitGUID]) do if rec and rec.start and rec.duration then -- Get spell name for this ID - local spellName = SpellInfo(spellID) + local spellName = GetSpellRecField(spellID, "name") if spellName then local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") if baseName == effect then @@ -360,7 +359,7 @@ function Extension.SyncComboDurationToPfUI(guid, spellID, duration) local unitLevel = 0 -- Check if this is the current target - local _, targetGUID = UnitExists("target") + local targetGUID = CleveRoids.GetGUID("target") if targetGUID == guid then unitName = UnitName("target") unitLevel = UnitLevel("target") or 0 @@ -381,7 +380,7 @@ function Extension.SyncComboDurationToPfUI(guid, spellID, duration) end -- Get spell name from spell ID - local spellName = SpellInfo(spellID) + local spellName = GetSpellRecField(spellID, "name") if not spellName then if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[pfUI Sync]|r Could not find spell name for ID " .. spellID) diff --git a/Conditionals.lua b/Conditionals.lua index aa4d508..fb8d92d 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -48,23 +48,30 @@ function CleveRoids.GetCachedTime() end function CleveRoids.GetCachedPlayerHealthPercent() - local max = UnitHealthMax("player") - return max > 0 and (100 * UnitHealth("player") / max) or 0 + local API = CleveRoids.NampowerAPI + local hp = API and API.GetUnitHealth and API.GetUnitHealth("player") or UnitHealth("player") + local max = API and API.GetUnitMaxHealth and API.GetUnitMaxHealth("player") or UnitHealthMax("player") + return max > 0 and (100 * hp / max) or 0 end function CleveRoids.GetCachedPlayerPowerPercent() - local max = UnitManaMax("player") - return max > 0 and (100 * UnitMana("player") / max) or 0 + local API = CleveRoids.NampowerAPI + local power = API and API.GetUnitPower and API.GetUnitPower("player") or UnitMana("player") + local max = API and API.GetUnitMaxPower and API.GetUnitMaxPower("player") or UnitManaMax("player") + return max > 0 and (100 * power / max) or 0 end function CleveRoids.GetCachedPlayerPower() - return UnitMana("player") + local API = CleveRoids.NampowerAPI + return API and API.GetUnitPower and API.GetUnitPower("player") or UnitMana("player") end function CleveRoids.GetCachedTargetHealthPercent() if not UnitExists("target") then return 0 end - local max = UnitHealthMax("target") - return max > 0 and (100 * UnitHealth("target") / max) or 0 + local API = CleveRoids.NampowerAPI + local hp = API and API.GetUnitHealth and API.GetUnitHealth("target") or UnitHealth("target") + local max = API and API.GetUnitMaxHealth and API.GetUnitMaxHealth("target") or UnitHealthMax("target") + return max > 0 and (100 * hp / max) or 0 end -- Cooldown uses original function directly @@ -82,7 +89,7 @@ function CleveRoids.GetMacroThrottle() return 0 end -- ============================================================================ -- PERFORMANCE: Cache spell name -> spell ID mappings for debuff lookups --- This avoids iterating personalDebuffs/sharedDebuffs and calling SpellInfo() repeatedly +-- This avoids iterating personalDebuffs/sharedDebuffs and calling GetSpellRecField() repeatedly local _spellNameToIDs = {} -- [spellName] = { spellID1, spellID2, ... } local _spellNameToIDsBuilt = false @@ -98,7 +105,7 @@ local function BuildSpellNameCache() if lib.personalDebuffs then for sid, _ in pairs(lib.personalDebuffs) do - local name = SpellInfo(sid) + local name = GetSpellRecField(sid, "name") if name then name = gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "") if not _spellNameToIDs[name] then @@ -111,7 +118,7 @@ local function BuildSpellNameCache() if lib.sharedDebuffs then for sid, _ in pairs(lib.sharedDebuffs) do - local name = SpellInfo(sid) + local name = GetSpellRecField(sid, "name") if name then name = gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "") if not _spellNameToIDs[name] then @@ -468,34 +475,24 @@ local stat_checks = { attackpower = function() local base, pos, neg = UnitAttackPower("player"); return base + pos + neg end, rap = function() local base, pos, neg = UnitRangedAttackPower("player"); return base + pos + neg end, rangedattackpower = function() local base, pos, neg = UnitRangedAttackPower("player"); return base + pos + neg end, - healing = function() return GetBonusHealing() end, - healingpower = function() return GetBonusHealing() end, + healing = function() local _, h = CleveRoids.NampowerAPI.GetSpellPower(); return h or 0 end, + healingpower = function() local _, h = CleveRoids.NampowerAPI.GetSpellPower(); return h or 0 end, - -- Bonus Spell Damage by School - arcane_power = function() return GetSpellBonusDamage(6) end, - fire_power = function() return GetSpellBonusDamage(3) end, - frost_power = function() return GetSpellBonusDamage(4) end, - nature_power = function() return GetSpellBonusDamage(2) end, - shadow_power = function() return GetSpellBonusDamage(5) end, + -- Bonus Spell Damage by School (Nampower v2.31+ GetSpellPower) + -- GetSpellPower() returns: physical, holy, fire, nature, frost, shadow, arcane + arcane_power = function() return select(7, CleveRoids.NampowerAPI.GetSpellPower()) or 0 end, + fire_power = function() return select(3, CleveRoids.NampowerAPI.GetSpellPower()) or 0 end, + frost_power = function() return select(5, CleveRoids.NampowerAPI.GetSpellPower()) or 0 end, + nature_power = function() return select(4, CleveRoids.NampowerAPI.GetSpellPower()) or 0 end, + shadow_power = function() return select(6, CleveRoids.NampowerAPI.GetSpellPower()) or 0 end, -- Highest spell power across all schools - -- Uses Nampower v2.31+ GetSpellPower when available (single call), falls back to per-school spell_power = function() - local API = CleveRoids.NampowerAPI - if API and API.features and API.features.hasGetSpellPower then - local p, h, fi, n, fr, s, a = API.GetSpellPower() - if p then - return math.max(p, h, fi, n, fr, s, a) - end + local p, h, fi, n, fr, s, a = CleveRoids.NampowerAPI.GetSpellPower() + if p then + return math.max(p, h, fi, n, fr, s, a) end - -- Fallback: max of per-school GetSpellBonusDamage calls - return math.max( - GetSpellBonusDamage(2) or 0, - GetSpellBonusDamage(3) or 0, - GetSpellBonusDamage(4) or 0, - GetSpellBonusDamage(5) or 0, - GetSpellBonusDamage(6) or 0 - ) + return 0 end, -- Defensive Stats @@ -940,7 +937,7 @@ function CleveRoids.FindAllCasterAuraByName(targetGuid, searchName) local remaining = auraData.duration + auraData.start - now if remaining > 0 then -- Get spell name and compare - local spellName = SpellInfo(spellId) + local spellName = GetSpellRecField(spellId, "name") if spellName then -- Strip rank and compare lowercase local baseName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "") @@ -994,7 +991,7 @@ end local function OnAutoAttackOther(attackerGuid, targetGuid, totalDamage, hitInfo, victimState, subDamageCount, blockedAmount, totalAbsorb, totalResist) -- Check if player is the attacker - local _, playerGuid = UnitExists("player") + local playerGuid = CleveRoids.GetGUID("player") if not playerGuid or attackerGuid ~= playerGuid then return -- Not player's attack, ignore end @@ -1007,6 +1004,44 @@ local function OnAutoAttackOther(attackerGuid, targetGuid, totalDamage, hitInfo, CleveRoids.LastSwing.absorbAmount = totalAbsorb or 0 CleveRoids.LastSwing.resistAmount = totalResist or 0 CleveRoids.LastSwing.targetGuid = targetGuid + + -- Paladin: refresh active Judgements on melee hit (Nampower fallback for UNIT_CASTEVENT) + if CleveRoids.playerClass == "PALADIN" and targetGuid then + local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil + if lib and lib.objects then + local normalizedTarget = CleveRoids.NormalizeGUID(targetGuid) + if normalizedTarget and lib.objects[normalizedTarget] then + for spellID, rec in pairs(lib.objects[normalizedTarget]) do + if lib.judgementSpells and lib.judgementSpells[spellID] and rec.start and rec.duration then + local remaining = rec.duration + rec.start - GetTime() + if remaining > 0 and rec.caster == "player" then + rec.start = GetTime() + + if CleveRoids.debug then + local spellName = GetSpellRecField and GetSpellRecField(spellID, "name") or "Unknown" + local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "") or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on melee hit - new duration: %ds", + baseName, spellID, rec.duration) + ) + end + + -- Sync to pfUI if loaded (pre-7.6 only) + if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then + local spellName = GetSpellRecField and GetSpellRecField(spellID, "name") or nil + local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "") + local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target") + local targetLevel = UnitLevel("target") or 0 + if targetName and baseName then + pfUI.api.libdebuff:AddEffect(targetName, targetLevel, baseName, rec.duration, "player") + end + end + end + end + end + end + end + end end -- Process AUTO_ATTACK_SELF event (player being attacked) @@ -1102,10 +1137,9 @@ local function OnAuraCastSelf(spellId, casterGuid, targetGuid, effect, effectAur -- Use the isBuffNotDebuff result determined above (avoids redundant slot scanning) local lib = CleveRoids.libdebuff if isBuffNotDebuff and spellId and durationMs and durationMs > 0 and lib and not lib.hasPfUIEnhanced then - local spellName = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") if spellName then - local _, playerGuidRaw = UnitExists("player") - local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw) + local playerGuid = CleveRoids.GetGUID("player") if playerGuid then lib.ownBuffCasts[playerGuid] = lib.ownBuffCasts[playerGuid] or {} lib.ownBuffCasts[playerGuid][spellName] = { @@ -1153,7 +1187,7 @@ local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAu -- Debug output when enabled if CleveRoids.debug then - local spellName = SpellInfo(spellId) or "Unknown" + local spellName = GetSpellRecField(spellId, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage(string.format( "|cff00ffff[AuraTrack]|r %s (ID:%d) on %s, dur=%.1fs", spellName, spellId, string.sub(tostring(targetGuid), 1, 16), durationMs / 1000 @@ -1164,7 +1198,7 @@ local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAu -- (AURA_CAST_ON_OTHER fires for both buffs and debuffs; BUFF_ADDED_OTHER confirms buff) local lib = CleveRoids.libdebuff if lib and not lib.hasPfUIEnhanced then - local spellNameForPending = SpellInfo and SpellInfo(spellId) + local spellNameForPending = GetSpellRecField and GetSpellRecField(spellId, "name") if spellNameForPending then local normTargetGuid = CleveRoids.NormalizeGUID(targetGuid) if normTargetGuid then @@ -1285,7 +1319,7 @@ autoAttackFrame:SetScript("OnEvent", function() end if spellId and spellId > 0 and durationMs and durationMs > 0 then - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") if playerGUID then if not CleveRoids.AllCasterAuraTracking[playerGUID] then CleveRoids.AllCasterAuraTracking[playerGUID] = {} @@ -1301,7 +1335,7 @@ autoAttackFrame:SetScript("OnEvent", function() } if CleveRoids.debug then - local spellName = SpellInfo and SpellInfo(spellId) or "Unknown" + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage(string.format( "|cff88ff88[AuraDurUpdate]|r %s (slot:%d, ID:%d) dur=%.1fs", spellName, auraSlot, spellId, durationSec @@ -1328,7 +1362,7 @@ autoAttackFrame:SetScript("OnEvent", function() elseif event == "BUFF_REMOVED_OTHER" or event == "DEBUFF_REMOVED_OTHER" then -- Instant cleanup of AllCasterAuraTracking when auras are removed - -- arg1=targetGuid, arg2=spellName, arg3=spellId, arg7=state (v2.32+: 0=added, 1=removed, 2=modified) + -- arg1=targetGuid, arg2=luaSlot, arg3=spellId, arg4=stackCount, arg5=auraLevel, arg6=auraSlot, arg7=state (v2.32+: 0=added, 1=removed, 2=modified) local guid = arg1 local spellId = arg3 local state = arg7 @@ -1505,7 +1539,7 @@ function CleveRoids.IsTargetDebuffCapped(unit) if not UnitExists(unit) then return false end -- Check cached AURA_CAST data by GUID - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if guid then local capData = CleveRoids.AuraCapStatus.targetCapStatus[guid] if capData and (GetTime() - capData.timestamp) < 30 then @@ -1515,20 +1549,6 @@ function CleveRoids.IsTargetDebuffCapped(unit) -- Fallback: count debuffs on target manually -- For NPCs: check up to 48 slots (16 debuff + 32 overflow in buff slots) - if not CleveRoids.hasSuperwow then - -- Without SuperWoW, can only see 16 debuff slots - local count = 0 - for i = 1, 16 do - if UnitDebuff(unit, i) then - count = count + 1 - else - break - end - end - return count >= 16 - end - - -- With SuperWoW: count all debuffs including overflow local debuffCount = 0 -- Count regular debuff slots (1-16, dense) @@ -1553,7 +1573,7 @@ function CleveRoids.IsTargetBuffCapped(unit) unit = unit or "target" if not UnitExists(unit) then return false end - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if guid then local capData = CleveRoids.AuraCapStatus.targetCapStatus[guid] if capData and (GetTime() - capData.timestamp) < 30 then @@ -1614,8 +1634,7 @@ local function IsPendingDebuffCast(spellName, targetUnit) -- Get target GUID for verification (only count as pending if casting AT this target) local targetGuid = nil if targetUnit and UnitExists(targetUnit) then - local _, guid = UnitExists(targetUnit) - targetGuid = guid + targetGuid = CleveRoids.GetGUID(targetUnit) end -- Check if currently CASTING this spell @@ -1686,29 +1705,7 @@ local function _get_debuff_timeleft(unitToken, auraName) auraName = string.gsub(auraName, "_", " ") 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 - local _, guid = UnitExists(unitToken) - if guid and lib.objects and lib.objects[guid] then - -- Check 1-48: debuff slots 1-16 + overflow debuffs in buff slots 1-32 - -- NOTE: Slots 1-16 are dense (break on nil), slots 17-48 are sparse (continue on nil) - -- Overflow debuffs in buff slots are mixed with regular buffs, so we can't break early - for i = 1, 48 do - local effect, _, _, _, _, duration, timeleft = lib:UnitDebuff(unitToken, i) - -- 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 - -- 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 - end - end - - -- Non-SuperWoW fallback + -- GUID-based debuff lookup via libdebuff (works with SuperWoW or Nampower) if lib and lib.UnitDebuff then for idx = 1, 48 do local effect, _, _, _, _, duration, timeleft = lib:UnitDebuff(unitToken, idx) @@ -1803,17 +1800,32 @@ function CleveRoids.CancelAura(auraName) -- v2.34+ path: cancel by spell ID (works for buff-capped overflow auras too) local API = CleveRoids.NampowerAPI - if API and API.features.hasCancelPlayerAuraSpellId and CleveRoids.hasSuperwow then - -- First scan visible buffs via GetPlayerBuff (fast, covers normal case) - while true do - local aura_ix = GetPlayerBuff(ix, "HELPFUL") - ix = ix + 1 - if aura_ix == -1 then break end - local bid = GetPlayerBuffID(aura_ix) - bid = (bid < -1) and (bid + 65536) or bid - if string.lower(SpellInfo(bid)) == auraName then - _G.CancelPlayerAuraSpellId(bid, 1) - return true + if API and API.features.hasCancelPlayerAuraSpellId then + -- First scan visible buffs via GetPlayerBuff + if CleveRoids.hasSuperwow then + -- SuperWoW path: GetPlayerBuffID provides spell ID directly + while true do + local aura_ix = GetPlayerBuff(ix, "HELPFUL") + ix = ix + 1 + if aura_ix == -1 then break end + local bid = GetPlayerBuffID(aura_ix) + bid = (bid < -1) and (bid + 65536) or bid + if string.lower(GetSpellRecField(bid, "name")) == auraName then + _G.CancelPlayerAuraSpellId(bid, 1) + return true + end + end + elseif API.features.hasGetPlayerAuraDuration and _G.GetPlayerAuraDuration then + -- Nampower path: scan buff aura slots 0-31 for spell IDs + for slot = 0, 31 do + local spellId = _G.GetPlayerAuraDuration(slot) + if spellId and spellId > 0 then + local name = GetSpellRecField(spellId, "name") + if name and string.lower(name) == auraName then + _G.CancelPlayerAuraSpellId(spellId, 1) + return true + end + end end end @@ -1823,7 +1835,7 @@ function CleveRoids.CancelAura(auraName) for slot = 0, 31 do local spellId = _G.GetPlayerAuraDuration(slot) if spellId and spellId > 0 then - local name = SpellInfo(spellId) + local name = GetSpellRecField(spellId, "name") if name and string.lower(name) == auraName then _G.CancelPlayerAuraSpellId(spellId, 1) return true @@ -1840,7 +1852,7 @@ function CleveRoids.CancelAura(auraName) if entry.durationSec and entry.durationSec > 0 and elapsed > entry.durationSec then CleveRoids.OverflowBuffs[spellId] = nil else - local name = SpellInfo(spellId) + local name = GetSpellRecField(spellId, "name") if name and string.lower(name) == auraName then _G.CancelPlayerAuraSpellId(spellId, 1) CleveRoids.OverflowBuffs[spellId] = nil @@ -1860,7 +1872,7 @@ function CleveRoids.CancelAura(auraName) if CleveRoids.hasSuperwow then local bid = GetPlayerBuffID(aura_ix) bid = (bid < -1) and (bid + 65536) or bid - if string.lower(SpellInfo(bid)) == auraName then + if string.lower(GetSpellRecField(bid, "name")) == auraName then CancelPlayerBuff(aura_ix) return true end @@ -2751,8 +2763,7 @@ function CleveRoids.CountEnemiesMatching(checkFunc) -- Save current target for restoration local currentTargetGuid = nil if UnitExists("target") then - local _, guid = UnitExists("target") - currentTargetGuid = guid + currentTargetGuid = CleveRoids.GetGUID("target") end local count = 0 @@ -2774,7 +2785,7 @@ function CleveRoids.CountEnemiesMatching(checkFunc) if not found then break end if not UnitExists("target") then break end - local _, currentGuid = UnitExists("target") + local currentGuid = CleveRoids.GetGUID("target") if not currentGuid then break end -- Cycle detection: stop when we see the first target again @@ -2956,7 +2967,7 @@ function CleveRoids.GetCursiveTimeRemaining(unit, spellName) if not CleveRoids.HasCursive() then return nil end if not unit or not UnitExists(unit) then return nil end - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return nil end -- Normalize spell name (lowercase, no rank) to match Cursive's format @@ -2978,7 +2989,7 @@ function CleveRoids.ValidateCursiveDebuff(unit, spellName, operator, amount) if not CleveRoids.HasCursive() then return false end if not unit or not UnitExists(unit) then return false end - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return false end -- Normalize spell name for Cursive lookup @@ -3009,7 +3020,7 @@ function CleveRoids.HasAnyCursiveDebuff(unit) if not CleveRoids.HasCursive() then return false end if not unit or not UnitExists(unit) then return false end - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return false end return Cursive.curses:HasAnyCurse(guid) == true @@ -3095,9 +3106,12 @@ end -- returns: True or false function CleveRoids.ValidatePower(unit, operator, amount) if not unit or not operator or not amount then return false end - local powerPercent = 100 / UnitManaMax(unit) * UnitMana(unit) + local API = CleveRoids.NampowerAPI + local power = API and API.GetUnitPower and API.GetUnitPower(unit) or UnitMana(unit) + local maxPower = API and API.GetUnitMaxPower and API.GetUnitMaxPower(unit) or UnitManaMax(unit) + local powerPercent = maxPower > 0 and (100 * power / maxPower) or 0 - if powerPercent and CleveRoids.operators[operator] then + if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](powerPercent, amount) end @@ -3111,7 +3125,8 @@ end -- returns: True or false function CleveRoids.ValidateRawPower(unit, operator, amount) if not unit or not operator or not amount then return false end - local power = UnitMana(unit) + local API = CleveRoids.NampowerAPI + local power = API and API.GetUnitPower and API.GetUnitPower(unit) or UnitMana(unit) if power and CleveRoids.operators[operator] then return CleveRoids.comparators[operator](power, amount) @@ -3149,7 +3164,10 @@ end -- returns: True or false function CleveRoids.ValidatePowerLost(unit, operator, amount) if not unit or not operator or not amount then return false end - local powerLost = UnitManaMax(unit) - UnitMana(unit) + local API = CleveRoids.NampowerAPI + local maxPower = API and API.GetUnitMaxPower and API.GetUnitMaxPower(unit) or UnitManaMax(unit) + local power = API and API.GetUnitPower and API.GetUnitPower(unit) or UnitMana(unit) + local powerLost = maxPower - power if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](powerLost, amount) @@ -3165,7 +3183,10 @@ end -- returns: True or false function CleveRoids.ValidateHp(unit, operator, amount) if not unit or not operator or not amount then return false end - local hpPercent = 100 / UnitHealthMax(unit) * UnitHealth(unit) + local API = CleveRoids.NampowerAPI + local hp = API and API.GetUnitHealth and API.GetUnitHealth(unit) or UnitHealth(unit) + local maxHp = API and API.GetUnitMaxHealth and API.GetUnitMaxHealth(unit) or UnitHealthMax(unit) + local hpPercent = maxHp > 0 and (100 * hp / maxHp) or 0 if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](hpPercent, amount) @@ -3181,7 +3202,8 @@ end -- returns: True or false function CleveRoids.ValidateRawHp(unit, operator, amount) if not unit or not operator or not amount then return false end - local rawhp = UnitHealth(unit) + local API = CleveRoids.NampowerAPI + local rawhp = API and API.GetUnitHealth and API.GetUnitHealth(unit) or UnitHealth(unit) if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](rawhp, amount) @@ -3197,7 +3219,10 @@ end -- returns: True or false function CleveRoids.ValidateHpLost(unit, operator, amount) if not unit or not operator or not amount then return false end - local hpLost = UnitHealthMax(unit) - UnitHealth(unit) + local API = CleveRoids.NampowerAPI + local maxHp = API and API.GetUnitMaxHealth and API.GetUnitMaxHealth(unit) or UnitHealthMax(unit) + local hp = API and API.GetUnitHealth and API.GetUnitHealth(unit) or UnitHealth(unit) + local hpLost = maxHp - hp if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](hpLost, amount) @@ -3287,7 +3312,17 @@ function CleveRoids.GetPlayerAura(index, isbuff) local bid = GetPlayerBuff(index, buffType) if bid < 0 then return end - local spellID = CleveRoids.hasSuperwow and GetPlayerBuffID(bid) + local spellID + if CleveRoids.hasSuperwow then + spellID = GetPlayerBuffID(bid) + elseif _G.GetPlayerAuraDuration then + -- Nampower v2.30+: GetPlayerAuraDuration uses same raw aura slot numbering as GetPlayerBuff + -- (0-31 for buffs, 32-47 for debuffs) + local sid = _G.GetPlayerAuraDuration(bid) + if sid and sid > 0 then + spellID = sid + end + end return GetPlayerBuffTexture(bid), GetPlayerBuffApplications(bid), spellID, GetPlayerBuffTimeLeft(bid) end @@ -3310,7 +3345,7 @@ local function GetLowercaseSpellName(spellID) local cached = _spellNameCache[spellID] if cached then return cached end - local name = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") if not name then return nil end -- Strip rank and lowercase @@ -3333,7 +3368,7 @@ local function GetSpellNames(spellID) return cached.base, cached.full end - local fullName = SpellInfo(spellID) + local fullName = GetSpellRecField(spellID, "name") if not fullName then return nil, nil end local baseName = _string_gsub(fullName, _RANK_PATTERN, "") @@ -3363,10 +3398,6 @@ end function CleveRoids.ValidateAura(unit, args, isbuff) if not args or not UnitExists(unit) then return false end - if not CleveRoids.hasSuperwow then - return false - end - if type(args) ~= "table" then args = {name = args} end @@ -3527,8 +3558,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff) if found and remaining == nil and isPlayer and isbuff and searchName then local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil if lib and lib.allBuffAuras then - local _, playerGuidRaw = UnitExists("player") - local playerGuid = CleveRoids.NormalizeGUID(playerGuidRaw) + local playerGuid = CleveRoids.GetGUID("player") if playerGuid and lib.allBuffAuras[playerGuid] then -- Try exact name match first local casters = lib.allBuffAuras[playerGuid][args.name] @@ -3558,7 +3588,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff) -- Guard: verify the spell isn't a visible debuff on the target (AllCasterAuraTracking -- stores both buffs and debuffs, so without this check [buff:DebuffName] could false-positive). if not found and not isPlayer and isbuff and (searchID or searchName) then - local _, targetGuid = UnitExists(unit) + local targetGuid = CleveRoids.GetGUID(unit) if targetGuid then -- Check if the spell is in a visible debuff slot — if so, it's a debuff, not a buff local isDebuff = false @@ -3610,8 +3640,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff) if nonPlayerAuraTimeRemaining == nil and isbuff then local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil if lib and lib.allBuffAuras then - local _, targetGuid = UnitExists(unit) - targetGuid = targetGuid and CleveRoids.NormalizeGUID(targetGuid) + local targetGuid = CleveRoids.GetGUID(unit) if targetGuid then local buffEntries = lib.allBuffAuras[targetGuid] if buffEntries then @@ -3648,7 +3677,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff) -- Second try: All-caster tracking from AURA_CAST events (works for any caster) -- Only use if libdebuff didn't find it (libdebuff has more accurate timing for player casts) if nonPlayerAuraTimeRemaining == nil then - local _, targetGuid = UnitExists(unit) + local targetGuid = CleveRoids.GetGUID(unit) if targetGuid then local remaining, casterGuid = CleveRoids.FindAllCasterAuraByName(targetGuid, args.name) @@ -3740,21 +3769,12 @@ function CleveRoids.ValidateUnitDebuff(unit, args) local texture, stacks, spellID, remaining local i - -- PERFORMANCE: For non-SuperWoW, early return if no texture registered - if not searchID and not CleveRoids.hasSuperwow and not CleveRoids.auraTextures[args.name] then - return false - end - -- For non-player units, check tracking table directly -- SIMPLE: Did the player cast this spell? Is the timer still valid? -- Defensive: verify libdebuff is a table before accessing properties local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil if unit ~= "player" and lib and lib.objects then - local _, guid = UnitExists(unit) - if not guid then return false end - - -- Normalize GUID to string for consistent table key lookups - guid = CleveRoids.NormalizeGUID(guid) + local guid = CleveRoids.GetGUID(unit) if not guid then return false end -- PERFORMANCE: Use cached spell name -> ID mapping instead of iterating every call @@ -3846,7 +3866,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args) -- Includes stack/time checks (e.g., [debuff:Sunder_Armor>#3]) not just existence. -- The isShared gate below ensures personal debuffs still require tracking table data. - if not found and CleveRoids.hasSuperwow then + if not found then -- FALLBACK: Only scan for SHARED debuffs (Sunder, Faerie Fire, etc.) -- Personal debuffs (Rip, Rake, Rupture, etc.) MUST be in tracking table -- to be considered "found" - this ensures [nodebuff] only finds YOUR debuffs @@ -3954,7 +3974,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args) found = true break end - elseif CleveRoids.hasSuperwow then + elseif spellID then local baseName, fullName = GetSpellNames(spellID) if baseName and (baseName == args.name or fullName == args.name) then found = true @@ -3979,7 +3999,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args) found = true break end - elseif CleveRoids.hasSuperwow then + elseif spellID then local baseName, fullName = GetSpellNames(spellID) if baseName and (baseName == args.name or fullName == args.name) then found = true @@ -4530,7 +4550,8 @@ function CleveRoids.GetActionButtonInfo(slot) if actionType == "MACRO" then return actionType, id, macroName elseif actionType == "SPELL" and id then - local spellName, rank = SpellInfo(id) + local spellName = GetSpellRecField(id, "name") + local rank = GetSpellRecField(id, "rank") return actionType, id, spellName, rank elseif actionType == "ITEM" and id then local item = CleveRoids.GetItem(id) @@ -4646,13 +4667,11 @@ function CleveRoids.CheckSpellCast(unit, spell) end end - -- Legacy fallback: UNIT_CASTEVENT-based spell_tracking (requires SuperWoW) - if not CleveRoids.hasSuperwow then return false end - + -- Legacy fallback: UNIT_CASTEVENT-based spell_tracking (SuperWoW or Nampower SPELL_START_SELF) if not CleveRoids.spell_tracking[guid] then return false else - if spell == SpellInfo(CleveRoids.spell_tracking[guid].spell_id) or (spell == "") then + if spell == GetSpellRecField(CleveRoids.spell_tracking[guid].spell_id, "name") or (spell == "") then return true end return false @@ -4934,7 +4953,7 @@ end function CleveRoids.ValidateUnitCCSingleMechanic(unit, mechanic) -- Use BuffLib if available (most accurate - tracks overflow debuffs and hidden auras) if CleveRoids.HasBuffLib() then - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return false end if unit == "player" then @@ -4955,7 +4974,7 @@ function CleveRoids.ValidateUnitAnyCrowdControl(unit) -- Use BuffLib if available if CleveRoids.HasBuffLib() then - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return false end if unit == "player" then @@ -4984,10 +5003,9 @@ function CleveRoids.ValidateUnitAnyCrowdControl(unit) return false end --- Direct CC check - scans unit debuffs using SuperWoW API +-- Direct CC check - scans unit debuffs for spell IDs to determine CC mechanics -- Works without BuffLib by using built-in spell mechanic table function CleveRoids.ValidateUnitCCDirect(unit, mechanic) - if not CleveRoids.hasSuperwow then return false end -- Players only have 16 debuff slots, no overflow -- Non-player units can have overflow debuffs in buff slots (17-48) @@ -6646,6 +6664,30 @@ CleveRoids.Keywords = { return PlayerIsSwimming() ~= 1 end, + -- [rooted] - Player is currently rooted (Nampower v2.36+) + rooted = function(conditionals) + if not CleveRoids.NampowerAPI.features.hasPlayerIsRooted then + if not CleveRoids._rootedErrorShown then + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r The [rooted] conditional requires Nampower v2.36.0 or newer.", 1, 0.5, 0.5) + CleveRoids._rootedErrorShown = true + end + return false + end + return PlayerIsRooted() == 1 + end, + + -- [norooted] - Player is NOT rooted (Nampower v2.36+) + norooted = function(conditionals) + if not CleveRoids.NampowerAPI.features.hasPlayerIsRooted then + if not CleveRoids._rootedErrorShown then + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r The [rooted] conditional requires Nampower v2.36.0 or newer.", 1, 0.5, 0.5) + CleveRoids._rootedErrorShown = true + end + return false + end + return PlayerIsRooted() ~= 1 + end, + distance = function(conditionals) if not CleveRoids.hasUnitXP then return false end @@ -7923,6 +7965,7 @@ CleveRoids.STATIC_CONDITIONALS = { mod = true, nomod = true, keydown = true, nokeydown = true, swimming = true, noswimming = true, + rooted = true, norooted = true, resting = true, noresting = true, } @@ -7940,7 +7983,7 @@ function CleveRoids.GetMultiscanScore(unit, priority, currentTargetGuid, specifi if not UnitCanAttack("player", unit) then return nil end -- Combat check: must be in combat with player, UNLESS it's current target OR specified @unit - local _, unitGuid = UnitExists(unit) + local unitGuid = CleveRoids.GetGUID(unit) local isCurrentTarget = currentTargetGuid and unitGuid == currentTargetGuid local isSpecifiedUnit = specifiedUnitGuid and unitGuid == specifiedUnitGuid if not isCurrentTarget and not isSpecifiedUnit and not UnitAffectingCombat(unit) then @@ -8062,15 +8105,13 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit) -- Save current target for restoration and combat-check exemption local currentTargetGuid = nil if UnitExists("target") then - local _, guid = UnitExists("target") - currentTargetGuid = guid + currentTargetGuid = CleveRoids.GetGUID("target") end -- Resolve specified @unit GUID (also exempt from combat check) local specifiedUnitGuid = nil if specifiedUnit and UnitExists(specifiedUnit) then - local _, guid = UnitExists(specifiedUnit) - specifiedUnitGuid = guid + specifiedUnitGuid = CleveRoids.GetGUID(specifiedUnit) end -- Handle raid mark priorities (direct unit reference) @@ -8079,8 +8120,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit) if UnitExists(markUnit) and UnitCanAttack("player", markUnit) then -- Validate against target-dependent conditionals if CleveRoids.ValidateMultiscanCandidate(conditionals, markUnit) then - local _, guid = UnitExists(markUnit) - return guid + return CleveRoids.GetGUID(markUnit) end end return nil -- Raid mark not found or doesn't pass conditionals @@ -8094,8 +8134,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit) if UnitExists(markUnit) and UnitCanAttack("player", markUnit) then -- Validate against target-dependent conditionals if CleveRoids.ValidateMultiscanCandidate(conditionals, markUnit) then - local _, guid = UnitExists(markUnit) - return guid + return CleveRoids.GetGUID(markUnit) end end end @@ -8108,7 +8147,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit) if not specifiedUnitGuid then local found = UnitXP("target", "nearestEnemy") if found and UnitExists("target") then - local _, foundGuid = UnitExists("target") + local foundGuid = CleveRoids.GetGUID("target") -- Restore original target if currentTargetGuid then TargetUnit(currentTargetGuid) @@ -8136,7 +8175,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit) if not specifiedUnitGuid then local found = UnitXP("target", "mostHP") if found and UnitExists("target") then - local _, foundGuid = UnitExists("target") + local foundGuid = CleveRoids.GetGUID("target") -- Restore original target if currentTargetGuid then TargetUnit(currentTargetGuid) @@ -8167,7 +8206,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit) local function evaluateCandidate(unit) if not UnitExists(unit) then return end - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid or seenGuids[guid] then return end seenGuids[guid] = true @@ -8213,7 +8252,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit) if not found then break end if not UnitExists("target") then break end - local _, currentGuid = UnitExists("target") + local currentGuid = CleveRoids.GetGUID("target") if not currentGuid then break end if firstGuid == nil then diff --git a/Core.lua b/Core.lua index 0d263c3..b1e1e89 100644 --- a/Core.lua +++ b/Core.lua @@ -106,16 +106,11 @@ requirementCheckFrame:SetScript("OnEvent", function() if arg1 ~= "SuperCleveRoidMacros" then return end -- Check requirements immediately when our addon loads - local hasSuperwow = CleveRoids.hasSuperwow local hasNampower = (IsSpellInRange ~= nil) local hasUnitXP = pcall(UnitXP, "nop", "nop") - if not hasSuperwow or not hasNampower or not hasUnitXP then + if not hasNampower or not hasUnitXP then -- Show errors - if not hasSuperwow then - CleveRoids.Print("|cFFFF0000SuperCleveRoidMacros|r requires |cFF00FFFFbalakethelock's SuperWoW|r:") - CleveRoids.Print("https://github.com/balakethelock/SuperWoW") - end if not hasNampower then CleveRoids.Print("|cFFFF0000SuperCleveRoidMacros|r requires |cFF00FFFFAvitasia's Nampower|r:") CleveRoids.Print("https://gitea.com/avitasia/nampower") @@ -2670,7 +2665,7 @@ function CleveRoids.DoTarget(msg) end -- Save original target GUID for potential restoration (SuperWoW returns GUID as 2nd value) - local _, originalTargetGuid = UnitExists("target") + local originalTargetGuid = CleveRoids.GetGUID("target") -- Handle [multiscan:priority] - use ResolveMultiscanTarget for enemy scanning -- ResolveMultiscanTarget handles its own target save/restore internally @@ -2829,7 +2824,7 @@ function CleveRoids.DoTarget(msg) found = UnitXP("target", scanMode) if not found then break end - local _, currentGuid = UnitExists("target") + local currentGuid = CleveRoids.GetGUID("target") if not currentGuid then break end -- Check if we've cycled back to start @@ -3728,10 +3723,6 @@ function CleveRoids.DoNoFirstAction(msg) end function CleveRoids.DoCastSequence(sequence) - if not CleveRoids.hasSuperwow then - CleveRoids.Print("|cFFFF0000/castsequence|r requires |cFF00FFFFSuperWoW|r.") - return - end if type(sequence) == "string" then sequence = CleveRoids.GetSequence(sequence) if not sequence then return end @@ -3788,7 +3779,7 @@ function CleveRoids.DoCastSequence(sequence) -- Capture target GUID for reset=target (only resets on NEW target, not same target) if sequence.reset and sequence.reset.target and UnitExists("target") then - local _, targetGuid = UnitExists("target") + local targetGuid = CleveRoids.GetGUID("target") sequence.lastTargetGuid = targetGuid end @@ -4927,9 +4918,18 @@ CleveRoids.Frame:RegisterEvent("PLAYER_REGEN_DISABLED") -- Entered actual combat CleveRoids.Frame:RegisterEvent("PLAYER_REGEN_ENABLED") -- Left actual combat (no threat) CleveRoids.Frame:RegisterEvent("UPDATE_SHAPESHIFT_FORM") CleveRoids.Frame:RegisterEvent("SPELL_UPDATE_COOLDOWN") -CleveRoids.Frame:RegisterEvent("UNIT_AURA") -CleveRoids.Frame:RegisterEvent("UNIT_HEALTH") -CleveRoids.Frame:RegisterEvent("UNIT_POWER") +-- Use GUID events when available (v2.39+), fall back to standard per-token events +if CleveRoids.NampowerAPI.features.hasUnitGuidEvents then + CleveRoids.Frame:RegisterEvent("UNIT_AURA_GUID") + CleveRoids.Frame:RegisterEvent("UNIT_HEALTH_GUID") + CleveRoids.Frame:RegisterEvent("UNIT_MANA_GUID") + CleveRoids.Frame:RegisterEvent("UNIT_RAGE_GUID") + CleveRoids.Frame:RegisterEvent("UNIT_ENERGY_GUID") +else + CleveRoids.Frame:RegisterEvent("UNIT_AURA") + CleveRoids.Frame:RegisterEvent("UNIT_HEALTH") + CleveRoids.Frame:RegisterEvent("UNIT_POWER") +end if CleveRoids.hasSuperwow then CleveRoids.Frame:RegisterEvent("UNIT_CASTEVENT") end @@ -4946,11 +4946,17 @@ CleveRoids.Frame:RegisterEvent("SPELLCAST_STOP") CleveRoids.Frame:RegisterEvent("SPELLCAST_FAILED") CleveRoids.Frame:RegisterEvent("SPELLCAST_INTERRUPTED") --- Nampower SPELL_CAST_EVENT for reliable channel tracking +-- Nampower SPELL_CAST_EVENT for reliable channel tracking + cast sequence + spell_tracking if GetCurrentCastingInfo then CleveRoids.Frame:RegisterEvent("SPELL_CAST_EVENT") end +-- Nampower v2.25+: SPELL_START_SELF for spell_tracking and cast sequence (Nampower fallback for UNIT_CASTEVENT) +if CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.features and CleveRoids.NampowerAPI.features.hasSpellStartEvents then + CleveRoids.Frame:RegisterEvent("SPELL_START_SELF") + CleveRoids.Frame:RegisterEvent("SPELL_FAILED_SELF") +end + -- Nampower v2.41+: keyboard input events for [keydown:X] conditional if CleveRoids.NampowerAPI.features.hasKeyEvents then CleveRoids.Frame:RegisterEvent("KEY_DOWN") @@ -5110,7 +5116,7 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time -- Refresh the Judgement by updating the start time rec.start = GetTime() - local spellName = SpellInfo(spellID) + local spellName = GetSpellRecField(spellID, "name") local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "") or "Unknown" if CleveRoids.debug then @@ -5146,7 +5152,7 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time -- Debug channel tracking if CleveRoids.ChannelTimeDebug then - local spellName = spell_id and SpellInfo and SpellInfo(spell_id) or "Unknown" + local spellName = spell_id and GetSpellRecField and GetSpellRecField(spell_id, "name") or "Unknown" if string.find(spellName, "Arcane") then DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[UNIT_CASTEVENT]|r %s: %s (ID:%s) caster=%s player=%s", action, spellName, tostring(spell_id), tostring(caster), tostring(CleveRoids.playerGuid))) @@ -5219,7 +5225,8 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time if CleveRoids.currentSequence and caster == CleveRoids.playerGuid then local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence) - local name, rank = SpellInfo(spell_id) + local name = GetSpellRecField(spell_id, "name") + local rank = GetSpellRecField(spell_id, "rank") local nameRank = (rank and rank ~= "") and (name .. "(" .. rank .. ")") or nil local isSeqSpell = active and active.action and ( active.action == name or @@ -5250,7 +5257,7 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time end -- Nampower SPELL_CAST_EVENT handler for reliable channel tracking --- This is the PRIMARY source of truth for channel state (not GetCurrentCastingInfo polling) +-- Also handles spell_tracking clearing and cast sequence advancement (Nampower fallback for UNIT_CASTEVENT) function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGuid, itemId) local CHANNEL = 4 @@ -5259,7 +5266,7 @@ function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGui CleveRoids.CurrentSpell.type = "channeled" CleveRoids.CurrentSpell.castingSpellId = spellId - local spellName = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") if spellName then CleveRoids.CurrentSpell.spellName = spellName end @@ -5267,6 +5274,149 @@ function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGui -- Force immediate action update CleveRoids.TestForAllActiveActions() end + + -- Nampower fallback: spell_tracking and cast sequence (when SuperWoW not available) + if not CleveRoids.hasSuperwow and spellId then + local playerGuid = CleveRoids.playerGuid or CleveRoids.GetGUID("player") + + -- Clear spell_tracking on success or failure + if success == 1 or success == 0 then + local cast = CleveRoids.spell_tracking[playerGuid] + if cast and cast.spell_id == spellId then + CleveRoids.spell_tracking[playerGuid] = nil + CleveRoids.spell_tracking["player"] = nil + end + end + + -- Cast sequence advancement + if CleveRoids.currentSequence and success == 1 then + local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence) + if active and active.action then + local name = GetSpellRecField(spellId, "name") + local rank = GetSpellRecField(spellId, "rank") + local nameRank = (rank and rank ~= "") and (name .. "(" .. rank .. ")") or nil + local isSeqSpell = (active.action == name or (nameRank and active.action == nameRank)) + + if isSeqSpell then + CleveRoids.currentSequence.status = 2 + CleveRoids.currentSequence.lastUpdate = GetTime() + CleveRoids.AdvanceSequence(CleveRoids.currentSequence) + CleveRoids.currentSequence = nil + end + end + end + + -- Cast sequence failure handling + if CleveRoids.currentSequence and success == 0 then + local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence) + if active and active.action then + local name = GetSpellRecField(spellId, "name") + local isSeqSpell = (active.action == name) + if isSeqSpell then + CleveRoids.currentSequence.status = 1 -- Reset to retry + end + end + end + + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end + end +end + +-- Nampower SPELL_START_SELF handler (v2.25+) +-- Handles spell_tracking population and cast sequence START detection (Nampower fallback for UNIT_CASTEVENT) +function CleveRoids.Frame:SPELL_START_SELF(casterGuid, targetGuid, spellId, castTimeMs, durationMs, spellType, ...) + -- Skip if SuperWoW is handling this via UNIT_CASTEVENT + if CleveRoids.hasSuperwow then return end + if not spellId then return end + + local playerGuid = CleveRoids.playerGuid or CleveRoids.GetGUID("player") + if casterGuid ~= playerGuid then return end + + -- Populate spell_tracking (equivalent to UNIT_CASTEVENT START/CHANNEL) + local isChannel = (spellType == 1) + local action = isChannel and "CHANNEL" or "START" + + if castTimeMs and castTimeMs > 0 then + CleveRoids.spell_tracking[casterGuid] = { + spell_id = spellId, + expires = GetTime() + castTimeMs / 1000, + type = action + } + CleveRoids.spell_tracking["player"] = CleveRoids.spell_tracking[casterGuid] + + -- Channel duration capture (equivalent to UNIT_CASTEVENT CHANNEL) + if isChannel then + CleveRoids.channelStartTime = GetTime() + local tooltipDuration = CleveRoids.GetChannelDurationFromTooltipByID(spellId) + if tooltipDuration then + CleveRoids.channelDuration = tooltipDuration + else + CleveRoids.channelDuration = castTimeMs / 1000 + end + end + + -- Cast duration capture (equivalent to UNIT_CASTEVENT START) + if not isChannel then + CleveRoids.castStartTime = GetTime() + CleveRoids.castDuration = castTimeMs / 1000 + end + end + + -- Cast sequence: set status=1 (casting) for cast-time spells + if CleveRoids.currentSequence and castTimeMs and castTimeMs > 0 then + local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence) + if active and active.action then + local name = GetSpellRecField(spellId, "name") + local rank = GetSpellRecField(spellId, "rank") + local nameRank = (rank and rank ~= "") and (name .. "(" .. rank .. ")") or nil + local isSeqSpell = (active.action == name or (nameRank and active.action == nameRank)) + + if isSeqSpell and CleveRoids.currentSequence.status == 0 then + CleveRoids.currentSequence.status = 1 + CleveRoids.currentSequence.expires = GetTime() + (castTimeMs / 1000) - 2 + end + end + end + + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +-- Nampower SPELL_FAILED_SELF handler (v2.25+) +-- Clears spell_tracking on failure (Nampower fallback for UNIT_CASTEVENT FAIL) +function CleveRoids.Frame:SPELL_FAILED_SELF(casterGuid, targetGuid, spellId, ...) + -- Skip if SuperWoW is handling this via UNIT_CASTEVENT + if CleveRoids.hasSuperwow then return end + if not spellId then return end + + local playerGuid = CleveRoids.playerGuid or CleveRoids.GetGUID("player") + if casterGuid ~= playerGuid then return end + + -- Clear spell_tracking + local cast = CleveRoids.spell_tracking[casterGuid] + if cast and cast.spell_id == spellId then + CleveRoids.spell_tracking[casterGuid] = nil + CleveRoids.spell_tracking["player"] = nil + end + + -- Cast sequence failure handling + if CleveRoids.currentSequence then + local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence) + if active and active.action then + local name = GetSpellRecField(spellId, "name") + local isSeqSpell = name and (active.action == name) + if isSeqSpell then + CleveRoids.currentSequence.status = 1 -- Reset to retry + end + end + end + + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end end function CleveRoids.Frame:SPELLCAST_CHANNEL_START() @@ -5295,7 +5445,7 @@ function CleveRoids.Frame:SPELLCAST_CHANNEL_START() -- Update spell info if spellId then CleveRoids.CurrentSpell.castingSpellId = spellId - local spellName = SpellInfo(spellId) + local spellName = GetSpellRecField(spellId, "name") if spellName then CleveRoids.CurrentSpell.spellName = spellName end @@ -5375,7 +5525,7 @@ function CleveRoids.Frame:SPELLCAST_START() -- Update spell info if spellId then CleveRoids.CurrentSpell.castingSpellId = spellId - local spellName = SpellInfo(spellId) + local spellName = GetSpellRecField(spellId, "name") if spellName then CleveRoids.CurrentSpell.spellName = spellName end @@ -5488,8 +5638,7 @@ function CleveRoids.Frame:PLAYER_TARGET_CHANGED() -- Instead of resetting, we remember each target's progress in the sequence local currentGuid = nil if UnitExists("target") then - local _, guid = UnitExists("target") - currentGuid = guid + currentGuid = CleveRoids.GetGUID("target") end for _, sequence in pairs(CleveRoids.Sequences) do @@ -5733,6 +5882,39 @@ function CleveRoids.Frame:UNIT_POWER() end end +-- GUID event handlers (v2.39+): fire once per unit state change instead of per-token +function CleveRoids.Frame:UNIT_AURA_GUID() + if CleveRoidMacros.realtime == 0 then + local now = GetTime() + if (now - CleveRoids.lastUnitAuraUpdate) >= CleveRoids.EVENT_THROTTLE then + CleveRoids.lastUnitAuraUpdate = now + CleveRoids.QueueActionUpdate() + end + end +end +function CleveRoids.Frame:UNIT_HEALTH_GUID() + if CleveRoidMacros.realtime == 0 then + local now = GetTime() + if (now - CleveRoids.lastUnitHealthUpdate) >= CleveRoids.EVENT_THROTTLE then + CleveRoids.lastUnitHealthUpdate = now + CleveRoids.QueueActionUpdate() + end + end +end +-- All power GUID events share the same handler +local function OnPowerGuidEvent() + if CleveRoidMacros.realtime == 0 then + local now = GetTime() + if (now - CleveRoids.lastUnitPowerUpdate) >= CleveRoids.EVENT_THROTTLE then + CleveRoids.lastUnitPowerUpdate = now + CleveRoids.QueueActionUpdate() + end + end +end +CleveRoids.Frame.UNIT_MANA_GUID = OnPowerGuidEvent +CleveRoids.Frame.UNIT_RAGE_GUID = OnPowerGuidEvent +CleveRoids.Frame.UNIT_ENERGY_GUID = OnPowerGuidEvent + function CleveRoids.Frame:SPELL_QUEUE_EVENT() if event == "SPELL_QUEUE_EVENT" then local eventCode = arg1 @@ -5751,8 +5933,8 @@ function CleveRoids.Frame:SPELL_QUEUE_EVENT() queueType = eventCode, queueTime = GetTime() } - if SpellInfo then - local name = SpellInfo(spellId) + if GetSpellRecField then + local name = GetSpellRecField(spellId, "name") if name then CleveRoids.queuedSpell.spellName = name end @@ -5792,8 +5974,8 @@ function CleveRoids.Frame:SPELL_CAST_EVENT() targetGuid = targetGuid, timestamp = GetTime() } - if SpellInfo then - local name = SpellInfo(spellId) + if GetSpellRecField then + local name = GetSpellRecField(spellId, "name") if name then CleveRoids.lastCastSpell.spellName = name end @@ -5826,7 +6008,7 @@ function CleveRoids.Frame:SPELL_CAST_EVENT() if cp > 0 then CleveRoids.pendingCasts[spellId].comboPoints = cp if CleveRoids.debug then - local castSpellName = SpellInfo and SpellInfo(spellId) or "Unknown" + local castSpellName = GetSpellRecField and GetSpellRecField(spellId, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ff88[SPELL_CAST_EVENT]|r Captured %d CP for %s (ID:%d)", cp, castSpellName, spellId) @@ -5980,11 +6162,9 @@ SlashCmdList["CLEVEROID"] = function(msg) DEFAULT_CHAT_FRAME:AddMessage("/cleveroid realtime 0 or 1 - Force realtime updates") DEFAULT_CHAT_FRAME:AddMessage("/cleveroid refresh X - Set refresh rate (1-10 updates/sec)") DEFAULT_CHAT_FRAME:AddMessage("/cleveroid macrocheck 0 or 1 - Enable/disable macro syntax checker") - if CleveRoids.hasSuperwow then - DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn - Manually set spell duration") - DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget - Forget learned duration(s)") - DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages") - end + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn - Manually set spell duration") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget - Forget learned duration(s)") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages") DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Spell Schools:|r") DEFAULT_CHAT_FRAME:AddMessage('/cleveroid listschools - List all learned spell schools') DEFAULT_CHAT_FRAME:AddMessage('/cleveroid clearschools - Clear learned spell school data') @@ -6067,18 +6247,14 @@ SlashCmdList["CLEVEROID"] = function(msg) -- learn (manual set duration) if cmd == "learn" then - if not CleveRoids.hasSuperwow then - CleveRoids.Print("Learning system requires SuperWoW client!") - return - end local spellID = tonumber(val) local duration = tonumber(val2) if spellID and duration then - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") CleveRoids_LearnedDurations = CleveRoids_LearnedDurations or {} CleveRoids_LearnedDurations[spellID] = CleveRoids_LearnedDurations[spellID] or {} CleveRoids_LearnedDurations[spellID][playerGUID] = duration - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" CleveRoids.Print("Set " .. spellName .. " (ID:" .. spellID .. ") duration to " .. duration .. "s") else CleveRoids.Print("Usage: /cleveroid learn - Manually set spell duration") @@ -6089,17 +6265,13 @@ SlashCmdList["CLEVEROID"] = function(msg) -- forget (delete learned duration) if cmd == "forget" or cmd == "unlearn" then - if not CleveRoids.hasSuperwow then - CleveRoids.Print("Learning system requires SuperWoW client!") - return - end if val == "all" then CleveRoids_LearnedDurations = {} CleveRoids.Print("Forgot all learned spell durations") else local spellID = tonumber(val) if spellID and CleveRoids_LearnedDurations and CleveRoids_LearnedDurations[spellID] then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" CleveRoids_LearnedDurations[spellID] = nil CleveRoids.Print("Forgot " .. spellName .. " (ID:" .. spellID .. ") duration") elseif spellID then @@ -6283,7 +6455,7 @@ SlashCmdList["CLEVEROID"] = function(msg) CleveRoids.Print(schoolColor .. string.upper(school) .. "|r (" .. table.getn(spellIDs) .. " spells):") for _, spellID in ipairs(spellIDs) do - local spellName = SpellInfo and SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField and GetSpellRecField(spellID, "name") or "Unknown" CleveRoids.Print(" " .. spellName .. " (ID:" .. spellID .. ")") end end @@ -6326,7 +6498,7 @@ SlashCmdList["CLEVEROID"] = function(msg) CleveRoids.Print("No learned combo durations yet. Cast finishers and let them expire!") else for spellID, cpData in pairs(CleveRoids_ComboDurations) do - local spellName = SpellInfo(spellID) or ("Spell " .. spellID) + local spellName = GetSpellRecField(spellID, "name") or ("Spell " .. spellID) CleveRoids.Print(spellName .. " (ID:" .. spellID .. "):") for cp = 1, 5 do if cpData[cp] then @@ -6408,7 +6580,7 @@ SlashCmdList["CLEVEROID"] = function(msg) return end - local spellName = SpellInfo(spellID) or ("Spell " .. spellID) + local spellName = GetSpellRecField(spellID, "name") or ("Spell " .. spellID) local modifier = CleveRoids.talentModifiers and CleveRoids.talentModifiers[spellID] if not modifier then @@ -6466,14 +6638,13 @@ SlashCmdList["CLEVEROID"] = function(msg) CleveRoids.Print("|cff88ff88=== Debuff Tracking Debug ===|r") - local _, guid = UnitExists("target") + local guid = CleveRoids.GetGUID("target") if not guid then CleveRoids.Print("|cffff0000No target selected!|r") return end local targetName = UnitName("target") or "Unknown" - guid = CleveRoids.NormalizeGUID(guid) CleveRoids.Print("Target: " .. targetName .. " (GUID: " .. tostring(guid) .. ")") -- Show tracking table for this target @@ -6485,7 +6656,7 @@ SlashCmdList["CLEVEROID"] = function(msg) for spellID, rec in pairs(lib.objects[guid]) do if rec and rec.start and rec.duration then local timeRemaining = rec.duration + rec.start - GetTime() - local spellName = SpellInfo and SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField and GetSpellRecField(spellID, "name") or "Unknown" local caster = rec.caster or "unknown" local stacks = rec.stacks or 0 if timeRemaining > 0 then @@ -6512,7 +6683,7 @@ SlashCmdList["CLEVEROID"] = function(msg) for i = 1, 16 do local texture, stacks, debuffType, spellID = UnitDebuff("target", i) if texture then - local spellName = SpellInfo and SpellInfo(spellID) or "slot" .. i + local spellName = GetSpellRecField and GetSpellRecField(spellID, "name") or "slot" .. i CleveRoids.Print(string.format(" Slot %d: [%d] %s (stacks: %d)", i, spellID or 0, spellName, stacks or 0)) debuffCount = debuffCount + 1 @@ -6532,7 +6703,7 @@ SlashCmdList["CLEVEROID"] = function(msg) -- Check if this might be an overflow debuff by checking libdebuff durations local isDebuff = lib and lib.durations and lib.durations[spellID] if isDebuff then - local spellName = SpellInfo and SpellInfo(spellID) or "slot" .. i + local spellName = GetSpellRecField and GetSpellRecField(spellID, "name") or "slot" .. i CleveRoids.Print(string.format(" Buff Slot %d (=Debuff %d): [%d] %s (stacks: %d) |cffff8800OVERFLOW|r", i, i + 16, spellID, spellName, stacks or 0)) overflowCount = overflowCount + 1 @@ -6570,10 +6741,10 @@ SlashCmdList["CLEVEROID"] = function(msg) end end end - -- Also check SpellInfo - if SpellInfo then + -- Also check GetSpellRecField + if GetSpellRecField then for id = 1, 30000 do - local name = SpellInfo(id) + local name = GetSpellRecField(id, "name") if name and string.lower(name) == string.lower(searchName) then local found = false for _, existingID in ipairs(foundIDs) do @@ -6739,7 +6910,7 @@ SlashCmdList["CLEVEROID"] = function(msg) if auraData.start and auraData.duration then local remaining = auraData.duration + auraData.start - now if remaining > 0 then - local spellName = SpellInfo(spellId) or ("ID:" .. spellId) + local spellName = GetSpellRecField(spellId, "name") or ("ID:" .. spellId) local display = unitName or (string.sub(targetGuid, 1, 16) .. "...") CleveRoids.Print(string.format(" %s on %s: %.1fs left", spellName, display, remaining)) trackingCount = trackingCount + 1 @@ -6755,7 +6926,7 @@ SlashCmdList["CLEVEROID"] = function(msg) if UnitExists("target") then CleveRoids.Print(" ") CleveRoids.Print("|cffffaa00Target Buff Check:|r") - local _, targetGuid = UnitExists("target") + local targetGuid = CleveRoids.GetGUID("target") CleveRoids.Print(" Target GUID: " .. tostring(targetGuid)) local targetData = CleveRoids.AllCasterAuraTracking[targetGuid] if targetData then @@ -6824,11 +6995,9 @@ SlashCmdList["CLEVEROID"] = function(msg) DEFAULT_CHAT_FRAME:AddMessage("/cleveroid - Show current settings") DEFAULT_CHAT_FRAME:AddMessage("/cleveroid realtime 0 or 1 - Force realtime updates (Default: 0. 1 = on, increases CPU load)") DEFAULT_CHAT_FRAME:AddMessage("/cleveroid refresh X - Set refresh rate (1 to 10 updates per second. Default: 5)") - if CleveRoids.hasSuperwow then - DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn - Manually set spell duration") - DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget - Forget learned duration(s)") - DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages") - end + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn - Manually set spell duration") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget - Forget learned duration(s)") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages") DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Immunity Tracking:|r") DEFAULT_CHAT_FRAME:AddMessage('/cleveroid listimmune [school] - List immunity data') DEFAULT_CHAT_FRAME:AddMessage('/cleveroid addimmune "" [buff] - Add immunity') diff --git a/Extensions/CursiveCustomSpells.lua b/Extensions/CursiveCustomSpells.lua index 500f912..bd9492a 100644 --- a/Extensions/CursiveCustomSpells.lua +++ b/Extensions/CursiveCustomSpells.lua @@ -208,8 +208,10 @@ local function InjectCustomSpells() local count = 0 for spellID, data in pairs(CleveRoids.CustomCursiveSpells) do - -- Get texture from SpellInfo (SuperWoW API) - local name, rank, texture = SpellInfo(spellID) + -- Get texture from GetSpellRecField + GetSpellIconTexture + local name = GetSpellRecField(spellID, "name") + local rank = GetSpellRecField(spellID, "rank") + local texture = CleveRoids.libdebuff and CleveRoids.libdebuff:GetCachedIcon(spellID) if texture then -- Always update/add (in case Cursive reloaded and cleared them) Cursive.curses.trackedCurseIds[spellID] = { @@ -340,7 +342,7 @@ local function HookJudgementDetection() if event ~= "CAST" or spellID ~= JUDGEMENT_SPELL_ID then return end -- Check if it's the player casting - local _, playerGuid = UnitExists("player") + local playerGuid = CleveRoids.GetGUID("player") if casterGuid ~= playerGuid then return end -- Detect which seal is active and get full judgement info @@ -448,7 +450,7 @@ meleeRefreshFrame:SetScript("OnEvent", function() -- Check if current target matches the hit target local currentTargetName = UnitName("target") if currentTargetName and currentTargetName == targetName then - local _, targetGuid = UnitExists("target") + local targetGuid = CleveRoids.GetGUID("target") if targetGuid then RefreshJudgementsOnTarget(targetGuid) end @@ -465,7 +467,7 @@ meleeAbilityFrame:SetScript("OnEvent", function() if event ~= "CAST" then return end -- Check if it's the player casting - local _, playerGuid = UnitExists("player") + local playerGuid = CleveRoids.GetGUID("player") if casterGuid ~= playerGuid then return end -- Skip Judgement itself (20271) - it applies, doesn't refresh @@ -522,7 +524,9 @@ CleveRoids.HandleConsoleCommand = function(msg) return end - local name, rank, texture = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") + local rank = GetSpellRecField(spellID, "rank") + local texture = CleveRoids.libdebuff and CleveRoids.libdebuff:GetCachedIcon(spellID) if not name then DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Error:|r Spell ID " .. spellID .. " not found.") return diff --git a/Extensions/OverflowBuffFrame.lua b/Extensions/OverflowBuffFrame.lua index 2ec1878..2d48624 100644 --- a/Extensions/OverflowBuffFrame.lua +++ b/Extensions/OverflowBuffFrame.lua @@ -120,7 +120,7 @@ local function CreateIconButton(parent, index, iconTable) local data = iconTable[index] if not data or not data.spellId then return end GameTooltip:SetOwner(btn, "ANCHOR_BOTTOMLEFT") - local spellName = SpellInfo and SpellInfo(data.spellId) or ("Spell " .. data.spellId) + local spellName = GetSpellRecField and GetSpellRecField(data.spellId, "name") or ("Spell " .. data.spellId) if spellName then local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") GameTooltip:AddLine(baseName, 1, 1, 1) @@ -222,7 +222,10 @@ local function GetPlayerOverflowBuffs() local now = GetTime() for spellId, entry in pairs(overflowBuffs) do - if entry.timestamp and entry.durationSec then + -- v3.0+: Skip hidden auras (not real overflow) + if _G.IsAuraHidden and _G.IsAuraHidden(spellId) == 1 then + -- Hidden aura, don't show in overflow UI + elseif entry.timestamp and entry.durationSec then local remaining = entry.durationSec - (now - entry.timestamp) if remaining > 0 then table.insert(results, { @@ -244,7 +247,7 @@ local function GetTargetOverflowBuffs() if not UnitExists("target") then return results end if not testMode and not (UnitInParty("target") or UnitInRaid("target")) then return results end - local _, targetGuid = UnitExists("target") + local targetGuid = CleveRoids.GetGUID("target") if not targetGuid then return results end local trackingData = CleveRoids.AllCasterAuraTracking @@ -267,7 +270,10 @@ local function GetTargetOverflowBuffs() local lib = CleveRoids.libdebuff for spellId, auraData in pairs(trackingData[targetGuid]) do - if auraData.start and auraData.duration then + -- v3.0+: Skip hidden auras (not real overflow) + if _G.IsAuraHidden and _G.IsAuraHidden(spellId) == 1 then + -- Hidden aura, skip + elseif auraData.start and auraData.duration then local remaining = auraData.duration + auraData.start - now if remaining > 0 then local isVisible = false @@ -410,11 +416,7 @@ local function RefreshDurations() -- Target durations if targetFrame and targetFrame:IsVisible() then - local targetGuid = nil - if UnitExists("target") then - local _, guid = UnitExists("target") - targetGuid = guid - end + local targetGuid = CleveRoids.GetGUID("target") for i = 1, lastTargetCount do local data = targetIcons[i] if data.spellId and targetGuid then @@ -499,7 +501,7 @@ end -- Inject test entries for the current target local function InjectTestTargetData() if not UnitExists("target") then return end - local _, targetGuid = UnitExists("target") + local targetGuid = CleveRoids.GetGUID("target") if not targetGuid then return end -- Clean up old test target if it changed diff --git a/NampowerAPI.lua b/NampowerAPI.lua index 543e419..db24f8e 100644 --- a/NampowerAPI.lua +++ b/NampowerAPI.lua @@ -157,7 +157,15 @@ Suffix matching is case-insensitive. Note: GetGUIDFromName is an internal C++ helper, NOT a new Lua global. - Current version: v2.41.0 + GetUnitGUID Rename, IsAuraHidden, Hidden Aura luaSlot Fix (v3.0+): + - UnitGUID renamed to GetUnitGUID (global function). The addon uses SuperWoW's + UnitExists for GUIDs so this rename is not breaking, but GetUnitGUID supports + extended tokens (mark1-mark8, owner/target/pet suffixes). + - IsAuraHidden(spellId): returns 1 if the spell's aura is hidden from Lua aura + APIs (UnitBuff/UnitDebuff won't show it). Useful for detecting hidden CC spells. + - luaSlot in BUFF/DEBUFF events now returns 0 for hidden auras (was incorrect before). + + Current version: v3.0.0 ]] local _G = _G or getfenv(0) @@ -352,6 +360,11 @@ API.VERSION_REQUIREMENTS = { -- v2.41+ - Keyboard events and extended unit token support ["KeyEvents"] = { 2, 41, 0 }, -- KEY_DOWN / KEY_UP events (keyboard input events) ["ExtendedUnitTokens"] = { 2, 41, 0 }, -- Internal unit token resolution now supports "owner"/"target"/"pet"/"mark" in all Nampower functions (SetMouseoverUnit, UseItemIdOrName, etc.) + + -- v3.0+ - GetUnitGUID rename, IsAuraHidden, hidden aura luaSlot fix + ["GetUnitGUID"] = { 3, 0, 0, "GetUnitGUID" }, + ["IsAuraHidden"] = { 3, 0, 0, "IsAuraHidden" }, + ["HiddenAuraLuaSlotFix"] = { 3, 0, 0 }, } -- Check if a specific feature is available @@ -530,6 +543,11 @@ local function InitializeFeatures() f.hasKeyEvents = API.HasFeature("KeyEvents") f.hasExtendedUnitTokens = API.HasFeature("ExtendedUnitTokens") + -- v3.0+ GetUnitGUID, IsAuraHidden, hidden aura luaSlot fix + f.hasGetUnitGUID = API.HasFeature("GetUnitGUID") + f.hasIsAuraHidden = API.HasFeature("IsAuraHidden") + f.hasHiddenAuraLuaSlotFix = API.HasFeature("HiddenAuraLuaSlotFix") + -- Runtime detection for enhanced spell functions (verify by testing) if f.hasEnhancedSpellFunctions and GetSpellTexture then local success, result = pcall(function() @@ -783,12 +801,6 @@ function API.GetSpellNameById(spellId) if name then return name, rank end end - -- Fall back to SpellInfo (SuperWoW) - if SpellInfo then - local name = SpellInfo(spellId) - if name then return name end - end - -- Fall back to GetSpellRecField if GetSpellRecField then local name = GetSpellRecField(spellId, "name") @@ -1265,7 +1277,7 @@ function API.FindUnitAuraInfo(unitToken, searchSpellId, searchNameLower) if searchSpellId then matched = (auraId == searchSpellId) elseif searchNameLower then - local name = SpellInfo and SpellInfo(auraId) + local name = GetSpellRecField and GetSpellRecField(auraId, "name") if name then -- Strip rank suffix for consistent matching local baseName = string.gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "") @@ -2691,11 +2703,60 @@ end -- INITIALIZATION -------------------------------------------------------------------------------- +-- Hook UnitBuff/UnitDebuff to append spell IDs when SuperWoW is not available +-- SuperWoW extends these to return spell IDs as extra return values. +-- Without SuperWoW, we use GetUnitField(unit, "aura") to look up spell IDs. +-- GetUnitField "aura" returns [1-32]=buff spellIDs, [33-48]=debuff spellIDs +local function InstallAuraSpellIdHooks() + if CleveRoids.hasSuperwow then return end -- SuperWoW already provides spell IDs + if not _G.GetUnitField then return end -- Need GetUnitField for aura data + + local _origUnitBuff = _G.UnitBuff + local _origUnitDebuff = _G.UnitDebuff + + -- UnitBuff(unit, index) => texture, stacks, spellID + -- SuperWoW returns: texture, stacks, spellID + _G.UnitBuff = function(unit, index) + local texture, stacks = _origUnitBuff(unit, index) + if not texture then return nil end + + local spellID = nil + local auras = _G.GetUnitField(unit, "aura") + if auras then + -- Buff index i maps to aura slot i (1-based) + spellID = auras[index] + if spellID and spellID <= 0 then spellID = nil end + end + + return texture, stacks, spellID + end + + -- UnitDebuff(unit, index) => texture, stacks, debuffType, spellID + -- SuperWoW returns: texture, stacks, debuffType, spellID + _G.UnitDebuff = function(unit, index) + local texture, stacks, debuffType = _origUnitDebuff(unit, index) + if not texture then return nil end + + local spellID = nil + local auras = _G.GetUnitField(unit, "aura") + if auras then + -- Debuff index i maps to aura slot 32 + i (1-based) + spellID = auras[32 + index] + if spellID and spellID <= 0 then spellID = nil end + end + + return texture, stacks, debuffType, spellID + end +end + -- Initialize the API module function API.Initialize() -- Detect enhanced spell functions DetectEnhancedSpellFunctions() + -- Install UnitBuff/UnitDebuff spell ID hooks (when SuperWoW not available) + InstallAuraSpellIdHooks() + -- Auto-enable required Nampower event CVars. -- Nampower reads CVars at DLL load time, so changes take effect on next reload. -- We set them now so every subsequent session works automatically. @@ -3247,5 +3308,70 @@ function API.LearnTalentRank(talentPage, talentIndex, rank) return true end +-- Get unit GUID by token (v3.0+) +-- Supports extended tokens: mark1-mark8, owner/target/pet suffixes +function API.GetUnitGUID(unitToken) + if not API.features.hasGetUnitGUID or not _G.GetUnitGUID then + return nil + end + return _G.GetUnitGUID(unitToken) +end + +-- Check if a spell's aura would be hidden from Lua aura APIs (v3.0+) +function API.IsAuraHidden(spellId) + if not API.features.hasIsAuraHidden or not _G.IsAuraHidden then + return nil + end + return _G.IsAuraHidden(spellId) +end + +-------------------------------------------------------------------------------- +-- HEALTH/POWER WRAPPERS (GetUnitField extended token support with fallback) +-------------------------------------------------------------------------------- + +-- Get unit health via GetUnitField (extended token support) with standard API fallback +function API.GetUnitHealth(unitToken) + if API.features.hasGetUnitField and GetUnitField then + local val = GetUnitField(unitToken, "health") + if val then return val end + end + return UnitHealth(unitToken) +end + +function API.GetUnitMaxHealth(unitToken) + if API.features.hasGetUnitField and GetUnitField then + local val = GetUnitField(unitToken, "maxHealth") + if val then return val end + end + return UnitHealthMax(unitToken) +end + +-- powerType: nil=current, 0=mana, 1=rage, 2=focus, 3=energy +-- GetUnitField uses power1-power4 fields +local POWER_FIELDS = { [0] = "power1", [1] = "power2", [2] = "power3", [3] = "power4" } +local MAX_POWER_FIELDS = { [0] = "maxPower1", [1] = "maxPower2", [2] = "maxPower3", [3] = "maxPower4" } + +function API.GetUnitPower(unitToken, powerType) + if API.features.hasGetUnitField and GetUnitField and powerType then + local field = POWER_FIELDS[powerType] + if field then + local val = GetUnitField(unitToken, field) + if val then return val end + end + end + return UnitMana(unitToken) +end + +function API.GetUnitMaxPower(unitToken, powerType) + if API.features.hasGetUnitField and GetUnitField and powerType then + local field = MAX_POWER_FIELDS[powerType] + if field then + local val = GetUnitField(unitToken, field) + if val then return val end + end + end + return UnitManaMax(unitToken) +end + -- Expose API globally for other addons _G.CleveRoidsNampowerAPI = API diff --git a/Utility.lua b/Utility.lua index af5dde8..1bd2bc7 100644 --- a/Utility.lua +++ b/Utility.lua @@ -51,6 +51,18 @@ function CleveRoids.NormalizeGUID(guid) return tostring(guid) end +-- Get GUID for a unit token (prefers Nampower GetUnitGUID for extended token support) +-- Returns: normalized GUID string, or nil +function CleveRoids.GetGUID(unit) + if _G.GetUnitGUID then + local guid = _G.GetUnitGUID(unit) + if guid then return tostring(guid) end + end + local _, guid = UnitExists(unit) + if guid then return tostring(guid) end + return nil +end + -- Hidden tooltip for scanning spell info local SpellScanTooltip = nil @@ -74,13 +86,7 @@ local function GetSpellSlotByID(targetSpellID) while true do local spellName = GetSpellName(i, BOOKTYPE_SPELL) if not spellName then break end - -- Get spell ID for this slot using SpellInfo if available - if SpellInfo then - local _, _, spellID = GetSpellName(i, BOOKTYPE_SPELL) - -- Try to get ID from the spell slot - local slot, book = i, BOOKTYPE_SPELL - -- SpellInfo needs the spell name to get ID, but we can check via GetSpellTexture match - end + -- Note: spell ID matching done via GetSpellTexture comparison if needed i = i + 1 end return nil, nil @@ -170,8 +176,8 @@ end -- Get channel duration for a spell by ID (looks up name first) function CleveRoids.GetChannelDurationFromTooltipByID(spellID) - if not spellID or not SpellInfo then return nil end - local spellName = SpellInfo(spellID) + if not spellID or not GetSpellRecField then return nil end + local spellName = GetSpellRecField(spellID, "name") if not spellName then return nil end return CleveRoids.GetSpellDurationFromTooltip(spellName) end @@ -769,7 +775,7 @@ function lib:HasPfUI76() return true end --- Icon caching helper: DBC lookup (fast) → SpellInfo fallback (slow) +-- Icon caching helper: DBC lookup via GetSpellRecField function lib:GetCachedIcon(spellId) if not spellId then return nil end if lib.iconCache[spellId] then return lib.iconCache[spellId] end @@ -784,12 +790,6 @@ function lib:GetCachedIcon(spellId) end end - -- Fallback to SpellInfo (slower but works without Nampower v2.27) - if not texture and SpellInfo then - local _, _, spellTex = SpellInfo(spellId) - texture = spellTex - end - -- Final fallback if not texture then texture = "Interface\\Icons\\INV_Misc_QuestionMark" @@ -838,6 +838,42 @@ function lib:InitPfUIIntegration() CleveRoidsLibDebuffLearnFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE") end + -- Unregister Nampower events from the libdebuff frame that pfUI handles. + -- All these handlers early-return when hasPfUIEnhanced, so unregistering + -- avoids wasted event dispatch overhead. + -- Uses pfUI's external hook system for any supplementary processing instead. + local ev = CleveRoidsLibDebuffFrame + if ev then + -- Events whose handlers early-return when hasPfUIEnhanced + ev:UnregisterEvent("SPELL_GO_SELF") + ev:UnregisterEvent("SPELL_GO_OTHER") + ev:UnregisterEvent("AURA_CAST_ON_SELF") + ev:UnregisterEvent("AURA_CAST_ON_OTHER") + ev:UnregisterEvent("DEBUFF_ADDED_OTHER") + ev:UnregisterEvent("DEBUFF_REMOVED_OTHER") + ev:UnregisterEvent("BUFF_ADDED_OTHER") + ev:UnregisterEvent("BUFF_REMOVED_SELF") + ev:UnregisterEvent("BUFF_REMOVED_OTHER") + + -- pfUI 7.6+ also handles cast tracking internally + if lib.hasPfUI76 then + ev:UnregisterEvent("SPELL_START_OTHER") + ev:UnregisterEvent("SPELL_FAILED_OTHER") + end + + -- Keep registered: SPELL_START_SELF (channel duration capture before early return), + -- UNIT_DIED (AllCasterAuraTracking + OverflowBuff cleanup), UNIT_CASTEVENT (SuperWoW), + -- PLAYER_TARGET_CHANGED, UNIT_AURA (SeedUnit) + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r Unregistered redundant events (pfUI handles via hooks)") + end + end + + -- Register pfUI libdebuff hooks for supplementary processing. + -- These fire after pfUI processes each event, avoiding duplicate event listeners. + lib:RegisterPfUIHooks() + if CleveRoids.debug then local v = pfUI.version local tierMsg = lib.hasPfUI76 and " (7.6+ cast tracking)" or "" @@ -879,6 +915,74 @@ function lib:InitPfUIIntegration() return false end +-- Register pfUI libdebuff external hooks for supplementary processing. +-- Hooks fire after pfUI processes each event, letting us react without +-- registering duplicate event listeners. +-- +-- Available hooks (registered on pfUI global tables): +-- pfUI.libdebuff_spell_go_hooks["key"] = fn(spellId, arg1..arg7) +-- pfUI.libdebuff_spell_go_other_hooks["key"] = fn(spellId, casterGuid, targetGuid) +-- pfUI.libdebuff_spell_start_self_hooks["key"] = fn(spellId, casterGuid, targetGuid, castTime) +-- pfUI.libdebuff_spell_start_other_hooks["key"] = fn(spellId, casterGuid, targetGuid, castTime) +-- pfUI.libdebuff_spell_failed_other_hooks["key"] = fn(casterGuid, spellId) +-- pfUI.libdebuff_spell_cast_hooks["key"] = fn(success, spellId, castType, targetGuid) +-- pfUI.libdebuff_aura_cast_on_self_hooks["key"] = fn(spellId, casterGuid, targetGuid) +-- pfUI.libdebuff_aura_cast_on_other_hooks["key"] = fn(spellId, casterGuid, targetGuid) +-- pfUI.libdebuff_debuff_added_other_hooks["key"] = fn(guid, luaSlot, spellId, stackCount) +-- pfUI.libdebuff_debuff_removed_other_hooks["key"] = fn(guid, luaSlot, spellId, stackCount) +-- pfUI.libdebuff_unit_health_hooks["key"] = fn(unitToken) +-- pfUI.libdebuff_unit_died_hooks["key"] = fn(guid) +-- pfUI.libdebuff_player_target_changed_hooks["key"] = fn() +-- +-- Note: AURA_CAST hooks don't provide durationMs/auraCapStatus, so +-- AllCasterAuraTracking and overflow buff tracking remain on their own +-- event frame (CleveRoidsAutoAttackFrame in Conditionals.lua). +function lib:RegisterPfUIHooks() + if not pfUI then return end + + local HOOK_KEY = "SuperCleveRoidMacros" + local registered = 0 + + -- UNIT_DIED hook: supplementary cleanup for our private tables + -- (AllCasterAuraTracking, OverflowBuffs) that pfUI doesn't manage. + -- Our UNIT_DIED event handler on the libdebuff frame also does this, + -- but the hook provides a second path in case event ordering shifts. + if type(pfUI.libdebuff_unit_died_hooks) == "table" then + pfUI.libdebuff_unit_died_hooks[HOOK_KEY] = function(guid) + if not guid then return end + guid = CleveRoids.NormalizeGUID(guid) + + -- Clean up AllCasterAuraTracking (our own table, not shared with pfUI) + if CleveRoids.AllCasterAuraTracking and CleveRoids.AllCasterAuraTracking[guid] then + CleveRoids.AllCasterAuraTracking[guid] = nil + end + + -- Clean up OverflowBuffs on player death + local playerGUID = CleveRoids.GetGUID("player") + if playerGUID and guid == playerGUID then + if CleveRoids.OverflowBuffs then + for k in pairs(CleveRoids.OverflowBuffs) do + CleveRoids.OverflowBuffs[k] = nil + end + end + if CleveRoids.AuraCapStatus then + CleveRoids.AuraCapStatus.playerBuffCapped = false + CleveRoids.AuraCapStatus.playerDebuffCapped = false + end + end + end + registered = registered + 1 + end + + lib.pfUIHooksRegistered = registered > 0 + + if CleveRoids.debug and registered > 0 then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff33ff99[libdebuff]|r Registered %d pfUI libdebuff hook(s)", registered) + ) + end +end + -- Unique debuffs: Same spell overwrites itself when cast by different player -- Only one instance can exist on a target (regardless of caster) lib.uniqueDebuffs = lib.uniqueDebuffs or { @@ -1253,17 +1357,12 @@ end function lib:GetDebuffCaster(unit, spellName) if not spellName then return nil end - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return nil end - guid = CleveRoids.NormalizeGUID(guid) -- Check own debuffs first if lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] then - local playerGuid = nil - if UnitExists then - local _, pg = UnitExists("player") - playerGuid = pg - end + local playerGuid = CleveRoids.GetGUID("player") return playerGuid end @@ -1301,9 +1400,8 @@ end function lib:IsOurDebuff(unit, spellName) if not spellName then return false end - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return false end - guid = CleveRoids.NormalizeGUID(guid) -- Check own debuffs if lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] then @@ -1907,7 +2005,8 @@ end -- Returns rank number (1, 2, 3, etc.) or 0 if no rank found function lib:GetSpellRank(spellID) if not spellID then return 0 end - local name, rankStr = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") + local rankStr = GetSpellRecField(spellID, "rank") if not rankStr or rankStr == "" then return 0 end -- Handle different rank formats @@ -1928,7 +2027,7 @@ end -- Helper function to get spell base name (without rank) function lib:GetSpellBaseName(spellID) if not spellID then return nil end - local name = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") if not name then return nil end -- Remove rank suffix @@ -2070,7 +2169,7 @@ function lib:GetDuration(spellID, casterGUID, comboPoints) local comboDuration = CleveRoids_ComboDurations[spellID][comboPoints] if comboDuration and comboDuration > 0 then if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffccccff[DEBUG GetDuration]|r %s (ID:%d) CP:%d -> %ds (from learned combo)", spellName, spellID, comboPoints, comboDuration) @@ -2085,7 +2184,7 @@ function lib:GetDuration(spellID, casterGUID, comboPoints) local learned = CleveRoids_LearnedDurations[spellID][casterGUID] if learned and learned > 0 then if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffccccff[DEBUG GetDuration]|r %s (ID:%d) -> %ds (from learned caster)", spellName, spellID, learned) @@ -2098,7 +2197,7 @@ function lib:GetDuration(spellID, casterGUID, comboPoints) -- Fall back to static database local staticDur = self.durations[spellID] or 0 if CleveRoids.debug and staticDur > 0 then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffccccff[DEBUG GetDuration]|r %s (ID:%d) -> %ds (from static DB)", spellName, spellID, staticDur) @@ -2126,8 +2225,8 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster) for trackedID, rec in pairs(lib.objects[guid]) do if lib.curseSpellIDs[trackedID] and trackedID ~= spellID then if CleveRoids.debug then - local oldName = SpellInfo(trackedID) or "Unknown" - local newName = SpellInfo(spellID) or "Unknown" + local oldName = GetSpellRecField(trackedID, "name") or "Unknown" + local newName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff8800[Curse Replace]|r %s replaced by %s on %s", oldName, newName, unitName or "target")) @@ -2150,7 +2249,7 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster) -- 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) + local spellName = GetSpellRecField(spellID, "name") if spellName and pflib.AddEffect then -- Get target level for pfUI's tracking structure @@ -2180,7 +2279,7 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster) -- DEBUG: Show what we stored if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" local casterStr = caster or "nil" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ffff[DEBUG AddEffect]|r %s (ID:%d) stored duration:%ds on %s, caster:%s, GUID:%s", @@ -2190,12 +2289,9 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster) end function lib:UnitDebuff(unit, id, filterCaster) - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return nil end - -- Normalize GUID to string for consistent table key lookups - guid = CleveRoids.NormalizeGUID(guid) - local texture, stacks, dtype, spellID = nil, nil, nil, nil -- SuperWoW debuff slots: 1-16 are regular debuffs, 17-48 overflow to buff slots 1-32 @@ -2217,7 +2313,7 @@ function lib:UnitDebuff(unit, id, filterCaster) if not texture or not spellID then return nil end - local name = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") local duration, timeleft, caster = nil, -1, nil local rec = lib.objects[guid] and lib.objects[guid][spellID] @@ -2252,7 +2348,7 @@ end -- Query buff data with duration and caster tracking (buff slots only) function lib:UnitBuff(unit, id, filterCaster) - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return nil end -- Only check buff slots @@ -2260,7 +2356,7 @@ function lib:UnitBuff(unit, id, filterCaster) if not texture or not spellID then return nil end - local name = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") local duration, timeleft, caster = nil, -1, nil local rec = lib.objects[guid] and lib.objects[guid][spellID] @@ -2290,7 +2386,7 @@ end -- Find a player-cast debuff by spell ID (searches all slots including buff slots) function lib:FindPlayerDebuff(unit, spellID) - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return nil end -- Check if we're tracking this spell for this unit @@ -2336,13 +2432,13 @@ function lib:FindPlayerDebuff(unit, spellID) if not texture then return nil end - local name = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") return name, nil, texture, stacks, nil, rec.duration, remaining, rec.caster end -- Find a player-cast buff by spell ID (searches buff slots only) function lib:FindPlayerBuff(unit, spellID) - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return nil end -- Check if we're tracking this spell for this unit @@ -2374,12 +2470,12 @@ function lib:FindPlayerBuff(unit, spellID) if not texture then return nil end - local name = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") return name, nil, texture, stacks, nil, rec.duration, remaining, rec.caster end local function SeedUnit(unit) - local _, guid = UnitExists(unit) + local guid = CleveRoids.GetGUID(unit) if not guid then return end local unitName = UnitName(unit) @@ -2415,7 +2511,7 @@ local function SeedUnit(unit) -- 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) + local spellName = GetSpellRecField(spellID, "name") if spellName and pflib.AddEffect then local targetLevel = UnitLevel(unit) or 1 local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") @@ -2427,7 +2523,7 @@ local function SeedUnit(unit) end if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) stack increased %d->%d, timer RESET to %ds (refresh detected)", spellName, spellID, oldStacks, stacks or 0, duration) @@ -2436,7 +2532,7 @@ local function SeedUnit(unit) else -- Stacks decreased (shouldn't happen normally) - just update stacks, preserve timer if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) updated stacks to %d (timer preserved)", spellName, spellID, stacks or 0) @@ -2457,7 +2553,7 @@ local function SeedUnit(unit) if CleveRoids_ComboDurations[spellID][cp] then duration = CleveRoids_ComboDurations[spellID][cp] if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) using learned %dCP duration:%ds", spellName, spellID, cp, duration) @@ -2504,7 +2600,7 @@ local function SeedUnit(unit) -- 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) + local spellName = GetSpellRecField(spellID, "name") if spellName and pflib.AddEffect then local targetLevel = UnitLevel(unit) or 1 local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") @@ -2516,7 +2612,7 @@ local function SeedUnit(unit) end if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) stack increased %d->%d, timer RESET to %ds (refresh detected)", spellName, spellID, oldStacks, stacks or 0, duration) @@ -2525,7 +2621,7 @@ local function SeedUnit(unit) else -- Stacks decreased (shouldn't happen normally) - just update stacks, preserve timer if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) updated stacks to %d (timer preserved)", spellName, spellID, stacks or 0) @@ -2543,7 +2639,7 @@ local function SeedUnit(unit) if CleveRoids_ComboDurations[spellID][cp] then duration = CleveRoids_ComboDurations[spellID][cp] if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) using learned %dCP duration:%ds", spellName, spellID, cp, duration) @@ -2684,8 +2780,7 @@ lib.trackedAfflictions = { function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID) if CleveRoids.debug then -- Compare Carnage GUID with current target GUID - local _, currentTargetGUID = UnitExists("target") - currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) + local currentTargetGUID = CleveRoids.GetGUID("target") local guidMatch = (targetGUID == currentTargetGUID) and "MATCH" or "MISMATCH" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff00ff[Carnage]|r ApplyCarnageRefresh called for %s (GUID:%s, current:%s, %s)", @@ -2777,7 +2872,7 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID) -- pfUI will pick up the new duration through our GetDuration/UnitDebuff hooks if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then local pflib = pfUI.api.libdebuff - local ripSpellName = SpellInfo(ripSpellID) + local ripSpellName = GetSpellRecField(ripSpellID, "name") local baseName = ripSpellName and string.gsub(ripSpellName, "%s*%(Rank %d+%)", "") or "Rip" if CleveRoids.debug then @@ -2890,7 +2985,7 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID) -- pfUI will pick up the new duration through our GetDuration/UnitDebuff hooks if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then local pflib = pfUI.api.libdebuff - local rakeSpellName = SpellInfo(rakeSpellID) + local rakeSpellName = GetSpellRecField(rakeSpellID, "name") local baseName = rakeSpellName and string.gsub(rakeSpellName, "%s*%(Rank %d+%)", "") or "Rake" if CleveRoids.debug then @@ -2946,7 +3041,7 @@ local _UnitExists = UnitExists local _UnitDebuff = UnitDebuff local _UnitIsDead = UnitIsDead local _UnitName = UnitName -local _SpellInfo = SpellInfo +local _GetSpellRecField = GetSpellRecField local _string_find = string.find local _string_format = string.format local _string_sub = string.sub @@ -3028,6 +3123,19 @@ delayedTrackingFrame:SetScript("OnUpdate", function() local hasSuperwow = CleveRoids.hasSuperwow local debug = CleveRoids.debug + -- Resolve a GUID to a queryable unit token + -- SuperWoW: returns GUID directly (SuperWoW extends WoW APIs to accept GUIDs) + -- Without SuperWoW: returns "target" if GUID matches current target, nil otherwise + local function ResolveGUIDUnit(guid) + if not guid then return nil end + if hasSuperwow then return guid end + local _, ctGUID = _UnitExists("target") + if ctGUID and CleveRoids.NormalizeGUID(ctGUID) == guid then + return "target" + end + return nil + end + -- Process pending judgement scans to detect actual debuff IDs if hasJudgements then local writeIdx = 0 @@ -3045,13 +3153,13 @@ delayedTrackingFrame:SetScript("OnUpdate", function() local _, currentTargetGUID = _UnitExists("target") currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) - if currentTargetGUID == pending.targetGUID and hasSuperwow then + if currentTargetGUID == pending.targetGUID then -- Scan all debuffs on target to find judgement-type debuffs for slot = 1, 16 do local _, _, _, debuffSpellID = _UnitDebuff("target", slot) if not debuffSpellID then break end - local debuffName = _SpellInfo(debuffSpellID) + local debuffName = _GetSpellRecField(debuffSpellID, "name") -- Check if this is a judgement debuff (name starts with "Judgement") if debuffName and _string_find(debuffName, "^Judgement") then -- Found a judgement debuff! Store it for refresh tracking @@ -3128,7 +3236,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- DEBUG: Log verification attempt for bleed spells if debug and isBleedSpell then - local spellNameDbg = _SpellInfo(pending.spellID) or "Unknown" + local spellNameDbg = _GetSpellRecField(pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff00aaff[Bleed Verify Start]|r %s (ID:%d) on %s - hasSuperwow:%s, targetGUID:%s", spellNameDbg, pending.spellID, pending.targetName or "Unknown", @@ -3136,19 +3244,19 @@ delayedTrackingFrame:SetScript("OnUpdate", function() ) end - if isBleedSpell and hasSuperwow and pending.targetGUID then + local verifyUnit = isBleedSpell and pending.targetGUID and ResolveGUIDUnit(pending.targetGUID) or nil + if isBleedSpell and verifyUnit then -- Check if mob is in bleed whitelist (skip verification for known bleeders) local isWhitelisted = CleveRoids.MobsThatBleed and CleveRoids.MobsThatBleed[pending.targetGUID] if not isWhitelisted then -- Check if target is dead - requires special handling - -- Note: SuperWoW allows GUID-based queries for all unit functions - if _UnitIsDead(pending.targetGUID) then + if _UnitIsDead(verifyUnit) then -- Target died - check if we saw "afflicted by" message before death if pending.verifiedByAffliction then -- We confirmed bleed landed via combat log before target died if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Bleed" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Bleed" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff00ff00[Bleed Verified]|r %s on %s confirmed via 'afflicted by' (target now dead)", spellNameDebug, pending.targetName or "Unknown") @@ -3167,7 +3275,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- Set bleedVerified = false so we don't add to tracking or remove existing immunity bleedVerified = false if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Bleed" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Bleed" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cffaaaaaa[Bleed Skip]|r %s on %s - target died too quickly (%.2fs), can't determine immunity", spellNameDebug, pending.targetName or "Unknown", timeSinceCast) @@ -3179,7 +3287,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- Target lived long enough but no "afflicted by" = immunity bleedVerified = false if pending.targetName and pending.targetName ~= "" then - local spellNameForImmunity = _SpellInfo(pending.spellID) or "Bleed" + local spellNameForImmunity = _GetSpellRecField(pending.spellID, "name") or "Bleed" if not CleveRoids_ImmunityData["bleed"] then CleveRoids_ImmunityData["bleed"] = {} @@ -3195,12 +3303,12 @@ delayedTrackingFrame:SetScript("OnUpdate", function() end end end - elseif not UnitExists(pending.targetGUID) then + elseif not UnitExists(verifyUnit) then -- Target despawned or GUID is invalid - can't verify, skip without recording immunity -- This is similar to one-shot kills: inconclusive result, don't record immunity bleedVerified = false if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Bleed" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Bleed" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cffaaaaaa[Bleed Skip]|r %s on %s - target no longer exists (despawned), can't determine immunity", spellNameDebug, pending.targetName or "Unknown") @@ -3209,12 +3317,12 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- Note: We set bleedVerified = false but DON'T record immunity -- This is intentional - despawned targets are inconclusive else - -- Target is alive - check debuffs by GUID (SuperWoW supports GUID-based queries) + -- Target is alive - check debuffs for bleed spell bleedVerified = false local totalDebuffs = 0 for slot = 1, 48 do - local _, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot) + local _, _, _, debuffSpellID = _UnitDebuff(verifyUnit, slot) if not debuffSpellID then if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil else @@ -3235,7 +3343,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- Record as BLEED immunity directly (bypass split damage override in RecordImmunity) -- RecordImmunity would record Rake/Pounce as "physical" (initial school), -- but we specifically detected the BLEED debuff didn't land - local spellNameForImmunity = _SpellInfo(pending.spellID) or "Bleed" + local spellNameForImmunity = _GetSpellRecField(pending.spellID, "name") or "Bleed" if not CleveRoids_ImmunityData["bleed"] then CleveRoids_ImmunityData["bleed"] = {} @@ -3252,7 +3360,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() else -- Many debuffs = likely pushed off at debuff cap if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Bleed" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Bleed" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cffff6600[Debuff Cap]|r %s not found on %s - likely pushed off (%d debuffs on target)", spellNameDebug, pending.targetName or "Unknown", totalDebuffs) @@ -3268,7 +3376,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() if bleedVerified then -- Debug: Show what we're about to add to tracking if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Unknown" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Unknown" local guidStr = _string_sub(_tostring(pending.targetGUID or "nil"), 1, 20) DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff88ff88[Pending Process]|r Adding %s (ID:%d, %ds) GUID:%s", @@ -3300,7 +3408,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() end if debug then - local spellName = _SpellInfo(pending.spellID) or "Unknown" + local spellName = _GetSpellRecField(pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff00ff00[Delayed Track]|r Applied %s (ID:%d) to tracking on %s", spellName, pending.spellID, pending.targetName or "Unknown") @@ -3383,10 +3491,11 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- 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) -- 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 + local ccVerifyUnit = not ccVerified and not pending.spellGoHit and not pending.spellGoMissed + and pending.targetGUID and ResolveGUIDUnit(pending.targetGUID) or nil + if ccVerifyUnit then -- Skip verification if target is dead (debuffs are removed on death) - if _UnitIsDead(pending.targetGUID) then + if _UnitIsDead(ccVerifyUnit) then ccVerified = true -- Assume CC landed, can't verify on dead target if debug then DEFAULT_CHAT_FRAME:AddMessage( @@ -3398,14 +3507,14 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- Method 1: Direct spell ID matching (scan debuffs for exact spell ID) if pending.spellID then for slot = 1, 48 do - local texture, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot) + local texture, _, _, debuffSpellID = _UnitDebuff(ccVerifyUnit, slot) if not texture then if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil else totalDebuffs = totalDebuffs + 1 -- Debug: Show each debuff found during CC verification if debug then - local debuffName = _SpellInfo(debuffSpellID) or "Unknown" + local debuffName = _GetSpellRecField(debuffSpellID, "name") or "Unknown" local mechanic = CleveRoids.GetSpellMechanic and CleveRoids.GetSpellMechanic(debuffSpellID) or 0 DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cffaaaaaa[CC Scan]|r Slot %d: %s (ID:%d, Mech:%d) - Looking for %s (ID:%d)", @@ -3430,7 +3539,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() pending.ccType) ) end - ccVerified = CleveRoids.ValidateUnitCC(pending.targetGUID, pending.ccType) + ccVerified = CleveRoids.ValidateUnitCC(ccVerifyUnit, pending.ccType) if debug then DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff00aaff[CC Verify]|r Mechanic check result: %s", @@ -3449,12 +3558,12 @@ delayedTrackingFrame:SetScript("OnUpdate", function() end -- If CC didn't land, check if it's immunity or debuff cap - if not ccVerified and not _UnitIsDead(pending.targetGUID) then + if not ccVerified and ccVerifyUnit and not _UnitIsDead(ccVerifyUnit) then -- totalDebuffs already counted above, reuse it - if totalDebuffs == 0 and hasSuperwow and pending.targetGUID then + if totalDebuffs == 0 then -- Count wasn't done (dead target check skipped counting), do it now for slot = 1, 48 do - local texture, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot) + local texture, _, _, debuffSpellID = _UnitDebuff(ccVerifyUnit, slot) if not texture then if slot <= 16 then break end else @@ -3494,7 +3603,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() 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") + local spellNameDebug = pending.spellName or (_GetSpellRecField(pending.spellID, "name") or "Unknown") DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff00aaff[Split CC Skip]|r %s CC resisted on %s - skipping CC immunity recording", spellNameDebug, resolvedTargetName or "Unknown") @@ -3578,7 +3687,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() local elapsed = currentTime - pending.timestamp if debug then - local spellName = pending.spellID and _SpellInfo(pending.spellID) or "Unknown" + local spellName = pending.spellID and _GetSpellRecField(pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff00aaff[Shared Check]|r %s elapsed:%.2fs (need 0.2s)", spellName, elapsed) ) @@ -3587,7 +3696,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- 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" + local spellNameDebug = pending.spellID and _GetSpellRecField(pending.spellID, "name") 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) @@ -3602,7 +3711,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() if pending.spellGoHit then debuffVerified = true if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Shared Debuff" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") 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") @@ -3612,13 +3721,14 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- Skip verification if target is dead (debuffs are removed on death) -- 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 + local sharedVerifyUnit = not debuffVerified and not pending.spellGoHit and not pending.spellGoMissed + and pending.targetGUID and ResolveGUIDUnit(pending.targetGUID) or nil + if sharedVerifyUnit then + if _UnitIsDead(sharedVerifyUnit) then -- Target died - can't verify immunity, assume debuff landed debuffVerified = true if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Shared Debuff" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Shared Debuff" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cffff6600[Shared Verify Skip]|r Target %s is dead - skipping immunity check for %s", pending.targetName or "Unknown", spellNameDebug) @@ -3627,7 +3737,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() else -- Target is alive - check if debuff exists for slot = 1, 48 do - local _, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot) + local _, _, _, debuffSpellID = _UnitDebuff(sharedVerifyUnit, slot) if not debuffSpellID then if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil else @@ -3648,7 +3758,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() if debuffVerified then -- Debuff landed - add to tracking if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Unknown" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff88ff88[Shared Verified]|r %s (ID:%d) verified on %s", spellNameDebug, pending.spellID, pending.targetName or "Unknown") @@ -3671,7 +3781,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() 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" + local spellNameDebug = _GetSpellRecField(pending.castSpellID or pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cff00aaff[Split CC Skip]|r %s CC resisted on %s - skipping immunity recording (physical damage landed)", spellNameDebug, pending.targetName or "Unknown") @@ -3688,7 +3798,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() CleveRoids_ImmunityData[pending.school][pending.targetName] = true if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Unknown" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cffff6600[Shared Immunity]|r %s is immune to %s (%s) - only %d debuffs on target", pending.targetName, pending.school, spellNameDebug, totalDebuffs) @@ -3698,7 +3808,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() else -- Many debuffs = likely pushed off at debuff cap if debug then - local spellNameDebug = _SpellInfo(pending.spellID) or "Unknown" + local spellNameDebug = _GetSpellRecField(pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( _string_format("|cffff6600[Shared Debuff Cap]|r %s not found on %s - likely pushed off (%d debuffs)", spellNameDebug, pending.targetName or "Unknown", totalDebuffs) @@ -3818,7 +3928,7 @@ ev:SetScript("OnEvent", function() -- Capture combo points when cast STARTS (before they're consumed) if (eventType == "START" or eventType == "CHANNEL") and spellID then - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") if casterGUID == playerGUID and targetGUID then -- If this is a combo scaling spell OR Ferocious Bite, capture combo points NOW (before consumption) local isComboSpell = CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) @@ -3829,7 +3939,7 @@ ev:SetScript("OnEvent", function() if currentCP and currentCP > 0 then CleveRoids.lastComboPoints = currentCP if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaaaff[UNIT_CASTEVENT START]|r Captured %d CP before casting %s (ID:%d)", currentCP, spellName, spellID) @@ -3867,7 +3977,7 @@ ev:SetScript("OnEvent", function() end if eventType == "CAST" and spellID then - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") if casterGUID == playerGUID and targetGUID then -- DRUID CARNAGE TALENT: Track Ferocious Bite cast for proc detection @@ -3882,8 +3992,7 @@ ev:SetScript("OnEvent", function() if carnageRank >= 1 then local targetName = lib.guidToName[targetGUID] if not targetName then - local _, currentTargetGUID = UnitExists("target") - currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) + local currentTargetGUID = CleveRoids.GetGUID("target") if currentTargetGUID == targetGUID then targetName = UnitName("target") lib.guidToName[targetGUID] = targetName @@ -3965,7 +4074,7 @@ ev:SetScript("OnEvent", function() -- Debug: Show what GetSpellCCType returns for this spell if CleveRoids.debug then - local spellNameDebug = SpellInfo(spellID) or "Unknown" + local spellNameDebug = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaaaaa[CC Check]|r %s (ID:%d) → ccType: %s", spellNameDebug, spellID, ccType or "nil") @@ -3979,12 +4088,15 @@ ev:SetScript("OnEvent", function() -- 2. Combat log "afflicted by" messages confirm successful CC -- 3. If neither detection method finds the CC, we record immunity local isHiddenCC = lib.hiddenCCSpells and lib.hiddenCCSpells[spellID] - local spellName = SpellInfo(spellID) + -- v3.0+: Dynamically detect hidden CC via IsAuraHidden + if not isHiddenCC and _G.IsAuraHidden then + isHiddenCC = (_G.IsAuraHidden(spellID) == 1) + end + local spellName = GetSpellRecField(spellID, "name") -- Get target name from cache or current target local ccTargetName = lib.guidToName[targetGUID] if not ccTargetName then - local _, currentTargetGUID = UnitExists("target") - currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) + local currentTargetGUID = CleveRoids.GetGUID("target") if currentTargetGUID == targetGUID then ccTargetName = UnitName("target") lib.guidToName[targetGUID] = ccTargetName @@ -4034,7 +4146,7 @@ ev:SetScript("OnEvent", function() -- DEBUG: Show what duration we calculated if CleveRoids.debug and duration then - local spellName = SpellInfo(trackingSpellID) or "Unknown" + local spellName = GetSpellRecField(trackingSpellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff00ff[DEBUG CAST]|r %s (ID:%d) CP:%s duration:%ds", spellName, trackingSpellID, tostring(comboPoints or "nil"), duration) @@ -4044,9 +4156,7 @@ ev:SetScript("OnEvent", function() if duration and duration > 0 then local targetName = lib.guidToName[targetGUID] if not targetName then - local _, currentTargetGUID = UnitExists("target") - -- IMPORTANT: Normalize GUID before comparison to avoid type mismatch - currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) + local currentTargetGUID = CleveRoids.GetGUID("target") if currentTargetGUID == targetGUID then targetName = UnitName("target") lib.guidToName[targetGUID] = targetName @@ -4066,7 +4176,7 @@ ev:SetScript("OnEvent", function() -- For combo spells, populate name-based tracking for pfUI compatibility if comboPoints and comboPoints > 0 then - local spellName = SpellInfo(spellID) + local spellName = GetSpellRecField(spellID, "name") if spellName and CleveRoids.ComboPointTracking then -- Remove rank from spell name to match pfUI's format local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") @@ -4121,7 +4231,7 @@ ev:SetScript("OnEvent", function() -- Check by ID (known judgement debuffs) OR by name (Turtle WoW custom cast spell IDs) local isJudgementCast = lib.judgementSpells[spellID] if not isJudgementCast and CleveRoids.playerClass == "PALADIN" then - local castName = _SpellInfo(spellID) + local castName = _GetSpellRecField(spellID, "name") isJudgementCast = castName and _string_find(castName, "^Judgement") end if CleveRoids.playerClass == "PALADIN" and isJudgementCast then @@ -4134,7 +4244,7 @@ ev:SetScript("OnEvent", function() end if CleveRoids.debug then - local spellName = SpellInfo(trackingSpellID) or "Unknown" + local spellName = GetSpellRecField(trackingSpellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[Pending Track]|r Scheduled %s (ID:%d) for tracking on %s (will apply if hit)", spellName, trackingSpellID, targetName or "Unknown") @@ -4153,7 +4263,7 @@ ev:SetScript("OnEvent", function() }) if CleveRoids.debug then - local spellName = SpellInfo(rankCheck.preserve) or "Unknown" + local spellName = GetSpellRecField(rankCheck.preserve, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffaaff00[Pending Track]|r Scheduled rank preserve for %s (ID:%d, %.1fs remaining) on %s", spellName, rankCheck.preserve, rankCheck.timeRemaining, targetName or "Unknown") @@ -4167,9 +4277,8 @@ ev:SetScript("OnEvent", function() local newStacks = 0 -- Check current stacks on target - local _, currentTargetGUID = UnitExists("target") - currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) - if currentTargetGUID == targetGUID and CleveRoids.hasSuperwow then + local currentTargetGUID = CleveRoids.GetGUID("target") + if currentTargetGUID == targetGUID then -- Scan debuff slots to find current stacks for i = 1, 16 do local _, existingStacks, _, existingSpellID = UnitDebuff("target", i) @@ -4208,7 +4317,7 @@ ev:SetScript("OnEvent", function() -- Determine spell school for immunity tracking -- GetSpellSchool is defined later in file, use CleveRoids wrapper if available local spellSchool = nil - local spellNameForSchool = SpellInfo(spellID) + local spellNameForSchool = GetSpellRecField(spellID, "name") if CleveRoids.GetSpellSchool then spellSchool = CleveRoids.GetSpellSchool(spellNameForSchool, spellID) end @@ -4238,7 +4347,7 @@ ev:SetScript("OnEvent", function() lib:AddEffect(targetGUID, targetName, trackingSpellID, trackingDuration, newStacks, "player") if CleveRoids.debug then - local spellName = SpellInfo(trackingSpellID) or "Unknown" + local spellName = GetSpellRecField(trackingSpellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ff00[Shared Refresh]|r %s (ID:%d) on %s - timer reset to %ds (stacks:%d)", spellName, trackingSpellID, targetName or "Unknown", trackingDuration, newStacks) @@ -4259,7 +4368,7 @@ ev:SetScript("OnEvent", function() }) if CleveRoids.debug then - local spellName = SpellInfo(trackingSpellID) or "Unknown" + local spellName = GetSpellRecField(trackingSpellID, "name") or "Unknown" local queueLen = table.getn(lib.pendingSharedDebuffs) local firstItem = lib.pendingSharedDebuffs[1] DEFAULT_CHAT_FRAME:AddMessage( @@ -4380,7 +4489,7 @@ ev:SetScript("OnEvent", function() -- SHARED DEBUFFS FROM OTHER PLAYERS: Track when other players cast shared debuffs -- This ensures Sunder Armor, Faerie Fire, etc. are tracked when ANY player casts them -- Personal debuffs are still only tracked from the player (we only care about our own) - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") if casterGUID ~= playerGUID and targetGUID then -- Check if this is a shared debuff we should track if lib.sharedDebuffs[spellID] then @@ -4389,8 +4498,7 @@ ev:SetScript("OnEvent", function() -- Get target name from cache or current target local targetName = lib.guidToName[targetGUID] if not targetName then - local _, currentTargetGUID = UnitExists("target") - currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) + local currentTargetGUID = CleveRoids.GetGUID("target") if currentTargetGUID == targetGUID then targetName = UnitName("target") lib.guidToName[targetGUID] = targetName @@ -4399,9 +4507,8 @@ ev:SetScript("OnEvent", function() -- For stacking debuffs, predict new stack count local newStacks = 1 - local _, currentTargetGUID = UnitExists("target") - currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) - if currentTargetGUID == targetGUID and CleveRoids.hasSuperwow then + local currentTargetGUID = CleveRoids.GetGUID("target") + if currentTargetGUID == targetGUID then -- Scan debuff slots to find current stacks for i = 1, 16 do local _, existingStacks, _, existingSpellID = UnitDebuff("target", i) @@ -4433,7 +4540,7 @@ ev:SetScript("OnEvent", function() lib:AddEffect(targetGUID, targetName, spellID, duration, newStacks, casterGUID) if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" local casterName = lib.guidToName[casterGUID] or "Other Player" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00aaff[Other Cast]|r %s cast %s (ID:%d) on %s - timer reset to %ds", @@ -4458,6 +4565,58 @@ ev:SetScript("OnEvent", function() CleveRoids._v238ChannelDuration = arg7 / 1000 CleveRoids._v238ChannelSpellId = arg2 end + + -- Nampower fallback for UNIT_CASTEVENT START/CHANNEL: capture combo points and Dark Harvest + -- Only needed when SuperWoW is not available (UNIT_CASTEVENT won't fire) + if event == "SPELL_START_SELF" and not CleveRoids.hasSuperwow then + local spellID = arg2 + local casterGuid = arg3 + local targetGuid = arg4 + local playerGUID = CleveRoids.GetGUID("player") + if casterGuid == playerGUID and spellID and targetGuid then + targetGuid = CleveRoids.NormalizeGUID(targetGuid) + + -- Capture combo points before consumption (equivalent to UNIT_CASTEVENT START) + local isComboSpell = CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID) + local isFerociousBite = CleveRoids.FerociousBiteSpellIDs and CleveRoids.FerociousBiteSpellIDs[spellID] + + if isComboSpell or isFerociousBite then + local currentCP = CleveRoids.GetComboPoints and CleveRoids.GetComboPoints() + if currentCP and currentCP > 0 then + CleveRoids.lastComboPoints = currentCP + if CleveRoids.debug then + local spellName = _GetSpellRecField(spellID, "name") or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + _string_format("|cffaaaaff[SPELL_START_SELF]|r Captured %d CP before casting %s (ID:%d)", + currentCP, spellName, spellID) + ) + end + end + end + + -- WARLOCK DARK HARVEST: Track channeling for DoT acceleration (TWoW Custom) + local spellType = arg8 + if spellType == 1 and CleveRoids.DarkHarvestSpellIDs and CleveRoids.DarkHarvestSpellIDs[spellID] then + local channelDuration = 8 -- Base Dark Harvest duration + CleveRoids.darkHarvestData = { + targetGUID = targetGuid, + spellID = spellID, + startTime = GetTime(), + channelDuration = channelDuration, + isActive = true + } + lib.ApplyDarkHarvestStart(targetGuid) + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + _string_format("|cff9482c9[Dark Harvest]|r Started channeling on %s (DoTs will tick 30%% faster)", + lib.guidToName[targetGuid] or "Unknown") + ) + end + end + end + end + -- pfUI 7.6 manages castTracking via its own SPELL_START handler if lib.hasPfUI76 then return end @@ -4470,7 +4629,7 @@ ev:SetScript("OnEvent", function() if not casterGuid or not spellId then return end - local spellName = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") local icon = lib:GetCachedIcon(spellId) local now = GetTime() @@ -4552,10 +4711,10 @@ ev:SetScript("OnEvent", function() if not spellId or not targetGuid then return end targetGuid = CleveRoids.NormalizeGUID(targetGuid) - local spellName = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") if not spellName then return end - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") local isOurs = (casterGuid == playerGUID) -- Annotate pending CC/shared debuffs with SPELL_GO hit/miss outcome @@ -4595,8 +4754,8 @@ ev:SetScript("OnEvent", function() -- Get target name for immunity tracking local targetName = lib.guidToName[targetGuid] if not targetName then - local _, currentTargetGUID = UnitExists("target") - if CleveRoids.NormalizeGUID(currentTargetGUID) == targetGuid then + local currentTargetGUID = CleveRoids.GetGUID("target") + if currentTargetGUID == targetGuid then targetName = UnitName("target") lib.guidToName[targetGuid] = targetName end @@ -4727,6 +4886,93 @@ ev:SetScript("OnEvent", function() end end + -- Nampower fallback: Cast-complete features from UNIT_CASTEVENT CAST + -- Only needed when SuperWoW is not available and spell hit the target + if isOurs and not CleveRoids.hasSuperwow and numHit > 0 then + -- DRUID CARNAGE TALENT: Track Ferocious Bite cast for proc detection + if CleveRoids.FerociousBiteSpellIDs and CleveRoids.FerociousBiteSpellIDs[spellId] then + local _, _, _, _, rank = GetTalentInfo(2, 17) + local carnageRank = tonumber(rank) or 0 + if carnageRank >= 1 then + local targetName = lib.guidToName[targetGuid] or UnitName("target") or "Unknown" + CleveRoids.lastFerociousBiteTime = GetTime() + CleveRoids.lastFerociousBiteTargetGUID = targetGuid + CleveRoids.lastFerociousBiteTargetName = targetName + CleveRoids.lastFerociousBiteSpellID = spellId + end + end + + -- SHAMAN MOLTEN BLAST: Track for Flame Shock refresh detection + if CleveRoids.MoltenBlastSpellIDs and CleveRoids.MoltenBlastSpellIDs[spellId] then + CleveRoids.lastMoltenBlastTime = GetTime() + CleveRoids.lastMoltenBlastTargetGUID = targetGuid + end + + -- WARLOCK CONFLAGRATE: Reduces Immolate duration by 3 seconds + if CleveRoids.ConflagrateSpellIDs and CleveRoids.ConflagrateSpellIDs[spellId] then + if lib.objects[targetGuid] then + for immolateID, _ in pairs(CleveRoids.ImmolateSpellIDs or {}) do + local rec = lib.objects[targetGuid][immolateID] + if rec and rec.duration and rec.start then + local remaining = rec.duration + rec.start - GetTime() + if remaining > 0 then + rec.duration = rec.duration - 3 + break + end + end + end + end + end + + -- CC IMMUNITY TRACKING: Check if this spell is a CC spell + local ccType = CleveRoids.GetSpellCCType and CleveRoids.GetSpellCCType(spellId) + if ccType then + local isHiddenCC = lib.hiddenCCSpells and lib.hiddenCCSpells[spellId] + if not isHiddenCC and _G.IsAuraHidden then + isHiddenCC = (_G.IsAuraHidden(spellId) == 1) + end + local ccTargetName = lib.guidToName[targetGuid] + if not ccTargetName then + local currentTargetGUID = CleveRoids.GetGUID("target") + if currentTargetGUID == targetGuid then + ccTargetName = UnitName("target") + lib.guidToName[targetGuid] = ccTargetName + end + end + table.insert(lib.pendingCCDebuffs, { + timestamp = GetTime(), + targetGUID = targetGuid, + targetName = ccTargetName, + spellID = spellId, + spellName = spellName, + ccType = ccType, + isHiddenCC = isHiddenCC, + spellGoHit = true, -- We already know it hit + }) + end + + -- DRUID CARNAGE: Save Rip cast data for potential Ferocious Bite refresh + if CleveRoids.RipSpellIDs and CleveRoids.RipSpellIDs[spellId] then + if CleveRoids.carnageDurationOverrides and CleveRoids.carnageDurationOverrides[spellId] then + CleveRoids.carnageDurationOverrides[spellId] = nil + end + end + + -- CARNAGE: Save Rake cast data for potential Ferocious Bite refresh + if CleveRoids.RakeSpellIDs and CleveRoids.RakeSpellIDs[spellId] then + if CleveRoids.carnageDurationOverrides and CleveRoids.carnageDurationOverrides[spellId] then + CleveRoids.carnageDurationOverrides[spellId] = nil + end + end + + -- Track last player cast for miss/dodge/parry removal + lib.lastPlayerCast = { + spellID = spellId, + targetGUID = targetGuid, + timestamp = GetTime() + } + end + elseif event == "AURA_CAST_ON_SELF" or event == "AURA_CAST_ON_OTHER" then -- Skip if pfUI enhanced tracking is active if lib.hasPfUIEnhanced then return end @@ -4739,7 +4985,9 @@ ev:SetScript("OnEvent", function() if not spellId or not targetGuid then return end targetGuid = CleveRoids.NormalizeGUID(targetGuid) - local spellName, spellRankStr, texture = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") + local spellRankStr = GetSpellRecField and GetSpellRecField(spellId, "rank") + local texture = lib:GetCachedIcon(spellId) if not spellName then return end -- Extract rank number @@ -4750,7 +4998,7 @@ ev:SetScript("OnEvent", function() local duration = durationMs and (durationMs / 1000) or 0 local now = GetTime() - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") local isOurs = (playerGUID and casterGuid == playerGUID) -- Check if this debuff recently failed (miss/dodge/parry) on this specific target @@ -4880,10 +5128,10 @@ ev:SetScript("OnEvent", function() if not guid or not slot or not spellId then return end guid = CleveRoids.NormalizeGUID(guid) - local spellName = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") if not spellName then return end - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") local isOurs = lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] ~= nil -- v2.32+: state == 2 means stack increase - update stacks on existing entry @@ -4925,12 +5173,15 @@ ev:SetScript("OnEvent", function() end -- Legacy: Update allSlots for slot tracking (pre-v2.30 or fallback) - lib.allSlots[guid] = lib.allSlots[guid] or {} - lib.allSlots[guid][slot] = { - spellName = spellName, - casterGuid = casterGuid, - isOurs = isOurs, - } + -- Skip if luaSlot is 0 (hidden aura in v3.0+) + if slot > 0 then + lib.allSlots[guid] = lib.allSlots[guid] or {} + lib.allSlots[guid][slot] = { + spellName = spellName, + casterGuid = casterGuid, + isOurs = isOurs, + } + end elseif event == "DEBUFF_REMOVED_OTHER" then -- Skip if pfUI enhanced tracking is active @@ -4946,7 +5197,7 @@ ev:SetScript("OnEvent", function() if not guid or not slot then return end guid = CleveRoids.NormalizeGUID(guid) - local spellName = spellId and SpellInfo and SpellInfo(spellId) + local spellName = spellId and GetSpellRecField and GetSpellRecField(spellId, "name") -- v2.32+: state == 2 means stack decrease - update stacks, don't remove if state == 2 and stacks and stacks > 0 then @@ -4963,7 +5214,8 @@ ev:SetScript("OnEvent", function() end -- Remove from ownSlots - if lib.ownSlots[guid] and lib.ownSlots[guid][slot] then + -- Skip if luaSlot is 0 (hidden aura in v3.0+) + if slot > 0 and lib.ownSlots[guid] and lib.ownSlots[guid][slot] then lib.ownSlots[guid][slot] = nil end @@ -4973,7 +5225,8 @@ ev:SetScript("OnEvent", function() end -- Legacy: Remove from allSlots and shift slots down (pre-v2.30 or fallback) - if lib.allSlots[guid] and lib.allSlots[guid][slot] then + -- Skip if luaSlot is 0 (hidden aura in v3.0+) + if slot > 0 and lib.allSlots[guid] and lib.allSlots[guid][slot] then lib.allSlots[guid][slot] = nil -- Shift slots down (only needed for legacy slot tracking) @@ -5003,7 +5256,7 @@ ev:SetScript("OnEvent", function() -- Consume pending AURA_CAST data for this buff local pending = lib.pendingBuffCasts[guid] and lib.pendingBuffCasts[guid][spellId] if pending then - local spellName = pending.spellName or (SpellInfo and SpellInfo(spellId)) + local spellName = pending.spellName or (GetSpellRecField and GetSpellRecField(spellId, "name")) if spellName then local casterGuid = pending.casterGuid local duration = pending.duration @@ -5019,8 +5272,7 @@ ev:SetScript("OnEvent", function() } -- ownBuffCasts: only if player is the caster - local _, playerGuidRaw = UnitExists("player") - local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw) + local playerGuid = CleveRoids.GetGUID("player") if playerGuid and casterGuid == playerGuid then lib.ownBuffCasts[guid] = lib.ownBuffCasts[guid] or {} lib.ownBuffCasts[guid][spellName] = { @@ -5043,9 +5295,8 @@ ev:SetScript("OnEvent", function() if not spellId then return end if state == 2 then return end -- Stack decrease only, not full removal - local spellName = SpellInfo and SpellInfo(spellId) - local _, playerGuidRaw = UnitExists("player") - local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") + local playerGuid = CleveRoids.GetGUID("player") if spellName and playerGuid then if lib.ownBuffCasts[playerGuid] then @@ -5077,7 +5328,7 @@ ev:SetScript("OnEvent", function() if not guid or not spellId then return end if state == 2 then return end -- Stack decrease only, not full removal - local spellName = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") if spellName then if lib.allBuffAuras[guid] then lib.allBuffAuras[guid][spellName] = nil @@ -5158,7 +5409,7 @@ ev:SetScript("OnEvent", function() end -- Clean up overflow buff tracking (death removes all buffs) - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") if playerGUID and guid == playerGUID then -- Player died: clear all overflow buff entries and reset cap status if CleveRoids.OverflowBuffs then @@ -5236,7 +5487,7 @@ evLearn:SetScript("OnEvent", function() local isFerociousBite = false if CleveRoids.FerociousBiteSpellIDs then for biteSpellID, _ in pairs(CleveRoids.FerociousBiteSpellIDs) do - local biteName = SpellInfo(biteSpellID) + local biteName = GetSpellRecField(biteSpellID, "name") if biteName then biteName = string.gsub(biteName, "%s*%(%s*Rank%s+%d+%s*%)", "") local messageSpellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "") @@ -5272,8 +5523,8 @@ evLearn:SetScript("OnEvent", function() for i, pending in ipairs(lib.pendingPersonalDebuffs) do -- Check both the triggered spell name AND the original cast spell name -- (e.g., "Pounce Bleed" is triggered by "Pounce", but combat log says "Pounce was dodged") - local pendingSpellName = SpellInfo(pending.spellID) - local castSpellName = pending.castSpellID and SpellInfo(pending.castSpellID) + local pendingSpellName = GetSpellRecField(pending.spellID, "name") + local castSpellName = pending.castSpellID and GetSpellRecField(pending.castSpellID, "name") local matchesTriggered = false local matchesCast = false @@ -5321,7 +5572,7 @@ evLearn:SetScript("OnEvent", function() local toRemove = {} for i, pending in ipairs(lib.pendingCCDebuffs) do - local pendingSpellName = SpellInfo(pending.spellID) + local pendingSpellName = GetSpellRecField(pending.spellID, "name") if pendingSpellName then pendingSpellName = string.gsub(pendingSpellName, "%s*%(%s*Rank%s+%d+%s*%)", "") if lower(pendingSpellName) == lower(messageSpellName) then @@ -5355,8 +5606,8 @@ evLearn:SetScript("OnEvent", function() for i, pending in ipairs(lib.pendingSharedDebuffs) do -- Check both the tracking spell name AND the original cast spell name - local pendingSpellName = SpellInfo(pending.spellID) - local castSpellName = pending.castSpellID and SpellInfo(pending.castSpellID) + local pendingSpellName = GetSpellRecField(pending.spellID, "name") + local castSpellName = pending.castSpellID and GetSpellRecField(pending.castSpellID, "name") local matchesTracking = false local matchesCast = false @@ -5403,7 +5654,7 @@ evLearn:SetScript("OnEvent", function() local castSpellID = lib.lastPlayerCast.spellID -- Get the spell name from the cast (strip rank) - local castSpellName = SpellInfo(castSpellID) + local castSpellName = GetSpellRecField(castSpellID, "name") if castSpellName then castSpellName = string.gsub(castSpellName, "%s*%(%s*Rank%s+%d+%s*%)", "") @@ -5429,7 +5680,7 @@ evLearn:SetScript("OnEvent", function() local matchingSpellIDs = {} if lib.personalDebuffs then for sid, _ in pairs(lib.personalDebuffs) do - local name = SpellInfo(sid) + local name = GetSpellRecField(sid, "name") if name then name = string.gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "") if name == castSpellName then @@ -5440,7 +5691,7 @@ evLearn:SetScript("OnEvent", function() end if lib.sharedDebuffs then for sid, _ in pairs(lib.sharedDebuffs) do - local name = SpellInfo(sid) + local name = GetSpellRecField(sid, "name") if name then name = string.gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "") if name == castSpellName then @@ -5516,7 +5767,7 @@ evLearn:SetScript("OnEvent", function() local targetName = lib.guidToName[targetGUID] 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) + local spellName = GetSpellRecField(flameShockID, "name") if spellName and pflib.AddEffect then local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") local targetLevel = UnitLevel(targetGUID) or UnitLevel("target") or 1 @@ -5565,7 +5816,7 @@ evCleanup:SetScript("OnEvent", function() -- Cleanup on zone change / login / death if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_DEAD" then -- Keep only current target's data - local _, currentGUID = UnitExists("target") + local currentGUID = CleveRoids.GetGUID("target") if currentGUID then local temp = lib.objects[currentGUID] lib.objects = {} @@ -5588,7 +5839,7 @@ evCleanup:SetScript("OnEvent", function() -- Remove expired effects from all GUIDs for guid, effects in pairs(lib.objects) do - local _, targetGUID = UnitExists("target") + local targetGUID = CleveRoids.GetGUID("target") local isCurrentTarget = (targetGUID == guid) -- Check if current target is dead @@ -5621,19 +5872,22 @@ evCleanup:SetScript("OnEvent", function() end) -- Judgement refresh on melee hits --- NOTE: Judgement refresh is now handled in Core.lua via UNIT_CASTEVENT (MAINHAND/OFFHAND) --- This chat-based fallback is only used if SuperWoW is not available +-- Priority: SuperWoW UNIT_CASTEVENT > Nampower AUTO_ATTACK_OTHER > Chat log fallback +-- This chat-based fallback is only used if neither SuperWoW nor Nampower v2.24+ is available local evJudgement = CreateFrame("Frame", "CleveRoidsLibDebuffJudgementRefreshFrame", UIParent) --- Only use chat-based detection if SuperWoW is not available -if not CleveRoids.hasSuperwow then +-- Only use chat-based detection if SuperWoW and Nampower auto-attack events are not available +local hasAutoAttackEvents = CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.features + and CleveRoids.NampowerAPI.features.hasAutoAttackEvents +if not CleveRoids.hasSuperwow and not hasAutoAttackEvents then evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS") evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES") end evJudgement:SetScript("OnEvent", function() - -- Skip if SuperWoW is available (handled by UNIT_CASTEVENT instead) + -- Skip if SuperWoW or Nampower auto-attack events handle this if CleveRoids.hasSuperwow then return end + if hasAutoAttackEvents then return end -- Only process for paladins if CleveRoids.playerClass ~= "PALADIN" then return end @@ -5657,11 +5911,10 @@ evJudgement:SetScript("OnEvent", function() if not hasHit then return end -- Get current target - local _, targetGUID = UnitExists("target") + local targetGUID = CleveRoids.GetGUID("target") if not targetGUID then return end - targetGUID = CleveRoids.NormalizeGUID(targetGUID) - if not targetGUID or not lib.objects[targetGUID] then return end + if not lib.objects[targetGUID] then return end -- Refresh all active Judgements on the target for spellID, rec in pairs(lib.objects[targetGUID]) do @@ -5673,7 +5926,7 @@ evJudgement:SetScript("OnEvent", function() rec.start = GetTime() if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" + local spellName = GetSpellRecField(spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on melee hit - new duration: %ds", spellName, spellID, rec.duration) @@ -5684,7 +5937,7 @@ evJudgement:SetScript("OnEvent", function() 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) + local spellName = GetSpellRecField(spellID, "name") if spellName and targetName then local effectName = string.gsub(spellName, "%s*%(Rank %d+%)", "") @@ -5846,7 +6099,7 @@ function CleveRoids.ApplyTalentModifier(spellID, baseDuration) local talentName = modifier.talent or ("Tab " .. modifier.tab .. " ID " .. modifier.id) DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff00ff[Talent Modifier]|r %s (ID:%d): %ds -> %ds (talent: %s rank %d, %s)", - SpellInfo(spellID) or "Unknown", spellID, baseDuration, modifiedDuration, + GetSpellRecField(spellID, "name") or "Unknown", spellID, baseDuration, modifiedDuration, talentName, talentRank, lookupMethod) ) end @@ -5877,7 +6130,7 @@ function CleveRoids.DiagnoseTalentModifier(spellID, baseDuration) DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00=== Talent Modifier Diagnostic ===|r") -- Check if spellID is valid - local spellName = SpellInfo(spellID) + local spellName = GetSpellRecField(spellID, "name") if not spellName then DEFAULT_CHAT_FRAME:AddMessage("|cffff0000ERROR: Invalid spell ID " .. tostring(spellID) .. "|r") return @@ -6051,7 +6304,7 @@ function CleveRoids.ApplyEquipmentModifier(spellID, baseDuration) DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff00ff[Equipment Modifier]|r %s (ID:%d): %ds -> %ds (item: %s [%d])", - SpellInfo(spellID) or "Unknown", spellID, baseDuration, modifiedDuration, + GetSpellRecField(spellID, "name") or "Unknown", spellID, baseDuration, modifiedDuration, itemName, itemID) ) end @@ -6110,7 +6363,7 @@ function CleveRoids.ApplyNampowerDurationModifier(spellID, baseDuration) if CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cffff00ff[Nampower Duration Modifier]|r %s (ID:%d): %ds -> %ds (flat: %+d, percent: %d%%)", - SpellInfo(spellID) or "Unknown", spellID, baseDuration, modified, + GetSpellRecField(spellID, "name") or "Unknown", spellID, baseDuration, modified, flat or 0, percent or 0) ) end @@ -6216,7 +6469,7 @@ function CleveRoids.ApplySetBonusModifier(spellID, baseDuration) if modifiedDuration ~= baseDuration and CleveRoids.debug then DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00ffff[Set Bonus Modifier]|r %s (ID:%d): %.1fs -> %.1fs (%d/%d pieces)", - SpellInfo(spellID) or "Unknown", spellID, baseDuration, modifiedDuration, + GetSpellRecField(spellID, "name") or "Unknown", spellID, baseDuration, modifiedDuration, equippedCount, modifier.threshold) ) end @@ -6492,7 +6745,7 @@ local function OnSpellDamageEvent() -- Check if this is a known bleed spell by name (fallback) - only if physical if schoolName == "physical" then - local spellName = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") if spellName then local baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "") local lower = string.lower(baseName) @@ -6514,7 +6767,7 @@ local function OnSpellDamageEvent() CleveRoids.spellSchoolMapping[spellId] = schoolName if CleveRoids.debug then - local spellName = SpellInfo and SpellInfo(spellId) or "Unknown" + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") or "Unknown" CleveRoids.Print(string.format("|cff88ff88[School Learned]|r %s (ID:%d) = %s (raw:%d)", spellName, spellId, schoolName, spellSchool)) end @@ -6551,8 +6804,8 @@ local function GetSpellSchool(spellName, spellID) local baseName = nil if spellName then baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "") - elseif spellID and SpellInfo then - local fullName = SpellInfo(spellID) + elseif spellID and GetSpellRecField then + local fullName = GetSpellRecField(spellID, "name") if fullName then baseName = string.gsub(fullName, "%s*%(.-%)%s*$", "") end @@ -6582,9 +6835,9 @@ local function GetSpellSchool(spellName, spellID) end end - -- If we only have spellID but no name, try to get name from SpellInfo - if spellID and not spellName and SpellInfo then - spellName = SpellInfo(spellID) + -- If we only have spellID but no name, try to get name from GetSpellRecField + if spellID and not spellName and GetSpellRecField then + spellName = GetSpellRecField(spellID, "name") end if not spellName then return nil end @@ -6731,14 +6984,13 @@ end -- Get current buffs on a unit local function GetUnitBuffs(unit) local buffs = {} - if not CleveRoids.hasSuperwow then return buffs end for i = 1, 32 do local texture, stacks, spellID = UnitBuff(unit, i) if not texture then break end if spellID then - local buffName = SpellInfo(spellID) + local buffName = GetSpellRecField(spellID, "name") if buffName then buffs[buffName] = true end @@ -6820,7 +7072,6 @@ local INVULNERABILITY_SPELL_IDS = { -- Uses only spell IDs from DBC mechanic 25 (INVULNERABILITY) for reliability -- Returns the buff name if found, nil otherwise local function HasImmunityGrantingBuff(unit) - if not CleveRoids.hasSuperwow then return nil end if not UnitExists(unit) then return nil end for i = 1, 32 do @@ -6828,7 +7079,7 @@ local function HasImmunityGrantingBuff(unit) if not texture then break end if spellID and INVULNERABILITY_SPELL_IDS[spellID] then - local buffName = SpellInfo(spellID) or ("SpellID:" .. spellID) + local buffName = GetSpellRecField(spellID, "name") or ("SpellID:" .. spellID) return buffName end end @@ -7146,7 +7397,7 @@ local function CancelPendingVerification(targetName, spellName) local matches = false if pending.targetName and string.lower(pending.targetName) == lowerTarget then if lowerSpell then - local pendingSpellName = pending.spellID and SpellInfo(pending.spellID) + local pendingSpellName = pending.spellID and GetSpellRecField(pending.spellID, "name") pendingSpellName = pendingSpellName and string.lower(string.gsub(pendingSpellName, "%s*%(.-%)%s*$", "")) if pendingSpellName and pendingSpellName == lowerSpell then matches = true @@ -7157,7 +7408,7 @@ local function CancelPendingVerification(targetName, spellName) end if matches then if debug then - local spellNameDebug = pending.spellID and SpellInfo(pending.spellID) or "Unknown" + local spellNameDebug = pending.spellID and GetSpellRecField(pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00aaff[Combat Log Immune]|r Cancelled pending shared debuff verification for %s on %s - combat log confirmed immunity", spellNameDebug, targetName) @@ -7178,9 +7429,9 @@ local function CancelPendingVerification(targetName, spellName) local matches = false if pending.targetName and string.lower(pending.targetName) == lowerTarget then if lowerSpell then - local pendingSpellName = pending.spellID and SpellInfo(pending.spellID) + local pendingSpellName = pending.spellID and GetSpellRecField(pending.spellID, "name") pendingSpellName = pendingSpellName and string.lower(string.gsub(pendingSpellName, "%s*%(.-%)%s*$", "")) - local castSpellName = pending.castSpellID and SpellInfo(pending.castSpellID) + local castSpellName = pending.castSpellID and GetSpellRecField(pending.castSpellID, "name") castSpellName = castSpellName and string.lower(string.gsub(castSpellName, "%s*%(.-%)%s*$", "")) if (pendingSpellName and pendingSpellName == lowerSpell) or (castSpellName and castSpellName == lowerSpell) then @@ -7192,7 +7443,7 @@ local function CancelPendingVerification(targetName, spellName) end if matches then if debug then - local spellNameDebug = pending.spellID and SpellInfo(pending.spellID) or "Unknown" + local spellNameDebug = pending.spellID and GetSpellRecField(pending.spellID, "name") or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( string.format("|cff00aaff[Combat Log Immune]|r Cancelled pending personal debuff verification for %s on %s - combat log confirmed immunity", spellNameDebug, targetName) @@ -7640,7 +7891,7 @@ local function ParseAfflictedCombatLog() for _, pending in ipairs(lib.pendingPersonalDebuffs) do if pending.targetName == targetName and not pending.verifiedByAffliction then -- Check if this pending entry matches the affliction spell - local pendingSpellName = pending.spellID and SpellInfo(pending.spellID) + local pendingSpellName = pending.spellID and GetSpellRecField(pending.spellID, "name") if pendingSpellName then -- Strip rank info for comparison pendingSpellName = string.gsub(pendingSpellName, "%s*%(.-%)%s*$", "") @@ -7681,7 +7932,7 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool) -- Universal debuff-based immunities (Banish, etc.) -- Banish makes target immune to most damage schools (not all spells) - if CleveRoids.hasSuperwow then + do local hasBanish = false -- Check debuffs first (Banish: 710 = Rank 1, 18647 = Rank 2) @@ -7787,16 +8038,14 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool) 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 + for i = 1, 32 do + local texture, stacks, spellID = UnitBuff(unitId, i) + if not texture then break end + if spellID then + local buffName = GetSpellRecField(spellID, "name") + if buffName and buffName == initialImmunityData.buff then + initialImmune = true + break end end end @@ -7813,16 +8062,14 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool) 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 + for i = 1, 32 do + local texture, stacks, spellID = UnitBuff(unitId, i) + if not texture then break end + if spellID then + local buffName = GetSpellRecField(spellID, "name") + if buffName and buffName == debuffImmunityData.buff then + debuffImmune = true + break end end end @@ -7881,16 +8128,14 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool) local requiredBuff = immunityData.buff -- Check target's buffs - if CleveRoids.hasSuperwow then - for i = 1, 32 do - local texture, stacks, spellID = UnitBuff(unitId, i) - if not texture then break end + 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 == requiredBuff then - return true - end + if spellID then + local buffName = GetSpellRecField(spellID, "name") + if buffName and buffName == requiredBuff then + return true end end end @@ -8284,7 +8529,7 @@ function CleveRoids.HasReactiveProc(spellName) -- If proc has a target GUID requirement, check if current target matches if procData.targetGUID then - local _, targetGUID = UnitExists("target") + local targetGUID = CleveRoids.GetGUID("target") if not targetGUID or targetGUID ~= procData.targetGUID then return false end @@ -8317,7 +8562,7 @@ function CleveRoids.ParseReactiveCombatLog(lowerMsg) return end - local _, targetGUID = UnitExists("target") + local targetGUID = CleveRoids.GetGUID("target") -- Check each reactive ability's trigger patterns -- NOTE: Overpower (enemy_dodge) ALWAYS uses combat log text parsing, even when SPELL_GO @@ -8384,6 +8629,25 @@ if originalUnitCastEvent then end end +-- Hook SPELL_START_SELF to clear reactive procs (Nampower fallback when SuperWoW not available) +local originalSpellStartSelf = CleveRoids.Frame and CleveRoids.Frame.SPELL_START_SELF +if originalSpellStartSelf and not CleveRoids.hasSuperwow then + CleveRoids.Frame.SPELL_START_SELF = function(...) + -- Call original handler first + if type(originalSpellStartSelf) == "function" then + originalSpellStartSelf(unpack(arg)) + end + + -- Clear reactive proc and resist state on spell cast start + -- SPELL_START_SELF args: casterGuid, targetGuid, spellId, ... + local spellId = arg[3] + if spellId then + CleveRoids.ClearReactiveProcOnCast(spellId) + CleveRoids.ClearResistState() + end + end +end + -- NAMPOWER v2.24+ AUTO_ATTACK EVENT HANDLER FOR REACTIVE ABILITIES -- Uses native events for dodge/parry/block detection when available. -- Falls back to combat log parsing for older Nampower versions. @@ -8409,11 +8673,11 @@ CleveRoids.usingNampowerAutoAttack = false -- Parameters: attackerGuid, targetGuid, totalDamage, hitInfo, victimState, ... function CleveRoids.ProcessAutoAttackEvent(isPlayerAttacker, attackerGuid, targetGuid, totalDamage, hitInfo, victimState) -- Get player GUID for comparison - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") if not playerGUID then return end -- Determine current target GUID - local _, currentTargetGUID = UnitExists("target") + local currentTargetGUID = CleveRoids.GetGUID("target") -- ======================================================================== -- OVERPOWER / SURPRISE ATTACK: Enemy dodges YOUR attack @@ -8509,8 +8773,8 @@ local function ProcessSpellMissSelf(spellId, targetGuid, missInfo) -- Resolve target name from GUID cache or current target local targetName = lib.guidToName[targetGuid] if not targetName then - local _, currentTargetGUID = UnitExists("target") - if CleveRoids.NormalizeGUID(currentTargetGUID) == CleveRoids.NormalizeGUID(targetGuid) then + local currentTargetGUID = CleveRoids.GetGUID("target") + if currentTargetGUID == targetGuid then targetName = UnitName("target") if targetName then lib.guidToName[targetGuid] = targetName @@ -8519,7 +8783,7 @@ local function ProcessSpellMissSelf(spellId, targetGuid, missInfo) end -- Resolve spell name - local spellName = SpellInfo and SpellInfo(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") local baseName = spellName and string.gsub(spellName, "%s*%(.-%)%s*$", "") or nil -- ======================================================================== @@ -8747,7 +9011,7 @@ local function ProcessSpellMissOther(spellId, casterGuid, targetGuid, missInfo) if not targetGuid or not missInfo then return end -- Only care when player is the target (victim) - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") if not playerGUID or targetGuid ~= playerGUID then return end -- DODGE: Enemy spell dodged by player → Revenge proc @@ -8924,7 +9188,7 @@ reactiveFrame:SetScript("OnEvent", function() local victimState = arg5 -- Check if player is the attacker - local _, playerGUID = UnitExists("player") + local playerGUID = CleveRoids.GetGUID("player") local isPlayerAttacker = (attackerGuid == playerGUID) CleveRoids.ProcessAutoAttackEvent(isPlayerAttacker, attackerGuid, targetGuid, totalDamage, hitInfo, victimState) @@ -8977,8 +9241,7 @@ reactiveFrame:SetScript("OnEvent", function() if not procTarget or procTarget == "0x0000000000000000" then procTarget = pending.targetGuid if not procTarget or procTarget == "0x0000000000000000" then - local _, currentTargetGUID = UnitExists("target") - procTarget = currentTargetGUID + procTarget = CleveRoids.GetGUID("target") end end @@ -9005,7 +9268,7 @@ reactiveFrame:SetScript("OnEvent", function() end if CleveRoids.debug then - local spellName = SpellInfo and SpellInfo(spellId) or tostring(spellId) + local spellName = GetSpellRecField and GetSpellRecField(spellId, "name") 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)) @@ -9081,7 +9344,7 @@ function CleveRoids.CheckResistState(resistType) if not state then return false end -- Must have current target that matches the GUID from resist event - local _, currentTargetGUID = UnitExists("target") + local currentTargetGUID = CleveRoids.GetGUID("target") if not currentTargetGUID or currentTargetGUID ~= state.targetGUID then return false end @@ -9111,7 +9374,7 @@ local function ParseResistCombatLog(lowerMsg) if not string.find(lowerMsg, "resist") then return end -- Get current target info for matching - local _, targetGUID = UnitExists("target") + local targetGUID = CleveRoids.GetGUID("target") if not targetGUID then return end local targetName = UnitName("target") @@ -9179,7 +9442,7 @@ local function HandleDebuffFade() local timestamp = GetTime() for spellID in pairs(lib.objects[targetGUID]) do - local name = SpellInfo(spellID) + local name = GetSpellRecField(spellID, "name") if name then name = gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "") if name == spellName then @@ -9230,7 +9493,7 @@ local function HandleDebuffFade() if rec.caster == "player" then local hasExpired = (rec.start + rec.duration + 1) <= timestamp local stillExists = false - local _, checkGUID = UnitExists("target") + local checkGUID = CleveRoids.GetGUID("target") if checkGUID == targetGUID then for i = 1, 16 do local _, _, _, checkSpellID = UnitDebuff("target", i) @@ -9247,7 +9510,7 @@ local function HandleDebuffFade() else -- Shared debuff: scan to verify it's gone local stillExists = false - local _, checkGUID = UnitExists("target") + local checkGUID = CleveRoids.GetGUID("target") if checkGUID == targetGUID then for i = 1, 16 do local _, _, _, checkSpellID = UnitDebuff("target", i)