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.
Delete unnecessary local 'i' declarations in Extensions/Mouseover/pfUI.lua (ResolvePfUnit, RegisterPartyScripts, RegisterRaidScripts, RegisterRaidMarkScripts). The for-loop headers already provide a local loop variable, so the explicit locals were redundant and could shadow variables. No functional change.
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.
pfUI.uf:EnableScripts binds pfUI.uf.OnEnter to every unitframe, which calls
Nampower's SetMouseoverUnit -- so the native "mouseover" token already resolves
to the hovered pfUI frame (player/target/party/raid/focus/pettarget/partytarget/
...). Every conditional consumer checks UnitExists("mouseover") before the
CleveRoids.mouseoverUnit fallback, so the 10 per-frame OnEnter/OnLeave hookers
here were pure duplication (they even chained pfUI's OnEnter).
Removed all of them. Kept RegisterRaidMarkScripts (raid-marker rows aren't
unitframes, so pfUI sets no mouseover for them -> still needed for mark1..mark8)
and HookPfCast (unrelated /pfcast conditional wrapper), plus the PfSet/PfClear/
resolve helpers the raid-mark path uses.
Rip/Rake/Conflagrate/MoltenBlast/DarkHarvest were hardcoded {rankID=true} sets
used only for membership tests. Replaced with a metatable-backed RankSet(seedID):
t[spellID] name-matches the spellID against one seed rank via C_Spell.GetSpellName,
which resolves every rank (no enumeration) and TWoW's custom spells (confirmed
36916 -> 'Molten Blast' on the TWoW client). Read shape is unchanged, so all
consumers work untouched. Locale-safe (name derived from the seed ID). Custom
seeds absent on a stock client resolve to nil and simply never match -- correct,
those spells can't be cast there.
Left hardcoded: the pairs()-iterated sets (FerociousBite/FlameShock/Immolate),
BleedSpellIDs (Pounce cast/bleed name collision), ComboScalingSpellsByID (carries
base/increment), PounceToBleedMapping (trigger relationship), MobsThatBleed (GUIDs).
PounceBleedSpellIDs, GetLastComboPointsForSpell, and GetLastDurationForSpell
were each definition-only with zero references anywhere in the addon. The
Pounce bleed IDs are still covered by BleedSpellIDs (detection) and
PounceToBleedMapping (cast->trigger).
scanDispel now calls C_UnitAuras.GetAuraDataByIndex(unit, i, filter) instead of
selecting GetBuffDataByIndex/GetDebuffDataByIndex -- the two are documented as
GetAuraDataByIndex with a HELPFUL/HARMFUL filter, and the index runs within the
filtered range identically. Drops the helpful-and-A-or-B function pick.
FindPlayerDebuff and FindPlayerBuff hand-scanned debuff/buff slots for a
spellID to pull icon + stacks. Both now use ClassicAPI.GetUnitAuraBySpellID
(AuraData.icon / .applications) -- FindPlayerDebuff unfiltered (walks both
ranges, was debuff-then-buff), FindPlayerBuff HELPFUL-filtered (buff-only).
Search order is immaterial since a spellID is only ever a buff or a debuff.
Replaces the 32-slot UnitBuff + C_Spell.GetSpellName name-compare loop with a
single ClassicAPI.GetAuraDataBySpellName(unit, name, "HELPFUL") lookup.
Locale-identical to the old compare (both use the localized Spell.dbc name).
Wrapper added next to GetUnitAuraBySpellID.
The Banish immunity check hand-scanned 16 debuff slots plus 32 buff slots
(NPC overflow) for spellID 710/18647. ClassicAPI.GetUnitAuraBySpellID walks
the whole aura array in one call per rank, so it finds the debuff wherever it
lands -- including an NPC's overflow buff slots -- dropping the two loops and
the UnitIsPlayer gate. Wrapper added alongside UnitHasDispelType.
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.
kmods gains lctrl/rctrl, lalt/ralt, lshift/rshift (ClassicAPI's side-specific
IsLeft*/IsRight* key checks), so [mod:lshift]/[mod:ralt] and castsequence
reset=lctrl work. mod/nomod now use ClassicAPI's IsModifierKeyDown instead of
OR-ing the three individual checks.
CheckWeaponImbueByName now resolves the applied temp-enchant ID to its
localized name via ClassicAPI (C_Item.GetEnchantInfo) and matches that
directly -- exact and locale-clean, replacing the green-text tooltip
heuristics for named enchants (poisons, oils, Mongoose, Windfury). Only
nameless enchants (sharpening/weightstones, no DBC name) fall through to
the tooltip scan, so no regression.
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.
Checks whether a saved ClassicAPI equipment set is currently equipped, via
C_EquipmentSet.GetEquipmentSetInfo's isEquipped flag (wrapped as
ClassicAPI.IsEquipmentSetEquipped). Named [equipset] rather than [set] to avoid
colliding with the existing tier/item-set piece-count conditional. Supports
OR-lists ([equipset:Raid/Farm]) and negation ([noequipset:PvP]). Pairs with the
conditional /equipset command: swap with one, branch on the other.
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.
Read a SPECIFIC power slot (Enum.PowerType) as a percentage via ClassicAPI
UnitPower/UnitPowerMax, rather than the unit's primary power. Adds mana/
mymana, rage/myrage, energy/myenergy (no-prefix = target, @unit-overridable;
my = player), each supporting operators and multi-comparison like [power]/
[mypower]. Works cross-form (druid mana in Cat) and cross-unit -- the
standout being [@target,mana:<15] to catch a caster near OOM. A unit with no
such pool (max <= 0) fails rather than reading 0%. Registered in the macro
error checker (needsArgs + operator hint); VALID_CONDITIONALS auto-populates
from Keywords.
ValidateHpLost now gets the health deficit in one call through
CleveRoids.ClassicAPI.UnitHealthMissing, mirroring how ValidatePowerLost
uses UnitPowerMissing, instead of hand-computing max - current via
NampowerAPI.
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).
Migrate the healing/*_power/spell_power conditionals off Nampower's
GetSpellPower to ClassicAPI GetSpellBonusDamage(school) / GetSpellBonusHealing.
Both read the same client field, so the per-school damage values are identical
while dropping the Nampower v2.31 gate.
Fixes healing/healingpower: GetSpellPower returns no healing value, so they were
returning the 2nd school (holy spell damage) as a stand-in for +healing.
GetSpellBonusHealing returns the real (derived) +healing.
Prune the now-unused Nampower GetSpellPower wrapper, feature-table entry, and
feature flag.
Replace numeric GetNumPartyMembers/GetNumRaidMembers checks with IsInGroup() and IsInRaid() in Conditionals.lua for the group and nogroup conditionals.
Refactor mouseover extensions to split initialization into two phases: OnLoad (empty) and OnAddOnLoad (actual setup). This ensures hooks are only registered when the target addon's globals are defined. Add guard clauses to check for required functions before hooking. Also remove Cursive's premature initialization attempt.
Add a GetSpellSchool bleed check (Priority 0.5) backed by DBC SpellMechanic
data instead of damage-event learning / name patterns:
- GetSpellMechanicByID == 15 catches spell-level bleeds (Garrote, Rupture,
Rend, Rip, Pounce, Deep Wounds).
- New ClassicAPI GetSpellEffectMechanics catches effect-level bleeds that the
spell-level field misses -- Rake is spell-level 0 with EffectMechanic[2]=15.
Wrap C_Spell.GetSpellEffectMechanics in ClassicAPI.lua (nil-guarded so older
builds fall back to spell-level only). Verified against the client Spell.dbc.
Also drop Hemorrhage from the bleed name-pattern fallback: the DBC gives it no
bleed mechanic (physical damage), so bleed-immune mobs don't resist it -- it was
a false positive. Existing learning / split-damage / patterns remain as fallback.
Replace hand-rolled ADDON_LOADED/PLAYER_LOGIN handlers with
EventUtil.ContinueOnAddOnLoaded / ContinueOnPlayerLogin, which fire
immediately if the event already happened -- removing the "we loaded before
the target addon and missed its ADDON_LOADED" workarounds.
- pfUI compat: ContinueOnAddOnLoaded("pfUI") + ContinueOnPlayerLogin; drops
the missed-event fallback (login path still re-runs SetupCompatibility).
- MacroErrorUI / MacroLengthWarn: ContinueOnAddOnLoaded("Blizzard_MacroUI"),
folding their manual "already loaded" checks.
- 9 Mouseover extensions (ag_UnitFrames, CT_RaidAssist, CT_UnitFrames,
DiscordUnitFrames, Grid, NotGrid, Cursive, sRaidFrames, PerfectRaid):
ContinueOnAddOnLoaded("<AddonName>", OnLoad). Since immediate-fire passes no
event args, the old `arg1 == "X"` checks are replaced by the addon-name gate
(global guards kept where present); also removes the buggy
UnregisterEvent("ADDON_LOADED", "Onload") no-ops that never fired.
Addon names match file names; drops support for renamed folders (e.g. -master).
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.
Migrate the link-scrape-then-discard sites to direct ClassicAPI reads:
- IndexEquippedItems / IndexEquipSlot: GetInventoryItemID for the id
(GetItemInfo still supplies the stored base link/texture)
- IndexItems bag+equip scans: GetContainerItemID/GetInventoryItemID +
C_Item.GetItemName (decorated) for the dedup fast-path; drop the now
dead GetContainerItemLink/GetInventoryItemLink locals
- GetItemFast cache validation: alloc-free id compare via
GetInventoryItemID/GetContainerItemID, decorated-name compare otherwise
- IsItemEquipped: GetInventoryItemID + C_Item.GetItemName
Left untouched: makeInventoryItem/makeBagItem and the GetItem/GetItemFast
scan loops, which build a real link that is stored on the item and later
fed to GameTooltip:SetHyperlink.
Note: the equipped-scan loops now start at slot 1 (GetInventoryItemID
requires slot>=1), so the ammo slot (0) is no longer walked by these
indexers -- ammo is not referenced by name anywhere in the addon.
Replace GetInventoryItemLink/GetContainerItemLink link-scraping with
direct ClassicAPI reads across the item conditionals:
- [equipped] cache build: GetInventoryItemID + C_Item.GetItemName
(decorated name replaces the old bracket-name / GetItemInfo two-step)
- slot-number -> item name resolves ([cd], [usable]/[nousable]):
C_Item.GetItemName location form
- HasItem / GetItemCooldown substring fallbacks: match the decorated
name instead of the raw link string
- numeric-slot presence check: GetInventoryItemID
Suffix decoration is preserved via the location form, so partial/
suffixed [equipped:...] matches behave as before.
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).
Macros with no chosen icon (e.g. "#showtooltip Shoot") display Blizzard's
default question mark in the macro UI. After each MacroFrame_Update, swap
that placeholder for the icon the action bar would show, resolved from the
macro's #showtooltip/first action via GetMacroByIndex.
- Only replaces icons that are currently the question mark, so user-chosen
icons are never touched.
- Covers both the list buttons (MacroButtonNIcon) and the selected-macro
detail icon.
- Purely cosmetic: never changes the saved icon; Blizzard repaints the
default on the next refresh if resolution fails or the macro changes.
- Installed in MacroErrorUI's existing hook path (gated on Blizzard_MacroUI
load and the SuperMacro guard).
Item-set membership and set info now read ItemSet.dbc directly through
ClassicAPI instead of the nampower/Reliquary glue, so set features no
longer require the Reliquary DLL to be installed.
- ClassicAPI.lua: add GetItemSetIDByID and GetItemSetInfo wrappers.
- Utility.lua: repoint ResolveSetItems, CountEquippedSetItemsBySetId,
GetEquippedItemSetInfo, and GetEquippedSetPieceCount at the ClassicAPI
accessors. Set-name matching now uses the DBC name (localized; identical
to the old enUS path on English clients).
- NampowerAPI.lua: remove the now-unused GetItemSet/GetItemSetId/
GetItemSetItems/GetItemSetBonuses, the dead GetSpellEffectRadius (no
callers, only radius source was Reliquary), and the orphaned RQ_SafeCall.
- Init.lua: drop hasReliquary detection and its startup feature line.
- Conditionals.lua: update the [set:] comment (no longer Reliquary-gated).
HasWeaponEquipped matched the localized GetItemInfo subtype string, which
required a hand-maintained translation of all 13 weapon/shield subtypes per
locale plus brittle "^Fist"/last-word string parsing.
Use the locale-independent class/subclass IDs from C_Item.GetItemInfoInstant
instead:
- Init.lua: WeaponTypeNames entries now carry numeric class + subClass set
(Axes/Swords/Maces span their 1H+2H subclasses; Shields are armor class 4).
- Conditionals.lua: HasWeaponEquipped resolves the slot's itemID via
ClassicAPI GetInventoryItemID, then compares classID/subClassID -- no link
parsing, no string munging.
- Localization.lua: drop the now-unused weapon-subtype strings from every
locale block (112 lines).
Remove localization data with no consumers anywhere in the addon:
- ItemTypes tables (Consumable/Reagent/Projectile/Trade Goods) - unused
- SpellRank patterns - unused (the live GetSpellRank is a BuffLib method)
- Spells keys Revenge, Overpower, Riposte, Surprise Attack, Lacerate,
Baited Shot, Counterattack, Arcane Surge - unused; only Shadowform,
Stealth, Prowl, Shadowmeld are referenced.
133 lines removed across all locale blocks; no behavior change.
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.
Add ClassicAPI.IsSwimming wrapper and route [swimming]/[noswimming] and
the swim/noswim aliases through it. Removes the Nampower v2.36
hasPlayerIsSwimming version gate and the "requires Nampower 2.36"
warning (ClassicAPI's IsSwimming is always available). Same semantics.
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.
- CancelAura: cancel matched buffs through ClassicAPI's
C_Spell.CancelSpellByID instead of nampower CancelPlayerAuraSpellId
and the legacy slot-based CancelPlayerBuff + tooltip-scan path. The
buff scan (SuperWoW GetPlayerBuffID / nampower raw GetPlayerAuraDuration
+ overflow tracking) and the boolean return are preserved, so the
~spell cancel-vs-cast toggle still works. CancelSpellByName isn't usable
here because it reports no match.
- Focus: GetFocusUnitId no longer warns when no focus is set. In a
fallback macro ([@focus] as one alternative) that's a normal state;
the clause now silently falls through like any unresolved @unit.
Dropped the unused warn parameter and updated both callers.
- ScheduleTimer now wraps C_Timer.After, removing the dedicated timer
frame + per-frame OnUpdate loop + timers queue.
- Aura-tracking cleanup -> C_Timer.NewTicker(5) (_auraCleanupTicker);
drop the UnitXP arm, the named global CleveRoids_AuraTrackingCleanupTimer,
and the inline "not hasUnitXP" fallback in OnAuraCastOther.
- Libdebuff cleanup -> C_Timer.NewTicker(30) (_cleanupTicker); drop the
UnitXP arm and CleveRoids_LibDebuffCleanupTimer.
- Shutdown cancels the tickers instead of UnitXP timer disarm.
Cleanup now always runs (was gated on hasUnitXP) and no longer depends
on UnitXP threaded timers or named global callbacks. Tickers self-guard
on isShuttingDown and are cancelled on shutdown.
Document the audit results in the backlog: the do-not-migrate traps
(C_UnitAuras expirationTime player-only, GetShapeshiftFormID != bar
index, DBC cast time base-only, UnitXP range superiority, nampower
FindPlayerItemSlot, [known]/[usable] richer than ClassicAPI), the wins
already done, and the marginal follow-ups. Also correct the stale
"graceful fallback" cross-cutting note to the mandatory/no-fallbacks
policy.
ClassicAPI exposes a native "focus" unit token (set via its own /focus
/ FOCUSTARGET keybind), accepted by every UnitX call.
- GetFocusUnitId falls back to the native "focus" token after the pfUI
emulated-focus check, so @focus / [cond:focus] / focus range checks
now work for non-pfUI users (previously focus only worked via pfUI).
- TryTargetFocus uses TargetUnit("focus") for an exact switch before the
fragile name-based TargetByName path.
- Add [focus]/[nofocus] conditionals (GetFocusUnitId ~= nil), covering
both pfUI and native focus; registered as boolean conditionals.
No addon /focus command is needed: ClassicAPI's companion addon already
registers /focus and /clearfocus.
- GetItemCooldownCached: resolve via ClassicAPI GetItemCooldown (by item
id/name) instead of FindItemLocation + nampower GetItemIdCooldown +
link parsing; equipment slots (1-19) still query the inventory slot.
- [stealth]/[nostealth] and [stl]/[nostl], and the Rogue branch of
GetCurrentShapeshiftIndex, now use ClassicAPI IsStealthed() (covers
Rogue Stealth + Druid Prowl) instead of class-gated localized buff
lookups. Note: [nostealth] is now true for non-stealth classes.
- CountEnemiesMatching nameplate scan uses C_NamePlate.GetNamePlateGUIDs()
instead of walking WorldFrame children and extracting GUIDs via
pcall(frame.GetName).
- Add ClassicAPI.IsStealthed / GetNamePlateGUIDs wrappers.
Resolve a slot's macro by its Blizzard macro index via ClassicAPI's
GetActionInfo instead of GetActionText -> name -> GetMacro, so action-bar
macros no longer depend on the macro name.
- Core.lua: GetAction uses GetActionInfo(slot) -> macro index ->
GetMacroByIndex; falls back to the name path for SuperMacro (no index).
ParseMacro split into a shared BuildMacro + ParseMacroByIndex (caches by
index) + ParseMacro(name). Added GetMacroByIndex. The macro cache is now
keyed by index for action-bar macros, so blank/duplicate names no longer
collide. GetMacroIndexByName remains only for genuine name references
({MacroName}, /runmacro, /macrocheck).
- MacroErrorUI.lua: remove the now-obsolete macro-name warnings (duplicate,
blank, spell-conflict, item-conflict) from both the live editor validation
and the on-close report; delete the unused name-set helpers.
- README: update the known-issue to note only name-referenced macros need
unique names.
Replace the three duplicated GetActionText -> GetMacroIndexByName ->
GetMacroInfo blocks in the GetActionTexture hook with a single
GetSlotMacroTexture helper that gets the macro slot directly from
ClassicAPI's GetActionInfo. More robust (no dependence on macro-name
lookups) and DRYs the fallback logic.
GetActionInfo:
- Add ClassicAPI.GetActionInfo wrapper; rewrite GetActionButtonInfo
(Generic.lua) to use it and resolve names via GetSpellRecField /
GetItem / GetMacroInfo, replacing the per-slot GameTooltip scan +
texture heuristic. Powers reactive-ability slot detection.
- Delete the dead SuperWoW-GetActionText copy of GetActionButtonInfo.
Spell IDs in the index:
- IndexSpells now uses ClassicAPI GetSpellInfo(slot, bookType), whose
10th return is the spellID, so every Spells entry carries .id (one
call also replaces GetSpellName + GetSpellTexture).
Dynamic pfUI action-button icons (pairs with the pfUI fork change):
- GameTooltip.SetAction hook renders spells via ClassicAPI
SetSpellByID(spell.id) instead of SetSpell(spellSlot, bookType) +
manual rank text. Items stay location-based (instance data).
- Drop the redundant GetActionSpellSlot -> GetSpellCooldown cooldown
shim in the pfUI handler; pfUI now routes through the hooked
GetActionCooldown when it defers macro scanning to us.
ClassicAPI is now required, not optional. The load-time requirement
check warns when it's missing (Core.lua), the same as Nampower/UnitXP,
and ClassicAPI-backed code calls the API directly with no fallbacks.
- ClassicAPI.lua: drop the HasUnitAuras/HasUnitSpeed/HasIsFalling
capability gates and pcall hedging; wrappers call C_UnitAuras /
GetUnitSpeed / IsFalling directly. Keep IsAvailable (used by the
requirement check) and the version helpers.
- [moving]: ClassicAPI GetUnitSpeed is the only source. Removed the
MonkeySpeed integration, the Nampower PlayerIsMoving branch, and the
100 Hz position-tracking buffer in Core.lua's OnUpdate (with its
POS_TRACK_INTERVAL constant and UnitPosition upvalue). Removed the
now-dead speed==nil guards and WarnNoSpeedSource.
- README: ClassicAPI moved to Required; MonkeySpeed dropped from
Supported Addons.
- docs/CLASSICAPI-TODO.md: record the mandatory/no-fallbacks policy.
List ClassicAPI in the Requirements table as Optional — it enables the
dispel-type conditionals ([magic], [curse], [disease], [poison],
[dispellable], [magicbuff]), which degrade gracefully without it.
Introduce ClassicAPI.lua, a feature-detection layer for the ClassicAPI
client mod that mirrors NampowerAPI's HasMinimumVersion pattern
(IsAvailable / HasMinimumVersion / HasUnitAuras). UnitHasDispelType
scans a unit's auras via C_UnitAuras and reports the dispel type,
scanning debuffs or buffs based on a helpful flag.
Add conditionals backed by it:
- Debuff side (defensive cleanse): [magic] [curse] [disease] [poison]
[dispellable] + negations
- Buff side (offensive dispel/strip): [magicbuff] [dispellablebuff]
+ negations
All are no-arg booleans, default to @target (honor @unit), and degrade
gracefully without ClassicAPI: positive checks return false, negated
return true, since vanilla cannot read aura dispel types. The existing
ValidateAura path is untouched. MacroErrorChecker auto-accepts them via
the Keywords table; they're registered in BOOLEAN_CONDITIONALS so they
parse as flags. Loaded after NampowerAPI in the TOC.
The wiki lives on the brues-code fork; update all 10 Full Documentation
links from jrc13245 to brues-code. SP_SwingTimer download links (a
separate project) are left untouched.
Document opportunities to use ClassicAPI (modern C_* API backport for
the 1.12 client) in the addon: C_UnitAuras for target aura/dispel-type
conditionals, GetWeaponEnchantInfo enchant IDs for poison/imbue checks,
GetUnitSpeed to drop the MonkeySpeed dependency, plus C_Spell, C_Timer,
UnitGUID, native focus, and Tier-3 items.
Each entry cites API.md line refs and the addon files it touches, and
notes the cross-cutting requirement to feature-detect with fallback
(mirroring the nampower HasMinimumVersion pattern).
The macro syntax checker validated commands against a hardcoded
VALID_COMMANDS table, so any command not on the list (e.g. /dump) was
flagged "Unknown command" even when the client/addon accepted it.
Add CleveRoids.IsRegisteredCommand: scans SlashCmdList and its
SLASH_<KEY>N globals into a cache (rebuilt on PLAYER_ENTERING_WORLD,
lazy-built on first use) so any command registered the addon way is
accepted automatically. Wire it into the validator alongside the
hardcoded table and the user whitelist.
Trim now-redundant hardcoded entries: the third-party addon block
(/aux, /rinse, /cursive, ...) and this addon's own self-registering
commands. Keep only commands NOT in SlashCmdList: Blizzard builtins
(/cast, /target, /run, ...), /focus (pfUI, only present when loaded),
and the chat/social/emote builtins FrameXML handles internally.