ui: resolve chat sender names at render time to fix [Say] prefix

When SMSG_MESSAGECHAT arrives before the entity has spawned or its
name is cached, senderName is empty and messages fell through to the
generic '[Say] message' branch. Fix:

- GameHandler::lookupName(guid): checks playerNameCache then entity
  manager (Unit subclass cast) at call time
- Chat display: resolves senderName via lookupName() at render time
  so messages show "Name says: msg" even if the name was unavailable
  when the packet was first parsed
This commit is contained in:
Kelsi 2026-03-10 15:18:00 -07:00
parent 4987388ce7
commit 942df21c66
2 changed files with 34 additions and 8 deletions

View file

@ -823,6 +823,22 @@ public:
// Player GUID // Player GUID
uint64_t getPlayerGuid() const { return playerGuid; } uint64_t getPlayerGuid() const { return playerGuid; }
// 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.
const std::string& lookupName(uint64_t guid) const {
static const std::string kEmpty;
auto it = playerNameCache.find(guid);
if (it != playerNameCache.end()) return it->second;
auto entity = entityManager.getEntity(guid);
if (entity) {
if (auto* unit = dynamic_cast<const Unit*>(entity.get())) {
if (!unit->getName().empty()) return unit->getName();
}
}
return kEmpty;
}
uint8_t getPlayerClass() const { uint8_t getPlayerClass() const {
const Character* ch = getActiveCharacter(); const Character* ch = getActiveCharacter();
return ch ? static_cast<uint8_t>(ch->characterClass) : 0; return ch ? static_cast<uint8_t>(ch->characterClass) : 0;

View file

@ -1181,6 +1181,16 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
if (!shouldShowMessage(msg, activeChatTab_)) continue; if (!shouldShowMessage(msg, activeChatTab_)) continue;
std::string processedMessage = replaceGenderPlaceholders(msg.message, gameHandler); std::string processedMessage = replaceGenderPlaceholders(msg.message, gameHandler);
// Resolve sender name at render time in case it wasn't available at parse time.
// This handles the race where SMSG_MESSAGECHAT arrives before the entity spawns.
const std::string& resolvedSenderName = [&]() -> const std::string& {
if (!msg.senderName.empty()) return msg.senderName;
if (msg.senderGuid == 0) return msg.senderName;
const std::string& cached = gameHandler.lookupName(msg.senderGuid);
if (!cached.empty()) return cached;
return msg.senderName;
}();
ImVec4 color = getChatTypeColor(msg.type); ImVec4 color = getChatTypeColor(msg.type);
// Optional timestamp prefix // Optional timestamp prefix
@ -1208,36 +1218,36 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
renderTextWithLinks(tsPrefix + processedMessage, color); renderTextWithLinks(tsPrefix + processedMessage, color);
} else if (msg.type == game::ChatType::TEXT_EMOTE) { } else if (msg.type == game::ChatType::TEXT_EMOTE) {
renderTextWithLinks(tsPrefix + processedMessage, color); renderTextWithLinks(tsPrefix + processedMessage, color);
} else if (!msg.senderName.empty()) { } else if (!resolvedSenderName.empty()) {
if (msg.type == game::ChatType::SAY || if (msg.type == game::ChatType::SAY ||
msg.type == game::ChatType::MONSTER_SAY || msg.type == game::ChatType::MONSTER_PARTY) { msg.type == game::ChatType::MONSTER_SAY || msg.type == game::ChatType::MONSTER_PARTY) {
std::string fullMsg = tsPrefix + tagPrefix + msg.senderName + " says: " + processedMessage; std::string fullMsg = tsPrefix + tagPrefix + resolvedSenderName + " says: " + processedMessage;
renderTextWithLinks(fullMsg, color); renderTextWithLinks(fullMsg, color);
} else if (msg.type == game::ChatType::YELL || msg.type == game::ChatType::MONSTER_YELL) { } else if (msg.type == game::ChatType::YELL || msg.type == game::ChatType::MONSTER_YELL) {
std::string fullMsg = tsPrefix + tagPrefix + msg.senderName + " yells: " + processedMessage; std::string fullMsg = tsPrefix + tagPrefix + resolvedSenderName + " yells: " + processedMessage;
renderTextWithLinks(fullMsg, color); renderTextWithLinks(fullMsg, color);
} else if (msg.type == game::ChatType::WHISPER || } else if (msg.type == game::ChatType::WHISPER ||
msg.type == game::ChatType::MONSTER_WHISPER || msg.type == game::ChatType::RAID_BOSS_WHISPER) { msg.type == game::ChatType::MONSTER_WHISPER || msg.type == game::ChatType::RAID_BOSS_WHISPER) {
std::string fullMsg = tsPrefix + tagPrefix + msg.senderName + " whispers: " + processedMessage; std::string fullMsg = tsPrefix + tagPrefix + resolvedSenderName + " whispers: " + processedMessage;
renderTextWithLinks(fullMsg, color); renderTextWithLinks(fullMsg, color);
} else if (msg.type == game::ChatType::WHISPER_INFORM) { } else if (msg.type == game::ChatType::WHISPER_INFORM) {
// Outgoing whisper — show "To Name: message" (WoW-style) // Outgoing whisper — show "To Name: message" (WoW-style)
const std::string& target = !msg.receiverName.empty() ? msg.receiverName : msg.senderName; const std::string& target = !msg.receiverName.empty() ? msg.receiverName : resolvedSenderName;
std::string fullMsg = tsPrefix + "To " + target + ": " + processedMessage; std::string fullMsg = tsPrefix + "To " + target + ": " + processedMessage;
renderTextWithLinks(fullMsg, color); renderTextWithLinks(fullMsg, color);
} else if (msg.type == game::ChatType::EMOTE || } else if (msg.type == game::ChatType::EMOTE ||
msg.type == game::ChatType::MONSTER_EMOTE || msg.type == game::ChatType::RAID_BOSS_EMOTE) { msg.type == game::ChatType::MONSTER_EMOTE || msg.type == game::ChatType::RAID_BOSS_EMOTE) {
std::string fullMsg = tsPrefix + tagPrefix + msg.senderName + " " + processedMessage; std::string fullMsg = tsPrefix + tagPrefix + resolvedSenderName + " " + processedMessage;
renderTextWithLinks(fullMsg, color); renderTextWithLinks(fullMsg, color);
} else if (msg.type == game::ChatType::CHANNEL && !msg.channelName.empty()) { } else if (msg.type == game::ChatType::CHANNEL && !msg.channelName.empty()) {
int chIdx = gameHandler.getChannelIndex(msg.channelName); int chIdx = gameHandler.getChannelIndex(msg.channelName);
std::string chDisplay = chIdx > 0 std::string chDisplay = chIdx > 0
? "[" + std::to_string(chIdx) + ". " + msg.channelName + "]" ? "[" + std::to_string(chIdx) + ". " + msg.channelName + "]"
: "[" + msg.channelName + "]"; : "[" + msg.channelName + "]";
std::string fullMsg = tsPrefix + chDisplay + " [" + tagPrefix + msg.senderName + "]: " + processedMessage; std::string fullMsg = tsPrefix + chDisplay + " [" + tagPrefix + resolvedSenderName + "]: " + processedMessage;
renderTextWithLinks(fullMsg, color); renderTextWithLinks(fullMsg, color);
} else { } else {
std::string fullMsg = tsPrefix + "[" + std::string(getChatTypeName(msg.type)) + "] " + tagPrefix + msg.senderName + ": " + processedMessage; std::string fullMsg = tsPrefix + "[" + std::string(getChatTypeName(msg.type)) + "] " + tagPrefix + resolvedSenderName + ": " + processedMessage;
renderTextWithLinks(fullMsg, color); renderTextWithLinks(fullMsg, color);
} }
} else { } else {