Skip to content

Commit 444875a

Browse files
committed
Add UnitNameFromGUID(guid) → (name, realm)
Modern backport that reuses GetPlayerInfoByGUID's lookup chain (engine NameCache → persistent on-disk fallback) but pushes only (name, realm) — narrower return shape for callers that only need the name. realm is always "" in vanilla (single-realm, engine doesn't populate per-player realm).
1 parent a7ffe1c commit 444875a

3 files changed

Lines changed: 100 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
4545
| [Map](docs/API.md#map) | `C_Map.GetBestMapForUnit` |
4646
| [MerchantFrame](docs/API.md#merchantframe) | `C_MerchantFrame.GetBuybackItemID`, `C_MerchantFrame.GetItemInfo`, `C_MerchantFrame.GetNumJunkItems`, `C_MerchantFrame.IsMerchantItemRefundable`, `C_MerchantFrame.IsSellAllJunkEnabled`, `C_MerchantFrame.SellAllJunkItems` |
4747
| [NamePlate](docs/API.md#nameplate) | `C_NamePlate.GetNamePlateForGUID`, `C_NamePlate.GetNamePlateForUnit`, `C_NamePlate.GetNamePlateGUIDs`, `C_NamePlate.GetNamePlates` |
48-
| [NameCache](docs/API.md#namecache) | `C_CreatureInfo.GetCreatureID`, `C_PlayerCache.GetPlayerInfoByName`, `C_PlayerCache.IsEnabled`, `C_PlayerCache.IsScanEnabled`, `C_PlayerCache.RememberPlayer`, `C_PlayerCache.SetEnabled`, `C_PlayerCache.SetScanEnabled`, `C_PlayerInfo.GUIDIsCreature`, `C_PlayerInfo.GUIDIsGameObject`, `C_PlayerInfo.GUIDIsPet`, `C_PlayerInfo.GUIDIsPlayer`, `GetPlayerInfoByGUID` |
48+
| [NameCache](docs/API.md#namecache) | `C_CreatureInfo.GetCreatureID`, `C_PlayerCache.GetPlayerInfoByName`, `C_PlayerCache.IsEnabled`, `C_PlayerCache.IsScanEnabled`, `C_PlayerCache.RememberPlayer`, `C_PlayerCache.SetEnabled`, `C_PlayerCache.SetScanEnabled`, `C_PlayerInfo.GUIDIsCreature`, `C_PlayerInfo.GUIDIsGameObject`, `C_PlayerInfo.GUIDIsPet`, `C_PlayerInfo.GUIDIsPlayer`, `GetPlayerInfoByGUID`, `UnitNameFromGUID` |
4949
| [Quest](docs/API.md#quest) | `C_QuestLog.GetQuestDetails`, `C_QuestLog.GetQuestIDForLogIndex`, `C_QuestLog.GetTitleForQuestID`, `C_QuestLog.IsOnQuest`, `C_QuestLog.IsQuestDataCachedByID`, `C_QuestLog.IsUnitOnQuest`, `C_QuestLog.RequestLoadQuestByID`, `GetQuestLogLeaderBoardID` |
5050
| [Spell](docs/API.md#spell) | `C_Spell.CancelSpellByID`, `C_Spell.CastAtCursor`, `C_Spell.DoesSpellExist`, `C_Spell.GetSchoolString`, `C_Spell.GetSpellCooldown`, `C_Spell.GetSpellDescription`, `C_Spell.GetSpellInfo`, `C_Spell.GetSpellLink`, `C_Spell.GetSpellName`, `C_Spell.GetSpellReagents`, `C_Spell.GetSpellSubtext`, `C_Spell.GetSpellTexture`, `C_Spell.IsAutoAttackSpell`, `C_Spell.IsCurrentSpell`, `C_Spell.IsRangedAutoAttackSpell`, `C_Spell.IsSelfBuff`, `C_Spell.IsSpellHarmful`, `C_Spell.IsSpellHelpful`, `C_Spell.IsSpellPassive`, `C_Spell.IsSpellUsable`, `C_Spell.SpellHasRange`, `CancelSpellByName`, `CastSpellNoToggle`, `GetCraftSpellID`, `GetSpellInfo`, `GetSpellLink`, `GetSpellSchool`, `IsHarmfulSpell`, `IsHelpfulSpell`, `IsPassiveSpell`, `IsPlayerSpell`, `IsSpellKnown`, `IsUsableSpell`, `SpellHasRange` |
5151
| [SpellBook](docs/API.md#spellbook) | `C_SpellBook.GetCurrentLevelSpells`, `C_SpellBook.GetSpellLevelLearned`, `C_SpellBook.GetSpellSkillLine`, `C_SpellBook.IsAutoAttackSpellBookItem`, `C_SpellBook.IsRangedAutoAttackSpellBookItem`, `FindSpellBookSlotByID` |

docs/API.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ build instructions.
249249

250250
- [NameCache](#namecache)
251251
- [`GetPlayerInfoByGUID(guid)`](#getplayerinfobyguidguid)
252+
- [`UnitNameFromGUID(guid)`](#unitnamefromguidguid)
252253
- [`C_PlayerCache.GetPlayerInfoByName(name)`](#c_playercachegetplayerinfobynamename)
253254
- [`C_PlayerInfo.GUIDIsPlayer(guid)` / `GUIDIsCreature` / `GUIDIsPet` / `GUIDIsGameObject`](#c_playerinfoguidisplayerguid--guidiscreature--guidispet--guidisgameobject)
254255
- [`C_CreatureInfo.GetCreatureID(guid)`](#c_creatureinfogetcreatureidguid)
@@ -6003,6 +6004,35 @@ instance lives at `0x00C0E228`; entry layout (name, realm, race,
60036004
sex, class) was reverse-engineered from the
60046005
`SMSG_NAME_QUERY_RESPONSE` write path at `0x0055F310`.
60056006

6007+
### `UnitNameFromGUID(guid)`
6008+
6009+
Returns `name, realm` for the player identified by `guid`, or `nil`
6010+
if no player with that GUID has been encountered yet. Same lookup
6011+
chain as [`GetPlayerInfoByGUID`](#getplayerinfobyguidguid) (engine
6012+
NameCache, persistent on-disk fallback when enabled) — just narrower
6013+
return shape for callers that only need the name.
6014+
6015+
```lua
6016+
local name, realm = UnitNameFromGUID(UnitGUID("target"))
6017+
if name then
6018+
ChatFrame1:AddMessage("Target: " .. name)
6019+
end
6020+
6021+
-- Resolving a chat link
6022+
local name, realm = UnitNameFromGUID("0x0000000000000777")
6023+
```
6024+
6025+
`realm` is always `""` in vanilla — the engine doesn't populate
6026+
per-player realm names and 1.12 has no cross-realm interaction.
6027+
We push the empty string rather than `nil` to match the convention
6028+
of the other player-info accessors; addons can gate on `realm == ""`.
6029+
6030+
Doesn't trigger a network query on miss. To populate an unknown
6031+
GUID, either let the engine receive a `SMSG_NAME_QUERY_RESPONSE`
6032+
through normal chat/target interaction, or call
6033+
[`C_PlayerCache.RememberPlayer`](#c_playercacherememberplayerguid-name-classtoken)
6034+
explicitly.
6035+
60066036
### `C_PlayerCache.GetPlayerInfoByName(name)`
60076037

60086038
Returns

src/player/Info.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,73 @@ int PushFromNameCacheEntry(void *L, const char *name,
182182
return 7;
183183
}
184184

185+
// `UnitNameFromGUID(guid)` → `(name, realm)`. Modern WoW backport;
186+
// vanilla 1.12 doesn't expose any GUID → name accessor at the Lua
187+
// surface, so addons that need to resolve a chat link or combat-log
188+
// GUID currently have to round-trip through `Script_GetPlayerInfoByGUID`
189+
// and discard 5 of the 7 returns. This is the same lookup with the
190+
// extra DBC reads (class / race / sex) skipped.
191+
//
192+
// `realm` is always `""` in vanilla — the engine's
193+
// `OFF_PLAYER_INFO_REALM` field is left blank for same-realm
194+
// characters, and vanilla has no cross-realm interaction. We still
195+
// push a string (empty) rather than nil so callers don't have to
196+
// special-case the second return; modern WoW returns nil for same-
197+
// realm, but `realm == ""` is just as easy to gate on.
198+
//
199+
// Returns nothing on:
200+
// - Missing or non-string arg (raises an error rather than return).
201+
// - Unparseable GUID string.
202+
// - Zero / NULL GUID.
203+
// - GUID not present in engine cache AND not in persistent NameCache
204+
// (i.e., we've never seen this player).
205+
//
206+
// Doesn't trigger a network query on miss — calling this on a
207+
// never-encountered GUID is a clean nil-return, same as
208+
// `GetPlayerInfoByGUID`. Use `C_PlayerCache.RememberPlayer` (or wait
209+
// for the engine's own SMSG_NAME_QUERY_RESPONSE) to populate.
210+
int __fastcall Script_UnitNameFromGUID(void *L) {
211+
if (!Game::Lua::IsString(L, 1)) {
212+
Game::Lua::Error(L, "Usage: UnitNameFromGUID(\"0x...\")");
213+
return 0;
214+
}
215+
const char *guidStr = Game::Lua::ToString(L, 1);
216+
uint32_t hi, lo;
217+
if (!ParseGUID(guidStr, hi, lo))
218+
return 0;
219+
if (hi == 0 && lo == 0)
220+
return 0;
221+
222+
auto fn = reinterpret_cast<LookupOrFetch_t>(Offsets::FUN_PLAYER_INFO_LOOKUP);
223+
auto *cache = reinterpret_cast<void *>(
224+
static_cast<uintptr_t>(Offsets::VAR_PLAYER_NAME_CACHE));
225+
226+
uint64_t cookie = 0;
227+
const uint8_t *entry = fn(cache, lo, hi, &cookie,
228+
nullptr, nullptr, 0);
229+
if (entry != nullptr) {
230+
const char *name = reinterpret_cast<const char *>(
231+
entry + Offsets::OFF_PLAYER_INFO_NAME);
232+
const char *realm = reinterpret_cast<const char *>(
233+
entry + Offsets::OFF_PLAYER_INFO_REALM);
234+
if (name == nullptr || *name == '\0')
235+
return 0;
236+
Game::Lua::PushString(L, name);
237+
Game::Lua::PushString(L, realm ? realm : "");
238+
return 2;
239+
}
240+
241+
// Engine miss — fall back to the persistent NameCache.
242+
const std::string *cachedName = nullptr;
243+
const NameCache::Entry *cached = NameCache::Lookup(
244+
(static_cast<uint64_t>(hi) << 32) | lo, &cachedName);
245+
if (cached == nullptr || cachedName == nullptr || cachedName->empty())
246+
return 0;
247+
Game::Lua::PushString(L, cachedName->c_str());
248+
Game::Lua::PushString(L, "");
249+
return 2;
250+
}
251+
185252
// `C_PlayerCache.GetPlayerInfoByName(name)` — name-keyed lookup over
186253
// the persistent NameCache. Returns `(localizedClass, englishClass,
187254
// localizedRace, englishRace, sex, name, realm, guid)` or nothing if
@@ -212,6 +279,8 @@ int __fastcall Script_C_PlayerCache_GetPlayerInfoByName(void *L) {
212279
static void RegisterLuaFunctions() {
213280
Game::Lua::RegisterGlobalFunction("GetPlayerInfoByGUID",
214281
&Script_GetPlayerInfoByGUID);
282+
Game::Lua::RegisterGlobalFunction("UnitNameFromGUID",
283+
&Script_UnitNameFromGUID);
215284
Game::Lua::RegisterTableFunction("C_PlayerCache", "GetPlayerInfoByName",
216285
&Script_C_PlayerCache_GetPlayerInfoByName);
217286
}

0 commit comments

Comments
 (0)