From 41844ecc6915adf8d4a4878559a9a1cf6716bd41 Mon Sep 17 00:00:00 2001 From: kelsi davis Date: Sat, 7 Feb 2026 13:17:01 -0800 Subject: [PATCH] Add Tier 4 commands: AFK/DND status and whisper reply - AFK commands: /afk, /away to toggle AFK status with optional message - DND commands: /dnd, /busy to toggle DND (Do Not Disturb) with optional message - Reply command: /r, /reply to respond to the last received whisper - Track last whisper sender automatically when receiving whispers - AFK and DND are mutually exclusive (activating one clears the other) --- include/game/game_handler.hpp | 14 +++++++ src/game/game_handler.cpp | 72 +++++++++++++++++++++++++++++++++++ src/ui/game_screen.cpp | 24 ++++++++++++ 3 files changed, 110 insertions(+) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 3fc4cf36..909e4cc3 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -254,6 +254,13 @@ public: // Duel void forfeitDuel(); + // AFK/DND status + void toggleAfk(const std::string& message = ""); + void toggleDnd(const std::string& message = ""); + void replyToLastWhisper(const std::string& message); + std::string getLastWhisperSender() const { return lastWhisperSender_; } + void setLastWhisperSender(const std::string& name) { lastWhisperSender_ = name; } + // ---- Phase 1: Name queries ---- void queryPlayerName(uint64_t guid); void queryCreatureInfo(uint32_t entry, uint64_t guid); @@ -660,6 +667,13 @@ private: // ---- Follow state ---- uint64_t followTargetGuid_ = 0; + // ---- AFK/DND status ---- + bool afkStatus_ = false; + bool dndStatus_ = false; + std::string afkMessage_; + std::string dndMessage_; + std::string lastWhisperSender_; + // ---- Online item tracking ---- struct OnlineItemInfo { uint32_t entry = 0; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index c9c9ed07..bcede487 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1481,6 +1481,11 @@ void GameHandler::handleMessageChat(network::Packet& packet) { chatHistory.erase(chatHistory.begin()); } + // Track whisper sender for /r command + if (data.type == ChatType::WHISPER && !data.senderName.empty()) { + lastWhisperSender_ = data.senderName; + } + // Log the message std::string senderInfo; if (!data.senderName.empty()) { @@ -2044,6 +2049,73 @@ void GameHandler::forfeitDuel() { LOG_INFO("Forfeited duel"); } +void GameHandler::toggleAfk(const std::string& message) { + afkStatus_ = !afkStatus_; + afkMessage_ = message; + + if (afkStatus_) { + if (message.empty()) { + addSystemChatMessage("You are now AFK."); + } else { + addSystemChatMessage("You are now AFK: " + message); + } + // If DND was active, turn it off + if (dndStatus_) { + dndStatus_ = false; + dndMessage_.clear(); + } + } else { + addSystemChatMessage("You are no longer AFK."); + afkMessage_.clear(); + } + + LOG_INFO("AFK status: ", afkStatus_, ", message: ", message); +} + +void GameHandler::toggleDnd(const std::string& message) { + dndStatus_ = !dndStatus_; + dndMessage_ = message; + + if (dndStatus_) { + if (message.empty()) { + addSystemChatMessage("You are now DND (Do Not Disturb)."); + } else { + addSystemChatMessage("You are now DND: " + message); + } + // If AFK was active, turn it off + if (afkStatus_) { + afkStatus_ = false; + afkMessage_.clear(); + } + } else { + addSystemChatMessage("You are no longer DND."); + dndMessage_.clear(); + } + + LOG_INFO("DND status: ", dndStatus_, ", message: ", message); +} + +void GameHandler::replyToLastWhisper(const std::string& message) { + if (state != WorldState::IN_WORLD || !socket) { + LOG_WARNING("Cannot send whisper: not in world or not connected"); + return; + } + + if (lastWhisperSender_.empty()) { + addSystemChatMessage("No one has whispered you yet."); + return; + } + + if (message.empty()) { + addSystemChatMessage("You must specify a message to send."); + return; + } + + // Send whisper using the standard message chat function + sendChatMessage(ChatType::WHISPER, message, lastWhisperSender_); + LOG_INFO("Replied to ", lastWhisperSender_, ": ", message); +} + void GameHandler::releaseSpirit() { if (!playerDead_) return; if (socket && state == WorldState::IN_WORLD) { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index f24cabeb..74ee85a3 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -1306,6 +1306,30 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) { return; } + // AFK command + if (cmdLower == "afk" || cmdLower == "away") { + std::string afkMsg = (spacePos != std::string::npos) ? command.substr(spacePos + 1) : ""; + gameHandler.toggleAfk(afkMsg); + chatInputBuffer[0] = '\0'; + return; + } + + // DND command + if (cmdLower == "dnd" || cmdLower == "busy") { + std::string dndMsg = (spacePos != std::string::npos) ? command.substr(spacePos + 1) : ""; + gameHandler.toggleDnd(dndMsg); + chatInputBuffer[0] = '\0'; + return; + } + + // Reply command + if (cmdLower == "r" || cmdLower == "reply") { + std::string replyMsg = (spacePos != std::string::npos) ? command.substr(spacePos + 1) : ""; + gameHandler.replyToLastWhisper(replyMsg); + chatInputBuffer[0] = '\0'; + return; + } + // Chat channel slash commands bool isChannelCommand = false; if (cmdLower == "s" || cmdLower == "say") {