From 0de27471757249d1cd7f6ddd6d2fee52688e3b41 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Fri, 6 Mar 2026 21:16:38 -0500 Subject: [PATCH] update stopattack and cleartarget --- Core.lua | 160 ++++++++++++++++++++----------------------------------- Init.lua | 1 - 2 files changed, 58 insertions(+), 103 deletions(-) diff --git a/Core.lua b/Core.lua index 3501bb5..eb86baf 100644 --- a/Core.lua +++ b/Core.lua @@ -69,114 +69,78 @@ local string_gsub = string.gsub local table_insert = table.insert local table_getn = table.getn --- Deferred stop-attack/clear-target/retarget frame --- CastSpellByName starts autoattack as a C++ side-effect that doesn't settle --- in the same Lua frame. Deferring to the next OnUpdate ensures AttackTarget() --- toggle catches the active attack state (same approach as CheapShot addon). --- Multiple frames are used to verify auto-attack is actually stopped. +-- Deferred stop-attack/clear-target system (CheapShot addon pattern) +-- Polls every frame for up to 0.5s. Must run in OnUpdate (not same-frame as +-- CastSpellByName) because C++ auto-attack state hasn't settled yet in the +-- casting frame. With both flags set, calls AttackTarget()+ClearTarget() +-- unconditionally each frame while target exists. local _DeferStopFrame = CreateFrame("Frame") -_DeferStopFrame:Hide() local _deferStopAttack = false local _deferClearTarget = false -local _deferRetarget = false local _deferStartTime = 0 -local _deferPhase = 0 -- 0=stop+clear, 1=retarget, 2=verify +local _deferAttackSlot = nil -- Cached attack action slot local DEFER_TIMEOUT = 0.5 _DeferStopFrame:SetScript("OnUpdate", function() - if _deferPhase == 0 then - -- Phase 0 (first frame): Toggle off auto-attack + clear target - if _deferStopAttack and UnitExists("target") then - AttackTarget() -- Toggle off while target still exists - end - -- ClearTarget immediately prevents any in-flight melee swing from landing - ClearTarget() - - -- If only cleartarget (no stopattack, no retarget), we're done - if not _deferStopAttack and not _deferRetarget then - _deferClearTarget = false - _deferPhase = 0 - _DeferStopFrame:Hide() - return - end - _deferPhase = 1 - return - end - - if _deferPhase == 1 then - -- Phase 1: Retarget after ClearTarget + AttackTarget have had a frame to settle - if _deferRetarget then - TargetNearestEnemy() - elseif not _deferClearTarget then - -- stopattack alone: retarget back to same target - TargetLastTarget() - end - -- cleartarget without retarget: leave cleared (no action needed) - - -- If no stopattack, we're done after retarget - if not _deferStopAttack then - _deferClearTarget = false - _deferRetarget = false - _deferPhase = 0 - _DeferStopFrame:Hide() - return - end - _deferPhase = 2 - return - end - - -- Phase 2: Verify auto-attack is stopped (poll until confirmed or timeout) - local slot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Attack) - local stillAttacking = slot and CleveRoids.Hooks.IsCurrentAction(slot) - - if stillAttacking then - -- Auto-attack still active — toggle off and clear target as safety - AttackTarget() - CleveRoids.CurrentSpell.autoAttack = false - if UnitExists("target") then - ClearTarget() - -- Will re-retarget on completion - end - end - - -- Done when confirmed stopped or timeout reached - if not stillAttacking or (GetTime() - _deferStartTime) >= DEFER_TIMEOUT then - -- Final retarget if we had to re-clear during verification - if not UnitExists("target") then - if _deferRetarget then - TargetNearestEnemy() - elseif not _deferClearTarget then - TargetLastTarget() - end - end + if (GetTime() - _deferStartTime) >= DEFER_TIMEOUT then _deferStopAttack = false _deferClearTarget = false - _deferRetarget = false - _deferPhase = 0 _DeferStopFrame:Hide() + return + end + + if UnitExists("target") then + if _deferStopAttack and _deferClearTarget then + -- CheapShot pattern: toggle + clear unconditionally. + -- Even if AttackTarget() toggles ON (was off), ClearTarget() prevents any swing. + AttackTarget() + ClearTarget() + CleveRoids.CurrentSpell.autoAttack = false + CleveRoids.CurrentSpell.autoAttackLock = false + elseif _deferStopAttack then + -- Stop only (keep target): only toggle if auto-attack is actually on + local isAttacking = _deferAttackSlot and CleveRoids.Hooks.IsCurrentAction(_deferAttackSlot) + if isAttacking then + AttackTarget() + CleveRoids.CurrentSpell.autoAttack = false + CleveRoids.CurrentSpell.autoAttackLock = false + _deferStopAttack = false + _DeferStopFrame:Hide() + end + elseif _deferClearTarget then + ClearTarget() + end + else + -- No target exists + if _deferStopAttack then + local isAttacking = _deferAttackSlot and CleveRoids.Hooks.IsCurrentAction(_deferAttackSlot) + if not isAttacking then + -- Auto-attack confirmed off, done + _deferStopAttack = false + _deferClearTarget = false + _DeferStopFrame:Hide() + end + else + -- Only cleartarget requested, target already gone + _deferClearTarget = false + _DeferStopFrame:Hide() + end end end) +_DeferStopFrame:Hide() function CleveRoids.DeferStopAttack() CleveRoids.CurrentSpell.autoAttack = false CleveRoids.CurrentSpell.autoAttackLock = false _deferStopAttack = true - _deferPhase = 0 + _deferAttackSlot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Attack) _deferStartTime = GetTime() _DeferStopFrame:Show() end function CleveRoids.DeferClearTarget() _deferClearTarget = true - if _deferPhase == 0 then - _deferStartTime = GetTime() - end - _DeferStopFrame:Show() -end - -function CleveRoids.DeferRetarget() - _deferRetarget = true - if _deferPhase == 0 then + if not _deferStopAttack then _deferStartTime = GetTime() end _DeferStopFrame:Show() @@ -213,7 +177,6 @@ local BOOLEAN_CONDITIONALS = { nomoving = true, stopattack = true, cleartarget = true, - retarget = true, } local requirementCheckFrame = CreateFrame("Frame") @@ -2655,11 +2618,12 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo -- Legacy path: !Attack without explicit conditionals (checkchanneled injected) if msg == CleveRoids.Localized.Attack and conditionals.checkchanneled then AttackTarget() - elseif (CleveRoids.hasSuperwow or CleveRoids.hasCastSpellByNameUnitToken) and conditionals.target then - -- Let Nampower DLL handle queuing natively via its CastSpellByName hook - CastSpellByName(castMsg, conditionals.target) else - CastSpellByName(castMsg) + if (CleveRoids.hasSuperwow or CleveRoids.hasCastSpellByNameUnitToken) and conditionals.target then + CastSpellByName(castMsg, conditionals.target) + else + CastSpellByName(castMsg) + end end else -- For other actions like UseContainerItem etc. @@ -2690,25 +2654,17 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo TargetLastTarget() end - -- [stopattack] modifier: stop autoattack after cast - -- Deferred to next frame because CastSpellByName starts autoattack as a C++ - -- side-effect that hasn't settled yet — AttackTarget() toggle misses it. + -- [stopattack]/[cleartarget]: Deferred to OnUpdate (next frame), matching + -- the CheapShot addon pattern. AttackTarget()/ClearTarget() don't work in + -- the same Lua context as CastSpellByName because C++ auto-attack state + -- hasn't settled yet. if conditionals.stopattack then CleveRoids.DeferStopAttack() end - - -- [cleartarget] modifier: clear target after cast (e.g., prevent combo point loss on CC'd target) - -- Deferred to next frame to catch autoattack side-effect from CastSpellByName. if conditionals.cleartarget then CleveRoids.DeferClearTarget() end - -- [retarget] modifier: clear target and target nearest enemy after cast - -- Deferred to next frame alongside stopattack/cleartarget. - if conditionals.retarget then - CleveRoids.DeferRetarget() - end - conditionals.target = origTarget return result end diff --git a/Init.lua b/Init.lua index ea1e3b5..5423e0a 100644 --- a/Init.lua +++ b/Init.lua @@ -173,7 +173,6 @@ CleveRoids.ignoreKeywords = { mouseuse = true, -- Post-cast modifier: auto-click AOE targeting circle at cursor stopattack = true, -- Post-cast modifier: stop autoattack after cast cleartarget = true, -- Post-cast modifier: clear target after cast (when used as conditional on /cast) - retarget = true, -- Post-cast modifier: clear target and target nearest enemy after cast } -- TODO: Localize?