mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
Fixed a critical bug where UnitBuff() calls in Utility.lua were incorrectly capturing 4 return values instead of 3, causing the spell ID variable to receive nil instead of the actual spell ID. This broke debuff overflow detection where debuffs shown as buffs would not be properly identified. According to SuperWoW documentation, the API returns: - UnitBuff(unit, index) → texture, stacks, spellID (3 values) - UnitDebuff(unit, index) → texture, stacks, debuffType, spellID (4 values) Changes: - Fixed lib:UnitDebuff() overflow fallback in Utility.lua:758 - Fixed GetUnitBuffs() helper function in Utility.lua:1108 - Fixed buff checking in CheckImmunity() function in Utility.lua:1282 - Reverted incorrect changes to Conditionals.lua (was already correct) This resolves the issue where macros like [nodebuff:Moonfire] would spam cast when the debuff had overflowed and was showing as a buff on the target.
2193 lines
76 KiB
Lua
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, debuffType, 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
|
|
}
|