From b948720ec350a907c90bba2db53b6d2ba7006f1c Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 2 Mar 2026 08:31:34 -0800 Subject: [PATCH] Fix NPC chat showing only name without message text Two bugs in SMSG_MESSAGECHAT parser for MONSTER_SAY/YELL/EMOTE: 1. Sender name included trailing null byte from server (nameLen includes null terminator). The embedded null in std::string caused ImGui to truncate the concatenated display string at the NPC name, hiding " says: " entirely. 2. Missing NamedGuid receiver name for non-player/non-pet targets. When the receiver GUID is a creature, the server writes an additional SizedCString (target name) that we weren't reading, shifting all subsequent field reads. Also adds MONSTER_WHISPER, MONSTER_PARTY, RAID_BOSS_EMOTE, RAID_BOSS_WHISPER chat types with proper parsing and display formatting (says/yells/whispers). --- include/game/world_packets.hpp | 6 +++++- src/game/game_handler.cpp | 3 ++- src/game/world_packets.cpp | 27 ++++++++++++++++++++++++--- src/ui/game_screen.cpp | 11 ++++++++++- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index cd34ab86..4067746e 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -605,7 +605,11 @@ enum class ChatType : uint8_t { RAID_LEADER = 27, RAID_WARNING = 28, ACHIEVEMENT = 29, - GUILD_ACHIEVEMENT = 30 + GUILD_ACHIEVEMENT = 30, + MONSTER_WHISPER = 42, + RAID_BOSS_WHISPER = 43, + RAID_BOSS_EMOTE = 44, + MONSTER_PARTY = 50 }; /** diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index d824ab9f..974534d7 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -5967,7 +5967,8 @@ void GameHandler::handleMessageChat(network::Packet& packet) { // Trigger chat bubble for SAY/YELL messages from others if (chatBubbleCallback_ && data.senderGuid != 0) { if (data.type == ChatType::SAY || data.type == ChatType::YELL || - data.type == ChatType::MONSTER_SAY || data.type == ChatType::MONSTER_YELL) { + data.type == ChatType::MONSTER_SAY || data.type == ChatType::MONSTER_YELL || + data.type == ChatType::MONSTER_PARTY) { bool isYell = (data.type == ChatType::YELL || data.type == ChatType::MONSTER_YELL); chatBubbleCallback_(data.senderGuid, data.message, isYell); } diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index acc7dea6..bb66e416 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -1370,17 +1370,38 @@ bool MessageChatParser::parse(network::Packet& packet, MessageChatData& data) { switch (data.type) { case ChatType::MONSTER_SAY: case ChatType::MONSTER_YELL: - case ChatType::MONSTER_EMOTE: { - // Read sender name length + name + case ChatType::MONSTER_EMOTE: + case ChatType::MONSTER_WHISPER: + case ChatType::MONSTER_PARTY: + case ChatType::RAID_BOSS_EMOTE: + case ChatType::RAID_BOSS_WHISPER: { + // Read sender name (SizedCString: uint32 len including null + chars) uint32_t nameLen = packet.readUInt32(); if (nameLen > 0 && nameLen < 256) { data.senderName.resize(nameLen); for (uint32_t i = 0; i < nameLen; ++i) { data.senderName[i] = static_cast(packet.readUInt8()); } + // Strip trailing null (server includes it in nameLen) + if (!data.senderName.empty() && data.senderName.back() == '\0') { + data.senderName.pop_back(); + } } - // Read receiver GUID + // Read receiver GUID (NamedGuid: guid + optional name for non-player targets) data.receiverGuid = packet.readUInt64(); + if (data.receiverGuid != 0) { + // Non-player, non-pet GUIDs have high type bits set (0xF1xx/0xF0xx range) + uint16_t highGuid = static_cast(data.receiverGuid >> 48); + bool isPlayer = (highGuid == 0x0000); + bool isPet = ((highGuid & 0xF0FF) == 0xF040) || ((highGuid & 0xF0FF) == 0xF014); + if (!isPlayer && !isPet) { + // Read receiver name (SizedCString) + uint32_t recvNameLen = packet.readUInt32(); + if (recvNameLen > 0 && recvNameLen < 256) { + packet.setReadPos(packet.getReadPos() + recvNameLen); + } + } + } break; } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 07c19570..5a232e23 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -1120,9 +1120,18 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) { } else if (msg.type == game::ChatType::TEXT_EMOTE) { renderTextWithLinks(tsPrefix + processedMessage, color); } else if (!msg.senderName.empty()) { - if (msg.type == game::ChatType::MONSTER_SAY || msg.type == game::ChatType::MONSTER_YELL) { + if (msg.type == game::ChatType::MONSTER_SAY || msg.type == game::ChatType::MONSTER_PARTY) { std::string fullMsg = tsPrefix + msg.senderName + " says: " + processedMessage; renderTextWithLinks(fullMsg, color); + } else if (msg.type == game::ChatType::MONSTER_YELL) { + std::string fullMsg = tsPrefix + msg.senderName + " yells: " + processedMessage; + renderTextWithLinks(fullMsg, color); + } else if (msg.type == game::ChatType::MONSTER_WHISPER || msg.type == game::ChatType::RAID_BOSS_WHISPER) { + std::string fullMsg = tsPrefix + msg.senderName + " whispers: " + processedMessage; + renderTextWithLinks(fullMsg, color); + } else if (msg.type == game::ChatType::MONSTER_EMOTE || msg.type == game::ChatType::RAID_BOSS_EMOTE) { + std::string fullMsg = tsPrefix + msg.senderName + " " + processedMessage; + renderTextWithLinks(fullMsg, color); } else if (msg.type == game::ChatType::CHANNEL && !msg.channelName.empty()) { int chIdx = gameHandler.getChannelIndex(msg.channelName); std::string chDisplay = chIdx > 0