diff --git a/Core.lua b/Core.lua index 49ac272..3501bb5 100644 --- a/Core.lua +++ b/Core.lua @@ -69,30 +69,91 @@ local string_gsub = string.gsub local table_insert = table.insert local table_getn = table.getn --- Deferred stop-attack/clear-target frame +-- 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. 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 DEFER_TIMEOUT = 0.5 _DeferStopFrame:SetScript("OnUpdate", function() - _DeferStopFrame:Hide() - if _deferStopAttack then - _deferStopAttack = false + 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 - AttackTarget() - if not _deferClearTarget then - ClearTarget() + 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 - end - if _deferClearTarget then + _deferStopAttack = false _deferClearTarget = false - ClearTarget() + _deferRetarget = false + _deferPhase = 0 + _DeferStopFrame:Hide() end end) @@ -100,11 +161,24 @@ function CleveRoids.DeferStopAttack() CleveRoids.CurrentSpell.autoAttack = false CleveRoids.CurrentSpell.autoAttackLock = false _deferStopAttack = true + _deferPhase = 0 + _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 + _deferStartTime = GetTime() + end _DeferStopFrame:Show() end @@ -139,6 +213,7 @@ local BOOLEAN_CONDITIONALS = { nomoving = true, stopattack = true, cleartarget = true, + retarget = true, } local requirementCheckFrame = CreateFrame("Frame") @@ -2628,6 +2703,12 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo 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 5423e0a..ea1e3b5 100644 --- a/Init.lua +++ b/Init.lua @@ -173,6 +173,7 @@ 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?