-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTooltip.cpp
More file actions
155 lines (141 loc) · 6.45 KB
/
Tooltip.cpp
File metadata and controls
155 lines (141 loc) · 6.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// This file is part of ClassicAPI.
//
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
// of the GNU Lesser General Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along with
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
#include "Game.h"
#include "Offsets.h"
#include "dbc/Lookup.h"
#include "spell/Tooltip.h"
#include <cstdint>
namespace Talent::Tooltip {
// Walks every (tab, talent) entry the player has loaded and returns
// the (1-based) tab/idx pair where `TalentEntry+0x00 == talentID`.
// Returns false if the talent isn't in any of the player's tabs —
// 1.12 only loads the local player's class talent data, so other
// classes' talents aren't findable here. Same access pattern as
// `Talent::Info::Script_GetTalentIDByIndex` (#42), inverted.
static bool FindTalentByID(uint32_t talentID, int *outTab, int *outIdx) {
const int tabCount = *reinterpret_cast<const int *>(
static_cast<uintptr_t>(Offsets::VAR_TALENT_TAB_COUNT));
auto *tabs = *reinterpret_cast<const uint8_t *const *const *>(
static_cast<uintptr_t>(Offsets::VAR_TALENT_TAB_INFO_ARRAY));
if (tabs == nullptr)
return false;
for (int t = 1; t <= tabCount; ++t) {
const uint8_t *tabInfo = tabs[t - 1];
if (tabInfo == nullptr)
continue;
const int numTalents = *reinterpret_cast<const int *>(
tabInfo + Offsets::OFF_TABINFO_NUM_TALENTS);
auto *talents = *reinterpret_cast<const uint8_t *const *>(
tabInfo + Offsets::OFF_TABINFO_TALENT_ARRAY);
if (talents == nullptr)
continue;
for (int i = 1; i <= numTalents; ++i) {
const uint8_t *entry =
talents + (i - 1) * Offsets::TALENT_ENTRY_STRIDE;
const uint32_t id = *reinterpret_cast<const uint32_t *>(entry);
if (id == talentID) {
*outTab = t;
*outIdx = i;
return true;
}
}
}
return false;
}
// Looks up the rank-1 spellID for a given talentID via Talent.dbc.
// Returns 0 if the ID is out of range, the slot is unpopulated, or
// the rank-1 SpellRank is missing. Records pointer is at the standard
// DBC class shape (`+0x08` of class instance), indexed directly by
// talentID; sparse — many slots are NULL across classes.
static uint32_t LookupTalentRank1Spell(uint32_t talentID) {
const uint8_t *rec = DBC::Record(
Offsets::VAR_TALENT_DBC_RECORDS, Offsets::VAR_TALENT_DBC_COUNT,
talentID);
if (rec == nullptr)
return 0;
return *reinterpret_cast<const uint32_t *>(rec + Offsets::OFF_TALENT_SPELL_RANK);
}
// `GameTooltip:SetTalentByID(talentID)` — modern method that renders a
// talent tooltip from just a Talent.dbc primary key. The 1.12
// equivalent is `GameTooltip:SetTalent(tabIndex, talentIndex)`, which
// only renders talents from the local player's loaded tabs (one class
// only).
//
// Two-tier resolution:
//
// 1. **Player-class fast path.** Walk the local player's TabInfo
// arrays for matching talentID. If found, dispatch to
// `Script_GameTooltip_SetTalent` (registry slot 34, `0x00535170`)
// with the resolved (tab, idx). Renders the rich talent tooltip
// with rank info, prereqs, "click to learn next rank" — same as
// hovering the talent in the talent UI.
//
// 2. **Cross-class fallback via Talent.dbc.** Look up the talent
// record by ID in the Talent.dbc array at `[VAR_TALENT_DBC_RECORDS]`,
// read the rank-1 spellID at `+0x10`, and dispatch the spell
// tooltip via `Spell::Tooltip::ShowByID` (the same path
// `GameTooltip:SetSpellByID` uses). Renders the spell info —
// cast time, range, mana cost, description — without the talent-
// specific decorations (rank counter, prereq lines), since those
// live in the per-player engine machinery we can't reach
// cross-class.
//
// Modern WoW's `SetTalentByID` shows a richer cross-class tooltip
// (talent name + "Rank 0/N" + spell description). Building that
// requires Lua-level GameTooltip:AddLine work; for now the spell
// tooltip is functionally equivalent for "what does this talent do?"
// queries, which is the dominant use case.
//
// Silent no-op when `talentID` doesn't match any record (Talent.dbc
// is sparse — talent IDs are not contiguous). Errors via `lua_error`
// only on type mismatch (non-table self, non-number talentID).
static int __fastcall Script_GameTooltipSetTalentByID(void *L) {
if (Game::Lua::Type(L, 1) != Game::Lua::TYPE_TABLE) {
Game::Lua::Error(L, "Usage: GameTooltip:SetTalentByID(talentID)");
return 0;
}
if (!Game::Lua::IsNumber(L, 2)) {
Game::Lua::Error(L, "Usage: GameTooltip:SetTalentByID(talentID)");
return 0;
}
const int talentID = static_cast<int>(Game::Lua::ToNumber(L, 2));
if (talentID <= 0)
return 0;
// Tier 1 — player's class. Rich talent tooltip via SetTalent.
int tab = 0, idx = 0;
if (FindTalentByID(static_cast<uint32_t>(talentID), &tab, &idx)) {
Game::Lua::SetTop(L, 1);
Game::Lua::PushNumber(L, static_cast<double>(tab));
Game::Lua::PushNumber(L, static_cast<double>(idx));
using Script_t = int(__fastcall *)(void *L);
auto fn = reinterpret_cast<Script_t>(Offsets::FUN_SCRIPT_GAMETOOLTIP_SET_TALENT);
return fn(L);
}
// Tier 2 — cross-class via Talent.dbc + spell tooltip fallback.
const uint32_t spellID = LookupTalentRank1Spell(static_cast<uint32_t>(talentID));
if (spellID == 0)
return 0;
Spell::Tooltip::ShowByID(L, static_cast<int>(spellID));
return 0;
}
static const Game::Lua::FrameMethodEntry g_methods[] = {
{"SetTalentByID", &Script_GameTooltipSetTalentByID},
};
static void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY),
g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
} // namespace Talent::Tooltip