From 96ae51a22867601ada0091e4546fa251504af8e1 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Sat, 3 Jan 2026 17:20:37 -0500 Subject: [PATCH] ensure all conditionals are noable --- Conditionals.lua | 143 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/Conditionals.lua b/Conditionals.lua index 565f436..398488a 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -3017,10 +3017,26 @@ CleveRoids.Keywords = { return conditionals.help and conditionals.target and UnitExists(conditionals.target) and UnitCanAssist("player", conditionals.target) end, + -- [nohelp] - Target is NOT friendly (cannot assist) + nohelp = function(conditionals) + if not conditionals.target or not UnitExists(conditionals.target) then + return true + end + return not UnitCanAssist("player", conditionals.target) + end, + harm = function(conditionals) return conditionals.harm and conditionals.target and UnitExists(conditionals.target) and UnitCanAttack("player", conditionals.target) end, + -- [noharm] - Target is NOT hostile (cannot attack) + noharm = function(conditionals) + if not conditionals.target or not UnitExists(conditionals.target) then + return true + end + return not UnitCanAttack("player", conditionals.target) + end, + stance = function(conditionals) local i = CleveRoids.GetCurrentShapeshiftIndex() return Or(conditionals.stance, function (v) @@ -3354,6 +3370,14 @@ CleveRoids.Keywords = { end) end, + -- [nomember] - check if target is NOT in party or raid + nomember = function(conditionals) + return not ( + CleveRoids.IsTargetInGroupType(conditionals.target, "party") + or CleveRoids.IsTargetInGroupType(conditionals.target, "raid") + ) + end, + -- [party] or [party:unitid] - check if unit is in your party -- Default unit is conditionals.target party = function(conditionals) @@ -3851,6 +3875,31 @@ CleveRoids.Keywords = { return false end, + -- [nochanneltime] - Negated channel time comparison + -- Usage: [nochanneltime:<0.5] = true if NOT (channeltime < 0.5), i.e., >= 0.5 seconds remaining + nochanneltime = function(conditionals) + -- Calculate remaining time (0 if not channeling) + local timeLeft = 0 + + if CleveRoids.CurrentSpell.type == "channeled" and CleveRoids.channelStartTime and CleveRoids.channelDuration then + local elapsed = GetTime() - CleveRoids.channelStartTime + timeLeft = CleveRoids.channelDuration - elapsed + if timeLeft < 0 then timeLeft = 0 end + end + + local check = conditionals.nochanneltime + + if type(check) == "table" and type(check[1]) == "table" then + check = check[1] + end + + if type(check) == "table" and check.operator and check.amount then + return not CleveRoids.comparators[check.operator](timeLeft, check.amount) + end + + return true + end, + casttime = function(conditionals) -- Calculate remaining time (0 if not casting) local timeLeft = 0 @@ -3877,6 +3926,31 @@ CleveRoids.Keywords = { return false end, + -- [nocasttime] - Negated cast time comparison + -- Usage: [nocasttime:<0.5] = true if NOT (casttime < 0.5), i.e., >= 0.5 seconds remaining + nocasttime = function(conditionals) + -- Calculate remaining time (0 if not casting) + local timeLeft = 0 + + if CleveRoids.CurrentSpell.type == "cast" and CleveRoids.castStartTime and CleveRoids.castDuration then + local elapsed = GetTime() - CleveRoids.castStartTime + timeLeft = CleveRoids.castDuration - elapsed + if timeLeft < 0 then timeLeft = 0 end + end + + local check = conditionals.nocasttime + + if type(check) == "table" and type(check[1]) == "table" then + check = check[1] + end + + if type(check) == "table" and check.operator and check.amount then + return not CleveRoids.comparators[check.operator](timeLeft, check.amount) + end + + return true + end, + targeting = function(conditionals) return Or(conditionals.targeting, function (unit) return (UnitIsUnit("targettarget", unit) == 1) @@ -3893,10 +3967,20 @@ CleveRoids.Keywords = { return UnitIsPlayer(conditionals.target) end, + -- [noisplayer] - Target is NOT a player (same as isnpc) + noisplayer = function(conditionals) + return not UnitIsPlayer(conditionals.target) + end, + isnpc = function(conditionals) return not UnitIsPlayer(conditionals.target) end, + -- [noisnpc] - Target is NOT an NPC (same as isplayer) + noisnpc = function(conditionals) + return UnitIsPlayer(conditionals.target) + end, + inrange = function(conditionals) if not IsSpellInRange then return end local API = CleveRoids.NampowerAPI @@ -3936,6 +4020,19 @@ CleveRoids.Keywords = { end, conditionals, "outrange") end, + -- [nooutrange] - Target is NOT out of range (same as inrange) + nooutrange = function(conditionals) + if not IsSpellInRange then return end + local API = CleveRoids.NampowerAPI + return NegatedMulti(conditionals.nooutrange, function(spellName) + local target = conditionals.target or "target" + local checkValue = spellName or conditionals.action + + local result = API.IsSpellInRange(checkValue, target) + return result == 1 -- In range = nooutrange passes + end, conditionals, "nooutrange") + end, + combo = function(conditionals) return Multi(conditionals.combo, function(args) return CleveRoids.ValidateComboPoints(args.operator, args.amount) @@ -4007,6 +4104,44 @@ CleveRoids.Keywords = { end, conditionals, "stat") end, + -- [nostat] - Negated stat comparison + -- Usage: [nostat:agi>100] = true if NOT (agi > 100), i.e., agi <= 100 + nostat = function(conditionals) + return NegatedMulti(conditionals.nostat, function(args) + if type(args) ~= "table" or not args.name then + return true -- Malformed = negation passes + end + + local stat_key = string.lower(args.name) + local get_stat_func = stat_checks[stat_key] + + if not get_stat_func then + return true -- Invalid stat = negation passes + end + + local current_value = get_stat_func() + if not current_value then return true end + + -- Handle multi-comparison + if args.comparisons and type(args.comparisons) == "table" then + for _, comp in ipairs(args.comparisons) do + if not CleveRoids.comparators[comp.operator] then + return true + end + if not CleveRoids.comparators[comp.operator](current_value, comp.amount) then + return true -- One failed = negation passes + end + end + return false -- All passed = negation fails + else + if not args.operator or not args.amount then + return true + end + return not CleveRoids.comparators[args.operator](current_value, args.amount) + end + end, conditionals, "nostat") + end, + class = function(conditionals) -- Determine which unit to check. Defaults to 'target' if no @unitid was specified. local unitToCheck = conditionals.target or "target" @@ -4242,6 +4377,14 @@ CleveRoids.Keywords = { return Multi(conditionals.mybuffcount,function (v) return CleveRoids.ValidatePlayerAuraCount(v.bigger, v.amount) end, conditionals, "mybuffcount") end, + -- [nomybuffcount] - Negated buff count comparison + -- Usage: [nomybuffcount:>15] = true if NOT (buffcount > 15), i.e., buffcount <= 15 + nomybuffcount = function(conditionals) + return NegatedMulti(conditionals.nomybuffcount, function(v) + return not CleveRoids.ValidatePlayerAuraCount(v.bigger, v.amount) + end, conditionals, "nomybuffcount") + end, + mhimbue = function(conditionals) local imbueName = nil diff --git a/README.md b/README.md index 6da03e1..446f66d 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna ## Conditionals Reference -All conditionals support negation with `no` prefix (e.g., `[nocombat]`, `[nobuff]`). +All conditionals support negation with `no` prefix (e.g., `[nocombat]`, `[nobuff]`, `[nohelp]`). Some also have semantic opposites: `help`/`harm`, `isplayer`/`isnpc`, `alive`/`dead`, `inrange`/`outrange`. ### Modifiers & Player State | Conditional | Example | Description |