fix inrange distance measuring, add distance range count threshold for enemy count, add filters for count mode, fix stopattack

This commit is contained in:
Jrc13245
2026-03-15 11:11:40 -04:00
parent de8760f872
commit 4ddac30726
3 changed files with 157 additions and 63 deletions
+127 -25
View File
@@ -5508,6 +5508,51 @@ function CleveRoids.IsUnitInMeleeRange(unit, cleaveRange)
return CheckInteractDistance(unit, 3)
end
-- Count mode filter functions for compound conditionals.
-- Allows count mode to combine checks: [meleerange:facing>1] counts enemies
-- in melee range AND facing. The filter name is the "name" field in count args.
local countModeFilters = {
facing = function(unit)
return UnitXP("behind", unit, "player") ~= true
end,
behind = function(unit)
return UnitXP("behind", "player", unit) == true
end,
meleerange = function(unit)
return CleveRoids.IsUnitInMeleeRange(unit, true)
end,
}
-- Parse the name field of count mode args for optional filter and distance threshold.
-- Supports: "facing", "behind", "meleerange" (filter only),
-- "30" (distance only),
-- "30:facing" or "30facing" (distance + filter)
-- Returns: filterFunc or nil, distanceThreshold or nil
local function ParseCountModeFilter(name)
if not name then return nil, nil end
local lower = string.lower(name)
local filter = countModeFilters[lower]
if filter then return filter, nil end
local dist = tonumber(name)
if dist then return nil, dist end
-- Compound with colon: "30:facing" (from distance:30:facing>1)
local num, qualifier = string.match(name, "^(%d+%.?%d*):(%a+)$")
if num and qualifier then
return countModeFilters[string.lower(qualifier)], tonumber(num)
end
-- Compound without separator: "30facing"
num, qualifier = string.match(name, "^(%d+%.?%d*)(%a+)$")
if num and qualifier then
return countModeFilters[string.lower(qualifier)], tonumber(num)
end
return nil, nil
end
-- A list of Conditionals and their functions to validate them
CleveRoids.Keywords = {
exists = function(conditionals)
@@ -7137,6 +7182,9 @@ CleveRoids.Keywords = {
return PlayerIsRooted() ~= 1
end,
-- [distance:<30] - Target is within 30 yards
-- [distance:30>1] - More than 1 enemy within 30 yards (count mode)
-- [distance:30facing>1] - More than 1 enemy within 30 yards AND facing (compound count)
distance = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
@@ -7145,6 +7193,19 @@ CleveRoids.Keywords = {
return false
end
-- Count mode: name encodes distance threshold + optional filter
local filter, distThreshold = ParseCountModeFilter(args.name)
if distThreshold then
local count = CleveRoids.CountEnemiesMatching(function(unit)
local dist = UnitXP("distanceBetween", "player", unit)
if not dist or dist > distThreshold then return false end
if filter and not filter(unit) then return false end
return true
end)
return CleveRoids.comparators[args.operator](count, args.amount)
end
-- Single-target: [distance:<30]
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
@@ -7155,6 +7216,9 @@ CleveRoids.Keywords = {
end, conditionals, "distance")
end,
-- [nodistance:<30] - Target is NOT within 30 yards
-- [nodistance:30>1] - More than 1 enemy NOT within 30 yards (count mode)
-- [nodistance:30facing>1] - More than 1 enemy NOT within 30 yards AND facing (compound count)
nodistance = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
@@ -7163,6 +7227,19 @@ CleveRoids.Keywords = {
return false
end
-- Count mode: name encodes distance threshold + optional filter
local filter, distThreshold = ParseCountModeFilter(args.name)
if distThreshold then
local count = CleveRoids.CountEnemiesMatching(function(unit)
local dist = UnitXP("distanceBetween", "player", unit)
if dist and dist <= distThreshold then return false end
if filter and not filter(unit) then return false end
return true
end)
return CleveRoids.comparators[args.operator](count, args.amount)
end
-- Single-target: [nodistance:<30]
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
@@ -7175,82 +7252,105 @@ CleveRoids.Keywords = {
-- [behind] - Player is behind target
-- [behind:>N] - Player is behind more than N enemies (count mode)
-- [behind:meleerange>N] - Behind more than N enemies AND in melee range (compound count)
-- [behind:10>N] - Behind more than N enemies AND within 10 yards (compound count)
behind = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
-- Check for count mode: [behind:>1]
local countArgs = CleveRoids.GetCountModeArgs(conditionals.behind)
if countArgs then
local filter, distThreshold = ParseCountModeFilter(countArgs.name)
local count = CleveRoids.CountEnemiesMatching(function(unit)
return UnitXP("behind", "player", unit) == true
if UnitXP("behind", "player", unit) ~= true then return false end
if distThreshold then
local dist = UnitXP("distanceBetween", "player", unit)
if not dist or dist > distThreshold then return false end
end
if filter and not filter(unit) then return false end
return true
end)
return CleveRoids.comparators[countArgs.operator](count, countArgs.amount)
end
-- Original single-target behavior
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
return UnitXP("behind", "player", unit) == true
end,
-- [nobehind] - Player is NOT behind target
-- [nobehind:>N] - Player is NOT behind more than N enemies (count mode)
-- [nobehind:>N] - NOT behind more than N enemies (count mode)
-- [nobehind:meleerange>N] - NOT behind more than N enemies AND in melee range (compound count)
nobehind = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
-- Check for count mode: [nobehind:>1]
local countArgs = CleveRoids.GetCountModeArgs(conditionals.nobehind)
if countArgs then
local filter, distThreshold = ParseCountModeFilter(countArgs.name)
local count = CleveRoids.CountEnemiesMatching(function(unit)
return UnitXP("behind", "player", unit) ~= true
if UnitXP("behind", "player", unit) == true then return false end
if distThreshold then
local dist = UnitXP("distanceBetween", "player", unit)
if not dist or dist > distThreshold then return false end
end
if filter and not filter(unit) then return false end
return true
end)
return CleveRoids.comparators[countArgs.operator](count, countArgs.amount)
end
-- Original single-target behavior
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
return UnitXP("behind", "player", unit) ~= true
end,
-- [facing] - Player is facing target (target is not behind player)
-- [facing:>N] - Player is facing more than N enemies (count mode)
-- [facing:meleerange>N] - Facing more than N enemies AND in melee range (compound count)
-- [facing:10>N] - Facing more than N enemies AND within 10 yards (compound count)
facing = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
-- Check for count mode: [facing:>1]
local countArgs = CleveRoids.GetCountModeArgs(conditionals.facing)
if countArgs then
local filter, distThreshold = ParseCountModeFilter(countArgs.name)
local count = CleveRoids.CountEnemiesMatching(function(unit)
return UnitXP("behind", unit, "player") ~= true
if UnitXP("behind", unit, "player") == true then return false end
if distThreshold then
local dist = UnitXP("distanceBetween", "player", unit)
if not dist or dist > distThreshold then return false end
end
if filter and not filter(unit) then return false end
return true
end)
return CleveRoids.comparators[countArgs.operator](count, countArgs.amount)
end
-- Original single-target behavior
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
return UnitXP("behind", unit, "player") ~= true
end,
-- [nofacing] - Player is NOT facing target (target is behind player)
-- [nofacing:>N] - Player is NOT facing more than N enemies (count mode)
-- [nofacing:>N] - NOT facing more than N enemies (count mode)
-- [nofacing:meleerange>N] - NOT facing more than N enemies AND in melee range (compound count)
nofacing = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
-- Check for count mode: [nofacing:>1]
local countArgs = CleveRoids.GetCountModeArgs(conditionals.nofacing)
if countArgs then
local filter, distThreshold = ParseCountModeFilter(countArgs.name)
local count = CleveRoids.CountEnemiesMatching(function(unit)
return UnitXP("behind", unit, "player") == true
if UnitXP("behind", unit, "player") ~= true then return false end
if distThreshold then
local dist = UnitXP("distanceBetween", "player", unit)
if not dist or dist > distThreshold then return false end
end
if filter and not filter(unit) then return false end
return true
end)
return CleveRoids.comparators[countArgs.operator](count, countArgs.amount)
end
-- Original single-target behavior
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
@@ -7301,39 +7401,41 @@ CleveRoids.Keywords = {
-- [meleerange] - Target is in melee range
-- [meleerange:>N] - More than N enemies are in melee range (count mode)
-- [meleerange:facing>N] - More than N enemies in melee range AND facing (compound count)
meleerange = function(conditionals)
-- Check for count mode: [meleerange:>1]
local countArgs = CleveRoids.GetCountModeArgs(conditionals.meleerange)
if countArgs then
local filter = ParseCountModeFilter(countArgs.name)
local count = CleveRoids.CountEnemiesMatching(function(unit)
return CleveRoids.IsUnitInMeleeRange(unit, true)
if not CleveRoids.IsUnitInMeleeRange(unit, true) then return false end
if filter and not filter(unit) then return false end
return true
end)
return CleveRoids.comparators[countArgs.operator](count, countArgs.amount)
end
-- Original single-target behavior
local unit = conditionals.target or "target"
if not UnitExists(unit) then return false end
return CleveRoids.IsUnitInMeleeRange(unit)
end,
-- [nomeleerange] - Target is NOT in melee range
-- [nomeleerange:>N] - More than N enemies are NOT in melee range (count mode)
-- [nomeleerange:facing>N] - More than N enemies NOT in melee range AND facing (compound count)
nomeleerange = function(conditionals)
-- Check for count mode: [nomeleerange:>1]
local countArgs = CleveRoids.GetCountModeArgs(conditionals.nomeleerange)
if countArgs then
local filter = ParseCountModeFilter(countArgs.name)
local count = CleveRoids.CountEnemiesMatching(function(unit)
return not CleveRoids.IsUnitInMeleeRange(unit, true)
if CleveRoids.IsUnitInMeleeRange(unit, true) then return false end
if filter and not filter(unit) then return false end
return true
end)
return CleveRoids.comparators[countArgs.operator](count, countArgs.amount)
end
-- Original single-target behavior
local unit = conditionals.target or "target"
if not UnitExists(unit) then return true end
return not CleveRoids.IsUnitInMeleeRange(unit)
end,
+21 -31
View File
@@ -70,14 +70,10 @@ local table_insert = table.insert
local table_getn = table.getn
-- Deferred stop-attack system (CheapShot pattern)
-- ClearTarget() starves auto-attack (no target = can't swing). OnUpdate polls
-- GetCurrentCastingInfo() — while autoattack is queued, keeps clearing target.
-- Once autoattack drops off, retargets via stored GUID. Timeout prevents
-- infinite loop if autoattack state gets stuck.
-- Stops autoattack and clears target. OnUpdate keeps clearing target while
-- autoattack is queued to prevent any swing from landing. No retarget.
local _DeferStopFrame = CreateFrame("Frame")
local _deferStopActive = false
local _deferRetargetGUID = nil
local _deferRetargetDone = false
local _deferStartTime = 0
local DEFER_STOP_TIMEOUT = 0.5
@@ -87,30 +83,21 @@ _DeferStopFrame:SetScript("OnUpdate", function()
return
end
if _deferRetargetGUID and not _deferRetargetDone then
-- Timeout: retarget and stop regardless of autoattack state
if (GetTime() - _deferStartTime) >= DEFER_STOP_TIMEOUT then
TargetUnit(_deferRetargetGUID)
_deferRetargetDone = true
_deferRetargetGUID = nil
_deferStopActive = false
_DeferStopFrame:Hide()
return
end
-- Timeout: stop polling
if (GetTime() - _deferStartTime) >= DEFER_STOP_TIMEOUT then
_deferStopActive = false
_DeferStopFrame:Hide()
return
end
local _,_,_,_,_,_,autoattack = GetCurrentCastingInfo()
if autoattack == 1 then
if UnitExists("target") then
ClearTarget()
end
else
TargetUnit(_deferRetargetGUID)
_deferRetargetDone = true
_deferRetargetGUID = nil
_deferStopActive = false
_DeferStopFrame:Hide()
-- While autoattack is queued, keep clearing target to prevent swings
local _,_,_,_,_,_,autoattack = GetCurrentCastingInfo()
if autoattack == 1 then
if UnitExists("target") then
ClearTarget()
end
else
-- Autoattack stopped, done
_deferStopActive = false
_DeferStopFrame:Hide()
end
@@ -120,8 +107,6 @@ _DeferStopFrame:Hide()
function CleveRoids.DeferStopAttack()
CleveRoids.CurrentSpell.autoAttack = false
CleveRoids.CurrentSpell.autoAttackLock = false
_deferRetargetGUID = CleveRoids.GetGUID("target")
_deferRetargetDone = false
_deferStartTime = GetTime()
-- Only toggle auto-attack OFF if it's currently active (avoid turning it ON)
local attackSlot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Attack)
@@ -2014,9 +1999,14 @@ function CleveRoids.ParseMsg(msg)
-- Split the conditional block by comma or space
for _, conditionGroups in CleveRoids.splitStringIgnoringQuotes(conditionBlock, {",", " "}) do
if conditionGroups ~= "" then
-- Split conditional groups by colon
-- Split conditional groups by colon (rejoin extra parts for multi-part args
-- e.g., distance:30:facing>1 → condition="distance", args="30:facing>1")
local conditionGroup = CleveRoids.splitStringIgnoringQuotes(conditionGroups, ":")
local condition, args = conditionGroup[1], conditionGroup[2]
local condition = conditionGroup[1]
local args = conditionGroup[2]
for _cgi = 3, table.getn(conditionGroup) do
args = (args or "") .. ":" .. conditionGroup[_cgi]
end
-- No args → the action is the implicit argument
if not args or args == "" then
+9 -7
View File
@@ -1064,15 +1064,17 @@ end
function API.GetSpellRange(spellId)
if not spellId or spellId == 0 then return nil end
-- First try rangeMax (Nampower may provide this as a resolved field)
local rangeMax = API.GetSpellField(spellId, "rangeMax")
if rangeMax and rangeMax > 0 then
return rangeMax / 10 -- Convert from game units to yards
end
-- Fallback: lookup rangeIndex in SpellRange table
local rangeIndex = API.GetSpellField(spellId, "rangeIndex")
if rangeIndex then
-- Prefer GetSpellRangeData (v4.0+) for runtime DBC lookup (covers custom ranges)
if API.features.hasGetSpellRangeData and _G.GetSpellRangeData then
local minRange, maxRange, flags, name = _G.GetSpellRangeData(rangeIndex)
if maxRange and maxRange > 0 then
return maxRange
end
end
-- Fallback: hardcoded SpellRange table
local range = API.SpellRangeTable[rangeIndex]
if range then
return range