item: dress GameTooltip:GetItem() name with random suffix

GameTooltip:GetItem() resolved the CGItem and built the suffixed link via
Item::Link::FromCGItem, but returned the base ItemStats name beside it --
so name and link disagreed ("Iridium Chain" vs "[Iridium Chain of the
Owl]"). The old comment claimed the engine didn't expose the dressed name
as a separate string; it does, via the same builder the link uses.

Extract that builder into a shared Item::Link::NameFromCGItem helper
(next to FromCGItem, wrapping FUN_ITEM_BUILD_INSTANCE_NAME) and use it in
both GetItem and C_Item.GetItemName so the returned name matches the link
and modern behavior. Verified in-game: GetItem() now returns "Iridium
Chain of the Owl" alongside its matching link.

Left the base-name reads that are correct as-is: itemID-only paths (mail,
cursor-by-id, GetItemInfo, equipment-set missing items) have no instance,
and the by-name *matching* in IsEquippedItem / bag lookups compares
against a caller string (a separate semantics question).
This commit is contained in:
Brues
2026-07-15 15:13:52 -05:00
parent aa0279a600
commit e8a9003bf0
4 changed files with 43 additions and 37 deletions
+13
View File
@@ -27,6 +27,9 @@ namespace {
using BuildItemLink_t = const char *(__fastcall *)(const void *cgItem);
using BuildInstanceName_t = void(__thiscall *)(const void *cgItem, char *out,
unsigned outSize);
// Cached ItemStats record lookup. Same `_GetRecord` pattern the rest of
// the codebase uses; passes a noop NULL callback so no SMSG fires for
// uncached items — caller treats "not cached" as a clean failure.
@@ -50,6 +53,16 @@ const char *FromCGItem(const uint8_t *cgItem) {
return fn(cgItem);
}
bool NameFromCGItem(const uint8_t *cgItem, char *out, size_t outSize) {
if (cgItem == nullptr || out == nullptr || outSize == 0)
return false;
out[0] = '\0';
auto fn = reinterpret_cast<BuildInstanceName_t>(
Offsets::FUN_ITEM_BUILD_INSTANCE_NAME);
fn(cgItem, out, static_cast<unsigned>(outSize));
return out[0] != '\0';
}
bool BasicFromItemID(uint32_t itemID, char *out, size_t outSize) {
if (out == nullptr || outSize == 0)
return false;
+11
View File
@@ -29,6 +29,17 @@ namespace Item::Link {
// produce for the same slot.
const char *FromCGItem(const uint8_t *cgItem);
// Writes the item's per-instance decorated *display name* into `out` —
// the same `[Name]` the engine stamps into `FromCGItem`'s link, e.g.
// "Iridium Chain of the Owl" (or the base name when the instance carries
// no random suffix). Reads the suffix ID off the CGItem descriptor
// (`+0x98`) and formats it through the engine's own name builder
// (`FUN_ITEM_BUILD_INSTANCE_NAME`). Returns false (and leaves `out`
// empty) for a null item or an empty build result, so callers can fall
// back to the base ItemStats name. Use whenever a name is returned
// alongside a `FromCGItem` link, so the two agree.
bool NameFromCGItem(const uint8_t *cgItem, char *out, size_t outSize);
// Builds a basic itemID-only hyperlink (`"|cffRRGGBB|Hitem:N:0:0:0|h[Name]|h|r"`)
// into `out` from the cached ItemStats record for `itemID`. No
// enchant / random-suffix decoration — use this when the caller has
+6 -19
View File
@@ -16,6 +16,7 @@
#include "item/Arg.h"
#include "item/Data.h"
#include "item/ID.h"
#include "item/Link.h"
#include "item/Location.h"
#include <cstdint>
@@ -56,27 +57,13 @@ int PushNameForItemID(void *L, int itemID) {
return 1;
}
// Engine's per-instance item-name builder: `__thiscall(CGItem *this,
// char *outBuf, uint outSize)`. Reads the instance's random-suffix ID off
// the descriptor (+0x98) and writes the decorated display name
// ("Ethereum Torque of the Sorcerer"), or the base name when there's no
// suffix. Same builder the item-link builder uses for the bracketed name,
// so this matches `GetItemLink`'s name and modern `C_Item.GetItemName`.
using BuildInstanceName_t = void(__thiscall *)(const void *cgItem, char *out,
unsigned outSize);
// Pushes the suffix-decorated display name for a resolved `CGItem`.
// Returns 0 (pushes nothing) for a null item or an empty build result,
// so the caller can fall back to the base-name-by-itemID path.
// Pushes the suffix-decorated display name for a resolved `CGItem` via
// the shared engine name builder. Returns 0 (pushes nothing) for a null
// item or an empty build result, so the caller can fall back to the
// base-name-by-itemID path.
int PushInstanceName(void *L, const uint8_t *cgItem) {
if (cgItem == nullptr)
return 0;
char buf[128];
buf[0] = '\0';
auto fn = reinterpret_cast<BuildInstanceName_t>(
Offsets::FUN_ITEM_BUILD_INSTANCE_NAME);
fn(cgItem, buf, sizeof(buf));
if (buf[0] == '\0')
if (!Item::Link::NameFromCGItem(cgItem, buf, sizeof(buf)))
return 0;
Game::Lua::PushString(L, buf);
return 1;
+13 -18
View File
@@ -220,24 +220,19 @@ static int __fastcall Script_GameTooltipGetItem(void *L) {
const char *link = Item::Link::FromCGItem(
static_cast<const uint8_t *>(cgItem));
if (link != nullptr && *link != '\0') {
// Engine's link builder also writes the dressed
// (random-suffixed) name into the link's `[Name]`
// slot; pull it out by reading the cached base name
// for the return value. The engine doesn't expose
// the dressed name as a separate string, so we
// return the base name — matches modern semantics
// where (name, link) name is the cached display name
// and the link is the full hyperlink.
const uint8_t *record = PeekItemRecord(static_cast<uint32_t>(itemID));
if (record != nullptr) {
const char *name = *reinterpret_cast<const char *const *>(
record + Offsets::OFF_ITEMSTATS_NAME);
if (name != nullptr && *name != '\0') {
Game::Lua::PushString(L, name);
Game::Lua::PushString(L, link);
Game::Lua::PushNumber(L, static_cast<double>(itemID));
return 3;
}
// Return the dressed (random-suffixed) name the engine
// wrote into the link's `[Name]` slot — built off the same
// CGItem via Item::Link::NameFromCGItem — so the returned
// name agrees with the link ("Iridium Chain of the Owl",
// not the base "Iridium Chain") and matches modern
// GameTooltip:GetItem() on suffixed items.
char name[128];
if (Item::Link::NameFromCGItem(
static_cast<const uint8_t *>(cgItem), name, sizeof(name))) {
Game::Lua::PushString(L, name);
Game::Lua::PushString(L, link);
Game::Lua::PushNumber(L, static_cast<double>(itemID));
return 3;
}
}
}