feat(action): C_ActionBar.GetSpell

C_ActionBar.GetSpell(actionID) returns the spell an action-bar slot
casts, or 0. It is the engine's own slot resolver (0x004E5A50), which
the cooldown, usable and range readers already go through: a spell
action is its spell, a macro is its cached primary spell (+0x564, the
one #showtooltip sets), and an item-by-ID action is the first on-use
spell in its item cache record. An empty slot, a slot outside 1..120,
an equipment set or a macro whose macro is gone gives 0. The spellbook
flag the resolver also hands back is not part of the answer.
This commit is contained in:
Brues
2026-11-03 00:16:02 -06:00
parent 426f6b6ec9
commit 5f94fb98bd
2 changed files with 57 additions and 0 deletions
+22
View File
@@ -15,6 +15,7 @@ build instructions.
- [`HasBonusActionBar()`](#hasbonusactionbar)
- [`C_ActionBar.ShouldShowKeyring()`](#c_actionbarshouldshowkeyring)
- [`C_ActionBar.EnableActionRangeCheck(slot, enable)`](#c_actionbarenableactionrangecheckslot-enable)
- [`C_ActionBar.GetSpell(actionID)`](#c_actionbargetspellactionid)
- [AddOns](#addons)
- [`C_AddOns.GetAddOnName(indexOrName)`](#c_addonsgetaddonnameindexorname)
@@ -1341,6 +1342,27 @@ end)
C_ActionBar.EnableActionRangeCheck(1, true)
```
### `C_ActionBar.GetSpell(actionID)`
Returns the ID of the spell that a 1-based action-bar slot casts. The
answer depends on what the slot holds:
- A spell gives its own spell ID.
- A macro gives the spell that its button shows. The button uses this
spell for its cooldown, usable state and range.
- An item gives the spell that the item casts when you use it.
If the slot is empty, has no spell, or is not in the range 1 to 120, the
function returns `0`. If the client does not have the data of an item yet,
the function also returns `0` for that item.
```lua
local actionType, id = GetActionInfo(1)
if actionType == "item" then
id = C_ActionBar.GetSpell(1)
end
```
## AddOns
`C_AddOns.*` getters that splat the
+35
View File
@@ -38,6 +38,14 @@
// through to an empty return. There is no bag-item tag: items are always
// by-itemID (0x80), and an earlier reading of the 0x40 "miss" as a bag item
// was wrong.
//
// `C_ActionBar.GetSpell(slot)` — the spell the slot stands for, 0 for none.
// It is the engine's own resolver (FUN_ACTION_SLOT_TO_SPELL), which every
// action reader (cooldown, usable, range, current) goes through: a spell
// action is its spell, a macro is its cached primary spell, and an item is
// its first on-use spell — retail's ActionButton.lua calls GetSpell for
// exactly that item case. The spellbook flag it also hands back is not part
// of the answer.
#include "Game.h"
#include "Offsets.h"
@@ -126,10 +134,37 @@ int __fastcall Script_GetActionInfo(void *L) {
return 0;
}
int __fastcall Script_GetSpell(void *L) {
if (!Game::Lua::IsNumber(L, 1)) {
Game::Lua::Error(L, "Usage: C_ActionBar.GetSpell(actionID)");
return 0;
}
// The resolver bounds-checks the slot itself and answers 0 outside it.
const int slot0 = static_cast<int>(Game::Lua::ToNumber(L, 1)) - 1;
uint32_t petFlag = 0;
const uint32_t spellID = reinterpret_cast<SlotToSpell_t>(
static_cast<uintptr_t>(Offsets::FUN_ACTION_SLOT_TO_SPELL))(
static_cast<uint32_t>(slot0), &petFlag);
Game::Lua::PushNumber(L, static_cast<double>(spellID));
return 1;
}
const Game::Doc::Field kGetSpellArgs[] = {
Game::Doc::Req("actionID", "luaIndex"),
};
const Game::Doc::Field kGetSpellRets[] = {
Game::Doc::Req("spellID", "number"),
};
const Game::Doc::Function kGetSpellDoc{
"The spell an action stands for: a spell's own ID, a macro's spell, or an item's "
"on-use spell; 0 when there is none.",
kGetSpellArgs, kGetSpellRets};
} // namespace
static void RegisterLuaFunctions() {
Game::Lua::RegisterGlobalFunction("GetActionInfo", &Script_GetActionInfo);
Game::Lua::RegisterTableFunction("C_ActionBar", "GetSpell", &Script_GetSpell, &kGetSpellDoc);
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};