@@ -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) {
212279static 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