--[[ Author: Dennis Werner Garske (DWG) / brian / Mewtiny License: MIT License ]] local _G = _G or getfenv(0) local CleveRoids = _G.CleveRoids or {} --This table maps stat keys to the functions that retrieve their values. local stat_checks = { -- Base Stats (Corrected to use the 'effective' stat with gear) str = function() local _, effective = UnitStat("player", 1); return effective end, strength = function() local _, effective = UnitStat("player", 1); return effective end, agi = function() local _, effective = UnitStat("player", 2); return effective end, agility = function() local _, effective = UnitStat("player", 2); return effective end, stam = function() local _, effective = UnitStat("player", 3); return effective end, stamina = function() local _, effective = UnitStat("player", 3); return effective end, int = function() local _, effective = UnitStat("player", 4); return effective end, intellect = function() local _, effective = UnitStat("player", 4); return effective end, spi = function() local _, effective = UnitStat("player", 5); return effective end, spirit = function() local _, effective = UnitStat("player", 5); return effective end, -- Combat Ratings (Corrected to use UnitAttackPower and UnitRangedAttackPower) ap = function() local base, pos, neg = UnitAttackPower("player"); return base + pos + neg end, 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, -- 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, -- Defensive Stats armor = function() local _, effective = UnitArmor("player"); return effective end, defense = function() local base, modifier = UnitDefense("player") return (base or 0) + (modifier or 0) end, -- Resistances arcane_res = function() local _, val = UnitResistance("player", 7); return val end, fire_res = function() local _, val = UnitResistance("player", 3); return val end, frost_res = function() local _, val = UnitResistance("player", 5); return val end, nature_res = function() local _, val = UnitResistance("player", 4); return val end, shadow_res = function() local _, val = UnitResistance("player", 6); return val end } local function And(t,func) if type(func) ~= "function" then return false end if type(t) ~= "table" then t = { [1] = t } end for k,v in pairs(t) do if not func(v) then return false end end return true end local function Or(t,func) if type(func) ~= "function" then return false end if type(t) ~= "table" then t = { [1] = t } end for k,v in pairs(t) do if func(v) then return true end end return false end -- pfUI debuff time helper (Vanilla 1.12.1 / Lua 5.0 safe) local function PFUI_HasLibDebuff() return type(pfUI) == "table" and type(pfUI.api) == "table" and type(pfUI.api.libdebuff) == "table" and type(pfUI.api.libdebuff.UnitDebuff) == "function" end -- Helper: Get debuff time-left (seconds) from CleveRoids.libdebuff only local function _get_debuff_timeleft(unitToken, auraName) -- SuperWoW path: GUID-based lookup if CleveRoids.hasSuperwow then local _, guid = UnitExists(unitToken) if guid and CleveRoids.libdebuff and CleveRoids.libdebuff.objects[guid] then for i = 1, 16 do local effect, _, _, _, _, duration, timeleft = CleveRoids.libdebuff:UnitDebuff(unitToken, i) if not effect then break end if effect == auraName and timeleft and timeleft >= 0 then return timeleft, duration end end end end -- Non-SuperWoW fallback if CleveRoids.libdebuff and CleveRoids.libdebuff.UnitDebuff then for idx = 1, 16 do local effect, _, _, _, _, duration, timeleft = CleveRoids.libdebuff:UnitDebuff(unitToken, idx) if not effect then break end if effect == auraName and timeleft and timeleft >= 0 then return timeleft, duration end end end return nil, nil end -- Validates that the given target is either friend (if [help]) or foe (if [harm]) -- target: The unit id to check -- help: Optional. If set to 1 then the target must be friendly. If set to 0 it must be an enemy. -- remarks: Will always return true if help is not given -- returns: Whether or not the given target can either be attacked or supported, depending on help function CleveRoids.CheckHelp(target, help) if help == nil then return true end if help then return UnitCanAssist("player", target) else return UnitCanAttack("player", target) end end -- Ensures the validity of the given target -- target: The unit id to check -- help: Optional. If set to 1 then the target must be friendly. If set to 0 it must be an enemy -- returns: Whether or not the target is a viable target function CleveRoids.IsValidTarget(target, help) -- If the conditional is not for @mouseover, use the existing logic. if target ~= "mouseover" then if not UnitExists(target) or not CleveRoids.CheckHelp(target, help) then return false end return true end -- --- START OF PATCH --- -- New logic to handle [@mouseover] with pfUI compatibility. local effectiveMouseoverUnit = "mouseover" -- Start with the default game token. -- Check if the default mouseover exists. If not, check pfUI's internal data, -- which is necessary because pfUI frames don't always update the default token. if not UnitExists(effectiveMouseoverUnit) then if pfUI and pfUI.uf and pfUI.uf.mouseover and pfUI.uf.mouseover.unit and UnitExists(pfUI.uf.mouseover.unit) then -- If pfUI has a valid mouseover unit recorded, use that instead. effectiveMouseoverUnit = pfUI.uf.mouseover.unit else -- If neither the default token nor the pfUI unit exists, there's no valid mouseover. return false end end -- --- END OF PATCH --- -- Finally, perform the help/harm check on the determined mouseover unit (either from the game or from pfUI). if not UnitExists(effectiveMouseoverUnit) or not CleveRoids.CheckHelp(effectiveMouseoverUnit, help) then return false end return true end -- Returns the current shapeshift / stance index -- returns: The index of the current shapeshift form / stance. 0 if in no shapeshift form / stance function CleveRoids.GetCurrentShapeshiftIndex() if CleveRoids.playerClass == "PRIEST" then return CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Shadowform"]) and 1 or 0 elseif CleveRoids.playerClass == "ROGUE" then return CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Stealth"]) and 1 or 0 end for i=1, GetNumShapeshiftForms() do _, _, active = GetShapeshiftFormInfo(i) if active then return i end end return 0 end function CleveRoids.CancelAura(auraName) local ix = 0 auraName = string.lower(string.gsub(auraName, "_"," ")) while true do local aura_ix = GetPlayerBuff(ix,"HELPFUL") ix = ix + 1 if aura_ix == -1 then break end if CleveRoids.hasSuperwow then local bid = GetPlayerBuffID(aura_ix) bid = (bid < -1) and (bid + 65536) or bid if string.lower(SpellInfo(bid)) == auraName then CancelPlayerBuff(aura_ix) return true end else AuraScanTooltip:SetPlayerBuff(aura_ix) local name = string.lower(getglobal("AuraScanTooltipTextLeft1"):GetText()) if name == auraName then CancelPlayerBuff(aura_ix) break end end end return false end function CleveRoids.HasGearEquipped(gearId) if not gearId then return false end local wantId = tonumber(gearId) local wantName = (type(gearId) == "string") and string.lower(gearId) or nil for slot = 1, 19 do local link = GetInventoryItemLink("player", slot) if link then -- Lua 5.0: use string.find with captures local _, _, id = string.find(link, "item:(%d+)") local _, _, nameInBrackets = string.find(link, "%[(.+)%]") if wantId and id and tonumber(id) == wantId then return true end if wantName and nameInBrackets and string.lower(nameInBrackets) == wantName then return true end -- Fallback: resolve via GetItemInfo using the numeric id if we have it if wantName and not nameInBrackets and id then local itemName = GetItemInfo(tonumber(id)) -- may be nil if not cached if itemName and string.lower(itemName) == wantName then return true end end end end return false end -- Checks whether or not the given weaponType is currently equipped -- weaponType: The name of the weapon's type (e.g. Axe, Shield, etc.) -- returns: True when equipped, otherwhise false function CleveRoids.HasWeaponEquipped(weaponType) if not CleveRoids.WeaponTypeNames[weaponType] then return false end local slotName = CleveRoids.WeaponTypeNames[weaponType].slot local localizedName = CleveRoids.WeaponTypeNames[weaponType].name local slotId = GetInventorySlotInfo(slotName) local slotLink = GetInventoryItemLink("player",slotId) if not slotLink then return false end local _,_,itemId = string.find(slotLink,"item:(%d+)") if not itemId then -- Also good to check if itemId was found return false end local _name,_link,_,_lvl,_type,subtype = GetItemInfo(itemId) -- just had to be special huh? local fist = string.find(subtype,"^Fist") -- drops things like the One-Handed prefix local _,_,subtype = string.find(subtype,"%s?(%S+)$") if subtype == localizedName or (fist and (CleveRoids.WeaponTypeNames[weaponType].name == CleveRoids.Localized.FistWeapon)) then return true end return false end -- Checks whether or not the given UnitId is in your party or your raid -- target: The UnitId of the target to check -- groupType: The name of the group type your target has to be in ("party" or "raid") -- returns: True when the given target is in the given groupType, otherwhise false function CleveRoids.IsTargetInGroupType(target, groupType) local groupSize = (groupType == "raid") and 40 or 5 for i = 1, groupSize do if UnitIsUnit(groupType..i, target) then return true end end return false end function CleveRoids.GetSpammableConditional(name) return CleveRoids.spamConditions[name] or "nomybuff" end -- Checks whether or not we're currently casting a channeled spell function CleveRoids.CheckChanneled(channeledSpell) if not channeledSpell then return false end -- Remove the "(Rank X)" part from the spells name in order to allow downranking local spellName = string.gsub(CleveRoids.CurrentSpell.spellName, "%(.-%)%s*", "") local channeled = string.gsub(channeledSpell, "%(.-%)%s*", "") if CleveRoids.CurrentSpell.type == "channeled" and spellName == channeled then return false end if channeled == CleveRoids.Localized.Attack then return not CleveRoids.CurrentSpell.autoAttack end if channeled == CleveRoids.Localized.AutoShot then return not CleveRoids.CurrentSpell.autoShot end if channeled == CleveRoids.Localized.Shoot then return not CleveRoids.CurrentSpell.wand end CleveRoids.CurrentSpell.spellName = channeled return true end function CleveRoids.ValidateComboPoints(operator, amount) if not operator or not amount then return false end local points = GetComboPoints() if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](points, amount) end return false end function CleveRoids.ValidateKnown(args) if not args then return false end if table.getn(CleveRoids.Talents) == 0 then CleveRoids.IndexTalents() end local effective_name_to_check local original_args_for_rank_check = args if type(args) ~= "table" then effective_name_to_check = args args = { name = args } else effective_name_to_check = args.name end local spell = CleveRoids.GetSpell(effective_name_to_check) local talent_points = nil if not spell then talent_points = CleveRoids.GetTalent(effective_name_to_check) end if not spell and talent_points == nil then return false end local arg_amount = nil local arg_operator = nil if type(original_args_for_rank_check) == "table" then arg_amount = original_args_for_rank_check.amount arg_operator = original_args_for_rank_check.operator end if spell then local spell_rank_str = spell.rank or (spell.highest and spell.highest.rank) or "" -- FLEXIBLY extract just the number from the rank string local _, _, spell_rank_num_str = string.find(spell_rank_str, "(%d+)") if not arg_amount and not arg_operator then return true elseif arg_amount and arg_operator and CleveRoids.operators[arg_operator] and spell_rank_num_str and spell_rank_num_str ~= "" then local numeric_rank = tonumber(spell_rank_num_str) if numeric_rank then return CleveRoids.comparators[arg_operator](numeric_rank, arg_amount) else return false end else return false end elseif talent_points ~= nil then if not arg_amount and not arg_operator then return talent_points > 0 elseif arg_amount and arg_operator and CleveRoids.operators[arg_operator] then return CleveRoids.comparators[arg_operator](talent_points, arg_amount) else return false end end return false end function CleveRoids.ValidateResting() return IsResting() end -- TODO: refactor numeric comparisons... -- Checks whether or not the given unit has power in percent vs the given amount -- unit: The unit we're checking -- operator: valid comparitive operator symbol -- amount: The required amount -- 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) if powerPercent and CleveRoids.operators[operator] then return CleveRoids.comparators[operator](powerPercent, amount) end return false end -- Checks whether or not the given unit has current power vs the given amount -- unit: The unit we're checking -- operator: valid comparitive operator symbol -- amount: The required amount -- 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) if power and CleveRoids.operators[operator] then return CleveRoids.comparators[operator](power, amount) end return false end -- Raw caster-form mana for druids (SuperWoW: 2nd return of UnitMana) function CleveRoids.ValidateDruidRawMana(unit, operator, amount) unit = unit or "player" if not operator or amount == nil then return false end if (CleveRoids.playerClass ~= "DRUID") then return false end -- SuperWoW returns: current-form power, caster-form mana local _, casterMana = UnitMana(unit) -- Fallback: if for some reason we didn't get a 2nd value and we're in caster form now if type(casterMana) ~= "number" then if UnitPowerType and UnitPowerType(unit) == 0 then casterMana = UnitMana(unit) else return false end end local cmp = CleveRoids.comparators and CleveRoids.comparators[operator] return cmp and cmp(casterMana, amount) or false end -- Checks whether or not the given unit has a power deficit vs the amount specified -- unit: The unit we're checking -- operator: valid comparitive operator symbol -- amount: The required amount -- 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) if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](powerLost, amount) end return false end -- Checks whether or not the given unit has hp in percent vs the given amount -- unit: The unit we're checking -- operator: valid comparitive operator symbol -- amount: The required amount -- 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) if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](hpPercent, amount) end return false end -- Checks whether or not the given unit has hp vs the given amount -- unit: The unit we're checking -- operator: valid comparitive operator symbol -- amount: The required amount -- 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) if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](rawhp, amount) end return false end -- Checks whether or not the given unit has an hp deficit vs the amount specified -- unit: The unit we're checking -- operator: valid comparitive operator symbol -- amount: The required amount -- 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) if CleveRoids.operators[operator] then return CleveRoids.comparators[operator](hpLost, amount) end return false end -- Checks whether the given creatureType is the same as the target's creature type -- creatureType: The type to check -- target: The target's unitID -- returns: True or false -- remarks: Allows for both localized and unlocalized type names function CleveRoids.ValidateCreatureType(creatureType, target) if not target then return false end local targetType = UnitCreatureType(target) if not targetType then return false end -- ooze or silithid etc local ct = string.lower(creatureType) local cl = UnitClassification(target) if (ct == "boss" and "worldboss" or ct) == cl then return true end if string.lower(creatureType) == "boss" then creatureType = "worldboss" end local englishType = CleveRoids.Localized.CreatureTypes[targetType] return ct == string.lower(targetType) or creatureType == englishType end -- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed function CleveRoids.ValidateCooldown(args, ignoreGCD) if not args then return false end if type(args) ~= "table" then -- Normalize the spell name by replacing underscores with spaces local normalized = string.gsub(args, "_", " ") args = {name = normalized} else -- Also normalize if it's already a table if args.name then args.name = string.gsub(args.name, "_", " ") end end local expires = CleveRoids.GetCooldown(args.name, ignoreGCD) if not args.operator and not args.amount then return expires > 0 elseif CleveRoids.operators[args.operator] then return CleveRoids.comparators[args.operator](expires - GetTime(), args.amount) end end function CleveRoids.GetPlayerAura(index, isbuff) if not index then return false end local buffType = isbuff and "HELPFUL" or "HARMFUL" local bid = GetPlayerBuff(index, buffType) if bid < 0 then return end local spellID = CleveRoids.hasSuperwow and GetPlayerBuffID(bid) return GetPlayerBuffTexture(bid), GetPlayerBuffApplications(bid), spellID, GetPlayerBuffTimeLeft(bid) 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 local isPlayer = UnitIsUnit(unit, "player") local found = false local stacks, remaining local i = isPlayer and 0 or 1 -- Primary search: BUFFS if isbuff==true, DEBUFFS if isbuff==false while true do local texture local current_spellID = nil if isPlayer then -- GetPlayerAura(index, isbuff) => texture, stacks, spellID, timeLeft texture, stacks, current_spellID, remaining = CleveRoids.GetPlayerAura(i, isbuff) else if isbuff then -- UnitBuff => texture, stacks, spellID texture, stacks, current_spellID = UnitBuff(unit, i) else -- UnitDebuff => texture, stacks, _, spellID texture, stacks, _, current_spellID = UnitDebuff(unit, i) end remaining = nil end if not texture then break end if current_spellID then local auraName = SpellInfo(current_spellID) if auraName and args.name then if string.find(string.lower(auraName), string.lower(args.name), 1, true) then found = true break end end end i = i + 1 end -- Overflow handling: when searching DEBUFFS on non-players, also scan BUFFS if not isbuff and not isPlayer and not found then i = 1 while true do local texture local current_spellID = nil -- UnitBuff => texture, stacks, spellID texture, stacks, current_spellID = UnitBuff(unit, i) if not texture then break end if current_spellID then local auraName = SpellInfo(current_spellID) if auraName and args.name then if string.find(string.lower(auraName), string.lower(args.name), 1, true) then found = true break end end end i = i + 1 end end local ops = CleveRoids.operators if not args.amount and not args.operator and not args.checkStacks then return found elseif isPlayer and not args.checkStacks and args.amount and ops[args.operator] then return CleveRoids.comparators[args.operator](remaining or -1, args.amount) elseif args.amount and args.checkStacks and ops[args.operator] then return CleveRoids.comparators[args.operator](stacks or -1, args.amount) else return false end end function CleveRoids.ValidateUnitBuff(unit, args) return CleveRoids.ValidateAura(unit, args, true) end function CleveRoids.ValidateUnitDebuff(unit, args) if not args or not UnitExists(unit) then return false end if type(args) ~= "table" then args = { name = args } end if not args.name then return false end local found = false local texture, stacks, spellID, remaining local i -- Step 1: Search DEBUFFS first i = (unit == "player") and 0 or 1 while true do if unit == "player" then texture, stacks, spellID, remaining = CleveRoids.GetPlayerAura(i, false) else texture, stacks, _, spellID = UnitDebuff(unit, i) end if not texture then break end if (CleveRoids.hasSuperwow and args.name == SpellInfo(spellID)) or (not CleveRoids.hasSuperwow and texture == CleveRoids.auraTextures[args.name]) then found = true break end i = i + 1 end -- Step 2: If not found, search BUFFS (overflow debuffs shown as buffs on some servers) if not found then i = (unit == "player") and 0 or 1 while true do if unit == "player" then texture, stacks, spellID, remaining = CleveRoids.GetPlayerAura(i, true) else texture, stacks, spellID = UnitBuff(unit, i) end if not texture then break end if (CleveRoids.hasSuperwow and args.name == SpellInfo(spellID)) or (not CleveRoids.hasSuperwow and texture == CleveRoids.auraTextures[args.name]) then found = true break end i = i + 1 end end -- Step 3: Perform conditional validation local ops = CleveRoids.operators local cmp = CleveRoids.comparators local hasNumCheck = (args.amount ~= nil) and (args.operator ~= nil) and ops[args.operator] -- Case A: No numeric/stack condition, just check for existence. if not hasNumCheck and not args.checkStacks then return found end -- Case B: Numeric/stack condition exists. if hasNumCheck then -- Stacks compare path if args.checkStacks then if found then return cmp[args.operator](stacks or 0, args.amount) else return cmp[args.operator](0, args.amount) end end -- Time-left compare path if unit == "player" then if not found then return false -- debuff doesn't exist, fail the check end local tl = remaining or 0 return cmp[args.operator](tl, args.amount) else -- Non-player: try pfUI → internal libdebuff → 0s local tl = _get_debuff_timeleft(unit, args.name) if tl ~= nil then return cmp[args.operator](tl or 0, args.amount) end if CleveRoids.libdebuff and CleveRoids.libdebuff.UnitDebuff then local atl = nil for idx = 1, 16 do local effect, _, _, _, _, duration, timeleft = CleveRoids.libdebuff:UnitDebuff(unit, idx) if effect == args.name then atl = (timeleft and timeleft >= 0) and timeleft or 0 break end end if atl ~= nil then return cmp[args.operator](atl, args.amount) end end -- No timers at all: treat missing/unknown as 0s and compare if not found then -- If debuff doesn't exist, treat as 0 seconds and compare return cmp[args.operator](0, args.amount) end -- If we reach here with no timer data, treat as 0 return cmp[args.operator](0, args.amount) end end -- If we get here, nothing matched return false end function CleveRoids.ValidatePlayerBuff(args) return CleveRoids.ValidateAura("player", args, true) end function CleveRoids.ValidatePlayerDebuff(args) return CleveRoids.ValidateAura("player", args, false) end -- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed function CleveRoids.GetCooldown(name, ignoreGCD) if not name then return 0 end -- Check if it's a spell first local spell = CleveRoids.GetSpell(name) if spell then local expires = CleveRoids.GetSpellCooldown(name, ignoreGCD) return expires -- GetSpellCooldown already returns absolute time end -- Not a spell, check if it's an item -- GetItemCooldown returns (remainingSeconds, totalDuration, enabled) local remaining, duration, enabled = CleveRoids.GetItemCooldown(name, ignoreGCD) -- Convert remaining seconds to absolute expiry time if remaining and remaining > 0 then return GetTime() + remaining end return 0 end -- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed -- Returns the cooldown of the given spellName or nil if no such spell was found function CleveRoids.GetSpellCooldown(spellName, ignoreGCD) if not spellName then return 0 end local spell = CleveRoids.GetSpell(spellName) if not spell then return 0 end local start, cd = GetSpellCooldown(spell.spellSlot, spell.bookType) if ignoreGCD and cd and cd > 0 and cd <= 1.5 then return 0 else return (start + cd) end end -- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed -- Hardened item cooldown resolver (Vanilla 1.12.1 / Lua 5.0) -- Returns: remainingSeconds, totalDuration, enabled function CleveRoids.GetItemCooldown(item) local start, duration, enable = nil, nil, nil local function _norm(s, d, e) s = tonumber(s) or 0 d = tonumber(d) or 0 e = tonumber(e) or 0 if d <= 0 or s <= 0 then return 0, 0, e end local rem = (s + d) - GetTime() if rem < 0 then rem = 0 end return rem, d, e end -- Helper: Check if a numeric value is a valid inventory slot (1-19) local function _isInventorySlot(num) return num and num >= 1 and num <= 19 end -- Case A: numeric value passed local numericItem = tonumber(item) if numericItem then -- Is it an inventory slot? if _isInventorySlot(numericItem) then start, duration, enable = GetInventoryItemCooldown("player", numericItem) return _norm(start, duration, enable) end -- Otherwise, treat it as an item ID - search equipped items first for slot = 0, 19 do local link = GetInventoryItemLink("player", slot) if link then local _, _, id = string.find(link, "item:(%d+)") if id and tonumber(id) == numericItem then start, duration, enable = GetInventoryItemCooldown("player", slot) return _norm(start, duration, enable) end end end -- Then search bags for the item ID for bag = 0, 4 do local size = GetContainerNumSlots(bag) if size and size > 0 then for slotIndex = 1, size do local link = GetContainerItemLink(bag, slotIndex) if link then local _, _, id = string.find(link, "item:(%d+)") if id and tonumber(id) == numericItem then start, duration, enable = GetContainerItemCooldown(bag, slotIndex) return _norm(start, duration, enable) end end end end end -- Item ID not found in inventory or bags return 0, 0, 0 end -- Case B: string item name -> try equipped slots first if type(item) == "string" and item ~= "" then local itemLower = string.lower(item) -- scan a few common equipment slots; expand if your engine needs more local slots = { 13, 14, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19 } -- trinkets first for i = 1, table.getn(slots) do local s = slots[i] local link = GetInventoryItemLink("player", s) if link then -- Extract item name from link using pattern [ItemName] local _, _, linkName = string.find(link, "%[(.+)%]") if linkName and string.lower(linkName) == itemLower then start, duration, enable = GetInventoryItemCooldown("player", s) return _norm(start, duration, enable) end -- Fallback: simple substring match if string.find(string.lower(link), itemLower, 1, true) then start, duration, enable = GetInventoryItemCooldown("player", s) return _norm(start, duration, enable) end end end -- Case C: search bags for the named item for bag = 0, 4 do local size = GetContainerNumSlots(bag) if size and size > 0 then for slotIndex = 1, size do local link = GetContainerItemLink(bag, slotIndex) if link then -- Extract item name from link using pattern [ItemName] local _, _, linkName = string.find(link, "%[(.+)%]") if linkName and string.lower(linkName) == itemLower then start, duration, enable = GetContainerItemCooldown(bag, slotIndex) return _norm(start, duration, enable) end -- Fallback: simple substring match if string.find(string.lower(link), itemLower, 1, true) then start, duration, enable = GetContainerItemCooldown(bag, slotIndex) return _norm(start, duration, enable) end end end end end end -- Fallback: unknown item → no cooldown return 0, 0, 0 end function CleveRoids.IsReactive(name) return CleveRoids.reactiveSpells[spellName] ~= nil end function CleveRoids.GetActionButtonInfo(slot) local macroName, actionType, id = GetActionText(slot) if actionType == "MACRO" then return actionType, id, macroName elseif actionType == "SPELL" and id then local spellName, rank = SpellInfo(id) return actionType, id, spellName, rank elseif actionType == "ITEM" and id then local item = CleveRoids.GetItem(id) return actionType, id, (item and item.name), (item and item.id) end end function CleveRoids.IsReactiveUsable(spellName) if not CleveRoids.reactiveSlots[spellName] then return false end local actionSlot = CleveRoids.reactiveSlots[spellName] local isUsable, oom = CleveRoids.Hooks.OriginalIsUsableAction(actionSlot) local start, duration = GetActionCooldown(actionSlot) if isUsable and (start == 0 or duration == 1.5) then -- 1.5 just means gcd is active return 1 else return nil, oom end end function CleveRoids.CheckSpellCast(unit, spell) if not CleveRoids.hasSuperwow then return false end local spell = spell or "" local _,guid = UnitExists(unit) if not guid or (guid and not CleveRoids.spell_tracking[guid]) then return false else -- are we casting a specific spell, or any spell if spell == SpellInfo(CleveRoids.spell_tracking[guid].spell_id) or (spell == "") then return true end return false end end -- A list of Conditionals and their functions to validate them CleveRoids.Keywords = { exists = function(conditionals) return UnitExists(conditionals.target) end, noexists = function(conditionals) return not UnitExists(conditionals.target) end, help = function(conditionals) return conditionals.help and conditionals.target and UnitExists(conditionals.target) and UnitCanAssist("player", conditionals.target) end, harm = function(conditionals) return conditionals.harm and conditionals.target and UnitExists(conditionals.target) and UnitCanAttack("player", conditionals.target) end, stance = function(conditionals) local i = CleveRoids.GetCurrentShapeshiftIndex() return Or(conditionals.stance, function (v) return (i == tonumber(v)) end) end, nostance = function(conditionals) local i = CleveRoids.GetCurrentShapeshiftIndex() local forbiddenStances = conditionals.nostance if type(forbiddenStances) ~= "table" then return i == 0 end return And(forbiddenStances, function (v) return (i ~= tonumber(v)) end) end, noform = function(conditionals) local i = CleveRoids.GetCurrentShapeshiftIndex() local forbiddenForms = conditionals.noform if type(forbiddenForms) ~= "table" then return i == 0 end return And(forbiddenForms, function (v) return (i ~= tonumber(v)) end) end, form = function(conditionals) local i = CleveRoids.GetCurrentShapeshiftIndex() return Or(conditionals.form, function (v) return (i == tonumber(v)) end) end, mod = function(conditionals) if type(conditionals.mod) ~= "table" then return CleveRoids.kmods.mod() end return Or(conditionals.mod, function(mod) return CleveRoids.kmods[mod]() end) end, nomod = function(conditionals) if type(conditionals.nomod) ~= "table" then return CleveRoids.kmods.nomod() end return And(conditionals.nomod, function(mod) return not CleveRoids.kmods[mod]() end) end, target = function(conditionals) return CleveRoids.IsValidTarget(conditionals.target, conditionals.help) end, combat = function(conditionals) -- Check if an argument like :target or :focus was provided. The parser turns this into a table. if type(conditionals.combat) == "table" then -- If so, run the check on the provided unit(s). return Or(conditionals.combat, function(unit) return UnitExists(unit) and UnitAffectingCombat(unit) end) else -- Otherwise, this is a bare [combat]. The value might be 'true' or a spell name. -- In either case, it should safely default to checking the player. return UnitAffectingCombat("player") end end, nocombat = function(conditionals) -- Check if an argument like :target or :focus was provided. if type(conditionals.nocombat) == "table" then -- If so, run the check on the provided unit(s). return And(conditionals.nocombat, function(unit) if not UnitExists(unit) then return true end return not UnitAffectingCombat(unit) end) else -- Otherwise, this is a bare [nocombat]. Default to checking the player. return not UnitAffectingCombat("player") end end, stealth = function(conditionals) return ( (CleveRoids.playerClass == "ROGUE" and CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Stealth"])) or (CleveRoids.playerClass == "DRUID" and CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Prowl"])) ) end, nostealth = function(conditionals) return ( (CleveRoids.playerClass == "ROGUE" and not CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Stealth"])) or (CleveRoids.playerClass == "DRUID" and not CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Prowl"])) ) end, casting = function(conditionals) if type(conditionals.casting) ~= "table" then return CleveRoids.CheckSpellCast(conditionals.target, "") end return Or(conditionals.casting, function (spell) return CleveRoids.CheckSpellCast(conditionals.target, spell) end) end, nocasting = function(conditionals) if type(conditionals.nocasting) ~= "table" then return CleveRoids.CheckSpellCast(conditionals.target, "") end return And(conditionals.nocasting, function (spell) return not CleveRoids.CheckSpellCast(conditionals.target, spell) end) end, zone = function(conditionals) local zone = GetRealZoneText() local sub_zone = GetSubZoneText() return Or(conditionals.zone, function (v) return (sub_zone ~= "" and (v == sub_zone) or (v == zone)) end) end, nozone = function(conditionals) local zone = GetRealZoneText() local sub_zone = GetSubZoneText() return And(conditionals.nozone, function (v) return not ((sub_zone ~= "" and v == sub_zone)) or (v == zone) end) end, equipped = function(conditionals) return And(conditionals.equipped, function (v) return (CleveRoids.HasWeaponEquipped(v) or CleveRoids.HasGearEquipped(v)) end) end, noequipped = function(conditionals) return And(conditionals.noequipped, function (v) return not (CleveRoids.HasWeaponEquipped(v) or CleveRoids.HasGearEquipped(v)) end) end, dead = function(conditionals) if not conditionals.target then return false end return UnitIsDeadOrGhost(conditionals.target) end, alive = function(conditionals) if not conditionals.target then return false end return not UnitIsDeadOrGhost(conditionals.target) end, noalive = function(conditionals) if not conditionals.target then return false end return UnitIsDeadOrGhost(conditionals.target) end, nodead = function(conditionals) if not conditionals.target then return false end return not UnitIsDeadOrGhost(conditionals.target) end, reactive = function(conditionals) return Or(conditionals.reactive, function (v) return CleveRoids.IsReactiveUsable(v) end) end, noreactive = function(conditionals) return And(conditionals.noreactive,function (v) return not CleveRoids.IsReactiveUsable(v) end) end, member = function(conditionals) return Or(conditionals.member, function(v) return CleveRoids.IsTargetInGroupType(conditionals.target, "party") or CleveRoids.IsTargetInGroupType(conditionals.target, "raid") end) end, party = function(conditionals) return CleveRoids.IsTargetInGroupType(conditionals.target, "party") end, noparty = function(conditionals) return not CleveRoids.IsTargetInGroupType(conditionals.target, "party") end, raid = function(conditionals) return CleveRoids.IsTargetInGroupType(conditionals.target, "raid") end, noraid = function(conditionals) return not CleveRoids.IsTargetInGroupType(conditionals.target, "raid") end, group = function(conditionals) if type(conditionals.group) ~= "table" then conditionals.group = { "party", "raid" } end return Or(conditionals.group, function(groups) if groups == "party" then return GetNumPartyMembers() > 0 elseif groups == "raid" then return GetNumRaidMembers() > 0 end end) end, checkchanneled = function(conditionals) return And(conditionals.checkchanneled, function(channeledSpells) return CleveRoids.CheckChanneled(channeledSpells) end) end, buff = function(conditionals) return And(conditionals.buff, function(v) return CleveRoids.ValidateUnitBuff(conditionals.target, v) end) end, nobuff = function(conditionals) return And(conditionals.nobuff, function(v) return not CleveRoids.ValidateUnitBuff(conditionals.target, v) end) end, debuff = function(conditionals) return And(conditionals.debuff, function(v) return CleveRoids.ValidateUnitDebuff(conditionals.target, v) end) end, nodebuff = function(conditionals) return And(conditionals.nodebuff, function(v) return not CleveRoids.ValidateUnitDebuff(conditionals.target, v) end) end, mybuff = function(conditionals) return And(conditionals.mybuff, function(v) return CleveRoids.ValidatePlayerBuff(v) end) end, nomybuff = function(conditionals) return And(conditionals.nomybuff, function(v) return not CleveRoids.ValidatePlayerBuff(v) end) end, mydebuff = function(conditionals) return And(conditionals.mydebuff, function(v) return CleveRoids.ValidatePlayerDebuff(v) end) end, nomydebuff = function(conditionals) return And(conditionals.nomydebuff, function(v) return not CleveRoids.ValidatePlayerDebuff(v) end) end, power = function(conditionals) return And(conditionals.power, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidatePower(conditionals.target, args.operator, args.amount) end) end, mypower = function(conditionals) return And(conditionals.mypower, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidatePower("player", args.operator, args.amount) end) end, rawpower = function(conditionals) return And(conditionals.rawpower, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateRawPower(conditionals.target, args.operator, args.amount) end) end, myrawpower = function(conditionals) return And(conditionals.myrawpower, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateRawPower("player", args.operator, args.amount) end) end, druidmana = function(conditionals) return And(conditionals.druidmana, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateDruidRawMana("player", args.operator, args.amount) end) end, powerlost = function(conditionals) return And(conditionals.powerlost, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidatePowerLost(conditionals.target, args.operator, args.amount) end) end, mypowerlost = function(conditionals) return And(conditionals.mypowerlost, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidatePowerLost("player", args.operator, args.amount) end) end, hp = function(conditionals) return And(conditionals.hp, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateHp(conditionals.target, args.operator, args.amount) end) end, myhp = function(conditionals) return And(conditionals.myhp, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateHp("player", args.operator, args.amount) end) end, rawhp = function(conditionals) return And(conditionals.rawhp, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateRawHp(conditionals.target, args.operator, args.amount) end) end, myrawhp = function(conditionals) return And(conditionals.myrawhp, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateRawHp("player", args.operator, args.amount) end) end, hplost = function(conditionals) return And(conditionals.hplost, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateHpLost(conditionals.target, args.operator, args.amount) end) end, myhplost = function(conditionals) return And(conditionals.myhplost, function(args) if type(args) ~= "table" then return false end return CleveRoids.ValidateHpLost("player", args.operator, args.amount) end) end, type = function(conditionals) return Or(conditionals.type, function(unittype) return CleveRoids.ValidateCreatureType(unittype, conditionals.target) end) end, notype = function(conditionals) return And(conditionals.notype, function(unittype) return not CleveRoids.ValidateCreatureType(unittype, conditionals.target) end) end, cooldown = function(conditionals) return And(conditionals.cooldown,function (v) return CleveRoids.ValidateCooldown(v, true) end) end, nocooldown = function(conditionals) return And(conditionals.nocooldown,function (v) return not CleveRoids.ValidateCooldown(v, true) end) end, cdgcd = function(conditionals) return And(conditionals.cdgcd,function (v) return CleveRoids.ValidateCooldown(v, false) end) end, nocdgcd = function(conditionals) return And(conditionals.nocdgcd,function (v) return not CleveRoids.ValidateCooldown(v, false) end) end, channeled = function(conditionals) return CleveRoids.CurrentSpell.type == "channeled" end, nochanneled = function(conditionals) return CleveRoids.CurrentSpell.type ~= "channeled" end, targeting = function(conditionals) return Or(conditionals.targeting, function (unit) return (UnitIsUnit("targettarget", unit) == 1) end) end, notargeting = function(conditionals) return And(conditionals.notargeting, function (unit) return UnitIsUnit("targettarget", unit) ~= 1 end) end, isplayer = function(conditionals) return UnitIsPlayer(conditionals.target) end, isnpc = function(conditionals) return not UnitIsPlayer(conditionals.target) end, inrange = function(conditionals) if not IsSpellInRange then return end return And(conditionals.inrange, function(spellName) return IsSpellInRange(spellName or conditionals.action, conditionals.target) == 1 end) end, noinrange = function(conditionals) if not IsSpellInRange then return end return And(conditionals.noinrange, function(spellName) return IsSpellInRange(spellName or conditionals.action, conditionals.target) == 0 end) end, combo = function(conditionals) return And(conditionals.combo, function(args) return CleveRoids.ValidateComboPoints(args.operator, args.amount) end) end, nocombo = function(conditionals) return And(conditionals.nocombo, function(args) return not CleveRoids.ValidateComboPoints(args.operator, args.amount) end) end, known = function(conditionals) return And(conditionals.known, function(args) return CleveRoids.ValidateKnown(args) end) end, noknown = function(conditionals) return And(conditionals.noknown, function(args) return not CleveRoids.ValidateKnown(args) end) end, resting = function() return IsResting() == 1 end, noresting = function() return IsResting() == nil end, stat = function(conditionals) return And(conditionals.stat, function(args) if type(args) ~= "table" or not args.name then return false -- Malformed arguments from the parser. end local stat_key = string.lower(args.name) local get_stat_func = stat_checks[stat_key] if not get_stat_func then return false -- The requested stat key is invalid. end local current_value = get_stat_func() if not current_value then return false end -- Check if this is a multi-comparison stat conditional -- args.comparisons will be a table of {operator=, amount=} if multiple if args.comparisons and type(args.comparisons) == "table" then -- ALL comparisons must pass (AND logic) for _, comp in ipairs(args.comparisons) do if not CleveRoids.comparators[comp.operator] then return false -- Invalid operator end if not CleveRoids.comparators[comp.operator](current_value, comp.amount) then return false -- One comparison failed, so the whole conditional fails end end return true -- All comparisons passed else -- Single comparison (backward compatibility) if not args.operator or not args.amount then return false end return CleveRoids.comparators[args.operator](current_value, args.amount) end end) end, class = function(conditionals) -- Determine which unit to check. Defaults to 'target' if no @unitid was specified. local unitToCheck = conditionals.target or "target" -- The conditional must fail if the unit doesn't exist OR is not a player. if not UnitExists(unitToCheck) or not UnitIsPlayer(unitToCheck) then return false end -- Get the player's class. local localizedClass, englishClass = UnitClass(unitToCheck) if not localizedClass then return false end -- Failsafe for unusual cases -- The "Or" helper handles multiple values like [class:Warrior/Druid]. return Or(conditionals.class, function(requiredClass) return string.lower(requiredClass) == string.lower(localizedClass) or string.lower(requiredClass) == string.lower(englishClass) end) end, noclass = function(conditionals) -- Determine which unit to check. Defaults to 'target' if no @unitid was specified. local unitToCheck = conditionals.target or "target" -- A unit that doesn't exist cannot have a specific player class. if not UnitExists(unitToCheck) then return true end -- An NPC cannot have a specific player class. if not UnitIsPlayer(unitToCheck) then return true end -- If we get here, the unit is a player. Now check their class. local localizedClass, englishClass = UnitClass(unitToCheck) -- A player should always have a class, but if not, this condition is still met. if not localizedClass then return true end -- The "And" helper ensures the player's class is not any of the forbidden classes. return And(conditionals.noclass, function(forbiddenClass) return string.lower(forbiddenClass) ~= string.lower(localizedClass) and string.lower(forbiddenClass) ~= string.lower(englishClass) end) end, pet = function(conditionals) if not UnitExists("pet") then return false end return Or(conditionals.pet, function(petType) local currentPet = UnitCreatureFamily("pet") if not currentPet then return false end return string.lower(currentPet) == string.lower(petType) end) end, nopet = function(conditionals) if not UnitExists("pet") then return true end return And(conditionals.nopet, function(petType) local currentPet = UnitCreatureFamily("pet") if not currentPet then return true end return string.lower(currentPet) ~= string.lower(petType) end) end }