[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.
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.
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.
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.
A paladin's white swing refreshes their own judgement debuff on the victim, and
the server's edit is packet-silent, so pfUI#45 was the visible symptom: the
timer ran to zero while the debuff stayed up. This addon worked around it three
ways at once -- a UNIT_CASTEVENT MAINHAND/OFFHAND handler in Core.lua, an
AUTO_ATTACK_OTHER fallback in Conditionals.lua, and a CHAT_MSG_COMBAT_SELF_HITS
parser in Utility.lua that string-matched "hit"/"crit" and guessed at melee vs
spell by looking for a parenthesis -- each rewriting rec.start and pushing the
result into pfUI by hand.
ClassicAPI mirrors the server rule in the DLL (src/aura/JudgementRefresh.cpp),
keyed off SMSG_ATTACKERSTATEUPDATE, which is the visible trigger for the
invisible edit. It matches judgements by their DBC marker -- SPELLFAMILY_PALADIN
with SPELL_ATTR_EX3_ALWAYS_HIT -- for any paladin's judgements, not only ours.
That last detail retires the auto-detection too. The hardcoded judgementSpells
list could not cover Turtle's custom judgement ranks, so a cast queued a pending
scan that read the target's debuffs 0.5s later, matched names against
"^Judgement", and grew the list at runtime. Matching on the DBC attribute needs
no list to grow. Gone with it: judgementSpells, pendingJudgements,
detectedJudgementDebuffIDs, the pending-scan pass and its queue buffer, and both
cast-site hooks.
The scan also registered each ID it discovered into lib.sharedDebuffs so
[debuff] fallback matching worked for custom IDs. That mattered when detection
went through the tracking store; the read path resolves auras through
C_UnitAuras now, which returns a debuff whether or not we have a row for it.
Same shape as Carnage, opposite direction: Dark Harvest is a channel that makes
the caster's DoTs on the target tick 30% faster, and the server never tells an
observer the remaining durations changed. So this addon stamped dhStartTime on
every tracked record when the channel began, dhEndTime when it stopped, and
subtracted 30% of the elapsed window from each remaining time on read.
ClassicAPI compresses the ticks in the DLL (src/turtle/DarkHarvest.cpp), so
expirationTime already reflects it.
Removed: GetDarkHarvestReduction, ApplyDarkHarvestStart, ApplyDarkHarvestEnd,
GetTimeRemainingWithDarkHarvest, the channel-start capture on both the
UNIT_CASTEVENT and nampower paths, the channel-stop finalizer in
SPELLCAST_CHANNEL_STOP, the darkHarvestData state, and DarkHarvestSpellIDs.
The darkHarvest flag in Extensions/CursiveCustomSpells.lua stays -- that is
spell metadata this addon exports to Cursive, not tracking of our own.
Carnage refreshes the caster's Rip and Rake when Ferocious Bite procs it, and
1.12 tells an observing caster nothing about it -- no packet carries a debuff's
new remaining duration on another unit. So this addon inferred the proc: track
every Ferocious Bite cast with its target, watch PLAYER_COMBO_POINTS for combo
points failing to drop to 0 within 0.5s, then rewrite the stored Rip and Rake
records and push the new durations into pfUI's tables by hand. Roughly 570 lines
across three files, and it could only ever be a guess, because the roll that
decides whether Carnage procs is server-side.
ClassicAPI does it in the DLL (src/turtle/Carnage.cpp), roll-gated, so
expirationTime is simply correct and there is nothing to infer.
Removed with it: ApplyCarnageRefresh, the Ferocious Bite cast tracking on both
the UNIT_CASTEVENT and nampower paths, the avoided-Bite clearing in the miss
handler, the Rip/Rake cast snapshots on the UNIT_CASTEVENT and SPELL_GO paths,
the Rake landed/failed verification hooks, the proc detector in
ComboPointTracker, and lastRipCast / lastRakeCast / RipSpellIDs / RakeSpellIDs,
which had no readers left afterwards.
carnageDurationOverrides goes too, and it never worked: every reference to it
was a write, a clear, or the 30-second sweep in Core.lua's cleanup. Nothing ever
read it to change a duration. FerociousBiteSpellIDs stays -- the combo-point
snapshot still needs to know a finisher when it sees one.
Three loops walked 1..36 by hand, two of them re-arguing in comments why 36
is the right number. It is: the client addresses 18 account-wide macros at
1-18 and 18 character-specific ones at 19-36, and that range is what
GetMacroInfo and C_Macro.SetMacroDisplay index. Nothing in the API hands it
back -- GetNumMacros() returns how many of each tab are used, which cannot be
summed into a range, because the character block still starts at 19 when the
account block is empty. Blizzard's MAX_MACROS is no better: it lives in the
load-on-demand Blizzard_MacroUI and is nil until the macro window is opened.
So CleveRoids.MAX_MACRO_SLOTS in Init.lua, carrying that reasoning once.
ValidateAllMacros also dropped a numAccountMacros it computed and never read.
ReleaseDisplays keeps sweeping the full range rather than the macros it knows
it published: PublishAllDisplays clears that record on each re-parse, so a
macro claimed before one and gone after it would keep our last published value
forever. Releasing a slot we never claimed costs nothing, and it runs once.
An out-of-date ClassicAPI did not degrade the addon, it decapitated it.
Utility.lua calls ev:RegisterUnitEvent at file scope (v1.15.0), so on an
older build that call raises and Lua abandons the rest of the chunk --
every definition below line 4118 of a 10040-line file, some 6000 lines,
silently never happens. What the user sees is the first one a common path
reaches: PLAYER_TARGET_CHANGED calling a nil ClearResistState, once per
target swap, forever.
The requirement check still said v1.12.1 (the positional
C_UnitAuras.UnitAura), so the affected client sailed through it and the
warning it did print promised only that "dispel-type conditionals will be
unavailable" -- describing a graceful degradation that was not happening.
The gate is now v1.15.0 and says the addon cannot finish loading, with the
installed version in the message the way the Nampower branch does it.
ClassicAPI.lua gains GetVersion() to decode CLASSIC_API_VERSION, keeping
the X*10000 + Y*100 + Z encoding inside that module, and its header notes
that the minimum has to rise with any ClassicAPI call adopted at file
scope -- that is the property that turns a version mismatch into a silent
half-load rather than a missing feature.
C_Macro.SetMacroDisplay also ships in v1.15.0, which retires the last
pre-ClassicAPI display path:
useClassicAPIDisplay folds into ClassicAPIMacroDisplay. The two were
always equal -- set together at load, cleared together in
ReleaseDisplays -- and one of them is a handshake ClassicAPI reads, so
a second name for the same state could only ever drift.
TestForAllActiveActions loses the per-slot ACTIONBAR_SLOT_CHANGED
fan-out. Publishing repaints every slot holding the macro through the
client's own notifier; the fan-out was what ran when SetMacroDisplay
was unavailable, and its one remaining reachable state was after
DisableAddon, where ReleaseDisplays has already handed all 36 macros
back and ClassicAPI repaints them itself.
The load-time feature detect stays, with a new reason: below the minimum
the addon is already broken, so a nil SetMacroDisplay now means ClassicAPI
is absent outright -- the case the requirement check warns about and then
keeps running.
Two handlers opened by testing arg1 against a fixed set of unit tokens, so
every other unit's copy of UNIT_AURA reached Lua only to be compared away
-- in a raid that is the bulk of them. ClassicAPI's RegisterUnitEvent
pushes that filter into the client, so the handler only runs for the units
named. Filtering is per (frame, event), so the other events sharing the
libdebuff frame are unaffected.
Utility.lua UNIT_AURA -> "target" (libdebuff seeding)
ComboPointTracker UNIT_AURA -> "target", "player"
Extensions register through CleveRoids.RegisterEvent, so ExtensionsManager
gains a RegisterUnitEvent alongside it.
Separately, the main frame's unit state streams (UNIT_AURA / UNIT_HEALTH /
UNIT_POWER, or the _GUID variants under Nampower v2.39+) are now registered
only in event-driven mode. Their handlers are wholly wrapped in
`realtime == 0`, so with realtime on every one of them crossed into Lua and
returned immediately while the OnUpdate did the refreshing. Registration is
applied at load, again at VARIABLES_LOADED once the saved setting is
readable, and whenever /cleveroid realtime flips it.
Those three stay plain RegisterEvent calls: their handlers ignore the unit
and refresh every macro, and a conditional may name any unit
([@party3,hp:<50], @focus, @mouseover), so narrowing the token set would
leave those icons stale. The _GUID variants could not be filtered anyway --
their first argument is a GUID, not a unit token.
Squash of the classicapi_next branch (10 commits). ClassicAPI is now a hard
requirement, and the Lua-side reimplementations it supersedes are gone:
net -1087 lines across 12 files.
Macro display: resolved macro actions are published through
C_Macro.SetMacroDisplay, so the client draws macro buttons and the
action-bar function overrides this addon used to install are removed.
Ownership is per macro -- ClassicAPI keeps the macros we never claim.
Auras and timing: ClassicAPI is the source of truth for non-player aura
timing via the positional C_UnitAuras.UnitAura, replacing the write-only
buff tracking tables and the overflow-slot fallback.
Cleanup: the hasPfUI76 flag and the pfUI 7.6 branches it gated, two
silently-shadowed code paths, and per-call allocations in the event and
publish paths (SPELL_CAST_EVENT merged). Packaging moves to
brues-code/packager@vCAPI.
Macro syntax: a clause may carry a leading run of [group] blocks sharing
one action, Blizzard-style -- groups are OR'd, first pass wins, and []
always passes. The ;-separated form is unchanged and mixes freely. @focus
clauses with no focus set now fail quietly instead of printing
"Invalid target" on the way past.
* feat: add /stopchannel command
* review: give /stopchanneling a real handler and warn when nampower can't honor it
---------
Co-authored-by: Brues <5278969+brues-code@users.noreply.github.com>
IsRelicSlot no longer tests playerClass against PALADIN/DRUID/SHAMAN. It now
reads CleveRoids.hasRelicSlot, sampled once in PLAYER_LOGIN.
Also folds PerformEquipSwap's slot-18 check into the same helper. Its class list
(HUNTER/WARRIOR/ROGUE/MAGE/WARLOCK/PRIEST) was the exact complement of the relic
classes, so it becomes `not IsRelicSlot(inventoryId)` and the two lists can no
longer drift apart.
Adds CleveRoids.conditionalAliases, applied in ParseMsg so a deprecated name
is rewritten to its current keyword before anything downstream (evaluation,
_groups) sees it. This is the mechanism for renamed modifiers, which -- unlike
predicates such as [stl] -> [stealth] -- have no Keywords function an alias
could simply point at.
[mouseuse] now resolves to [cursor], routing old macros through ClassicAPI's
C_Spell.CastAtCursor / C_Item.UseAtCursor. Drops the post-cast block that cast
normally and then faked a mouse click via CameraOrSelectOrMoveStart/Stop to
place the AoE circle, along with its workaround for that call spuriously
starting auto-attack.
MacroErrorChecker seeds VALID_CONDITIONALS from the alias table, so the old
names stay valid syntax without needing a placeholder ignoreKeywords entry.
scanDispel now reads each slot through the positional C_UnitAuras.UnitAura
(added in ClassicAPI v1.12.1) instead of the table-return GetAuraDataByIndex,
dropping a throwaway AuraData table per slot on the up-to-48-slot scan behind
the [magic]/[curse]/[disease]/[poison]/[dispellable] conditionals.
Since UnitAura is called directly with no fallback, gate on it: the load-time
check in Core.lua warns (matching its warn-don't-disable design) when
ClassicAPI is older than v1.12.1, wiring up the previously-unused
ClassicAPI.HasMinimumVersion.
- Resolve Feed Pet from spell ID 6991 (C_Spell.GetSpellName) so /feedpet works on non-enUS clients without a Localized.Spells entry
- Fix CleveRoids.IsReactive referencing undefined global spellName instead of its name parameter
UPDATE_MACROS and SPELLS_CHANGED each did a full spell + talent + pet +
120-slot action-bar rebuild inline, and both fire several times during
login as the spellbook and macros populate - so the rebuild ran 4-6
times in the first ~2s, most of it duplicated work fanning 120 button
updates out to Blizzard/pfUI/Bongos each pass.
Defer the rebuild instead: UPDATE_MACROS, SPELLS_CHANGED and PLAYER_LOGIN
arm a 0.3s debounce (macroRebuildTime); the update loop runs RebuildMacros
once when the burst settles. RebuildMacros skips the action-bar pass until
`ready` (GetAction early-returns before then, and the +1.5s init timer
builds the bars once anyway), and BAG_UPDATE_DELAYED's bar rebuild is
gated on `ready` too. Net: IndexActionBars runs once at login (the
suppressed init-timer pass) instead of 4-6 times, IndexSpells ~2x instead
of ~5x. Runtime macro edits and bag changes still do a full unsuppressed
rebuild, just debounced.
ClassicAPI hooks the global GetItemInfo to auto-warm the item cache on a
miss and fires GET_ITEM_INFO_RECEIVED when the async fill lands, so the
tooltip-scan warmup is obsolete: IndexItems's own GetItemInfo calls
already trigger the same warmup, and owned items (bags + equipped) are
priority-prefetched by the engine. The old warmup also assumed the fill
was synchronous, which no longer holds.
Drop DoWDBWarmup and its login scheduling; instead listen for
GET_ITEM_INFO_RECEIVED and run a debounced re-index. IndexItems records
owned itemIDs it could not resolve into pendingItemInfo, and the handler
ignores any fill not in that set (quest DB scans, AH sweeps, chat-link
hovers, inspects) in O(1) so unrelated bursts do not cause reindex churn.
GetSpell/GetItem now resolve explicit spell:/item: ID forms. spell:<id>
prefers the player's spellbook entry via FindSpellBookSlotByID (per-rank
and pet aware) for full cost/cooldown/usability, falling back to an
id-only entry rendered via SetSpellByID for spells not in the book.
item:<id> reuses the existing numeric lookup for location-aware tooltips.
Guard the id-only path against nil spellSlot/cost in TestForActiveAction
and GetActionCooldown so display-only spell references don't crash.
Remove the dead, no-op GetSpellSlotByID stub.
HasGearEquipped now wraps ClassicAPI's native slot-walk, which
short-circuits on first match. Removes BuildEquipmentCache, the
_equipped* cache tables/invalidation, and the InvalidateEquipmentCache
call from PLAYER_EQUIPMENT_CHANGED. [equipped] reads live engine state,
so no cache staleness surface remains; also gains item-link support.
Replace the full IndexItems + action-bar rebuild with a single-slot
IndexEquipSlot using the event's arg1 (slot) and arg2 (hasCurrent),
and drop the now-pointless throttle/deferral. Bag-side deltas remain
covered by BAG_UPDATE_DELAYED; [equipped] runs off its own cache.
Detects a spell-school interrupt lockout (Counterspell/Kick/Pummel/Earth
Shock) on the player -- the server-side lockout that no debuff scan can see,
since it's a SMSG_SPELL_COOLDOWN packet, not an aura. ClassicAPI's
C_LossOfControl aggregates it as SCHOOL_INTERRUPT with a lockoutSchool mask.
ClassicAPI.GetSchoolLockout() returns the locked school mask; ValidateSchoolLocked
tests a school name against it (bitmask check without the 5.1-only % operator).
[locked] = any school kicked, [locked:frost] = that school, [locked:fire/frost]
= either; [nolocked:...] negates with AND (neither). Registered in
BOOLEAN_CONDITIONALS for the bare form; LOSS_OF_CONTROL_ADDED/UPDATE wired to
refresh the icon. Full silences remain [mycc:silence].
The /target candidate search's last SuperWoW-gated block walked WorldFrame
children and scraped GUIDs via pcall(frame.GetName, frame, 1). ClassicAPI now
provides modern nameplateN unit tokens that work with UnitExists/UnitCanAttack/
TargetUnit directly, so it collapses to iterating nameplate1..40 -- no SuperWoW
dependency, no WorldFrame hackery, and it also picks up default vanilla
nameplates. Scans the full range since assigned slots can be sparse.
Uses ClassicAPI's modern C_Item.GetWeaponEnchantInfo (12-tuple with
enchant IDs) plus C_Item.GetEnchantInfo (ID -> localized name) to match
WHICH temporary weapon enchant is applied -- by SpellItemEnchantment ID or
name -- not just that one exists. Locale-proof and exact, unlike
[mhimbue:Name]'s green-text tooltip scan.
ClassicAPI.lua gains GetWeaponEnchant(slot) (centralizes the tuple
indexing, vanilla-global fallback) and GetEnchantName(id). ValidateWeaponImbue
now reads through GetWeaponEnchant. New keywords mhenchant/nomhenchant/
ohenchant/noohenchant support bare (any enchant), single ID/name, and
OR-lists ([mhenchant:2823/Deadly_Poison]); registered in BOOLEAN_CONDITIONALS
for the bare form and auto-added to VALID_CONDITIONALS via Keywords.
ClassicAPI registers a bare /equipset that resolves a name and calls
C_EquipmentSet.UseEquipmentSet with no conditional layer. DoEquipSet routes
it through DoWithConditionals like the other /equip* commands, so it gains
[combat]/[mod]/@unit/etc. and ';'-separated fallthrough (first set whose
conditionals pass wins). Console.lua reclaims the EQUIP_SET handler (keeping
ClassicAPI's localized aliases) and falls back to registering /equipset itself
if the API command isn't present.
Add ClassicAPI UnitPower/UnitPowerMax/UnitPowerMissing/UnitPowerType wrappers
and route the power conditionals through them:
- ValidatePower/ValidateRawPower/GetCachedPlayerPower(Percent) use UnitPower /
UnitPowerMax (omitted type = primary power, matching the old UnitMana path).
- ValidatePowerLost uses UnitPowerMissing (one call vs max - current).
- powertype/nopowertype use ClassicAPI.UnitPowerType.
Replace the SuperWoW "2nd return of UnitMana = caster mana" druid hack in
ValidateDruidRawMana and the #showtooltip OOM check with UnitPower(unit, 0),
which reads the mana slot directly and survives shapeshift. Verified in Cat
Form: UnitPower('player', 0) returns caster mana while UnitPower('player', 3)
returns energy.
Remove the now-dead Nampower GetUnitField-based GetUnitPower/GetUnitMaxPower
wrappers and POWER_FIELDS tables (no caller passed a powerType, so that path
never ran).
Swap 166 call sites from GetSpellRecField(id, "name") to
C_Spell.GetSpellName(id) and GetSpellRecField(id, "rank") to
C_Spell.GetSpellSubtext(id) across Core, Conditionals, Utility,
ComboPointTracker, CursiveCustomSpells, pfUI, OverflowBuffFrame, and
Generic. Guard forms and the _GetSpellRecField alias calls collapse to the
direct C_Spell call.
GetSpellRecField stays for fields with no C_Spell equivalent (school,
spellIconID, mechanic, effectMechanic, effectApplyAuraName, stackAmount,
rangeIndex) and inside the NampowerAPI wrapper layer.
Back them with ClassicAPI IsMounted() and UnitStandState("player").
mounted/nomounted are player mount state; standing = stand state 0,
sitting = any non-standing pose (its complement). Player-only, registered
in Keywords, BOOLEAN_CONDITIONALS, and STATIC_CONDITIONALS like [stealth].
The previous reclaim stack-overflowed when LeafVillageAchievements loaded
after us (its captured "original" was our own function, so reclaiming onto
the top created an ours<->LVA loop). Add a reentrancy guard plus a base
SendChatMessage reference so a hook cycle routes straight to the base
function instead of recursing.
Gate the reclaim on LeafVillageAchievements OR LeafVillageLegends (both hook
SendChatMessage and can bump ours off the top -- LVA orphans it, LVL wraps
and re-installs on timers). Keep the anchored ^#showtooltip match since being
on top means we see the pristine line.
Our #showtooltip filter hooks the global SendChatMessage. An addon that
snapshots SendChatMessage at its own file-load and later calls that
snapshot directly (e.g. LeafVillageAchievements at PLAYER_ENTERING_WORLD+3s)
orphans our hook if it loaded before us -- its snapshot predates our filter,
so #showtooltip leaks to chat. This bites the fork specifically: it sorts
after LeafVillageAchievements alphabetically, so it loads too late to be in
the snapshot, whereas upstream "CleveRoidMacros" sorted before it.
Make the filter a named function and add EnsureSendChatMessageHook, which
re-asserts it as the outermost SendChatMessage hook (no-op once on top).
OnUpdate calls it each throttled tick via a cheap identity check, so we
reclaim the top of the chain within a frame of being displaced. Normal
messages still flow through the chained-over hook untouched.
GetSpellCost now reads power cost and reagents straight from Spell.dbc via
C_Spell.GetSpellPowerCost (effective, talent-modified cost) and
C_Spell.GetSpellReagents (itemID), dropping the GameTooltip owner/scan
frames, their font strings, and all locale-dependent line parsing.
Reagent counting is now itemID-based end to end: GetReagentCount takes an
itemID and matches by id in the Items cache / bag scan. This removes the
hardcoded English reagent tables (_ReagentBySpell, _ReagentIdByName) and
the name-matching bag-scan tooltip, so it works on any client locale.
Verified in-game that GetSpellReagents covers DBC reagent spells (Vanish
-> Flash Powder), which the hand table previously special-cased.
The localized reagent name is still used for countedItemTypes registration
(recognizing a reagent item placed on the action bar); when that name
isn't cached yet, warm it via the ClassicAPI Item mixin
(Item:CreateFromItemID/ContinueOnItemLoad) and register it once it lands.
Adds a new /cancelform slash command (Console.lua) wired to CleveRoids.DoCancelForm. Replaces the previous _unshiftAction with _cancelFormAction that calls CancelShapeshiftForm, and updates DoUnshift to delegate to DoCancelForm for backwards compatibility. Also fixes a unit name check by replacing GetUnitName with UnitName in DoRetarget.
Replace GetInventoryItemLink/GetContainerItemLink link-scraping (built
solely to regex out item:ID or the bracketed name) with direct ClassicAPI
reads:
- id/presence: GetInventoryItemID, C_Container.GetContainerItemID
- suffix-sensitive names (equip-by-name on gear): C_Item.GetItemName
location form, which now carries random-suffix decoration
- suffix-free names (consumables/reagents/poisons) in hot bag scans:
id + C_Item.GetItemNameByID (base name, zero table allocation)
Also covers /use equipped-slot resolution and the WDB warm scans. Drops
the now-dead GetContainerItemLink/GetInventoryItemLink/string_find
upvalue aliases. No behavior change; equip-by-name keeps full suffixed
matching via the decorated location form.
The autoAttack flag could drift stale-true (optimistic set after
AttackTarget, or a target dying without PLAYER_LEAVE_COMBAT), making
/startattack skip the attack until the target was dropped and reselected.
The old fallback called the overridden IsCurrentAction, which just echoes
the cached flag for the attack slot, so it never detected drift. Use the
original Hooks.IsCurrentAction as ground truth and sync the flag to it.
GameTooltip.SetAction used GameTooltip:SetSpellByID for spell actions, which
renders the static DBC tooltip. For spells we resolved from the player's own
spellbook, use GameTooltip:SetSpell(spellSlot, bookType) instead so the
tooltip shows live player-accurate data (mana cost, cooldown, range coloring,
reagent counts). spellSlot/bookType/id all come from the same spellbook index
entry, so it renders the identical spell and rank. SetSpellByID remains as a
defensive fallback for entries without a slot. Applied to both the direct and
nested-macro tooltip paths.
Register PLAYER_STARTED_MOVING (ClassicAPI, edge-detected off the WASD/
autorun key state) and queue an action update so [moving]/[nomoving] macro
icons repaint when movement starts.
PLAYER_STOPPED_MOVING is deliberately NOT used -- it's key-release based and
misses real stops (running into geometry, click-to-move, roots, knockback).
Instead, STARTED kicks off a 0.1s C_Timer.NewTicker that watches
IsPlayerMoving() (the same speed>0/falling signal [moving] evaluates) and, on
the actual stop, refreshes once and cancels itself. No timer exists while
stopped, so there's no idle cost; guarded on isShuttingDown and event-driven
mode (realtime==0).
Lean on ClassicAPI's backported readers instead of link-string parsing
and a hand-maintained mechanic table.
- ClassicAPI.lua: add wrappers GetCursorInfo/CursorHoldsItemID,
GetContainerItemID, GetInventoryItemID, and GetSpellMechanicByID.
- Core.lua: in the manual equip fallback, verify the cursor holds the
intended item (CursorHoldsItemID) before EquipCursorItem, aborting only
on a definitive mismatch so empty/unknown cursors behave as before.
- NampowerAPI.lua / Utility.lua: replace GetContainerItemLink /
GetInventoryItemLink + "item:(%d+)" parsing with direct C_Container/
inventory ID lookups at the sites that only need the itemID; route the
equipped-set scans through the GetEquippedItemID helper.
- Conditionals.lua / Utility.lua: replace the 785-entry CCSpellMechanics
fallback table with C_Spell.GetSpellMechanicByID in GetSpellMechanic and
GetSpellCCType (BuffLib / nampower paths unchanged); delete the table.
Unify both ! mechanisms (bare !spell gate-injection and bracketed
[cond] !spell dispatch skip) on ClassicAPI's CastSpellNoToggle, which
no-ops auto-repeat (Shoot/Auto Shot/Wand) and toggle auras
(forms/stances/aspects/seals) instead of toggling them off.
- Bare !spell now just sets conditionals.noSpam (no injected gate
conditional); removed spamConditions and GetSpammableConditional.
- Dispatch: melee Attack still uses AttackTarget (CastSpellNoToggle
doesn't cover the melee swing). For everything else under !, keep the
ValidatePlayerBuff anti-refresh skip (CastSpellNoToggle only covers
true toggle auras, not regular self-buffs like Mark of the Wild),
otherwise CastSpellNoToggle. This is more reliable for auto-repeat
than the old CheckChanneled path.
CheckChanneled is retained (still used by the [channeled]/[checkchanneled]
conditionals).
Per design preference, drop the @ and the ParseMsg special-casing:
[cursor] is now a normal modifier keyword (added to ignoreKeywords,
mirroring [mouseuse]) rather than a pseudo unit token. The cast/use
dispatch still routes through ClassicAPI's CastAtCursor / UseAtCursor
(checked via conditionals.cursor). Reverts the @cursor ParseMsg branch.
@cursor places a ground-target spell or on-use item at the cursor via
ClassicAPI's engine-level placement instead of the [mouseuse] reticle-
click simulation (so no auto-attack side effect).
- ParseMsg flags @cursor as conditionals.atCursor rather than setting a
bogus "cursor" unit target, so it doesn't affect help/harm/exists/range
checks or pass an invalid token to CastSpellByName.
- DoWithConditionals routes atCursor casts through C_Spell.CastAtCursor
(spellID resolved from the spell index / GetSpellIdForName) and item
uses through C_Item.UseAtCursor. Both fall back to a normal cast/use
for non-ground actions.
New syntax, so existing macros are unaffected; MacroErrorChecker already
accepts @cursor.
The AuraScanTooltip scanning frame was only used by CancelAura's legacy
tooltip path, which was removed when CancelAura moved to ClassicAPI's
C_Spell.CancelSpellByID. Nothing references it now.