mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
Compare commits
5
Commits
87ea8bdd41
...
v3.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eddc49fc60 | ||
|
|
54e3c3b3f4 | ||
|
|
4fa254b4e6 | ||
|
|
b82d06d061 | ||
|
|
891467cd7e |
+14
-14
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user