diff --git a/Compatibility/SuperMacro.lua b/Compatibility/SuperMacro.lua index 2698d7c..366c824 100644 --- a/Compatibility/SuperMacro.lua +++ b/Compatibility/SuperMacro.lua @@ -63,14 +63,35 @@ do CRM.Hooks.RunLine = CRM.Hooks.RunLine or orig_RunLine _G.RunLine = function(...) - -- Check macro stop flags before processing any line - if CRM.stopMacroFlag or CRM.skipMacroFlag then - return true -- Skip this line, tell SM we handled it - end - -- SuperMacro calls RunLine(line) one line at a time; handle first arg. local text = arg and arg[1] + -- IMPORTANT: Check for /nofirstaction BEFORE the stop flag check + -- This allows /nofirstaction to clear the stopMacroFlag set by /firstaction + if type(text) == "string" then + local _, _, nofirstactionArgs = string.find(text, "^%s*/nofirstaction%s*(.*)") + if nofirstactionArgs then + -- Capture stopOnCastFlag BEFORE DoNoFirstAction clears it + local wasFirstActionActive = CRM.stopOnCastFlag + if type(CleveRoids.DoNoFirstAction) == "function" then + pcall(CleveRoids.DoNoFirstAction, nofirstactionArgs or "") + end + -- Also clear stopMacroFlag if it was set by firstaction mechanism + if wasFirstActionActive and CRM.stopMacroFlag then + CRM.stopMacroFlag = false + end + return true -- We handled it + end + end + + -- Check macro stop flags before processing any line + -- (but /nofirstaction was already handled above) + if CRM.stopMacroFlag or CRM.skipMacroFlag then + -- If stopOnCastFlag is true, this is firstaction mode - skip but continue + -- If stopOnCastFlag is false, this is /stopmacro - skip the line + return true -- Skip this line, tell SM we handled it + end + if type(text) == "string" then -- 1) special-case /castsequence → call CRM directly and RETURN TRUE local b, e, rest = string.find(text, "^%s*/castsequence%s*(.*)") diff --git a/Conditionals.lua b/Conditionals.lua index 6407258..30892a9 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -1471,15 +1471,47 @@ function CleveRoids.CheckChanneled(channeledSpell) -- Special cases for auto-attacks if channeled == CleveRoids.Localized.Attack then - return not CleveRoids.CurrentSpell.autoAttack + -- Check event-based tracking first + if CleveRoids.CurrentSpell.autoAttack then + return false + end + -- Fallback: check action bar state for more reliable detection + local slot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Attack) + if slot and IsCurrentAction(slot) then + CleveRoids.CurrentSpell.autoAttack = true + return false + end + return true end if channeled == CleveRoids.Localized.AutoShot then - return not CleveRoids.CurrentSpell.autoShot + -- Check event-based tracking first (most common) + if CleveRoids.CurrentSpell.autoShot then + return false + end + -- Fallback: check action bar state via IsAutoRepeatAction for more reliable detection + -- This catches cases where the event hasn't fired yet due to timing + local slot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.AutoShot) + if slot and IsAutoRepeatAction(slot) then + -- Sync the event-based flag for consistency + CleveRoids.CurrentSpell.autoShot = true + return false + end + return true end if channeled == CleveRoids.Localized.Shoot then - return not CleveRoids.CurrentSpell.wand + -- Check event-based tracking first + if CleveRoids.CurrentSpell.wand then + return false + end + -- Fallback: check action bar state for more reliable detection + local slot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Shoot) + if slot and IsAutoRepeatAction(slot) then + CleveRoids.CurrentSpell.wand = true + return false + end + return true end -- If none of the special cases matched, allow the cast (not channeling the specified spell) diff --git a/Console.lua b/Console.lua index 229f383..49d3caf 100644 --- a/Console.lua +++ b/Console.lua @@ -86,7 +86,17 @@ end -- This ensures we have a fallback for non-conditional use. local StartAttack = function(msg) if not UnitExists("target") or UnitIsDead("target") then TargetNearestEnemy() end - if not CleveRoids.CurrentSpell.autoAttack and not CleveRoids.CurrentSpell.autoAttackLock and UnitExists("target") and UnitCanAttack("player","target") then + -- Check both event-based flag AND action bar state for reliable detection + local isAttacking = CleveRoids.CurrentSpell.autoAttack + if not isAttacking then + -- Fallback: check action bar state via IsCurrentAction + local slot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Attack) + if slot and IsCurrentAction(slot) then + CleveRoids.CurrentSpell.autoAttack = true + isAttacking = true + end + end + if not isAttacking and not CleveRoids.CurrentSpell.autoAttackLock and UnitExists("target") and UnitCanAttack("player","target") then CleveRoids.CurrentSpell.autoAttackLock = true CleveRoids.autoAttackLockElapsed = GetTime() AttackTarget() diff --git a/Core.lua b/Core.lua index 0c86fb6..2e61ab3 100644 --- a/Core.lua +++ b/Core.lua @@ -2384,8 +2384,12 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo end end if action == CastSpellByName then - -- Let Nampower DLL handle queuing natively via its CastSpellByName hook - if CleveRoids.hasSuperwow and conditionals.target then + -- Special case: !Attack should use AttackTarget() which doesn't toggle + -- This is more reliable than CastSpellByName("Attack") which toggles on/off + if msg == CleveRoids.Localized.Attack and conditionals.checkchanneled then + AttackTarget() + elseif CleveRoids.hasSuperwow and conditionals.target then + -- Let Nampower DLL handle queuing natively via its CastSpellByName hook CastSpellByName(castMsg, conditionals.target) else CastSpellByName(castMsg) @@ -2668,7 +2672,17 @@ end -- PERFORMANCE: Module-level action to avoid closure allocation per call local function _startAttackAction() if not UnitExists("target") or UnitIsDead("target") then TargetNearestEnemy() end - if not CleveRoids.CurrentSpell.autoAttack and not CleveRoids.CurrentSpell.autoAttackLock and UnitExists("target") and UnitCanAttack("player", "target") then + -- Check both event-based flag AND action bar state for reliable detection + local isAttacking = CleveRoids.CurrentSpell.autoAttack + if not isAttacking then + -- Fallback: check action bar state via IsCurrentAction + local slot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Attack) + if slot and IsCurrentAction(slot) then + CleveRoids.CurrentSpell.autoAttack = true + isAttacking = true + end + end + if not isAttacking and not CleveRoids.CurrentSpell.autoAttackLock and UnitExists("target") and UnitCanAttack("player", "target") then CleveRoids.CurrentSpell.autoAttackLock = true CleveRoids.autoAttackLockElapsed = GetTime() AttackTarget() diff --git a/Extensions/Mouseover/Blizzard.lua b/Extensions/Mouseover/Blizzard.lua index 00f70ca..59ab6aa 100644 --- a/Extensions/Mouseover/Blizzard.lua +++ b/Extensions/Mouseover/Blizzard.lua @@ -24,6 +24,10 @@ function Extension.RegisterMouseoverForFrame(frame, unit) frame:SetScript("OnLeave", function() CleveRoids.ClearMouseoverFrom("blizz") + -- Also clear "native" source to prevent sticky highlights. + -- When SetMouseoverUnit("") is called, UPDATE_MOUSEOVER_UNIT fires but + -- selfTriggered causes it to skip, leaving "native" stale. + CleveRoids.ClearMouseoverFrom("native") if onleave then onleave() end diff --git a/Extensions/Mouseover/CT_RaidAssist.lua b/Extensions/Mouseover/CT_RaidAssist.lua index dfcc0b6..16b7276 100644 --- a/Extensions/Mouseover/CT_RaidAssist.lua +++ b/Extensions/Mouseover/CT_RaidAssist.lua @@ -45,6 +45,7 @@ end function Extension.OnLeave() CleveRoids.ClearMouseoverFrom("ctra") + CleveRoids.ClearMouseoverFrom("native") end function Extension.OnLoad() diff --git a/Extensions/Mouseover/CT_UnitFrames.lua b/Extensions/Mouseover/CT_UnitFrames.lua index e51605d..6b6b607 100644 --- a/Extensions/Mouseover/CT_UnitFrames.lua +++ b/Extensions/Mouseover/CT_UnitFrames.lua @@ -20,6 +20,7 @@ function Extension.SetHook(widget) widget:SetScript("OnLeave", function() hookedOnLeave() CleveRoids.ClearMouseoverFrom("ctuf") + CleveRoids.ClearMouseoverFrom("native") end) end diff --git a/Extensions/Mouseover/DiscordUnitFrames.lua b/Extensions/Mouseover/DiscordUnitFrames.lua index 2ccea31..5e3dbfb 100644 --- a/Extensions/Mouseover/DiscordUnitFrames.lua +++ b/Extensions/Mouseover/DiscordUnitFrames.lua @@ -14,6 +14,7 @@ end function Extension.OnLeaveFrame() CleveRoids.ClearMouseoverFrom("duf") + CleveRoids.ClearMouseoverFrom("native") end function Extension.OnEnterElement() @@ -22,6 +23,7 @@ end function Extension.OnLeaveElement() CleveRoids.ClearMouseoverFrom("duf") + CleveRoids.ClearMouseoverFrom("native") end function Extension.OnLoad() diff --git a/Extensions/Mouseover/Dragonflight3.lua b/Extensions/Mouseover/Dragonflight3.lua index 576e8c2..20d7c73 100644 --- a/Extensions/Mouseover/Dragonflight3.lua +++ b/Extensions/Mouseover/Dragonflight3.lua @@ -33,6 +33,7 @@ function Extension.HookFrame(frame) frame:SetScript("OnLeave", function() CleveRoids.ClearMouseoverFrom("df3") + CleveRoids.ClearMouseoverFrom("native") if origOnLeave then origOnLeave() end end) diff --git a/Extensions/Mouseover/FocusFrame.lua b/Extensions/Mouseover/FocusFrame.lua index 656f2f3..67713c8 100644 --- a/Extensions/Mouseover/FocusFrame.lua +++ b/Extensions/Mouseover/FocusFrame.lua @@ -20,6 +20,7 @@ function Extension.RegisterMouseoverForFrame(frame) frame:SetScript("OnLeave", function() CleveRoids.ClearMouseoverFrom("focus") + CleveRoids.ClearMouseoverFrom("native") if onleave then onleave() end diff --git a/Extensions/Mouseover/GameTooltip.lua b/Extensions/Mouseover/GameTooltip.lua index 2ea70ae..b35f57c 100644 --- a/Extensions/Mouseover/GameTooltip.lua +++ b/Extensions/Mouseover/GameTooltip.lua @@ -15,6 +15,9 @@ end function Extension.OnClose() CleveRoids.ClearMouseoverFrom("tooltip") + -- Also clear native source when tooltip closes to prevent sticky highlights + -- The native source may have been set from hovering units in the 3D world + CleveRoids.ClearMouseoverFrom("native") end -- Native UPDATE_MOUSEOVER_UNIT handler (priority 2) @@ -46,6 +49,11 @@ function Extension.OnNonUnitTooltip() -- Clear the tooltip source (lowest priority fallback) CleveRoids.ClearMouseoverFrom("tooltip") + -- Also clear the "native" source since we're explicitly clearing mouseover. + -- Without this, a stale "native" source (from hovering units in 3D world) + -- would persist and cause sticky highlights/tooltips. + CleveRoids.ClearMouseoverFrom("native") + -- Also explicitly clear the game's mouseover to ensure UnitIsPlayer returns false. -- This is defensive - the main fix is in Utility.lua:apply() using "" instead of nil. if CleveRoids.hasSuperwow and _G.SetMouseoverUnit then @@ -73,6 +81,25 @@ function Extension.OnLoad() Extension.HookMethod(_G["GameTooltip"], "SetTradeSkillItem", "OnNonUnitTooltip") Extension.HookMethod(_G["GameTooltip"], "SetTrainerService", "OnNonUnitTooltip") Extension.HookMethod(_G["GameTooltip"], "SetHyperlink", "OnNonUnitTooltip") + + -- Additional tooltip methods that can cause sticky mouseover state + -- Pet action bar, stance bar, buffs/debuffs, and other UI elements + Extension.HookMethod(_G["GameTooltip"], "SetPetAction", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetShapeshift", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetUnitBuff", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetUnitDebuff", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetPlayerBuff", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetUnitAura", "OnNonUnitTooltip") + + -- Auction, mail, guild bank, and other UI tooltips + Extension.HookMethod(_G["GameTooltip"], "SetAuctionItem", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetAuctionSellItem", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetGuildBankItem", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetInboxItem", "OnNonUnitTooltip") + Extension.HookMethod(_G["GameTooltip"], "SetSendMailItem", "OnNonUnitTooltip") + + -- Note: SetOwner and SetText are NOT hooked because they're used for ALL tooltips + -- including unit tooltips. Hooking them would incorrectly clear mouseover for units. end _G["CleveRoids"] = CleveRoids diff --git a/Extensions/Mouseover/Grid.lua b/Extensions/Mouseover/Grid.lua index 09143fb..3122375 100644 --- a/Extensions/Mouseover/Grid.lua +++ b/Extensions/Mouseover/Grid.lua @@ -16,6 +16,7 @@ end function Extension.OnLeave() CleveRoids.ClearMouseoverFrom("grid") + CleveRoids.ClearMouseoverFrom("native") end function Extension.OnLoad() diff --git a/Extensions/Mouseover/NotGrid.lua b/Extensions/Mouseover/NotGrid.lua index 0a02e72..b677124 100644 --- a/Extensions/Mouseover/NotGrid.lua +++ b/Extensions/Mouseover/NotGrid.lua @@ -16,6 +16,7 @@ end function Extension.OnLeave() CleveRoids.ClearMouseoverFrom("ngrid") + CleveRoids.ClearMouseoverFrom("native") end function CleveRoids:NotGrid_CreateFrames() diff --git a/Extensions/Mouseover/PerfectRaid.lua b/Extensions/Mouseover/PerfectRaid.lua index 2b341a8..38ba175 100644 --- a/Extensions/Mouseover/PerfectRaid.lua +++ b/Extensions/Mouseover/PerfectRaid.lua @@ -16,6 +16,7 @@ end function Extension.OnLeave() CleveRoids.ClearMouseoverFrom("praid") + CleveRoids.ClearMouseoverFrom("native") end function Extension.OnLoad() diff --git a/Extensions/Mouseover/XPerl.lua b/Extensions/Mouseover/XPerl.lua index 9641382..511bd3b 100644 --- a/Extensions/Mouseover/XPerl.lua +++ b/Extensions/Mouseover/XPerl.lua @@ -25,6 +25,7 @@ end function Extension.OnLeave() CleveRoids.ClearMouseoverFrom("xperl") + CleveRoids.ClearMouseoverFrom("native") end function Extension.DelayedInit() diff --git a/Extensions/Mouseover/ag_UnitFrames.lua b/Extensions/Mouseover/ag_UnitFrames.lua index ce92cbd..3493d76 100644 --- a/Extensions/Mouseover/ag_UnitFrames.lua +++ b/Extensions/Mouseover/ag_UnitFrames.lua @@ -12,6 +12,7 @@ end function Extension.OnLeave() CleveRoids.ClearMouseoverFrom("aguf") + CleveRoids.ClearMouseoverFrom("native") end -- Because AddOns are loaded in alphabetical order, this callback will never see the aUF loaded message, had to do a workaround... diff --git a/Extensions/Mouseover/pfUI.lua b/Extensions/Mouseover/pfUI.lua index 787f9e3..325312a 100644 --- a/Extensions/Mouseover/pfUI.lua +++ b/Extensions/Mouseover/pfUI.lua @@ -88,6 +88,10 @@ local function PfClear(frame) if frame and frame.__cr_src then CleveRoids.ClearMouseoverFrom(frame.__cr_src) frame.__cr_src = nil + -- Also clear "native" source to prevent sticky highlights. + -- When SetMouseoverUnit("") is called, UPDATE_MOUSEOVER_UNIT fires but + -- selfTriggered causes it to skip, leaving "native" stale. + CleveRoids.ClearMouseoverFrom("native") end end diff --git a/Extensions/Mouseover/sRaidFrames.lua b/Extensions/Mouseover/sRaidFrames.lua index d0c7400..a6f2524 100644 --- a/Extensions/Mouseover/sRaidFrames.lua +++ b/Extensions/Mouseover/sRaidFrames.lua @@ -14,6 +14,7 @@ end function Extension.OnLeave() CleveRoids.ClearMouseoverFrom("sraid") + CleveRoids.ClearMouseoverFrom("native") end function Extension.OnLoad()