5 Commits
Author SHA1 Message Date
Brues eddc49fc60 Make [button:N] mean the invoking button, as retail does
[button:N] was a held-state modifier: IsMouseButtonDown(name), true while you
physically hold that button. Retail's is a property of the activation -- which
button clicked the action button -- and a keybind press counts as button 1
because it activates through the left-click path. Different in kind, and the
visible break was [button:1] Rejuvenation; Regrowth casting Regrowth from a
keybind, where retail casts Rejuvenation.

It now reads ClassicAPI's GetMouseButtonClicked, defaulting to LeftButton when
no click dispatch is running. That default IS the keybind rule: Button:Click()
with no argument reports LeftButton, and a macro run from anywhere else answers
button 1 the same way.

This also reverts 891467c. That commit refreshed the action bar on
GLOBAL_MOUSE_DOWN/UP so the icon could track the held state -- under the real
semantics there is no such state to track, and the icon correctly sits on
whatever button 1 resolves to. PLAYER_TOTEM_UPDATE stays; it shared the comment
and inherits it.

Requires ClassicAPI v1.15.8, up from v1.15.0. v1.15.8 scoped
GetMouseButtonClicked to the click dispatch; before it a HELD button kept the
value alive (by design -- OnDragStart reads it), so turning the camera with
right-click down made every keybind press read RightButton. The function exists
below v1.15.8 and answers wrongly, and a conditional that is quietly wrong is
worse than one that warns, so the floor moved rather than the conditional being
gated.

Bare [button] / [nobutton] survive as an extension retail has no equivalent for,
now meaning "a click dispatch is running" -- the practical "activated by a
keybind, not a click" test, and no longer leaning on anything but the documented
nil.
2026-09-15 01:17:06 -05:00
Brues 54e3c3b3f4 Drop the dead C_LossOfControl gate
The LOSS_OF_CONTROL_ADDED/UPDATE registration was wrapped in a
`type(C_LossOfControl) == "table"` check, to keep an older ClassicAPI without
the namespace from erroring on an unknown event. That can't happen:
C_LossOfControl ships in v1.10.0, and the addon refuses to finish loading below
v1.15.0. Same shape as the health/power fallbacks removed just before this.

GetSchoolLockout's comment claimed it "Returns 0 for a client without
C_LossOfControl", which was not merely obsolete but wrong -- the function
indexes C_LossOfControl unguarded on its first line, so a missing namespace
raises rather than returning 0. The sentence describes a guard that was never
there.

Left alone: the C_Macro.SetMacroDisplay feature-detect in Init.lua looks like
the same pattern but isn't. SetMacroDisplay lands in v1.15.0, exactly the floor,
so a nil there means the client mod is absent outright, and the flag doubles as
the internal "may we publish" guard that ReleaseDisplays clears.
2026-09-14 20:15:42 -05:00
Brues 4fa254b4e6 Drop the unreachable fallbacks in the health/power wrappers
UnitHealthMissing, UnitPower, UnitPowerMax and UnitPowerMissing each carried an
`X or function(...)` fallback computing the value from UnitHealth/UnitMana. None
could run: the addon refuses to finish loading below ClassicAPI v1.15.0, and all
four globals ship at or below v1.10.0. The file header already promised as much
-- "the wrappers below call the API directly -- no fallbacks" -- and these four
were the only ones that didn't.

The UnitPower fallback was worse than dead. It dropped powerType and returned
UnitMana(unit), so a rage or energy read would have quietly answered with mana;
UnitPowerMax did the same with UnitManaMax. Only unreachability kept that from
being a bug.

They were also the file's only plain `API.X =` assignments, bound to the global
at load time. Now all 38 wrappers are `function API.X(...)`, resolving per call
like their neighbours -- no practical difference, since the DLL registers these
globals before any addon Lua runs.

Every caller passes (unit) or (unit, powerType); both pass straight through.
2026-09-14 19:47:46 -05:00
Brues b82d06d061 Add [totem] and [nototem] conditionals
A totem is a summoned creature, not an aura, so nothing in the addon could see
one. Strength of Earth and Mana Spring could be faked through [nomybuff], but
Searing, Magma, Fire Nova, Tremor and Grounding put nothing on the player at
all, and those are the ones worth conditioning a recast on. There was no
workaround.

