feat: cache player class/race from name queries for UnitClass/UnitRace

Add playerClassRaceCache_ that stores classId and raceId from
SMSG_NAME_QUERY_RESPONSE. This enables UnitClass and UnitRace to return
correct data for players who were previously seen but are now out of
UPDATE_OBJECT range.

Fallback chain for UnitClass/UnitRace is now:
1. Entity update fields (UNIT_FIELD_BYTES_0) — for nearby entities
2. Name query cache — for previously queried players
3. getPlayerClass/Race() — for the local player

This improves class-colored names in chat, unit frames, and nameplates
for players who move out of view range.
This commit is contained in:
Kelsi 2026-03-21 04:11:48 -07:00
parent d6a25ca8f2
commit cfb9e09e1d
3 changed files with 21 additions and 7 deletions

View file

@ -1235,6 +1235,16 @@ public:
// Player GUID // Player GUID
uint64_t getPlayerGuid() const { return playerGuid; } uint64_t getPlayerGuid() const { return playerGuid; }
// Look up class/race for a player GUID from name query cache. Returns 0 if unknown.
uint8_t lookupPlayerClass(uint64_t guid) const {
auto it = playerClassRaceCache_.find(guid);
return it != playerClassRaceCache_.end() ? it->second.classId : 0;
}
uint8_t lookupPlayerRace(uint64_t guid) const {
auto it = playerClassRaceCache_.find(guid);
return it != playerClassRaceCache_.end() ? it->second.raceId : 0;
}
// Look up a display name for any guid: checks playerNameCache then entity manager. // Look up a display name for any guid: checks playerNameCache then entity manager.
// Returns empty string if unknown. Used by chat display to resolve names at render time. // Returns empty string if unknown. Used by chat display to resolve names at render time.
const std::string& lookupName(uint64_t guid) const { const std::string& lookupName(uint64_t guid) const {
@ -2710,6 +2720,9 @@ private:
// ---- Phase 1: Name caches ---- // ---- Phase 1: Name caches ----
std::unordered_map<uint64_t, std::string> playerNameCache; std::unordered_map<uint64_t, std::string> playerNameCache;
// Class/race cache from SMSG_NAME_QUERY_RESPONSE (guid → {classId, raceId})
struct PlayerClassRace { uint8_t classId = 0; uint8_t raceId = 0; };
std::unordered_map<uint64_t, PlayerClassRace> playerClassRaceCache_;
std::unordered_set<uint64_t> pendingNameQueries; std::unordered_set<uint64_t> pendingNameQueries;
std::unordered_map<uint32_t, CreatureQueryResponseData> creatureInfoCache; std::unordered_map<uint32_t, CreatureQueryResponseData> creatureInfoCache;
std::unordered_set<uint32_t> pendingCreatureQueries; std::unordered_set<uint32_t> pendingCreatureQueries;

View file

@ -295,14 +295,9 @@ static int lua_UnitClass(lua_State* L) {
classId = static_cast<uint8_t>((bytes0 >> 8) & 0xFF); classId = static_cast<uint8_t>((bytes0 >> 8) & 0xFF);
} }
} }
// Fallback: check party/raid member data // Fallback: check name query class/race cache
if (classId == 0 && guid != 0) { if (classId == 0 && guid != 0) {
for (const auto& m : gh->getPartyData().members) { classId = gh->lookupPlayerClass(guid);
if (m.guid == guid && m.hasPartyStats) {
// Party stats don't have class, but check guild roster
break;
}
}
} }
} }
const char* name = (classId > 0 && classId < 12) ? kClasses[classId] : "Unknown"; const char* name = (classId > 0 && classId < 12) ? kClasses[classId] : "Unknown";
@ -493,6 +488,8 @@ static int lua_UnitRace(lua_State* L) {
game::fieldIndex(game::UF::UNIT_FIELD_BYTES_0)); game::fieldIndex(game::UF::UNIT_FIELD_BYTES_0));
raceId = static_cast<uint8_t>(bytes0 & 0xFF); raceId = static_cast<uint8_t>(bytes0 & 0xFF);
} }
// Fallback: name query class/race cache
if (raceId == 0) raceId = gh->lookupPlayerRace(guid);
} }
} }
const char* name = (raceId > 0 && raceId < 12) ? kRaces[raceId] : "Unknown"; const char* name = (raceId > 0 && raceId < 12) ? kRaces[raceId] : "Unknown";

View file

@ -14819,6 +14819,10 @@ void GameHandler::handleNameQueryResponse(network::Packet& packet) {
if (data.isValid()) { if (data.isValid()) {
playerNameCache[data.guid] = data.name; playerNameCache[data.guid] = data.name;
// Cache class/race from name query for UnitClass/UnitRace fallback
if (data.classId != 0 || data.race != 0) {
playerClassRaceCache_[data.guid] = {data.classId, data.race};
}
// Update entity name // Update entity name
auto entity = entityManager.getEntity(data.guid); auto entity = entityManager.getEntity(data.guid);
if (entity && entity->getType() == ObjectType::PLAYER) { if (entity && entity->getType() == ObjectType::PLAYER) {