redo range tracking for self cast, ground targeting, channeled/unit targeting spells, and ignore direct spells

This commit is contained in:
Jrc13245
2025-12-22 15:37:13 -05:00
parent ca50bb5b55
commit cfd44e30a3
2 changed files with 171 additions and 32 deletions
+53 -21
View File
@@ -875,29 +875,30 @@ function CleveRoids.TestForActiveAction(actions)
end
end
-- Check for self-cast spells first (always in range regardless of target)
-- Check range using API wrapper (handles all cases properly)
local API = CleveRoids.NampowerAPI
if spellId and spellId > 0 and API.GetSpellField then
local rangeIndex = API.GetSpellField(spellId, "rangeIndex")
-- rangeIndex 0 = Self Only, 14 = Self Only (alternate), 23 = Touch
if rangeIndex == 0 or rangeIndex == 14 or rangeIndex == 23 then
actions.active.inRange = 1 -- Self-targeted spells are always in range
elseif UnitExists(unit) then
-- Use API wrapper which has UnitXP fallback for channeled spells
local r = API.IsSpellInRange(spellId, unit)
local checkValue = spellId and spellId > 0 and spellId or castName
if UnitExists(unit) then
-- We have a target - use API wrapper for proper range checking
-- API.IsSpellInRange handles: native check, -1 for self-cast, UnitXP fallback
if API then
local r = API.IsSpellInRange(checkValue, unit)
if r ~= nil then
-- Got a definitive answer (0 = out of range, 1 = in range)
actions.active.inRange = r
end
-- If r is nil, inRange stays at -1 (unknown) and we'll use proxy/original
end
elseif UnitExists(unit) then
-- Fallback: use API wrapper with spell name if no spell ID
local r = API.IsSpellInRange(castName, unit)
if r ~= nil then
actions.active.inRange = r
elseif IsSpellInRange then
-- No API, use native directly
local nativeResult = IsSpellInRange(checkValue, unit)
if nativeResult == 0 or nativeResult == 1 then
actions.active.inRange = nativeResult
elseif nativeResult == -1 then
-- Self-cast/ground-targeted spell, always in range
actions.active.inRange = 1
end
end
end
-- No target: don't set inRange, let proxy slot / original behavior handle it
end
-- Check if spell is usable first (handles forms, stances, and power type correctly)
@@ -3437,8 +3438,7 @@ CleveRoids.Hooks.ActionHasRange = ActionHasRange
function ActionHasRange(slot)
if not slot then return nil end
local actions = CleveRoids.GetAction(slot)
-- Only override range when #showtooltip is present and we have valid range data
-- inRange == -1 means IsSpellInRange couldn't determine (channeled spells, etc.)
-- Only override for our macros with #showtooltip
if actions and actions.tooltip and actions.active then
if actions.active.inRange ~= -1 then
return 1 -- Has range check with valid data
@@ -3451,6 +3451,7 @@ function ActionHasRange(slot)
end
end
end
-- Not a macro we're tracking - pass through to original
return CleveRoids.Hooks.ActionHasRange(slot)
end
@@ -3458,8 +3459,7 @@ CleveRoids.Hooks.IsActionInRange = IsActionInRange
function IsActionInRange(slot, unit)
if not slot then return nil end
local actions = CleveRoids.GetAction(slot)
-- Only override range when #showtooltip is present and we have valid range data
-- inRange == -1 means IsSpellInRange couldn't determine (channeled spells, etc.)
-- Only override for our macros with #showtooltip
if actions and actions.tooltip and actions.active and actions.active.type == "spell" then
if actions.active.inRange ~= -1 then
return actions.active.inRange
@@ -3472,6 +3472,7 @@ function IsActionInRange(slot, unit)
end
end
end
-- Not a macro we're tracking - pass through to original
return CleveRoids.Hooks.IsActionInRange(slot, unit)
end
@@ -4994,7 +4995,38 @@ SlashCmdList["CLEVEROID"] = function(msg)
if UnitExists("target") then
local finalResult = API.IsSpellInRange(spellId, "target")
CleveRoids.Print("API.IsSpellInRange: " .. tostring(finalResult))
else
-- Test without target
local finalResult = API.IsSpellInRange(spellId, "player")
CleveRoids.Print("API.IsSpellInRange (no target, using player): " .. tostring(finalResult))
end
-- Target type detection
local targetA = API.GetSpellField(spellId, "effectImplicitTargetA")
local targetB = API.GetSpellField(spellId, "effectImplicitTargetB")
-- Format target data (handle tables)
local function formatTarget(t)
if not t then return "|cffffff00nil|r" end
if type(t) == "table" then
local parts = {}
for i = 1, 3 do
table.insert(parts, tostring(t[i] or 0))
end
return "{" .. table.concat(parts, ", ") .. "}"
end
return tostring(t)
end
CleveRoids.Print("effectImplicitTargetA: " .. formatTarget(targetA))
CleveRoids.Print("effectImplicitTargetB: " .. formatTarget(targetB))
local isUnitTargeted = API.IsUnitTargetedSpell(spellId)
CleveRoids.Print("Is unit-targeted spell: " .. (isUnitTargeted == true and "|cff00ff00YES|r" or isUnitTargeted == false and "|cffff0000NO|r" or "|cffffff00UNKNOWN|r"))
-- Self-cast/ground-targeted detection summary
local isSelfCast = (isUnitTargeted == false)
CleveRoids.Print("Detected as self/ground-targeted: " .. (isSelfCast and "|cff00ff00YES|r" or "|cffff0000NO|r"))
end
end
+118 -11
View File
@@ -379,6 +379,82 @@ API.SpellRangeTable = {
[40] = 150, -- 150 yard range
}
-- Unit target types that require distance checking (from DBC)
-- These target OTHER units, not self - so distance matters
-- Excludes TARGET_UNIT_CASTER (1) which is self-cast and always in range
local UNIT_TARGET_TYPES_NEED_RANGE = {
[5] = true, -- TARGET_UNIT_PET
[6] = true, -- TARGET_UNIT_TARGET_ENEMY
[21] = true, -- TARGET_UNIT_TARGET_ALLY
[22] = true, -- TARGET_UNIT_PARTY
[23] = true, -- TARGET_UNIT_PARTY_AROUND_CASTER
[25] = true, -- TARGET_UNIT_PET (alternate)
[38] = true, -- TARGET_UNIT_TARGET_ANY
}
-- Helper to check a target value or array for unit targeting
local function checkTargetForUnitType(target)
if not target then return nil end
-- If it's a table (array of 3 effect targets), check each element
if type(target) == "table" then
for i = 1, 3 do
local val = target[i]
if val and UNIT_TARGET_TYPES_NEED_RANGE[val] then
return true -- Found a unit-targeting effect
end
end
-- Check if any element has a value (even if not unit-targeting)
for i = 1, 3 do
if target[i] and target[i] ~= 0 then
return false -- Has target data but not unit-targeting
end
end
return nil
end
-- If it's a number, check directly
if type(target) == "number" then
if UNIT_TARGET_TYPES_NEED_RANGE[target] then
return true
end
if target ~= 0 then
return false -- Has target data but not unit-targeting
end
end
return nil
end
-- Check if a spell requires distance checking to another unit
-- Returns true if the spell targets other units (needs range check)
-- Returns false if self-cast, ground-targeted, or area effect (always "in range")
-- Returns nil if unknown
function API.IsUnitTargetedSpell(spellId)
if not spellId or spellId == 0 then return nil end
-- Check effectImplicitTargetA
local targetA = API.GetSpellField(spellId, "effectImplicitTargetA")
local resultA = checkTargetForUnitType(targetA)
if resultA == true then
return true -- Targets other units, needs range check
end
-- Also check targetB
local targetB = API.GetSpellField(spellId, "effectImplicitTargetB")
local resultB = checkTargetForUnitType(targetB)
if resultB == true then
return true -- Targets other units, needs range check
end
-- If either returned false (has data but not unit-targeting), spell doesn't need range check
if resultA == false or resultB == false then
return false
end
return nil -- Unknown
end
-- Get spell range (max range in yards)
function API.GetSpellRange(spellId)
if not spellId or spellId == 0 then return nil end
@@ -1547,20 +1623,51 @@ function API.IsSpellInRange(spellIdentifier, unit)
if rangeIndex == 0 or rangeIndex == 14 or rangeIndex == 23 then
return 1 -- Self-targeted spells are always in range
end
end
-- Try native IsSpellInRange for non-self spells
if IsSpellInRange then
local result = IsSpellInRange(checkValue, unit)
-- result == 1 (in range), 0 (out of range), -1 (invalid/non-unit-targeted), nil (error)
if result == 0 or result == 1 then
return result
-- Fallback: if rangeIndex lookup failed but spell range is 0, it's self-only
if rangeIndex == nil then
local spellRange = API.GetSpellRange(spellId)
if spellRange == 0 or spellRange == nil then
return 1 -- Assume self-only if range is 0 or unknown
end
end
-- For -1 (ground-targeted like Blizzard), fall through to UnitXP fallback
end
-- Fallback: Use spell range from record + UnitXP distance check
-- This handles channeled spells where IsSpellInRange returns nil or -1
-- Try native IsSpellInRange (wrapped in pcall to handle invalid spell IDs)
local nativeResult = nil
if IsSpellInRange then
local success, result = pcall(IsSpellInRange, checkValue, unit)
if success then
nativeResult = result
-- result == 1 (in range), 0 (out of range), -1 (invalid/non-unit-targeted), nil (error)
if nativeResult == 0 or nativeResult == 1 then
return nativeResult
end
end
-- If pcall failed, nativeResult stays nil and we fall through to UnitXP
end
-- If native returned -1, check DBC target type to determine handling
if nativeResult == -1 and spellId and spellId > 0 then
local isUnitTargeted = API.IsUnitTargetedSpell(spellId)
if isUnitTargeted then
-- Unit-targeted spell (like channeled Arcane Missiles) - use distance check
local spellRange = API.GetSpellRange(spellId)
if spellRange and spellRange > 0 and CleveRoids.hasUnitXP and UnitExists(unit) then
local distance = UnitXP("distanceBetween", "player", unit)
if distance then
return (distance <= spellRange) and 1 or 0
end
end
-- Fall through to return 1 if we can't check distance
end
-- Non-unit-targeted spell (self-cast like Presence of Mind, or ground-targeted like Blizzard)
-- Always in range
return 1
end
-- For nil results (native couldn't determine), try UnitXP distance check
if spellId and spellId > 0 and CleveRoids.hasUnitXP and UnitExists(unit) then
local spellRange = API.GetSpellRange(spellId)
if spellRange and spellRange > 0 then