Skip to content

Commit a40236e

Browse files
committed
spell: add GameTooltip:AddSpellByID(spellID)
Append counterpart to SetSpellByID -- adds a spell's tooltip (name, cast time, cost, range, description) to the current tooltip WITHOUT clearing existing lines, so callers can compose tooltips like AddLine(header) + AddSpellByID(id). BuildSpellTooltip's last arg gates the clear: SetSpellByID passes 0 (clears via FUN_00530050 first); AddSpellByID passes 1 (skips the clear and appends). That append path is the engine's talent "next rank" preview, so it emits a "Next rank:" header instead of the spell name and doesn't stash the displayed spellID -- we overwrite that header line (at the pre-build numLines index) with the real name from Spell.dbc and let GetSpell keep reflecting the base tooltip. Verified in-game.
1 parent 538beda commit a40236e

4 files changed

Lines changed: 103 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
3838
| [FriendList](docs/API.md#friendlist) | `C_FriendList.IsWhoQueryPending`, `C_FriendList.SendWhoQueryByName` |
3939
| [GameObject](docs/API.md#gameobject) | `C_GameObjectInfo.GetGameObjectInfoByID`, `C_GameObjectInfo.RequestLoadGameObjectByID`, `ClosestGameObjectPosition` |
4040
| [Gossip](docs/API.md#gossip) | `C_GossipInfo.CloseGossip`, `C_GossipInfo.GetActiveQuests`, `C_GossipInfo.GetAvailableQuests`, `C_GossipInfo.GetNumActiveQuests`, `C_GossipInfo.GetNumAvailableQuests`, `C_GossipInfo.GetNumOptions`, `C_GossipInfo.GetOptions`, `C_GossipInfo.GetText`, `C_GossipInfo.SelectActiveQuest`, `C_GossipInfo.SelectAvailableQuest`, `C_GossipInfo.SelectOption`, `C_GossipInfo.SelectOptionByIndex` |
41-
| [GameTooltip](docs/API.md#gametooltip) | `GameTooltip:GetGameObject`, `GameTooltip:GetItem`, `GameTooltip:GetOwner`, `GameTooltip:GetSpell`, `GameTooltip:GetUnitGUID`, `GameTooltip:HasGameObject`, `GameTooltip:HasItem`, `GameTooltip:HasSpell`, `GameTooltip:HasUnit`, `GameTooltip:IsEquippedItem`, `GameTooltip:SetEquipmentSet`, `GameTooltip:SetHyperlinkCompareItem`, `GameTooltip:SetInventoryItemByID`, `GameTooltip:SetItemByGUID`, `GameTooltip:SetItemByID`, `GameTooltip:SetSpellByID`, `GameTooltip:SetTalentByID`, `GameTooltip:SetUnitAura`, `OnTooltipSetItem` (script) |
41+
| [GameTooltip](docs/API.md#gametooltip) | `GameTooltip:AddSpellByID`, `GameTooltip:GetGameObject`, `GameTooltip:GetItem`, `GameTooltip:GetOwner`, `GameTooltip:GetSpell`, `GameTooltip:GetUnitGUID`, `GameTooltip:HasGameObject`, `GameTooltip:HasItem`, `GameTooltip:HasSpell`, `GameTooltip:HasUnit`, `GameTooltip:IsEquippedItem`, `GameTooltip:SetEquipmentSet`, `GameTooltip:SetHyperlinkCompareItem`, `GameTooltip:SetInventoryItemByID`, `GameTooltip:SetItemByGUID`, `GameTooltip:SetItemByID`, `GameTooltip:SetSpellByID`, `GameTooltip:SetTalentByID`, `GameTooltip:SetUnitAura`, `OnTooltipSetItem` (script) |
4242
| [Hooks](docs/API.md#hooks) | `hooksecurefunc` |
4343
| [Input](docs/API.md#input) | `GetMouseButtonClicked`, `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsMouseButtonDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
4444
| [Instance](docs/API.md#instance) | `GetInstanceInfo` |

docs/API.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ build instructions.
161161

162162
- [GameTooltip](#gametooltip)
163163
- [`GameTooltip:SetSpellByID(spellID)`](#gametooltipsetspellbyidspellid)
164+
- [`GameTooltip:AddSpellByID(spellID)`](#gametooltipaddspellbyidspellid)
164165
- [`GameTooltip:SetTalentByID(talentID)`](#gametooltipsettalentbyidtalentid)
165166
- [`GameTooltip:SetInventoryItemByID(itemID)`](#gametooltipsetinventoryitembyiditemid)
166167
- [`GameTooltip:SetHyperlinkCompareItem("itemLink" [, offset, shiftButton, comparisonTooltip])`](#gametooltipsethyperlinkcompareitemitemlink--offset-shiftbutton-comparisontooltip)
@@ -3787,6 +3788,32 @@ GameTooltip:SetSpellByID(133) -- Fireball
37873788
GameTooltip:Show()
37883789
```
37893790

3791+
### `GameTooltip:AddSpellByID(spellID)`
3792+
3793+
The append counterpart to [`SetSpellByID`](#gametooltipsetspellbyidspellid):
3794+
adds a spell's full tooltip (name, cast time, cost, range, description) to
3795+
the *current* tooltip **without clearing** the existing lines. Works for any
3796+
`spellID`, learned or not. Lets you compose tooltips — e.g. a header line
3797+
plus a spell block.
3798+
3799+
```lua
3800+
GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR")
3801+
GameTooltip:AddLine("Rank 3 grants:")
3802+
GameTooltip:AddSpellByID(133) -- Fireball, appended below the header
3803+
GameTooltip:Show()
3804+
```
3805+
3806+
Unlike `SetSpellByID`, this does **not** update what
3807+
[`GetSpell`](#gametooltipgetspell) reports — the appended spell isn't the
3808+
tooltip's "primary" spell, so `GetSpell` keeps reflecting whatever `SetX`
3809+
call (if any) built the base tooltip. Silent no-op for `spellID <= 0` or an
3810+
unknown spell.
3811+
3812+
> Vanilla's tooltip builder only exposes "append" via its internal talent
3813+
> "next rank" preview, which emits a `Next rank:` header instead of the
3814+
> spell name; we overwrite that header line with the real name so the
3815+
> appended block reads normally.
3816+
37903817
### `GameTooltip:GetItem()`
37913818

37923819
Returns `(name, link, itemID)` for whichever item the tooltip is

src/spell/Tooltip.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,71 @@ static int __fastcall Script_GameTooltipSetSpellByID(void *L) {
5555
return 0;
5656
}
5757

58+
// `GameTooltip:AddSpellByID(spellID)` — APPENDS a spell's tooltip to the
59+
// current one instead of clearing+rebuilding (the `SetSpellByID` behavior).
60+
// The natural pair to `SetSpellByID`; lets callers compose tooltips like
61+
// `AddLine(talentName) + AddSpellByID(rankSpellID)`.
62+
//
63+
// `BuildSpellTooltip`'s last arg (`param_7`) gates the clear: `0` calls the
64+
// per-tooltip Clear (`FUN_00530050`) first — that's `SetSpellByID`; non-zero
65+
// SKIPS the clear and appends. The append path is the engine's talent
66+
// "next rank" preview, so it (a) emits a `"\nNext rank:"` header line
67+
// instead of the spell name, and (b) does NOT stash the displayed spellID at
68+
// `+0x39C` (so `GetSpell` keeps reflecting the base tooltip — correct for an
69+
// append). We fix (a) by overwriting that header line — which lands at index
70+
// `numLines`-before-the-build (0-based) — with the real spell name via the
71+
// line pool's left-text FontString. Verified against `FUN_0052E610`.
72+
void AppendByID(void *L, int spellID) {
73+
if (spellID <= 0)
74+
return;
75+
void *tooltipObj = Game::Lua::ResolveObject(L, 1);
76+
if (tooltipObj == nullptr)
77+
return;
78+
auto *tt = static_cast<uint8_t *>(tooltipObj);
79+
80+
const int before = *reinterpret_cast<const int *>(
81+
tt + Offsets::OFF_GAMETOOLTIP_NUM_LINES);
82+
83+
auto BuildSpellTooltip =
84+
reinterpret_cast<BuildSpellTooltip_t>(Offsets::FUN_GAMETOOLTIP_BUILD_SPELL_TOOLTIP);
85+
BuildSpellTooltip(tooltipObj, spellID, 0, 0, 0, 0, 0, /*param_7 (append)=*/1);
86+
87+
const int after = *reinterpret_cast<const int *>(
88+
tt + Offsets::OFF_GAMETOOLTIP_NUM_LINES);
89+
if (after <= before)
90+
return; // nothing appended (bad spellID / missing Spell.dbc record)
91+
92+
const uint8_t *record = Spell::Lookup::RecordForID(spellID);
93+
if (record == nullptr)
94+
return;
95+
const int locale = *reinterpret_cast<const int *>(Offsets::VAR_LOCALE_INDEX);
96+
const char *name = *reinterpret_cast<const char *const *>(
97+
record + OFF_SPELL_NAME + locale * 4);
98+
if (name == nullptr || name[0] == '\0')
99+
return;
100+
101+
// Left-text FontString array: descriptor at +0x324, data ptr at +0x8.
102+
auto **textLeft = *reinterpret_cast<void ***>(
103+
tt + Offsets::OFF_GAMETOOLTIP_TEXTLEFT_DESC + 8);
104+
if (textLeft == nullptr)
105+
return;
106+
void *fs = textLeft[before];
107+
if (fs == nullptr)
108+
return;
109+
using SetText_t = void(__thiscall *)(void *fs, const char *text, int flag);
110+
reinterpret_cast<SetText_t>(Offsets::FUN_FONTSTRING_SET_TEXT)(fs, name, 0);
111+
}
112+
113+
static int __fastcall Script_GameTooltipAddSpellByID(void *L) {
114+
if (Game::Lua::Type(L, 1) != Game::Lua::TYPE_TABLE ||
115+
!Game::Lua::IsNumber(L, 2)) {
116+
Game::Lua::Error(L, "Usage: GameTooltip:AddSpellByID(spellID)");
117+
return 0;
118+
}
119+
AppendByID(L, static_cast<int>(Game::Lua::ToNumber(L, 2)));
120+
return 0;
121+
}
122+
58123
// `GameTooltip:GetSpell()` → (name, rank, spellID) for whichever spell
59124
// the tooltip is currently displaying, or nothing if it isn't showing
60125
// a spell. BuildSpellTooltip writes the spellID to `tooltip+0x39C`;
@@ -126,6 +191,7 @@ static int __fastcall Script_GameTooltipHasSpell(void *L) {
126191

127192
static const Game::Lua::FrameMethodEntry g_methods[] = {
128193
{"SetSpellByID", &Script_GameTooltipSetSpellByID},
194+
{"AddSpellByID", &Script_GameTooltipAddSpellByID},
129195
{"GetSpell", &Script_GameTooltipGetSpell},
130196
{"HasSpell", &Script_GameTooltipHasSpell},
131197
};

src/spell/Tooltip.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,13 @@ namespace Spell::Tooltip {
2626
// so we render the rank-1 spell tooltip instead).
2727
void ShowByID(void *L, int spellID);
2828

29+
// Like `ShowByID` but APPENDS the spell's tooltip to the current one
30+
// (no clear), and overwrites the engine's talent "next rank" header with
31+
// the real spell name. Backs `GameTooltip:AddSpellByID`. Silent no-op if
32+
// `spellID <= 0`, stack[1] isn't a frame object, or the build added no
33+
// lines. Does not stash the displayed spellID (so `GetSpell` still
34+
// reflects the base tooltip). Useful for cross-class talent tooltips
35+
// (`AddLine(talentName) + AppendByID(rankSpellID)`).
36+
void AppendByID(void *L, int spellID);
37+
2938
} // namespace Spell::Tooltip

0 commit comments

Comments
 (0)