Files
SuperCleveRoidMacros/Conditionals.lua
T
2025-11-16 09:46:51 -05:00

2193 lines
76 KiB
Lua

--[[
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
-- Handle both numeric IDs and string IDs like "5196"
local wantId = tonumber(gearId)
local wantName = (type(gearId) == "string" and not wantId) and string.lower(gearId) or nil
for slot = 1, 19 do
local link = GetInventoryItemLink("player", slot)
if link then
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
if wantName and not nameInBrackets and id then
local itemName = GetItemInfo(tonumber(id))
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.ValidateLevel(unit, operator, amount)
if not unit or not operator or not amount then return false end
local level = UnitLevel(unit)
if level and CleveRoids.operators[operator] then
return CleveRoids.comparators[operator](level, 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, "_", " ")
-- NEW: If this is a numeric slot (1-19), resolve to the equipped item's name
local slotNum = tonumber(normalized)
if slotNum and slotNum >= 1 and slotNum <= 19 then
local link = GetInventoryItemLink("player", slotNum)
if link then
-- Extract item name from link
local _, _, itemName = string.find(link, "%[(.+)%]")
if itemName then
normalized = itemName
end
end
end
args = {name = normalized}
else
-- Also normalize if it's already a table
if args.name then
args.name = string.gsub(args.name, "_", " ")
-- NEW: If this is a numeric slot (1-19), resolve to the equipped item's name
local slotNum = tonumber(args.name)
if slotNum and slotNum >= 1 and slotNum <= 19 then
local link = GetInventoryItemLink("player", slotNum)
if link then
-- Extract item name from link
local _, _, itemName = string.find(link, "%[(.+)%]")
if itemName then
args.name = itemName
end
end
end
end
end
local expires = CleveRoids.GetCooldown(args.name, ignoreGCD)
if not args.operator and not args.amount then
return expires > GetTime()
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.lower(auraName) == string.lower(args.name) 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.lower(auraName) == string.lower(args.name) 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
function CleveRoids.ValidateWeaponImbue(slot, imbueName)
-- Check if weapon has enchant via API
local hasMainEnchant, mainExpiration, mainCharges, hasOffEnchant, offExpiration, offCharges = GetWeaponEnchantInfo()
local hasEnchant, expiration, charges
if slot == "mh" then
hasEnchant = hasMainEnchant
expiration = mainExpiration
charges = mainCharges
else
hasEnchant = hasOffEnchant
expiration = offExpiration
charges = offCharges
end
-- Only consider temporary enchants (with time or charges)
-- This filters out permanent enchants like Crusader, Lifestealing, etc.
local hasTemporaryEnchant = hasEnchant and (expiration and expiration > 0 or charges and charges > 0)
-- If no specific imbue requested, return temporary enchant status
if not imbueName or imbueName == "" then
return hasTemporaryEnchant
end
-- If no temporary enchant, don't bother scanning
if not hasTemporaryEnchant then
return false
end
-- For specific imbue names, scan tooltip to match the name
-- BUT only check lines that have time markers (temporary enchants)
-- This prevents matching weapon stats like "Equip: ... critical strike ..."
-- Create tooltip scanner if needed
if not CleveRoidsTooltip then
CreateFrame("GameTooltip", "CleveRoidsTooltip", nil, "GameTooltipTemplate")
end
-- Scan weapon tooltip
CleveRoidsTooltip:SetOwner(UIParent, "ANCHOR_NONE")
CleveRoidsTooltip:ClearLines()
CleveRoidsTooltip:SetInventoryItem("player", slot == "mh" and 16 or 17)
-- Normalize search term once
local searchTerm = string.lower(string.gsub(imbueName, "_", " "))
-- Look for green text with time markers - check ALL green lines with time
for i = 1, CleveRoidsTooltip:NumLines() do
local text = _G["CleveRoidsTooltipTextLeft"..i]
if text then
local line = text:GetText()
if line then
local r, g, b = text:GetTextColor()
-- Green text indicates enchant
if g > 0.8 and r < 0.2 and b < 0.2 then
local lowerLine = string.lower(line)
-- Only check lines with time markers (temporary enchants)
-- This skips permanent weapon stats like "Equip: ... critical strike ..."
if string.find(lowerLine, "%(") and (string.find(lowerLine, " min%)") or string.find(lowerLine, " sec%)") or string.find(lowerLine, " charge")) then
-- This is a temporary enchant line, check if it matches
if string.find(lowerLine, searchTerm, 1, true) then
return true -- Found it!
end
end
end
end
end
end
-- Checked all lines, didn't find it
return 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.ValidatePlayerAuraCount(bigger, amount)
local aura_ix = -1
local num = 0
while true do
aura_ix = GetPlayerBuff(num,"HELPFUL|PASSIVE")
if aura_ix == -1 then break end
num = num + 1
end
if bigger == 0 then
return num < tonumber(amount)
else
return num > tonumber(amount)
end
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)
-- Use Nampower's IsSpellUsable if available (more accurate)
if IsSpellUsable then
local usable, oom = IsSpellUsable(spellName)
if usable == 1 and oom ~= 1 then
return 1
else
return nil, oom
end
end
-- Fallback to original method
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
-- Check if any spell is usable (not just reactive)
function CleveRoids.CheckSpellUsable(spellName)
if not spellName then return false end
-- Use Nampower's IsSpellUsable if available
if IsSpellUsable then
local usable, oom = IsSpellUsable(spellName)
return (usable == 1 and oom ~= 1)
end
-- Fallback: check if spell exists and player has mana/rage/energy
local spell = CleveRoids.GetSpell(spellName)
if not spell then return false end
-- Check mana cost
local currentPower = UnitMana("player")
if spell.cost and currentPower < spell.cost then
return false
end
-- Check cooldown (ignore GCD)
local start, duration = GetSpellCooldown(spell.spellSlot, spell.bookType)
if start > 0 and duration > 1.5 then
return false
end
return true
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)
local itemsToCheck = {}
-- Case 1: conditionals.equipped is a string (e.g., [equipped]ItemName)
if type(conditionals.equipped) == "string" then
table.insert(itemsToCheck, conditionals.equipped)
-- Case 2: conditionals.equipped is a table (e.g., [equipped:Shields])
elseif type(conditionals.equipped) == "table" and table.getn(conditionals.equipped) > 0 then
itemsToCheck = conditionals.equipped
-- Case 3: No value provided, check the action
elseif conditionals.action then
table.insert(itemsToCheck, conditionals.action)
else
return false
end
-- Check all items
return Or(itemsToCheck, function(v)
return (CleveRoids.HasWeaponEquipped(v) or CleveRoids.HasGearEquipped(v))
end)
end,
noequipped = function(conditionals)
local itemsToCheck = {}
-- Case 1: conditionals.noequipped is a string (e.g., [noequipped]ItemName)
if type(conditionals.noequipped) == "string" then
table.insert(itemsToCheck, conditionals.noequipped)
-- Case 2: conditionals.noequipped is a table (e.g., [noequipped:Shields])
elseif type(conditionals.noequipped) == "table" and table.getn(conditionals.noequipped) > 0 then
itemsToCheck = conditionals.noequipped
-- Case 3: No value provided, check the action
elseif conditionals.action then
table.insert(itemsToCheck, conditionals.action)
else
return false
end
-- Check all items - ALL must be NOT equipped for this to pass
return And(itemsToCheck, 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,
usable = function(conditionals)
return Or(conditionals.usable, function(name)
-- If checking a reactive spell, use reactive logic
if CleveRoids.reactiveSpells[name] then
return CleveRoids.IsReactiveUsable(name)
end
-- Check if it's a spell first
local spell = CleveRoids.GetSpell(name)
if spell then
return CleveRoids.CheckSpellUsable(name)
end
-- Not a spell - check if it's an item or slot number
local itemName = name
local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then
-- Resolve slot number to item name
local link = GetInventoryItemLink("player", slotNum)
if link then
local _, _, extractedName = string.find(link, "%[(.+)%]")
if extractedName then
itemName = extractedName
end
end
end
-- Check item cooldown (0 remaining = usable)
local remaining = CleveRoids.GetItemCooldown(itemName)
return remaining == 0
end)
end,
nousable = function(conditionals)
return And(conditionals.nousable, function(name)
-- If checking a reactive spell, use reactive logic
if CleveRoids.reactiveSpells[name] then
return not CleveRoids.IsReactiveUsable(name)
end
-- Check if it's a spell first
local spell = CleveRoids.GetSpell(name)
if spell then
return not CleveRoids.CheckSpellUsable(name)
end
-- Not a spell - check if it's an item or slot number
local itemName = name
local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then
-- Resolve slot number to item name
local link = GetInventoryItemLink("player", slotNum)
if link then
local _, _, extractedName = string.find(link, "%[(.+)%]")
if extractedName then
itemName = extractedName
end
end
end
-- Check item cooldown (>0 remaining = not usable)
local remaining = CleveRoids.GetItemCooldown(itemName)
return remaining > 0
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,
level = function(conditionals)
return And(conditionals.level, function(args)
if type(args) ~= "table" then return false end
return CleveRoids.ValidateLevel(conditionals.target, args.operator, args.amount)
end)
end,
mylevel = function(conditionals)
return And(conditionals.mylevel, function(args)
if type(args) ~= "table" then return false end
return CleveRoids.ValidateLevel("player", 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)
if GetCurrentCastingInfo then
local _, _, _, _, channeling = GetCurrentCastingInfo()
return channeling == 1
end
return CleveRoids.CurrentSpell.type == "channeled"
end,
nochanneled = function(conditionals)
if GetCurrentCastingInfo then
local _, _, _, _, channeling = GetCurrentCastingInfo()
return channeling ~= 1
end
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)
local target = conditionals.target or "target"
local checkValue = spellName or conditionals.action
-- Try to convert spell name to ID for better accuracy (Nampower)
if type(checkValue) == "string" and GetSpellIdForName then
local spellId = GetSpellIdForName(checkValue)
if spellId and spellId > 0 then
checkValue = spellId
end
end
return IsSpellInRange(checkValue, target) == 1
end)
end,
noinrange = function(conditionals)
if not IsSpellInRange then return end
return And(conditionals.noinrange, function(spellName)
local target = conditionals.target or "target"
local checkValue = spellName or conditionals.action
if type(checkValue) == "string" and GetSpellIdForName then
local spellId = GetSpellIdForName(checkValue)
if spellId and spellId > 0 then
checkValue = spellId
end
end
return IsSpellInRange(checkValue, target) == 0
end)
end,
outrange = function(conditionals)
if not IsSpellInRange then return end
return And(conditionals.outrange, function(spellName)
local target = conditionals.target or "target"
local checkValue = spellName or conditionals.action
if type(checkValue) == "string" and GetSpellIdForName then
local spellId = GetSpellIdForName(checkValue)
if spellId and spellId > 0 then
checkValue = spellId
end
end
return IsSpellInRange(checkValue, 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,
swimming = function(conditionals)
-- Check if "Aquatic Form" is in the reactive list and usable
return CleveRoids.IsReactiveUsable("Aquatic Form")
end,
noswimming = function(conditionals)
-- Check if "Aquatic Form" is NOT usable
return not CleveRoids.IsReactiveUsable("Aquatic Form")
end,
distance = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
return And(conditionals.distance, function(args)
if type(args) ~= "table" or not args.operator or not args.amount then
return false
end
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
local distance = UnitXP("distanceBetween", "player", unit)
if not distance then return false end
return CleveRoids.comparators[args.operator](distance, args.amount)
end)
end,
nodistance = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
return And(conditionals.nodistance, function(args)
if type(args) ~= "table" or not args.operator or not args.amount then
return false
end
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
local distance = UnitXP("distanceBetween", "player", unit)
if not distance then return false end
return not CleveRoids.comparators[args.operator](distance, args.amount)
end)
end,
behind = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
return UnitXP("behind", "player", unit) == true
end,
nobehind = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
return UnitXP("behind", "player", unit) ~= true
end,
insight = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
return UnitXP("inSight", "player", unit) == true
end,
noinsight = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
return UnitXP("inSight", "player", unit) ~= true
end,
meleerange = function(conditionals)
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
if CleveRoids.hasUnitXP then
local distance = UnitXP("distanceBetween", "player", unit, "meleeAutoAttack")
return distance and distance <= 5
else
-- Fallback: use CheckInteractDistance (3 = melee range)
return CheckInteractDistance(unit, 3)
end
end,
nomeleerange = function(conditionals)
local unit = conditionals.target or "target"
if not UnitExists(unit) then return true end
if CleveRoids.hasUnitXP then
local distance = UnitXP("distanceBetween", "player", unit, "meleeAutoAttack")
return not distance or distance > 5
else
return not CheckInteractDistance(unit, 3)
end
end,
queuedspell = function(conditionals)
if not CleveRoids.hasNampower then return false end
if not CleveRoids.queuedSpell then return false end
-- If no specific spell name provided, check if ANY spell is queued
if not conditionals.queuedspell or table.getn(conditionals.queuedspell) == 0 then
return true
end
-- Check if specific spell is queued
return Or(conditionals.queuedspell, function(spellName)
if not CleveRoids.queuedSpell.spellName then return false end
local queuedName = string.gsub(CleveRoids.queuedSpell.spellName, "%s*%(.-%)%s*$", "")
local checkName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
return string.lower(queuedName) == string.lower(checkName)
end)
end,
noqueuedspell = function(conditionals)
if not CleveRoids.hasNampower then return false end
-- If no specific spell name, check if NO spell is queued
if not conditionals.noqueuedspell or table.getn(conditionals.noqueuedspell) == 0 then
return CleveRoids.queuedSpell == nil
end
-- Check if specific spell is NOT queued
if not CleveRoids.queuedSpell or not CleveRoids.queuedSpell.spellName then
return true
end
return And(conditionals.noqueuedspell, function(spellName)
local queuedName = string.gsub(CleveRoids.queuedSpell.spellName, "%s*%(.-%)%s*$", "")
local checkName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
return string.lower(queuedName) ~= string.lower(checkName)
end)
end,
onswingpending = function(conditionals)
if not GetCurrentCastingInfo then return false end
local _, _, _, _, _, onswing = GetCurrentCastingInfo()
return onswing == 1
end,
noonswingpending = function(conditionals)
if not GetCurrentCastingInfo then return true end
local _, _, _, _, _, onswing = GetCurrentCastingInfo()
return onswing ~= 1
end,
mybuffcount = function(conditionals)
return And(conditionals.mybuffcount,function (v) return CleveRoids.ValidatePlayerAuraCount(v.bigger, v.amount) end)
end,
mhimbue = function(conditionals)
local imbueName = nil
-- Case 1: conditionals.mhimbue is a string (e.g., [mhimbue]Frostbrand)
if type(conditionals.mhimbue) == "string" then
imbueName = conditionals.mhimbue
-- Case 2: conditionals.mhimbue is a table (e.g., [mhimbue:Frostbrand])
elseif type(conditionals.mhimbue) == "table" and table.getn(conditionals.mhimbue) > 0 then
imbueName = conditionals.mhimbue[1] -- Use first value
-- Case 3: Boolean true means check for any imbue
elseif conditionals.mhimbue == true then
imbueName = nil -- Check for existence only
end
return CleveRoids.ValidateWeaponImbue("mh", imbueName)
end,
nomhimbue = function(conditionals)
local imbueName = nil
-- Case 1: conditionals.nomhimbue is a string
if type(conditionals.nomhimbue) == "string" then
imbueName = conditionals.nomhimbue
-- Case 2: conditionals.nomhimbue is a table
elseif type(conditionals.nomhimbue) == "table" and table.getn(conditionals.nomhimbue) > 0 then
imbueName = conditionals.nomhimbue[1]
-- Case 3: Boolean true
elseif conditionals.nomhimbue == true then
imbueName = nil
end
return not CleveRoids.ValidateWeaponImbue("mh", imbueName)
end,
ohimbue = function(conditionals)
local imbueName = nil
-- Case 1: conditionals.ohimbue is a string
if type(conditionals.ohimbue) == "string" then
imbueName = conditionals.ohimbue
-- Case 2: conditionals.ohimbue is a table
elseif type(conditionals.ohimbue) == "table" and table.getn(conditionals.ohimbue) > 0 then
imbueName = conditionals.ohimbue[1]
-- Case 3: Boolean true
elseif conditionals.ohimbue == true then
imbueName = nil
end
return CleveRoids.ValidateWeaponImbue("oh", imbueName)
end,
noohimbue = function(conditionals)
local imbueName = nil
-- Case 1: conditionals.noohimbue is a string
if type(conditionals.noohimbue) == "string" then
imbueName = conditionals.noohimbue
-- Case 2: conditionals.noohimbue is a table
elseif type(conditionals.noohimbue) == "table" and table.getn(conditionals.noohimbue) > 0 then
imbueName = conditionals.noohimbue[1]
-- Case 3: Boolean true
elseif conditionals.noohimbue == true then
imbueName = nil
end
return not CleveRoids.ValidateWeaponImbue("oh", imbueName)
end,
immune = function(conditionals)
-- Check if target is immune to the spell being cast or damage school
-- Usage: [immune] SpellName OR [immune:SpellName] OR [immune:fire]
local checkValue = nil
-- Case 1: [immune:SpellName] or [immune:fire]
if type(conditionals.immune) == "table" and table.getn(conditionals.immune) > 0 then
checkValue = conditionals.immune[1]
elseif type(conditionals.immune) == "string" then
checkValue = conditionals.immune
-- Case 2: [immune] SpellName (check the action being cast)
elseif conditionals.action then
checkValue = conditionals.action
end
if not checkValue then
return false
end
return CleveRoids.CheckImmunity(conditionals.target or "target", checkValue)
end,
noimmune = function(conditionals)
-- Check if target is NOT immune to the spell being cast or damage school
-- Usage: [noimmune] SpellName OR [noimmune:SpellName] OR [noimmune:fire]
local checkValue = nil
-- Case 1: [noimmune:SpellName] or [noimmune:fire]
if type(conditionals.noimmune) == "table" and table.getn(conditionals.noimmune) > 0 then
checkValue = conditionals.noimmune[1]
elseif type(conditionals.noimmune) == "string" then
checkValue = conditionals.noimmune
-- Case 2: [noimmune] SpellName (check the action being cast)
elseif conditionals.action then
checkValue = conditionals.action
end
if not checkValue then
return true -- If we can't determine spell/school, assume not immune
end
return not CleveRoids.CheckImmunity(conditionals.target or "target", checkValue)
end
}