item: decorate C_Item.GetItemName location form with random suffix

The location form of C_Item.GetItemName points at a real item instance,
so its name should carry random-suffix decoration ("... of the Owl") the
way modern WoW does -- but we were reading m_name[0] off the itemID-keyed
ItemStats_C cache, which only holds the base name.

Resolve the location to its CGItem and build the name via the engine's
own per-instance name builder (FUN_005D8BC0): it reads the suffix ID off
the descriptor (+0x98) and formats base + ItemRandomProperties localized
suffix through the engine's ITEM_SUFFIX_TEMPLATE. This is the same
builder FUN_GAMETOOLTIP_BUILD_ITEM_LINK wraps in brackets, so the output
now matches GetItemLink's bracketed name and modern C_Item.GetItemName
exactly (verified in-game: "Iridium Chain of the Owl"). Falls back to the
base ItemStats name (and warms the cache) when the instance build yields
nothing.

GetItemNameByID is unchanged -- an itemID has no instance, so base name
only is correct.
This commit is contained in:
Brues
2026-07-15 15:06:04 -05:00
parent a754d1ba22
commit aa0279a600
2 changed files with 50 additions and 5 deletions
+12
View File
@@ -287,6 +287,18 @@ enum Offsets {
// the item isn't in the player's bag/equipment.
FUN_GAMETOOLTIP_BUILD_ITEM_LINK = 0x0052AE00,
// CGItem → decorated instance display name. __thiscall(ecx = CGItem *,
// char *outBuf, uint outSize). Reads the instance's random-suffix ID
// off the descriptor (+0x98, gated on the broken flag) and writes the
// plain — uncolored, unbracketed — display name into outBuf:
// "Ethereum Torque of the Sorcerer" when a suffix is present, the base
// ItemStats name otherwise (via the item cache + ItemRandomProperties
// localized suffix at record +0x1c). This is the inner name builder
// FUN_GAMETOOLTIP_BUILD_ITEM_LINK wraps in brackets, so the name it
// produces matches GetItemLink's bracketed name and modern
// C_Item.GetItemName exactly. Inner formatter FUN_005D8B00.
FUN_ITEM_BUILD_INSTANCE_NAME = 0x005D8BC0,
// Engine's inventory swap-and-send. Same primitive
// `Script_EquipCursorItem` (0x00489660) uses after the cursor's
// source location has been resolved. Sends opcode 0x10D
+38 -5
View File
@@ -56,18 +56,51 @@ 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.
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')
return 0;
Game::Lua::PushString(L, buf);
return 1;
}
} // namespace
// `C_Item.GetItemName(itemLocation)` — returns the cached display name
// of the item at the location, or nil for empty / uncached / invalid
// inputs. Reads `m_name[0]` straight off `ItemStats_C` — no engine
// round-trip, no `GetItemInfo` chaining.
// `C_Item.GetItemName(itemLocation)` — returns the display name of the
// item at the location, or nil for empty / uncached / invalid inputs.
// The location points at a real item instance, so the name carries any
// random-suffix decoration ("... of the Sorcerer") — built off the
// CGItem via the engine's own name builder, matching modern WoW and
// `C_Item.GetItemLink`'s bracketed name. Falls back to the base
// `ItemStats_C` name (and warms the cache) if the instance build yields
// nothing.
static int __fastcall Script_C_Item_GetItemName(void *L) {
if (!Item::Location::IsLocationArg(L, 1)) {
Game::Lua::Error(L, "Usage: C_Item.GetItemName(itemLocation)");
return 0;
}
return PushNameForItemID(L, Item::ID::FromCGItem(Item::Location::Resolve(L, 1)));
const uint8_t *cgItem = Item::Location::Resolve(L, 1);
if (int pushed = PushInstanceName(L, cgItem))
return pushed;
return PushNameForItemID(L, Item::ID::FromCGItem(cgItem));
}
// `C_Item.GetItemNameByID(itemInfo)` — returns the cached display name