mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
clarify actual multi and noable multi usage, add AND operator for multi, and fix swintimer to be more intuitive.
This commit is contained in:
+88
-59
@@ -75,6 +75,31 @@ local function Or(t,func)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Helper to choose And() or Or() based on operator metadata
|
||||
-- For negated conditionals:
|
||||
-- - If operator is OR (or not specified), use Or() -> any negation matches
|
||||
-- - If operator is AND, use And() -> all negations must match
|
||||
local function NegatedMulti(t, func, conditionals, condition)
|
||||
if type(func) ~= "function" then return false end
|
||||
if type(t) ~= "table" then
|
||||
t = { [1] = t }
|
||||
end
|
||||
|
||||
-- Check operator type from metadata
|
||||
local operatorType = "OR" -- default
|
||||
if conditionals and conditionals._operators and conditionals._operators[condition] then
|
||||
operatorType = conditionals._operators[condition]
|
||||
end
|
||||
|
||||
if operatorType == "AND" then
|
||||
-- AND operator: ALL values must fail the check (original And behavior)
|
||||
return And(t, func)
|
||||
else
|
||||
-- OR operator (default): ANY value can fail the check
|
||||
return Or(t, func)
|
||||
end
|
||||
end
|
||||
|
||||
-- pfUI debuff time helper (Vanilla 1.12.1 / Lua 5.0 safe)
|
||||
local function PFUI_HasLibDebuff()
|
||||
return type(pfUI) == "table"
|
||||
@@ -340,8 +365,8 @@ end
|
||||
|
||||
-- Validates swing timer percentage for SP_SwingTimer addon integration
|
||||
-- operator: Comparison operator (>, <, =, >=, <=, ~=)
|
||||
-- amount: Percentage of swing time (e.g., 20 means 20% of attack speed)
|
||||
-- returns: True if st_timer [operator] (attackSpeed * percent/100)
|
||||
-- amount: Percentage of swing time elapsed (e.g., 20 means 20% of swing has elapsed)
|
||||
-- returns: True if percentElapsed [operator] amount
|
||||
function CleveRoids.ValidateSwingTimer(operator, amount)
|
||||
if not operator or not amount then return false end
|
||||
|
||||
@@ -359,12 +384,15 @@ function CleveRoids.ValidateSwingTimer(operator, amount)
|
||||
local attackSpeed = UnitAttackSpeed("player")
|
||||
if not attackSpeed or attackSpeed <= 0 then return false end
|
||||
|
||||
-- Calculate threshold: percentage of swing time
|
||||
local threshold = attackSpeed * (amount / 100)
|
||||
-- Calculate percentage of swing elapsed
|
||||
-- st_timer counts down from attackSpeed to 0 (time remaining)
|
||||
-- So: timeElapsed = attackSpeed - st_timer
|
||||
local timeElapsed = attackSpeed - st_timer
|
||||
local percentElapsed = (timeElapsed / attackSpeed) * 100
|
||||
|
||||
-- Compare current swing timer against threshold
|
||||
-- Compare percent elapsed against threshold
|
||||
if CleveRoids.operators[operator] then
|
||||
return CleveRoids.comparators[operator](st_timer, threshold)
|
||||
return CleveRoids.comparators[operator](percentElapsed, amount)
|
||||
end
|
||||
|
||||
return false
|
||||
@@ -1276,9 +1304,9 @@ CleveRoids.Keywords = {
|
||||
if type(forbiddenStances) ~= "table" then
|
||||
return i == 0
|
||||
end
|
||||
return And(forbiddenStances, function (v)
|
||||
return NegatedMulti(forbiddenStances, function (v)
|
||||
return (i ~= tonumber(v))
|
||||
end)
|
||||
end, conditionals, "nostance")
|
||||
end,
|
||||
|
||||
noform = function(conditionals)
|
||||
@@ -1287,9 +1315,9 @@ CleveRoids.Keywords = {
|
||||
if type(forbiddenForms) ~= "table" then
|
||||
return i == 0
|
||||
end
|
||||
return And(forbiddenForms, function (v)
|
||||
return NegatedMulti(forbiddenForms, function (v)
|
||||
return (i ~= tonumber(v))
|
||||
end)
|
||||
end, conditionals, "noform")
|
||||
end,
|
||||
|
||||
form = function(conditionals)
|
||||
@@ -1312,9 +1340,9 @@ CleveRoids.Keywords = {
|
||||
if type(conditionals.nomod) ~= "table" then
|
||||
return CleveRoids.kmods.nomod()
|
||||
end
|
||||
return And(conditionals.nomod, function(mod)
|
||||
return NegatedMulti(conditionals.nomod, function(mod)
|
||||
return not CleveRoids.kmods[mod]()
|
||||
end)
|
||||
end, conditionals, "nomod")
|
||||
end,
|
||||
|
||||
target = function(conditionals)
|
||||
@@ -1339,12 +1367,12 @@ CleveRoids.Keywords = {
|
||||
-- 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)
|
||||
return NegatedMulti(conditionals.nocombat, function(unit)
|
||||
if not UnitExists(unit) then
|
||||
return true
|
||||
end
|
||||
return not UnitAffectingCombat(unit)
|
||||
end)
|
||||
end, conditionals, "nocombat")
|
||||
else
|
||||
-- Otherwise, this is a bare [nocombat]. Default to checking the player.
|
||||
return not UnitAffectingCombat("player")
|
||||
@@ -1374,9 +1402,9 @@ CleveRoids.Keywords = {
|
||||
|
||||
nocasting = function(conditionals)
|
||||
if type(conditionals.nocasting) ~= "table" then return not CleveRoids.CheckSpellCast(conditionals.target, "") end
|
||||
return And(conditionals.nocasting, function (spell)
|
||||
return NegatedMulti(conditionals.nocasting, function (spell)
|
||||
return not CleveRoids.CheckSpellCast(conditionals.target, spell)
|
||||
end)
|
||||
end, conditionals, "nocasting")
|
||||
end,
|
||||
|
||||
-- NEW: Direct player casting check using Nampower's GetCurrentCastingInfo
|
||||
@@ -1415,9 +1443,9 @@ CleveRoids.Keywords = {
|
||||
nozone = function(conditionals)
|
||||
local zone = GetRealZoneText()
|
||||
local sub_zone = GetSubZoneText()
|
||||
return And(conditionals.nozone, function (v)
|
||||
return NegatedMulti(conditionals.nozone, function (v)
|
||||
return not ((sub_zone ~= "" and v == sub_zone)) or (v == zone)
|
||||
end)
|
||||
end, conditionals, "nozone")
|
||||
end,
|
||||
|
||||
equipped = function(conditionals)
|
||||
@@ -1491,9 +1519,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
noreactive = function(conditionals)
|
||||
return And(conditionals.noreactive, function (v)
|
||||
return NegatedMulti(conditionals.noreactive, function (v)
|
||||
return not CleveRoids.IsReactiveUsable(v)
|
||||
end)
|
||||
end, conditionals, "noreactive")
|
||||
end,
|
||||
|
||||
usable = function(conditionals)
|
||||
@@ -1530,7 +1558,7 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
nousable = function(conditionals)
|
||||
return And(conditionals.nousable, function(name)
|
||||
return NegatedMulti(conditionals.nousable, function(name)
|
||||
-- If checking a reactive spell, use reactive logic
|
||||
if CleveRoids.reactiveSpells[name] then
|
||||
return not CleveRoids.IsReactiveUsable(name)
|
||||
@@ -1559,7 +1587,7 @@ CleveRoids.Keywords = {
|
||||
-- Check item cooldown (>0 remaining = not usable)
|
||||
local remaining = CleveRoids.GetItemCooldown(itemName)
|
||||
return remaining > 0
|
||||
end)
|
||||
end, conditionals, "nousable")
|
||||
end,
|
||||
|
||||
member = function(conditionals)
|
||||
@@ -1612,9 +1640,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
nobuff = function(conditionals)
|
||||
return And(conditionals.nobuff, function(v)
|
||||
return NegatedMulti(conditionals.nobuff, function(v)
|
||||
return not CleveRoids.ValidateUnitBuff(conditionals.target, v)
|
||||
end)
|
||||
end, conditionals, "nobuff")
|
||||
end,
|
||||
|
||||
debuff = function(conditionals)
|
||||
@@ -1624,9 +1652,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
nodebuff = function(conditionals)
|
||||
return And(conditionals.nodebuff, function(v)
|
||||
return NegatedMulti(conditionals.nodebuff, function(v)
|
||||
return not CleveRoids.ValidateUnitDebuff(conditionals.target, v)
|
||||
end)
|
||||
end, conditionals, "nodebuff")
|
||||
end,
|
||||
|
||||
mybuff = function(conditionals)
|
||||
@@ -1636,9 +1664,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
nomybuff = function(conditionals)
|
||||
return And(conditionals.nomybuff, function(v)
|
||||
return NegatedMulti(conditionals.nomybuff, function(v)
|
||||
return not CleveRoids.ValidatePlayerBuff(v)
|
||||
end)
|
||||
end, conditionals, "nomybuff")
|
||||
end,
|
||||
|
||||
mydebuff = function(conditionals)
|
||||
@@ -1648,9 +1676,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
nomydebuff = function(conditionals)
|
||||
return And(conditionals.nomydebuff, function(v)
|
||||
return NegatedMulti(conditionals.nomydebuff, function(v)
|
||||
return not CleveRoids.ValidatePlayerDebuff(v)
|
||||
end)
|
||||
end, conditionals, "nomydebuff")
|
||||
end,
|
||||
|
||||
power = function(conditionals)
|
||||
@@ -1765,9 +1793,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
notype = function(conditionals)
|
||||
return And(conditionals.notype, function(unittype)
|
||||
return NegatedMulti(conditionals.notype, function(unittype)
|
||||
return not CleveRoids.ValidateCreatureType(unittype, conditionals.target)
|
||||
end)
|
||||
end, conditionals, "notype")
|
||||
end,
|
||||
|
||||
cooldown = function(conditionals)
|
||||
@@ -1777,9 +1805,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
nocooldown = function(conditionals)
|
||||
return And(conditionals.nocooldown,function (v)
|
||||
return NegatedMulti(conditionals.nocooldown,function (v)
|
||||
return not CleveRoids.ValidateCooldown(v, true)
|
||||
end)
|
||||
end, conditionals, "nocooldown")
|
||||
end,
|
||||
|
||||
cdgcd = function(conditionals)
|
||||
@@ -1789,9 +1817,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
nocdgcd = function(conditionals)
|
||||
return And(conditionals.nocdgcd,function (v)
|
||||
return NegatedMulti(conditionals.nocdgcd,function (v)
|
||||
return not CleveRoids.ValidateCooldown(v, false)
|
||||
end)
|
||||
end, conditionals, "nocdgcd")
|
||||
end,
|
||||
|
||||
channeled = function(conditionals)
|
||||
@@ -1817,9 +1845,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
notargeting = function(conditionals)
|
||||
return And(conditionals.notargeting, function (unit)
|
||||
return NegatedMulti(conditionals.notargeting, function (unit)
|
||||
return UnitIsUnit("targettarget", unit) ~= 1
|
||||
end)
|
||||
end, conditionals, "notargeting")
|
||||
end,
|
||||
|
||||
isplayer = function(conditionals)
|
||||
@@ -1850,7 +1878,7 @@ CleveRoids.Keywords = {
|
||||
|
||||
noinrange = function(conditionals)
|
||||
if not IsSpellInRange then return end
|
||||
return And(conditionals.noinrange, function(spellName)
|
||||
return NegatedMulti(conditionals.noinrange, function(spellName)
|
||||
local target = conditionals.target or "target"
|
||||
local checkValue = spellName or conditionals.action
|
||||
|
||||
@@ -1862,7 +1890,7 @@ CleveRoids.Keywords = {
|
||||
end
|
||||
|
||||
return IsSpellInRange(checkValue, target) == 0
|
||||
end)
|
||||
end, conditionals, "noinrange")
|
||||
end,
|
||||
|
||||
outrange = function(conditionals)
|
||||
@@ -1889,9 +1917,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
nocombo = function(conditionals)
|
||||
return And(conditionals.nocombo, function(args)
|
||||
return NegatedMulti(conditionals.nocombo, function(args)
|
||||
return not CleveRoids.ValidateComboPoints(args.operator, args.amount)
|
||||
end)
|
||||
end, conditionals, "nocombo")
|
||||
end,
|
||||
|
||||
known = function(conditionals)
|
||||
@@ -1901,9 +1929,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
noknown = function(conditionals)
|
||||
return And(conditionals.noknown, function(args)
|
||||
return NegatedMulti(conditionals.noknown, function(args)
|
||||
return not CleveRoids.ValidateKnown(args)
|
||||
end)
|
||||
end, conditionals, "noknown")
|
||||
end,
|
||||
|
||||
resting = function()
|
||||
@@ -1991,10 +2019,10 @@ CleveRoids.Keywords = {
|
||||
-- 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)
|
||||
-- The "NegatedMulti" helper ensures the player's class is not any of the forbidden classes.
|
||||
return NegatedMulti(conditionals.noclass, function(forbiddenClass)
|
||||
return string.lower(forbiddenClass) ~= string.lower(localizedClass) and string.lower(forbiddenClass) ~= string.lower(englishClass)
|
||||
end)
|
||||
end, conditionals, "noclass")
|
||||
end,
|
||||
|
||||
pet = function(conditionals)
|
||||
@@ -2016,13 +2044,13 @@ CleveRoids.Keywords = {
|
||||
return true
|
||||
end
|
||||
|
||||
return And(conditionals.nopet, function(petType)
|
||||
return NegatedMulti(conditionals.nopet, function(petType)
|
||||
local currentPet = UnitCreatureFamily("pet")
|
||||
if not currentPet then
|
||||
return true
|
||||
end
|
||||
return string.lower(currentPet) ~= string.lower(petType)
|
||||
end)
|
||||
end, conditionals, "nopet")
|
||||
end,
|
||||
|
||||
swimming = function(conditionals)
|
||||
@@ -2056,7 +2084,7 @@ CleveRoids.Keywords = {
|
||||
nodistance = function(conditionals)
|
||||
if not CleveRoids.hasUnitXP then return false end
|
||||
|
||||
return And(conditionals.nodistance, function(args)
|
||||
return NegatedMulti(conditionals.nodistance, function(args)
|
||||
if type(args) ~= "table" or not args.operator or not args.amount then
|
||||
return false
|
||||
end
|
||||
@@ -2068,7 +2096,7 @@ CleveRoids.Keywords = {
|
||||
if not distance then return false end
|
||||
|
||||
return not CleveRoids.comparators[args.operator](distance, args.amount)
|
||||
end)
|
||||
end, conditionals, "nodistance")
|
||||
end,
|
||||
|
||||
behind = function(conditionals)
|
||||
@@ -2163,11 +2191,11 @@ CleveRoids.Keywords = {
|
||||
return true
|
||||
end
|
||||
|
||||
return And(conditionals.noqueuedspell, function(spellName)
|
||||
return NegatedMulti(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, conditionals, "noqueuedspell")
|
||||
end,
|
||||
|
||||
onswingpending = function(conditionals)
|
||||
@@ -2301,8 +2329,9 @@ CleveRoids.Keywords = {
|
||||
end,
|
||||
|
||||
-- SP_SwingTimer integration conditionals
|
||||
-- Checks if swing timer is at a percentage of attack speed
|
||||
-- Usage: [swingtimer:>20] = st_timer > UnitAttackSpeed("player") * 0.2
|
||||
-- Checks percentage of swing time that has elapsed
|
||||
-- Usage: [swingtimer:<15] = less than 15% of swing has elapsed (early in swing)
|
||||
-- [swingtimer:>80] = more than 80% of swing has elapsed (late in swing)
|
||||
swingtimer = function(conditionals)
|
||||
return And(conditionals.swingtimer, function(args)
|
||||
if type(args) ~= "table" then return false end
|
||||
@@ -2320,17 +2349,17 @@ CleveRoids.Keywords = {
|
||||
|
||||
-- Negated swingtimer
|
||||
noswingtimer = function(conditionals)
|
||||
return And(conditionals.noswingtimer, function(args)
|
||||
return NegatedMulti(conditionals.noswingtimer, function(args)
|
||||
if type(args) ~= "table" then return false end
|
||||
return not CleveRoids.ValidateSwingTimer(args.operator, args.amount)
|
||||
end)
|
||||
end, conditionals, "noswingtimer")
|
||||
end,
|
||||
|
||||
-- Alias for noswingtimer
|
||||
nostimer = function(conditionals)
|
||||
return And(conditionals.nostimer, function(args)
|
||||
return NegatedMulti(conditionals.nostimer, function(args)
|
||||
if type(args) ~= "table" then return false end
|
||||
return not CleveRoids.ValidateSwingTimer(args.operator, args.amount)
|
||||
end)
|
||||
end, conditionals, "nostimer")
|
||||
end
|
||||
}
|
||||
|
||||
@@ -1239,8 +1239,33 @@ function CleveRoids.ParseMsg(msg)
|
||||
conditionals[condition] = { conditionals[condition] }
|
||||
end
|
||||
|
||||
-- Split args by '/' for multiple values
|
||||
for _, arg_item in CleveRoids.splitString(args, "/") do
|
||||
-- Detect which separator is used: / (OR) or & (AND)
|
||||
-- Initialize metadata table if needed
|
||||
if not conditionals._operators then
|
||||
conditionals._operators = {}
|
||||
end
|
||||
|
||||
-- Check which separator is present
|
||||
local hasSlash = string.find(args, "/")
|
||||
local hasAmpersand = string.find(args, "&")
|
||||
local separator = "/"
|
||||
local operatorType = "OR"
|
||||
|
||||
if hasAmpersand and not hasSlash then
|
||||
separator = "&"
|
||||
operatorType = "AND"
|
||||
elseif hasAmpersand and hasSlash then
|
||||
-- Both separators present - default to / (OR) and warn
|
||||
-- Could add a warning here in the future
|
||||
separator = "/"
|
||||
operatorType = "OR"
|
||||
end
|
||||
|
||||
-- Store the operator type for this conditional
|
||||
conditionals._operators[condition] = operatorType
|
||||
|
||||
-- Split args by the determined separator
|
||||
for _, arg_item in CleveRoids.splitString(args, separator) do
|
||||
local processed_arg = CleveRoids.Trim(arg_item)
|
||||
|
||||
processed_arg = string.gsub(processed_arg, '"', "")
|
||||
|
||||
@@ -107,6 +107,7 @@ CleveRoids.ignoreKeywords = {
|
||||
action = true,
|
||||
ignoretooltip = true,
|
||||
cancelaura = true,
|
||||
_operators = true, -- Metadata for AND/OR operator tracking
|
||||
}
|
||||
|
||||
-- TODO: Localize?
|
||||
|
||||
@@ -93,27 +93,41 @@ Both single-line and multi-line formats work identically:
|
||||
* **Default**: Most conditionals default to `@target` if not specified
|
||||
|
||||
### Multi-Value Conditionals
|
||||
Some conditionals accept multiple values with `/` separator:
|
||||
Some conditionals accept multiple values with operators:
|
||||
|
||||
#### OR Operator (`/`)
|
||||
* **Any match wins**: `[zone:Stormwind/Ironforge]` = in Stormwind OR Ironforge
|
||||
* **Works with negation**: `[nozone:Stormwind_City/Ironforge]` = NOT in Stormwind OR NOT in Ironforge
|
||||
* True if you're outside either zone (or both)
|
||||
* Example: `[nomypower:>10/<20]` = power is not >10 OR not <20 (true if power is 5-10 or ≥20)
|
||||
|
||||
#### AND Operator (`&`)
|
||||
* **All must match**: `[zone:Stormwind_City&Elwynn_Forest]` = in both zones (subzone & zone check)
|
||||
* **Works with negation**: `[nozone:Stormwind_City&Ironforge]` = NOT in Stormwind AND NOT in Ironforge
|
||||
* True only if you're in neither zone
|
||||
* Example: `[nomybuff:Mark_of_the_Wild&Thorns]` = missing both Mark AND Thorns
|
||||
|
||||
**Important**: The `/` operator ALWAYS means OR, even for negated conditionals. Use `&` for AND logic.
|
||||
|
||||
* **Marked as "Multi"** in the conditionals table below
|
||||
|
||||
### Negation (No-able Conditionals)
|
||||
Prefix with `no` to negate:
|
||||
* **All must be false**: `[nozone:Stormwind/Ironforge]` = NOT in Stormwind AND NOT in Ironforge
|
||||
* **Marked as "Noable"** in the conditionals table below
|
||||
* **Operators apply**: Use `/` for OR logic, `&` for AND logic (see above)
|
||||
|
||||
### Numerical Comparisons
|
||||
For hp, power, cooldown, etc:
|
||||
* **Operators**: `<`, `>`, `=`, `<=`, `>=`, `~=`
|
||||
* **Format**: `[condition:>50]` or `[condition:"Name">50]`
|
||||
* **Stacks vs Time**: Use `>#` for stacks/rank, no `#` for time
|
||||
* `[buff:"Mark of the Wild">#5]` = 5+ stacks
|
||||
* `[buff:"Mark_of_the_Wild">#5]` = 5+ stacks
|
||||
* `[mybuff:"Renew"<4]` = less than 4 seconds remaining
|
||||
|
||||
### Omitting Values
|
||||
If the conditional value matches the action, you can omit it:
|
||||
* `[debuff:"Sunder Armor"<#5] Sunder Armor` = `[debuff:<#5] Sunder Armor`
|
||||
* `[nobuff:"Mark of the Wild"] Mark of the Wild` = `[nobuff] Mark of the Wild`
|
||||
* `[debuff:"Sunder_Armor"<#5] Sunder Armor` = `[debuff:<#5] Sunder Armor`
|
||||
* `[nobuff:"Mark_of_the_Wild"] Mark of the Wild` = `[nobuff] Mark of the Wild`
|
||||
|
||||
### Item IDs
|
||||
Use item IDs instead of names to avoid cache issues:
|
||||
@@ -162,6 +176,41 @@ Use slot numbers (1-19) with `/use`:
|
||||
/cast Faerie Fire (Feral)(Rank 4) -- CORRECT
|
||||
```
|
||||
|
||||
### Multi-Value Operator Examples
|
||||
|
||||
**OR Operator (`/`)** - Any value matches:
|
||||
```
|
||||
# Cast if in Stormwind OR Ironforge
|
||||
/cast [zone:Stormwind_City/Ironforge] Hearthstone
|
||||
|
||||
# Cast if NOT in Stormwind OR NOT in Ironforge (true if outside either/both)
|
||||
/cast [nozone:Stormwind_City/Ironforge] Mount
|
||||
|
||||
# Cast if power is NOT >10 OR NOT <20 (true if power is 5-10 or ≥20)
|
||||
/cast [nomypower:>10/<20] Spell
|
||||
```
|
||||
|
||||
**AND Operator (`&`)** - All values must match:
|
||||
```
|
||||
# Cast if missing both Mark of the Wild AND Thorns
|
||||
/cast [nobuff:Mark_of_the_Wild&Thorns] Mark of the Wild
|
||||
|
||||
# Cast if NOT alt AND NOT ctrl (only true if pressing neither)
|
||||
/cast [nomod:alt&ctrl] Fireball
|
||||
|
||||
# Cast if target has neither Corruption AND Curse of Agony
|
||||
/cast [nodebuff:Corruption&Curse_of_Agony] Corruption
|
||||
```
|
||||
|
||||
**Practical Comparison**:
|
||||
```
|
||||
# OR: Reapply if missing EITHER buff
|
||||
/cast [nomybuff:Mark_of_the_Wild/Thorns] Mark of the Wild
|
||||
|
||||
# AND: Only apply if missing BOTH buffs
|
||||
/cast [nomybuff:Mark_of_the_Wild&Thorns] Mark of the Wild
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# What's Different
|
||||
@@ -572,8 +621,8 @@ See the **[Macro Syntax Guide](#macro-syntax-guide)** above for detailed syntax
|
||||
| stance | [stance:0/1/2/3/4/5] | * | * | If the player is in stance #.<br/>Supports Shadowform and Stealth as stance 1.|
|
||||
| stat | [stat:stat>=x/<=y] | * | | Check if one of the players statistics is greater or less than a specific number. Available Stats: str/strength, agi/agility, stam/stamina, int/intellect, spi/spirit, ap/attackpower, rap/rangedattackpower, healing/healingpower, arcane_power, fire_power, frost_power, nature_power, shadow_power, armor, defense, arcane_res, fire_res, frost_res, nature_res, shadow_res. |
|
||||
| stealth | [stealth] | | * | If the player is in Stealth or Prowl. |
|
||||
| swingtimer | [swingtimer:>20] | * | * | If the swing timer is at a percentage of attack speed. Requires [SP_SwingTimer](https://github.com/jrc13245/SP_SwingTimer) addon. See [below](#swing-timer-integration) for details.|
|
||||
| stimer | [stimer:<=50] | * | * | Alias for `swingtimer` |
|
||||
| swingtimer | [swingtimer:<15] | * | * | If a percentage of swing time has elapsed. `<15` = early in swing, `>80` = late in swing. Requires [SP_SwingTimer](https://github.com/jrc13245/SP_SwingTimer) addon. See [below](#swing-timer-integration) for details.|
|
||||
| stimer | [stimer:<15] | * | * | Alias for `swingtimer` |
|
||||
| swimming | [swimming] | | * | Druid only, works like reactive but for aquatic form, must have aquatic form on one of your non-stance actionbars. |
|
||||
| usable | [usable]<br/>[usable:"Spell Name"] | * | * | If the spell or item is usable (not on cooldown, have reagents, etc.). |
|
||||
| zone | [zone:"Zone"]<br/>[zone:"Zone"/"Another Zone"] | * | * | If the player is in one or more zones of the given name. |
|
||||
@@ -733,54 +782,56 @@ The addon integrates with [SP_SwingTimer](https://github.com/jrc13245/SP_SwingTi
|
||||
- If SP_SwingTimer is not loaded, the conditional will return false and display an error message once
|
||||
|
||||
**How It Works:**
|
||||
The conditional compares the current swing timer (`st_timer`) against a percentage of your attack speed.
|
||||
The conditional checks what percentage of your swing time has **elapsed** (not remaining).
|
||||
|
||||
`[swingtimer:>20]` checks if `st_timer > UnitAttackSpeed("player") * 0.2`
|
||||
`[swingtimer:<15]` checks if less than 15% of your swing has elapsed (early in swing).
|
||||
|
||||
This means "cast if more than 20% of my swing time remains."
|
||||
This allows you to cast abilities like Slam at the optimal time in your swing cycle.
|
||||
|
||||
**Syntax:**
|
||||
- `[swingtimer:>X]` - True if swing timer > X% of attack speed
|
||||
- `[swingtimer:<X]` - True if swing timer < X% of attack speed
|
||||
- `[swingtimer:>=X]` - True if swing timer >= X% of attack speed
|
||||
- `[swingtimer:<=X]` - True if swing timer <= X% of attack speed
|
||||
- `[stimer:>X]` - Alias for swingtimer
|
||||
- `[swingtimer:<X]` - True if less than X% of swing has elapsed (early in swing)
|
||||
- `[swingtimer:>X]` - True if more than X% of swing has elapsed (late in swing)
|
||||
- `[swingtimer:>=X]` - True if X% or more of swing has elapsed
|
||||
- `[swingtimer:<=X]` - True if X% or less of swing has elapsed
|
||||
- `[stimer:<X]` - Alias for swingtimer
|
||||
|
||||
**Negation:**
|
||||
- `[noswingtimer:<80]` - True if NOT (swing timer < 80% of attack speed)
|
||||
- `[nostimer:>20]` - Alias for noswingtimer
|
||||
- `[noswingtimer:<15]` - True if NOT (less than 15% elapsed) = 15% or more has elapsed
|
||||
- `[nostimer:>80]` - Alias for noswingtimer
|
||||
|
||||
**Example Macros:**
|
||||
|
||||
```lua
|
||||
-- Cast Slam only if more than 20% of swing time remains
|
||||
-- Equivalent to: /run if st_timer>UnitAttackSpeed("player")*0.2 then CastSpellByName("Slam") end
|
||||
-- Cast Slam early in the swing (first 15% of swing time)
|
||||
-- This is optimal for Slam to avoid delaying your next auto-attack
|
||||
#showtooltip Slam
|
||||
/cast [swingtimer:>20] Slam
|
||||
/cast [swingtimer:<15] Slam
|
||||
```
|
||||
|
||||
```lua
|
||||
-- Heroic Strike queue management - only queue if swing timer is low
|
||||
-- Heroic Strike queue management - only queue late in swing
|
||||
#showtooltip Heroic Strike
|
||||
/cast [stimer:<30] Heroic Strike
|
||||
/cast [stimer:>70] Heroic Strike
|
||||
```
|
||||
|
||||
```lua
|
||||
-- Complex rotation with swing timer awareness
|
||||
#showtooltip
|
||||
/cast [swingtimer:>25] Slam
|
||||
/cast Heroic Strike
|
||||
/cast [swingtimer:<15] Slam
|
||||
/cast [swingtimer:>80] Heroic Strike
|
||||
/cast Bloodthirst
|
||||
```
|
||||
|
||||
**Understanding the Percentage:**
|
||||
- 100% = Full swing time (just attacked, full cooldown remaining)
|
||||
- 50% = Half of swing time remaining
|
||||
- 20% = Only 20% of swing time left before next auto-attack
|
||||
- 0% = About to swing
|
||||
- 0% = Just attacked (swing just started)
|
||||
- 15% = Early in swing (optimal for Slam)
|
||||
- 50% = Half of swing time has elapsed
|
||||
- 80% = Late in swing (close to next auto-attack)
|
||||
- 100% = About to swing
|
||||
|
||||
For a warrior with a 3.5 second weapon:
|
||||
- `[swingtimer:>20]` = True if st_timer > 0.7 seconds
|
||||
- `[swingtimer:>50]` = True if st_timer > 1.75 seconds
|
||||
- `[swingtimer:<15]` = True when less than 0.525 seconds have elapsed (early)
|
||||
- `[swingtimer:>80]` = True when more than 2.8 seconds have elapsed (late)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user