From cfd44e30a38f63693ecd65d8185c24e7c325b71d Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Mon, 22 Dec 2025 15:37:13 -0500 Subject: [PATCH] redo range tracking for self cast, ground targeting, channeled/unit targeting spells, and ignore direct spells --- Core.lua | 74 +++++++++++++++++++-------- NampowerAPI.lua | 129 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 171 insertions(+), 32 deletions(-) diff --git a/Core.lua b/Core.lua index a64be16..3a630ff 100644 --- a/Core.lua +++ b/Core.lua @@ -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 diff --git a/NampowerAPI.lua b/NampowerAPI.lua index a65bebe..3bdb5f5 100644 --- a/NampowerAPI.lua +++ b/NampowerAPI.lua @@ -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