ClassicAPI backports the TBC totem bar (src/totem/Tracker.cpp), tracking the
player's own summons from their SMSG_SPELL_GO and classifying them by the
summon spell's SUMMON_TOTEM_SLOT1..4 effect, so Turtle's custom totems
self-classify. Ungated like GLOBAL_MOUSE_*: the tracker landed in v1.10.0, far
below the v1.15.0 floor the addon already refuses to run below.

[totem:X] takes an element (fire/earth/water/air), a slot number, or the totem's
own name -- ResolveTotemSlot tries the slot table first, then matches X against
the name standing in each slot, so [totem:Searing_Totem] asks for that totem
rather than whatever holds the fire slot. Bare [totem] takes the action as its
argument the way [mybuff] does, making /cast [nototem] Searing Totem a complete
recast macro.

Comparisons follow the aura precedent exactly: an empty slot reads -1, the same
"missing counts as least" ValidateAura uses, so [totem:Searing_Totem<5] passes
while the totem is expiring AND while it is absent -- one clause, no [nototem]
needed. A totem is either up or it isn't, so #N stack comparisons read 1 for a
standing totem. The parser needed nothing: its name/operator/amount capture is
generic, so multi-comparisons ([totem:X>2&<8]), OR groups and the De Morgan
flip all came for free.

Occupancy is read from GetTotemInfo's totemName, never from the time left.
GetTotemInfo's first return is TOOL presence -- whether the player carries the
slot's Earth/Fire/Water/Air Totem item -- not whether a totem is out, and a
summon spell with no SpellDuration row reports 0 seconds, which is "up with no
timer", not "absent".

Icon refresh needs two feeds. PLAYER_TOTEM_UPDATE covers a slot changing hands
(dropped, expired, killed, recalled). It does not fire as a totem counts toward
zero, and unlike an aura there is no ambient event stream to ride -- UNIT_AURA
is what quietly keeps [mybuff:X<N] honest -- so a [totem:X<N] icon held its last
answer until some unrelated event queued a refresh: the macro sat on the
question mark through the window it should have lit up. Hence the once-a-second
re-test in OnUpdate while any totem timer runs, skipped in realtime mode, which
re-tests everything anyway. Only the icon was ever at stake; the click path
evaluates conditionals live.
2026-09-14 19:28:24 -05:00
Brues 891467cd7e Refresh action bars on ClassicAPI's global mouse events
A [button:N] macro's icon never moved. The conditional reads IsMouseButtonDown
live, but nothing ever noticed that state changing: the OnUpdate's input poll
samples alt/shift/ctrl only, and no event fired for a mouse button, so no
refresh was queued and the icon sat on whatever the macro resolved to with no
button held.

ClassicAPI fires GLOBAL_MOUSE_DOWN / GLOBAL_MOUSE_UP on every raw press and
release, whether or not the click lands on a frame. Registering both and queuing
an action update is the whole fix -- the existing path re-runs TestAction per
action, so the conditional is read fresh and PublishDisplay hands the new answer
to C_Macro.SetMacroDisplay.

Ungated, unlike the Nampower-backed input events next to it: GLOBAL_MOUSE_* has
been in ClassicAPI since v1.0.0, far below the v1.15.0 floor the addon already
refuses to run below.

The handlers set isActionUpdateQueued directly rather than calling
QueueActionUpdate, as KEY_DOWN does: these fire from the message pump, which can
beat VARIABLES_LOADED to the CleveRoidMacros table QueueActionUpdate reads.

