diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index b07a2d2d..ba1602d1 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -59,6 +59,9 @@ private: std::vector chatTabMatches_; // matching command list int chatTabMatchIdx_ = -1; // active match index (-1 = inactive) + // Mention notification: plays a sound when the player's name appears in chat + size_t chatMentionSeenCount_ = 0; // how many messages have been scanned for mentions + // Chat tabs int activeChatTab_ = 0; struct ChatTab { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index a119f1e8..bd1bcaed 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -1489,6 +1489,39 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) { } }; + // Determine local player name for mention detection (case-insensitive) + std::string selfNameLower; + { + const auto* ch = gameHandler.getActiveCharacter(); + if (ch && !ch->name.empty()) { + selfNameLower = ch->name; + for (auto& c : selfNameLower) c = static_cast(std::tolower(static_cast(c))); + } + } + + // Scan NEW messages (beyond chatMentionSeenCount_) for mentions and play notification sound + if (!selfNameLower.empty() && chatHistory.size() > chatMentionSeenCount_) { + for (size_t mi = chatMentionSeenCount_; mi < chatHistory.size(); ++mi) { + const auto& mMsg = chatHistory[mi]; + // Skip outgoing whispers, system, and monster messages + if (mMsg.type == game::ChatType::WHISPER_INFORM || + mMsg.type == game::ChatType::SYSTEM) continue; + // Case-insensitive search in message body + std::string bodyLower = mMsg.message; + for (auto& c : bodyLower) c = static_cast(std::tolower(static_cast(c))); + if (bodyLower.find(selfNameLower) != std::string::npos) { + if (auto* renderer = core::Application::getInstance().getRenderer()) { + if (auto* ui = renderer->getUiSoundManager()) + ui->playWhisperReceived(); + } + break; // play at most once per scan pass + } + } + chatMentionSeenCount_ = chatHistory.size(); + } else if (chatHistory.size() <= chatMentionSeenCount_) { + chatMentionSeenCount_ = chatHistory.size(); // reset if history was cleared + } + int chatMsgIdx = 0; for (const auto& msg : chatHistory) { if (!shouldShowMessage(msg, activeChatTab_)) continue; @@ -1572,10 +1605,30 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) { } } + // Detect mention: does this message contain the local player's name? + bool isMention = false; + if (!selfNameLower.empty() && + msg.type != game::ChatType::WHISPER_INFORM && + msg.type != game::ChatType::SYSTEM) { + std::string msgLower = fullMsg; + for (auto& c : msgLower) c = static_cast(std::tolower(static_cast(c))); + isMention = (msgLower.find(selfNameLower) != std::string::npos); + } + // Render message in a group so we can attach a right-click context menu ImGui::PushID(chatMsgIdx++); + if (isMention) { + // Golden highlight strip behind the text + ImVec2 groupMin = ImGui::GetCursorScreenPos(); + float availW = ImGui::GetContentRegionAvail().x; + float lineH = ImGui::GetTextLineHeightWithSpacing(); + ImGui::GetWindowDrawList()->AddRectFilled( + groupMin, + ImVec2(groupMin.x + availW, groupMin.y + lineH), + IM_COL32(255, 200, 50, 45)); // soft golden tint + } ImGui::BeginGroup(); - renderTextWithLinks(fullMsg, color); + renderTextWithLinks(fullMsg, isMention ? ImVec4(1.0f, 0.9f, 0.35f, 1.0f) : color); ImGui::EndGroup(); // Right-click context menu (only for player messages with a sender)