-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInfo.cpp
More file actions
241 lines (217 loc) · 10.4 KB
/
Info.cpp
File metadata and controls
241 lines (217 loc) · 10.4 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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// 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 "item/Arg.h"
#include "item/Data.h"
#include "item/ID.h"
#include "item/Location.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
namespace Item::Info {
using GetItemRecord_t = const uint8_t *(__thiscall *)(void *cache, uint32_t itemID,
const uint64_t *guid, void *callback,
void *userData, bool requestIfMissing);
static const char *PushedOrEmpty(const char *s) { return (s != nullptr && s[0] != 0) ? s : ""; }
static int CurrentLocaleIndex() { return *reinterpret_cast<int *>(Offsets::VAR_LOCALE_INDEX); }
static const uint8_t *FetchItemRecord(uint32_t itemID) {
auto fn = reinterpret_cast<GetItemRecord_t>(Offsets::FUN_DBCACHE_ITEMSTATS_GET_RECORD);
auto *cache = reinterpret_cast<void *>(Offsets::VAR_ITEMDB_CACHE);
const uint64_t zeroGuid = 0;
return fn(cache, itemID, &zeroGuid, nullptr, nullptr, false);
}
static const char *LookupItemClassName(uint32_t classID) {
return PushedOrEmpty(DBC::LocalizedField(
Offsets::VAR_ITEMCLASS_RECORDS, Offsets::VAR_ITEMCLASS_COUNT,
classID, Offsets::OFF_ITEMCLASS_NAMES));
}
static const char *LookupItemSubClassName(uint32_t classID, uint32_t subClassID) {
const int count = *reinterpret_cast<int *>(Offsets::VAR_ITEMSUBCLASS_COUNT);
if (count <= 0)
return "";
auto *records = *reinterpret_cast<const uint8_t *const *>(Offsets::VAR_ITEMSUBCLASS_RECORDS);
if (records == nullptr)
return "";
const int locale = CurrentLocaleIndex();
for (int i = 0; i < count; i++) {
const uint8_t *record = records + i * Offsets::OFF_ITEMSUBCLASS_RECORD_STRIDE;
if (*reinterpret_cast<const uint32_t *>(record + 0x00) != classID)
continue;
if (*reinterpret_cast<const uint32_t *>(record + 0x04) != subClassID)
continue;
// Mirror Script_GetItemInfo's fallback chain at 0x48E311..0x48E32C:
// try the verbose name first, fall back to the short name.
const char *verbose = *reinterpret_cast<const char *const *>(
record + Offsets::OFF_ITEMSUBCLASS_DISPLAY_NAME + locale * 4);
if (verbose != nullptr && verbose[0] != 0)
return verbose;
const char *shortName = *reinterpret_cast<const char *const *>(
record + Offsets::OFF_ITEMSUBCLASS_NAME + locale * 4);
return PushedOrEmpty(shortName);
}
return "";
}
static const char *LookupInvType(uint32_t invType) {
if (invType > Offsets::INVTYPE_TABLE_MAX_INDEX)
return "";
auto **table = reinterpret_cast<const char **>(Offsets::VAR_INVTYPE_STRING_TABLE);
return PushedOrEmpty(table[invType]);
}
static bool BuildIconPath(uint32_t displayInfoID, char *out, size_t outSize) {
if (out == nullptr || outSize == 0)
return false;
out[0] = 0;
const char *iconName = DBC::StringField(
Offsets::VAR_ITEMDISPLAYINFO_RECORDS, Offsets::VAR_ITEMDISPLAYINFO_COUNT,
displayInfoID, Offsets::OFF_ITEMDISPLAYINFO_ICON);
if (iconName == nullptr)
return false;
snprintf(out, outSize, "Interface\\Icons\\%s", iconName);
return true;
}
static int __fastcall Script_GetItemInfoInstant(void *L) {
const int itemID = Item::Arg::ResolveItemID(L, 1);
if (itemID <= 0)
return 0;
const uint8_t *record = FetchItemRecord(static_cast<uint32_t>(itemID));
if (record == nullptr)
return 0;
const uint32_t classID =
*reinterpret_cast<const uint32_t *>(record + Offsets::OFF_ITEMSTATS_CLASS);
const uint32_t subClassID =
*reinterpret_cast<const uint32_t *>(record + Offsets::OFF_ITEMSTATS_SUBCLASS);
const uint32_t displayInfoID =
*reinterpret_cast<const uint32_t *>(record + Offsets::OFF_ITEMSTATS_DISPLAY_INFO_ID);
const uint32_t invType =
*reinterpret_cast<const uint32_t *>(record + Offsets::OFF_ITEMSTATS_INVENTORY_TYPE);
char iconPath[260];
if (!BuildIconPath(displayInfoID, iconPath, sizeof(iconPath)))
iconPath[0] = 0;
Game::Lua::PushNumber(L, static_cast<double>(itemID));
Game::Lua::PushString(L, LookupItemClassName(classID));
Game::Lua::PushString(L, LookupItemSubClassName(classID, subClassID));
Game::Lua::PushString(L, LookupInvType(invType));
Game::Lua::PushString(L, iconPath);
Game::Lua::PushNumber(L, static_cast<double>(classID));
Game::Lua::PushNumber(L, static_cast<double>(subClassID));
return 7;
}
// Pushes the icon path for `itemID` and returns 1, or returns 0 (= nil
// to Lua) if the item isn't cached, the displayInfoID is missing, or
// the icon record's path slot is empty. Shared by all three
// icon-accessor entry points.
static int PushIconForItemID(void *L, int itemID) {
if (itemID <= 0)
return 0;
const uint8_t *record = FetchItemRecord(static_cast<uint32_t>(itemID));
if (record == nullptr)
return 0;
const uint32_t displayInfoID = *reinterpret_cast<const uint32_t *>(
record + Offsets::OFF_ITEMSTATS_DISPLAY_INFO_ID);
char iconPath[260];
if (!BuildIconPath(displayInfoID, iconPath, sizeof(iconPath)))
return 0;
Game::Lua::PushString(L, iconPath);
return 1;
}
// `GetItemIcon(itemID)` — global, takes a numeric itemID only. Modern
// WoW returns a fileID:number; we surface the icon path string since
// 1.12 has no fileID system. Direct passthrough to
// `texture:SetTexture(...)`.
static int __fastcall Script_GetItemIcon(void *L) {
if (!Game::Lua::IsNumber(L, 1)) {
Game::Lua::Error(L, "Usage: GetItemIcon(itemID)");
return 0;
}
return PushIconForItemID(L, static_cast<int>(Game::Lua::ToNumber(L, 1)));
}
// `C_Item.GetItemIcon(itemLocation)` — table-shape ItemLocation form.
// Routes through `Item::Location::Resolve` → CGItem → itemID, then the
// same cache lookup as the other variants.
static int __fastcall Script_C_Item_GetItemIcon(void *L) {
if (!Item::Location::IsLocationArg(L, 1)) {
Game::Lua::Error(L, "Usage: C_Item.GetItemIcon(itemLocation)");
return 0;
}
const int itemID = Item::ID::FromCGItem(Item::Location::Resolve(L, 1));
return PushIconForItemID(L, itemID);
}
// `C_Item.GetItemIconByID(itemInfo)` — accepts numeric itemID or
// `"item:NN"` string (full chat links work too via the same parser
// `GetItemInfoInstant` uses).
static int __fastcall Script_C_Item_GetItemIconByID(void *L) {
return PushIconForItemID(L, Item::Arg::ResolveItemID(L, 1));
}
// Convert 1.12's raw `BagFamily` ID to the modern bitmask encoding.
// Vanilla stores `arrow=1, bullet=2, soul shard=3, herb=6, ...`; Wrath
// flipped to `1 << (ID-1)` (`arrow=0x1, bullet=0x2, soul shard=0x4,
// herb=0x20, ...`) and addons backported from Wrath+ expect the
// bitmask. We always surface the modern shape so callers don't have to
// branch on client version. See `Offsets::OFF_ITEMSTATS_BAG_FAMILY`
// for the empirical verification.
uint32_t BagFamilyIdToBitmask(uint32_t rawId) {
return (rawId == 0) ? 0 : (1u << (rawId - 1));
}
// `C_Item.GetItemFamily(item)` — returns the BagFamily bitmask for an
// item (modern encoding: `1 << (ID-1)`), or `nil` if the item isn't
// in the cache. Used by auto-routing addons that decide which
// specialty bag a looted item should land in.
//
// Accepts a numeric `itemID` or a string containing `"item:NNN"` (full
// chat links work) — same parser as `GetItemInfoInstant`.
//
// **Auto-warmup on cache miss.** Returns nil for uncached items but
// kicks off the cache fill in the background, so a follow-up call
// after `GET_ITEM_INFO_RECEIVED` lands the value. Mirrors the
// observed 1.15 behavior (nil first, value after retry) — the engine
// itself triggers the load. We diverge from 1.15 in one detail: we
// fire `GET_ITEM_INFO_RECEIVED` when the data arrives (matching our
// other implicit-warmup paths like `GetItemInfo` and `SetItemByID`),
// whereas 1.15's `GetItemFamily` is a silent fill. Consistency
// within our DLL's event surface > faithful 1.15 mimicry — addons
// that already listen for the event get notified for free.
//
// Modern WoW returns 0 for items the cache lookup fails on; we return
// nil so callers can distinguish "item not cached" from "item exists
// but has family 0 (general-purpose)". Both are safe to treat as "no
// preference" but keeping them distinct helps debugging.
//
// 1.12 vanilla server data sparsely populates the field for **bags**
// themselves (only quivers seem to carry it on Turtle WoW). Individual
// loot items do carry the field reliably (arrows, bullets, shards,
// herbs all return their proper raw IDs).
static int __fastcall Script_C_Item_GetItemFamily(void *L) {
const int itemID = Item::Arg::ResolveItemID(L, 1);
if (itemID <= 0)
return 0;
const uint8_t *record = FetchItemRecord(static_cast<uint32_t>(itemID));
if (record == nullptr) {
Item::Data::WarmCache(static_cast<uint32_t>(itemID));
return 0;
}
const uint32_t rawId = *reinterpret_cast<const uint32_t *>(
record + Offsets::OFF_ITEMSTATS_BAG_FAMILY);
Game::Lua::PushNumber(L, static_cast<double>(BagFamilyIdToBitmask(rawId)));
return 1;
}
static void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_Item", "GetItemInfoInstant", &Script_GetItemInfoInstant);
Game::Lua::RegisterGlobalFunction("GetItemIcon", &Script_GetItemIcon);
Game::Lua::RegisterTableFunction("C_Item", "GetItemIcon", &Script_C_Item_GetItemIcon);
Game::Lua::RegisterTableFunction("C_Item", "GetItemIconByID", &Script_C_Item_GetItemIconByID);
Game::Lua::RegisterTableFunction("C_Item", "GetItemFamily", &Script_C_Item_GetItemFamily);
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
} // namespace Item::Info