arg1 carries the button name and goes deliberately unused. The mask behind
IsMouseButtonDown is maintained by the same hook that fires the event, so
mirroring the payload into Lua would copy state the API already exposes -- and
the refresh covers every macro regardless, since any of them may test any
button.
2026-09-14 15:31:43 -05:00
6 changed files with 207 additions and 45 deletions
+14 -14
View File
@@ -3,7 +3,8 @@
ClassicAPI is a client mod (sibling to Nampower/SuperWoW) that backports the
modern C_* API into the 1.12.1 Lua environment. It is a HARD REQUIREMENT of
this addon (ClassicAPI v1.15.0+, which added frame:RegisterUnitEvent), so the
this addon (ClassicAPI v1.15.8+, which scoped GetMouseButtonClicked to the
click dispatch; v1.15.0 added frame:RegisterUnitEvent), so the
wrappers below call the API directly — no fallbacks. The load-time requirement
check (Core.lua) uses IsAvailable() to warn when the DLL is missing and
HasMinimumVersion() when it's too old; users who don't want ClassicAPI should
@@ -247,8 +248,8 @@ end
-- Pummel / Earth Shock lockout) on the player, or 0 when not kicked. Read from
-- C_LossOfControl, which synthesizes the lockout from the server's own
-- SMSG_SPELL_COOLDOWN packet -- a state no debuff scan can see. Also returns the
-- seconds remaining (nil if ClassicAPI didn't observe the applying cast). Returns
-- 0 for a client without C_LossOfControl. Player-only (vanilla LoC is local-only).
-- seconds remaining (nil if ClassicAPI didn't observe the applying cast).
-- Player-only (vanilla LoC is local-only).
function API.GetSchoolLockout()
local n = C_LossOfControl.GetActiveLossOfControlDataCount() or 0
for i = 1, n do
@@ -312,10 +313,9 @@ end
-- Unit Health
--------------------------------------------------------------------------------
-- Health deficit (max - current) for `unit` in one call. Falls back to
-- UnitHealthMax - UnitHealth without ClassicAPI.
API.UnitHealthMissing = UnitHealthMissing or function(unit)
return (UnitHealthMax(unit) or 0) - (UnitHealth(unit) or 0)
-- Health deficit (max - current) for `unit` in one call.
function API.UnitHealthMissing(unit)
return UnitHealthMissing(unit)
end
--------------------------------------------------------------------------------
@@ -324,18 +324,18 @@ end
-- Current power for a specific Enum.PowerType (0=Mana, 1=Rage, 2=Focus,
-- 3=Energy, 4=Happiness), or the unit's primary power when powerType is omitted.
-- Display-divided (rage reads 0..100). Falls back to UnitMana without ClassicAPI.
API.UnitPower = UnitPower or function(unit, powerType)
return UnitMana(unit)
-- Display-divided (rage reads 0..100).
function API.UnitPower(unit, powerType)
return UnitPower(unit, powerType)
end
API.UnitPowerMax = UnitPowerMax or function(unit, powerType)
return UnitManaMax(unit)
function API.UnitPowerMax(unit, powerType)
return UnitPowerMax(unit, powerType)
end
-- Power deficit (max - current) for the type / primary power, in one call.
API.UnitPowerMissing = UnitPowerMissing or function(unit, powerType)
return (UnitManaMax(unit) or 0) - (UnitMana(unit) or 0)
function API.UnitPowerMissing(unit, powerType)
return UnitPowerMissing(unit, powerType)
end
-- Unit's primary power type as an integer (0=Mana .. 4=Happiness).
+133 -10
View File
@@ -5241,32 +5241,154 @@ local function ResolvePetHappinessState(value)
return PET_HAPPINESS_STATES[GetLowercaseString(value)]
end
-- ClassicAPI's totem bar is TBC-ordered: SUMMON_TOTEM_SLOT1..4 in Spell.dbc.
local TOTEM_SLOTS = {
fire = 1,
earth = 2,
water = 3,
air = 4,
["1"] = 1,
["2"] = 2,
["3"] = 3,
["4"] = 4,
}
-- Resolve a [totem:X] argument to a slot. An element name or slot number names
-- the slot directly; anything else is matched against the name of the totem
-- standing in each slot, so [totem:Searing_Totem] asks for that totem rather
-- than "whatever is in the fire slot". nil when neither matches -- which
-- includes naming a totem that is not currently out.
local function ResolveTotemSlot(name)
if type(name) ~= "string" or name == "" then return nil end
local searchName = GetLowercaseString(
CleveRoids.Trim(string.gsub(CleveRoids.StripRank(name), "_", " ")))
local slot = TOTEM_SLOTS[searchName]
if slot then return slot end
for i = 1, 4 do
-- Second return, not the first: GetTotemInfo's haveTotem reports whether
-- the player carries the slot's TOOL item, not whether a totem is out.
local _, totemName = GetTotemInfo(i)
if totemName and totemName ~= "" and GetLowercaseString(totemName) == searchName then
return i
end
end
end
-- True while any totem slot has a timer running. Drives the OnUpdate re-test
-- that keeps [totem:X<N] icons honest -- see the caller for why polling is the
-- only option. GetTotemTimeLeft, not GetTotemInfo: this asks whether a countdown
-- is in progress (a totem with no timer has nothing to go stale), and it reads
-- the slot alone, where GetTotemInfo walks the bags for the tool item.
function CleveRoids.AnyTotemTimerRunning()
for i = 1, 4 do
local timeLeft = GetTotemTimeLeft(i)
if timeLeft and timeLeft > 0 then return true end
end
return false
end
-- [totem:X] / [nototem:X], shaped like the aura validators: X is a plain name or
-- a parsed comparison entry, comparisons read seconds left, and `#N` stack
-- comparisons read 1 for a standing totem -- a totem is either up or it isn't.
--
-- An empty slot reads -1 on both axes, the same "missing counts as least" the
-- aura path uses, so [totem:Searing_Totem<5] passes while the totem is expiring
-- AND while it is absent: one clause for the whole recast macro.
local function ValidateTotem(args)
if not args then return false end
if type(args) ~= "table" then
args = { name = args }
end
local remaining, stacks = -1, -1
local slot = ResolveTotemSlot(args.name)
if slot then
-- Occupancy comes from the name, not from the time left: a totem whose
-- summon spell carries no SpellDuration row reads 0 seconds, and that is
-- "up with no timer", not "absent".
local _, totemName = GetTotemInfo(slot)
if totemName and totemName ~= "" then
stacks = 1
remaining = GetTotemTimeLeft(slot)
end
end
local ops = CleveRoids.operators
local cmp = CleveRoids.comparators
-- Multi-comparison (e.g. >2&<8) - ALL must pass
if args.comparisons and type(args.comparisons) == "table" then
for _, comp in ipairs(args.comparisons) do
if not ops[comp.operator] then return false end
local value = comp.checkStacks and stacks or remaining
if not cmp[comp.operator](value, comp.amount) then return false end
end
return true
end
if not args.amount and not args.operator and not args.checkStacks then
return stacks == 1
elseif args.amount and ops[args.operator] then
return cmp[args.operator](args.checkStacks and stacks or remaining, args.amount)
else
return false
end
end
-- A list of Conditionals and their functions to validate them
CleveRoids.Keywords = {
-- [button:N] — true while mouse button N is held (1=Left, 2=Right, 3=Middle,
-- 4/5=extra). Routed through Multi so OR/AND lists and repeated groups behave
-- like every other argument conditional ([button:1/2] = left or right).
-- [button] with no argument — true if any mapped mouse button is held.
-- [button:N] — N is the button that INVOKED this action (1=Left, 2=Right,
-- 3=Middle, 4/5=extra), not one held down as a modifier. That is retail's
-- meaning, and it is why a keybind press counts as button 1: retail activates
-- through the left-click path, so [button:1] passes for a keybind and a
-- left-click alike, while [button:2] passes only for an actual right-click.
-- Routed through Multi so OR/AND lists behave like every other argument
-- conditional ([button:1/2] = invoked by left or right).
--
-- Bare [button] / [nobutton] ask whether a click drove this at all -- the one
-- thing the underlying data says that retail's conditional cannot express,
-- and the practical "activated by a keybind, not a click" test.
button = function(conditionals)
if type(conditionals.button) ~= "table" then
return CleveRoids.AnyMouseButtonDown()
return CleveRoids.WasClickActivated()
end
local invoking = CleveRoids.GetActivatingButton()
return Multi(conditionals.button, function(button)
local name = CleveRoids.buttons[button]
return name and IsMouseButtonDown(name) or false
return CleveRoids.buttons[button] == invoking
end, conditionals, "button")
end,
nobutton = function(conditionals)
if type(conditionals.nobutton) ~= "table" then
return not CleveRoids.AnyMouseButtonDown()
return not CleveRoids.WasClickActivated()
end
local invoking = CleveRoids.GetActivatingButton()
return NegatedMulti(conditionals.nobutton, function(button)
local name = CleveRoids.buttons[button]
return not (name and IsMouseButtonDown(name))
return CleveRoids.buttons[button] ~= invoking
end, conditionals, "nobutton")
end,
-- [totem:X] — X names a totem-bar slot (fire/earth/water/air, or 1-4) or the
-- totem itself ([totem:Searing_Totem]). Bare [totem] takes the action as its
-- argument, as [mybuff] does, so /cast [nototem] Searing Totem is the whole
-- recast macro. Time and stack comparisons follow the aura precedent; see
-- ValidateTotem.
totem = function(conditionals)
return Multi(conditionals.totem, function(v)
return ValidateTotem(v)
end, conditionals, "totem")
end,
nototem = function(conditionals)
return NegatedMulti(conditionals.nototem, function(v)
return not ValidateTotem(v)
end, conditionals, "nototem")
end,
exists = function(conditionals)
return UnitExists(conditionals.target)
end,
@@ -9200,6 +9322,7 @@ CleveRoids.STATIC_CONDITIONALS = {
mod = true, nomod = true,
keydown = true, nokeydown = true,
button = true, nobutton = true,
totem = true, nototem = true,
swimming = true, noswimming = true, swim = true, noswim = true,
indoors = true, noindoors = true, outdoors = true, nooutdoors = true,
rooted = true, norooted = true,
+38 -10
View File
@@ -201,9 +201,13 @@ requirementCheckFrame:SetScript("OnEvent", function()
-- v1.15.0 added frame:RegisterUnitEvent, which Utility.lua calls at file scope.
-- On an older build that call raises, aborting the rest of the chunk -- so this
-- is not a degraded-features warning, it's "the addon did not finish loading".
local hasClassicAPI1150 = hasClassicAPI and CleveRoids.ClassicAPI.HasMinimumVersion(1, 15, 0)
-- v1.15.8 scoped GetMouseButtonClicked to the click dispatch. Below it the
-- function exists but a held button keeps its value, so [button:N] reads the
-- button you are turning the camera with -- wrong quietly, which is the worse
-- failure and why the floor moved rather than the conditional being gated.
local hasClassicAPI1158 = hasClassicAPI and CleveRoids.ClassicAPI.HasMinimumVersion(1, 15, 8)
if not hasNampower30 or not hasUnitXP or not hasClassicAPI or not hasClassicAPI1150 then
if not hasNampower30 or not hasUnitXP or not hasClassicAPI or not hasClassicAPI1158 then
-- Show warnings (don't disable — tearing down a partially-initialized addon causes hangs)
if not hasNampower then
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFAvitasia's Nampower v3.0.0+|r is required:")
@@ -223,9 +227,9 @@ requirementCheckFrame:SetScript("OnEvent", function()
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI|r is required:")
CleveRoids.Print("https://github.com/brues-code/ClassicAPI")
CleveRoids.Print("Dispel-type and movement conditionals will be unavailable without it.")
elseif not hasClassicAPI1150 then
elseif not hasClassicAPI1158 then
local major, minor, patch = CleveRoids.ClassicAPI.GetVersion()
CleveRoids.Print(format("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI v1.15.0+|r is required (you have v%d.%d.%d):", major, minor, patch))
CleveRoids.Print(format("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI v1.15.8+|r is required (you have v%d.%d.%d):", major, minor, patch))
CleveRoids.Print("https://github.com/brues-code/ClassicAPI")
CleveRoids.Print("The addon cannot finish loading on this version -- update ClassicAPI.")
end
@@ -4190,6 +4194,21 @@ function CleveRoids.OnUpdate(self)
end
end
-- A totem's timer runs down with nothing behind it: PLAYER_TOTEM_UPDATE fires
-- when a slot changes hands, never as one counts toward zero, so a
-- [totem:X<N] icon would hold the answer from its last refresh until some
-- unrelated event queued another. Unlike an aura, there is no ambient event
-- stream to ride -- UNIT_AURA is what quietly keeps [mybuff:X<N] honest -- so
-- a re-test once a second while any totem timer runs is the whole mechanism.
-- Only the icon is at stake: the click path evaluates conditionals live.
-- Realtime mode re-tests everything each tick and needs none of this.
if CRM.realtime == 0 and time >= (CR.nextTotemPoll or 0) then
CR.nextTotemPoll = time + 1
if CR.AnyTotemTimerRunning() then
CR.isActionUpdateQueued = true
end
end
-- Check the saved variable to decide which update mode to use.
if CRM.realtime == 1 then
-- Realtime Mode: Force an update on every throttled tick for maximum responsiveness.
@@ -4447,12 +4466,8 @@ CleveRoids.Frame:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
CleveRoids.Frame:RegisterEvent("SPELL_UPDATE_COOLDOWN")
CleveRoids.Frame:RegisterEvent("PLAYER_STARTED_MOVING")
-- ClassicAPI loss-of-control (school-interrupt lockout) for [locked]/[nolocked].
-- Gated on the namespace so an older ClassicAPI without it doesn't error on an
-- unknown event.
if type(C_LossOfControl) == "table" then
CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_ADDED")
CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_UPDATE")
end
CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_ADDED")
CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_UPDATE")
-- The unit state streams that drive icon refresh: GUID events when Nampower
-- provides them (v2.39+, one event per unit change rather than one per token),
-- else the stock per-token events. These cannot become RegisterUnitEvent calls
@@ -4516,6 +4531,11 @@ if CleveRoids.NampowerAPI.features.hasKeyEvents then
CleveRoids.Frame:RegisterEvent("KEY_UP")
end
-- ClassicAPI totem-bar tracking, for [totem:X] icon refresh. Ungated, unlike the
-- Nampower features above: it long predates the ClassicAPI floor the addon
-- already refuses to run below.
CleveRoids.Frame:RegisterEvent("PLAYER_TOTEM_UPDATE")
-- NOTE: SuperMacro hook installation is handled by Compatibility/SuperMacro.lua
-- which has the complete implementation including the INTERCEPT path for all commands
@@ -5567,6 +5587,14 @@ function CleveRoids.Frame:KEY_UP()
CleveRoids.isActionUpdateQueued = true
end
-- ClassicAPI PLAYER_TOTEM_UPDATE: arg1 = the slot (1 Fire .. 4 Air) whose totem
-- was dropped, expired, killed or recalled. The slot goes unread and every macro
-- is refreshed: a [totem:X] macro may name any slot, and [nototem:X] flips on any
-- of them.
function CleveRoids.Frame:PLAYER_TOTEM_UPDATE()
CleveRoids.QueueActionUpdate()
end
-- Base SendChatMessage captured at first hook; used to break a hook cycle.
local baseSendChatMessage = SendChatMessage
local sendingChatMessage = false
+3 -2
View File
@@ -45,8 +45,9 @@ CleveRoids.supported = CleveRoids.hasTurtle
-- "may we call C_Macro.SetMacroDisplay" guard, so the two can never disagree.
--
-- Feature-detect rather than version-check: SetMacroDisplay ships in ClassicAPI
-- v1.15.0, this addon's minimum, so a nil here means the client mod is missing
-- outright -- the case Core.lua's requirement check warns about but keeps running.
-- v1.15.0, below this addon's minimum, so a nil here means the client mod is
-- missing outright -- the case Core.lua's requirement check warns about but
-- keeps running.
CleveRoids.ClassicAPIMacroDisplay =
(type(C_Macro) == "table" and C_Macro.SetMacroDisplay ~= nil) and true or false
+1 -1
View File
@@ -10,7 +10,7 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna
|-----|:--------:|---------|
| [Nampower](https://github.com/brues-code/nampower/releases) (v3.0.0+) | ✅ | Spell queueing, DBC data, auto-attack events |
| [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3/releases) | ✅ | Distance checks, `[multiscan]` enemy scanning |
| [ClassicAPI](https://github.com/brues-code/ClassicAPI/releases) (v1.15.0+) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed, unit-filtered events |
| [ClassicAPI](https://github.com/brues-code/ClassicAPI/releases) (v1.15.8+) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed, unit-filtered events |
## Installation
+18 -8
View File
@@ -734,14 +734,24 @@ CleveRoids.buttons = {
['5'] = 'Button5',
}
-- True while any mapped mouse button is held. Backs the argument-less [button] /
-- [nobutton], mirroring how a bare [mod] means "any modifier". Bare [nobutton] is
-- the practical "activated by a keybind, not a click" test.
function CleveRoids.AnyMouseButtonDown()
for _, name in pairs(CleveRoids.buttons) do
if IsMouseButtonDown(name) then return true end
end
return false
-- The button that invoked the action now running, as retail's [button:N] means
-- it. ClassicAPI v1.15.8+ scopes GetMouseButtonClicked to the click dispatch, so
-- it reads the button of the handler this macro is running under -- through the
-- helpers the handler calls, not just the handler itself -- and nil at any other
-- time. A held button no longer keeps it set, which is what made mouse-turning
-- poison every keybind press before v1.15.8.
--
-- nil outside a click dispatch, so the tooltip resolves as button 1 and sits
-- still rather than tracking the mouse. Button:Click() with no argument reports
-- LeftButton, which is how a keybind comes out as button 1 on retail.
function CleveRoids.GetActivatingButton()
return GetMouseButtonClicked() or "LeftButton"
end
-- True while a click dispatch is what is running this action. Backs bare
-- [button] / [nobutton], which retail has no equivalent for.
function CleveRoids.WasClickActivated()
return GetMouseButtonClicked() ~= nil
end
CleveRoids.kmods = {