From 4f792dbf6a2a72c44c151f342eee23825264b899 Mon Sep 17 00:00:00 2001 From: Hamood-bot Date: Fri, 31 Oct 2025 03:11:15 +0200 Subject: [PATCH 1/4] feat: Add chat prefixes to distinguish All and Observer messages Adds [All] prefix for messages sent to all players and [Observers] prefix for observer chat. Allies-only messages show no prefix for cleaner display. This improves chat clarity by making it immediately obvious when a message was sent to everyone versus just to allies, following the convention used in games like League of Legends. --- .../Source/GameNetwork/ConnectionManager.cpp | 57 ++++++++++++++++++- .../Source/GameNetwork/ConnectionManager.cpp | 57 ++++++++++++++++++- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index be9a3fcf51..534d13138e 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -619,20 +619,71 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) name = m_connections[playerID]->getUser()->GetName(); //DEBUG_LOG(("connection is non-NULL, using %ls", name.str())); } - unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); -// DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", playerID, msg->getPlayerMask(), unitext.str())); AsciiString playerName; playerName.format("player%d", msg->getPlayerID()); const Player *player = ThePlayerList->findPlayerWithNameKey( TheNameKeyGenerator->nameToKey( playerName ) ); if (!player) { + unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); TheInGameUI->message(UnicodeString(L"%ls"), unitext.str()); return; } + // TheSuperHackers @feature TheSuperHackers 31/10/2025 Add chat prefix to distinguish between All/Observers + // Allies chat has no prefix, only All and Observers are marked + UnicodeString chatPrefix; Bool fromObserver = !player->isPlayerActive(); - Bool amIObserver = !ThePlayerList->getLocalPlayer()->isPlayerActive(); + const Player *localPlayer = ThePlayerList->getLocalPlayer(); + + if (fromObserver) + { + // Message from an observer + chatPrefix = L"[Observers] "; + } + else + { + // Count how many active (non-observer) players receive this message + Int activePlayers = 0; + Int alliesCount = 0; + + for (Int i = 0; i < MAX_SLOTS; ++i) + { + if ((1 << i) & msg->getPlayerMask()) + { + AsciiString checkPlayerName; + checkPlayerName.format("player%d", i); + const Player *checkPlayer = ThePlayerList->findPlayerWithNameKey(TheNameKeyGenerator->nameToKey(checkPlayerName)); + + if (checkPlayer && checkPlayer->isPlayerActive()) + { + activePlayers++; + + // Check if this recipient is an ally of the sender + if (player->getRelationship(checkPlayer->getDefaultTeam()) == ALLIES && + checkPlayer->getRelationship(player->getDefaultTeam()) == ALLIES) + { + alliesCount++; + } + } + } + } + + // Only mark "All" chat, allies chat has no prefix + if (activePlayers > alliesCount) + { + chatPrefix = L"[All] "; + } + else + { + chatPrefix = L""; + } + } + + unitext.format(L"%ls[%ls] %ls", chatPrefix.str(), name.str(), msg->getText().str()); +// DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", playerID, msg->getPlayerMask(), unitext.str())); + + Bool amIObserver = !localPlayer->isPlayerActive(); Bool canSeeChat = (amIObserver || !fromObserver) && !TheGameInfo->getConstSlot(playerID)->isMuted(); if ( ((1<getPlayerMask() ) && canSeeChat ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index a1f6a2ca8b..e2ec701d4c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -619,20 +619,71 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) name = m_connections[playerID]->getUser()->GetName(); //DEBUG_LOG(("connection is non-NULL, using %ls", name.str())); } - unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); -// DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", playerID, msg->getPlayerMask(), unitext.str())); AsciiString playerName; playerName.format("player%d", msg->getPlayerID()); const Player *player = ThePlayerList->findPlayerWithNameKey( TheNameKeyGenerator->nameToKey( playerName ) ); if (!player) { + unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); TheInGameUI->message(UnicodeString(L"%ls"), unitext.str()); return; } + // TheSuperHackers @feature TheSuperHackers 31/10/2025 Add chat prefix to distinguish between All/Observers + // Allies chat has no prefix, only All and Observers are marked + UnicodeString chatPrefix; Bool fromObserver = !player->isPlayerActive(); - Bool amIObserver = !ThePlayerList->getLocalPlayer()->isPlayerActive(); + const Player *localPlayer = ThePlayerList->getLocalPlayer(); + + if (fromObserver) + { + // Message from an observer + chatPrefix = L"[Observers] "; + } + else + { + // Count how many active (non-observer) players receive this message + Int activePlayers = 0; + Int alliesCount = 0; + + for (Int i = 0; i < MAX_SLOTS; ++i) + { + if ((1 << i) & msg->getPlayerMask()) + { + AsciiString checkPlayerName; + checkPlayerName.format("player%d", i); + const Player *checkPlayer = ThePlayerList->findPlayerWithNameKey(TheNameKeyGenerator->nameToKey(checkPlayerName)); + + if (checkPlayer && checkPlayer->isPlayerActive()) + { + activePlayers++; + + // Check if this recipient is an ally of the sender + if (player->getRelationship(checkPlayer->getDefaultTeam()) == ALLIES && + checkPlayer->getRelationship(player->getDefaultTeam()) == ALLIES) + { + alliesCount++; + } + } + } + } + + // Only mark "All" chat, allies chat has no prefix + if (activePlayers > alliesCount) + { + chatPrefix = L"[All] "; + } + else + { + chatPrefix = L""; + } + } + + unitext.format(L"%ls[%ls] %ls", chatPrefix.str(), name.str(), msg->getText().str()); +// DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", playerID, msg->getPlayerMask(), unitext.str())); + + Bool amIObserver = !localPlayer->isPlayerActive(); Bool canSeeChat = (amIObserver || !fromObserver) && !TheGameInfo->getConstSlot(playerID)->isMuted(); if ( ((1<getPlayerMask() ) && canSeeChat ) From 61a85a792de4711542433fa53788b5e5af9d38d9 Mon Sep 17 00:00:00 2001 From: Hamood-bot Date: Fri, 31 Oct 2025 11:50:40 +0200 Subject: [PATCH 2/4] refactor: Use localization for team chat prefix and reverse logic - Changed from prefixing global/observer messages to prefixing team messages - Global messages now have no prefix (default behavior) - Team messages are prefixed with (TEAM) using GUI:Team localization key - Removed observer message prefix as it's unnecessary - Used TheGameText->FETCH_OR_SUBSTITUTE() for proper localization support - Format: Global chat: '[Player Name] Message' - Format: Team chat: '(TEAM) [Player Name] Message' - Applied changes to both GeneralsMD and Generals codebases --- .../Source/GameNetwork/ConnectionManager.cpp | 36 +++++++++---------- .../Source/GameNetwork/ConnectionManager.cpp | 36 +++++++++---------- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 534d13138e..33eac79909 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -630,18 +630,13 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) return; } - // TheSuperHackers @feature TheSuperHackers 31/10/2025 Add chat prefix to distinguish between All/Observers - // Allies chat has no prefix, only All and Observers are marked - UnicodeString chatPrefix; + // TheSuperHackers @feature TheSuperHackers 31/10/2025 Add team chat prefix to distinguish from global messages + // Global chat has no prefix (default), team messages are prefixed with (TEAM) + Bool isTeamMessage = FALSE; Bool fromObserver = !player->isPlayerActive(); const Player *localPlayer = ThePlayerList->getLocalPlayer(); - if (fromObserver) - { - // Message from an observer - chatPrefix = L"[Observers] "; - } - else + if (player->isPlayerActive()) { // Count how many active (non-observer) players receive this message Int activePlayers = 0; @@ -669,18 +664,21 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) } } - // Only mark "All" chat, allies chat has no prefix - if (activePlayers > alliesCount) - { - chatPrefix = L"[All] "; - } - else - { - chatPrefix = L""; - } + // Team message: sent to allies only (not to all active players) + isTeamMessage = (activePlayers == alliesCount && alliesCount > 0); } - unitext.format(L"%ls[%ls] %ls", chatPrefix.str(), name.str(), msg->getText().str()); + if (isTeamMessage) + { + // Format: (TEAM) [Player Name] Message + UnicodeString teamPrefix = TheGameText->FETCH_OR_SUBSTITUTE("GUI:Team", L"TEAM"); + unitext.format(L"(%ls) [%ls] %ls", teamPrefix.str(), name.str(), msg->getText().str()); + } + else + { + // Format: [Player Name] Message (no prefix for global/observer chat) + unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); + } // DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", playerID, msg->getPlayerMask(), unitext.str())); Bool amIObserver = !localPlayer->isPlayerActive(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index e2ec701d4c..675593b0d1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -630,18 +630,13 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) return; } - // TheSuperHackers @feature TheSuperHackers 31/10/2025 Add chat prefix to distinguish between All/Observers - // Allies chat has no prefix, only All and Observers are marked - UnicodeString chatPrefix; + // TheSuperHackers @feature TheSuperHackers 31/10/2025 Add team chat prefix to distinguish from global messages + // Global chat has no prefix (default), team messages are prefixed with (TEAM) + Bool isTeamMessage = FALSE; Bool fromObserver = !player->isPlayerActive(); const Player *localPlayer = ThePlayerList->getLocalPlayer(); - if (fromObserver) - { - // Message from an observer - chatPrefix = L"[Observers] "; - } - else + if (player->isPlayerActive()) { // Count how many active (non-observer) players receive this message Int activePlayers = 0; @@ -669,18 +664,21 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) } } - // Only mark "All" chat, allies chat has no prefix - if (activePlayers > alliesCount) - { - chatPrefix = L"[All] "; - } - else - { - chatPrefix = L""; - } + // Team message: sent to allies only (not to all active players) + isTeamMessage = (activePlayers == alliesCount && alliesCount > 0); } - unitext.format(L"%ls[%ls] %ls", chatPrefix.str(), name.str(), msg->getText().str()); + if (isTeamMessage) + { + // Format: (TEAM) [Player Name] Message + UnicodeString teamPrefix = TheGameText->FETCH_OR_SUBSTITUTE("GUI:Team", L"TEAM"); + unitext.format(L"(%ls) [%ls] %ls", teamPrefix.str(), name.str(), msg->getText().str()); + } + else + { + // Format: [Player Name] Message (no prefix for global/observer chat) + unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); + } // DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", playerID, msg->getPlayerMask(), unitext.str())); Bool amIObserver = !localPlayer->isPlayerActive(); From 5626ae0333b485572074231d91516f15e3c5504c Mon Sep 17 00:00:00 2001 From: Hamood-bot Date: Fri, 31 Oct 2025 17:22:42 +0200 Subject: [PATCH 3/4] refactor: Mirror GeneralsMD team-chat refactor into Generals ConnectionManager::processChat() - Add isTeamChat helper and localized GUI:Team prefix --- .../Source/GameNetwork/ConnectionManager.cpp | 123 ++++++++++-------- 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 33eac79909..ef01caa9ff 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -630,59 +630,78 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) return; } - // TheSuperHackers @feature TheSuperHackers 31/10/2025 Add team chat prefix to distinguish from global messages - // Global chat has no prefix (default), team messages are prefixed with (TEAM) - Bool isTeamMessage = FALSE; - Bool fromObserver = !player->isPlayerActive(); - const Player *localPlayer = ThePlayerList->getLocalPlayer(); - - if (player->isPlayerActive()) - { - // Count how many active (non-observer) players receive this message - Int activePlayers = 0; - Int alliesCount = 0; - - for (Int i = 0; i < MAX_SLOTS; ++i) - { - if ((1 << i) & msg->getPlayerMask()) - { - AsciiString checkPlayerName; - checkPlayerName.format("player%d", i); - const Player *checkPlayer = ThePlayerList->findPlayerWithNameKey(TheNameKeyGenerator->nameToKey(checkPlayerName)); - - if (checkPlayer && checkPlayer->isPlayerActive()) - { - activePlayers++; - - // Check if this recipient is an ally of the sender - if (player->getRelationship(checkPlayer->getDefaultTeam()) == ALLIES && - checkPlayer->getRelationship(player->getDefaultTeam()) == ALLIES) - { - alliesCount++; - } - } - } - } - - // Team message: sent to allies only (not to all active players) - isTeamMessage = (activePlayers == alliesCount && alliesCount > 0); - } + // ============================================================= +// ConnectionManager::processChat() +// Refactored by TheSuperHackers @feature - 31/10/2025 +// Simplified team chat detection, clean formatting logic. +// ============================================================= - if (isTeamMessage) - { - // Format: (TEAM) [Player Name] Message - UnicodeString teamPrefix = TheGameText->FETCH_OR_SUBSTITUTE("GUI:Team", L"TEAM"); - unitext.format(L"(%ls) [%ls] %ls", teamPrefix.str(), name.str(), msg->getText().str()); - } - else - { - // Format: [Player Name] Message (no prefix for global/observer chat) - unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); - } -// DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", playerID, msg->getPlayerMask(), unitext.str())); - - Bool amIObserver = !localPlayer->isPlayerActive(); - Bool canSeeChat = (amIObserver || !fromObserver) && !TheGameInfo->getConstSlot(playerID)->isMuted(); +static Bool isTeamChat(const Player* sender, UInt32 mask) +{ + Int allies = 0; + Int recipients = 0; + + for (Int i = 0; i < MAX_SLOTS; ++i) + { + if ((1 << i) & mask) + { + const Player* receiver = ThePlayerList->getConstSlot(i); + if (receiver && receiver->isPlayerActive()) + { + recipients++; + if (sender->getRelationship(receiver->getDefaultTeam()) == ALLIES) + allies++; + } + } + } + + // Team message if all recipients are allies and at least one exists + return (recipients > 0 && recipients == allies); +} + +void ConnectionManager::processChat(Int playerID, ChatMessage* msg) +{ + const Player* player = ThePlayerList->getConstSlot(playerID); + if (!player) return; + + const Player* localPlayer = ThePlayerList->getLocalPlayer(); + Bool fromObserver = !player->isPlayerActive(); + + UnicodeString name(player->getDisplayName()); + UnicodeString unitext; + + // Determine whether this is a team message + Bool isTeamMessage = FALSE; + if (player->isPlayerActive()) + { + isTeamMessage = isTeamChat(player, msg->getPlayerMask()); + } + + // Format the message string + if (isTeamMessage) + { + // Format: (TEAM) [Player Name] Message + UnicodeString teamPrefix = TheGameText->FETCH_OR_SUBSTITUTE("GUI:Team", L"TEAM"); + unitext.format(L"(%ls) [%ls] %ls", teamPrefix.str(), name.str(), msg->getText().str()); + } + else + { + // Format: [Player Name] Message (no prefix for global/observer chat) + unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); + } + + // DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", + // playerID, msg->getPlayerMask(), unitext.str())); + + Bool amIObserver = !localPlayer->isPlayerActive(); + Bool canSeeChat = (amIObserver || !fromObserver) && !TheGameInfo->getConstSlot(playerID)->isMuted(); + + if (((1 << m_localSlot) & msg->getPlayerMask()) && canSeeChat) + { + TheInGameUI->message(UnicodeString(L"%ls"), unitext.str()); + } +} +; if ( ((1<getPlayerMask() ) && canSeeChat ) { From a8a5138be42136188304f58d23d6c55466c3cbf0 Mon Sep 17 00:00:00 2001 From: Hamood-bot Date: Fri, 31 Oct 2025 17:32:26 +0200 Subject: [PATCH 4/4] refactor(chat): use FETCH_OR_SUBSTITUTE_FORMAT localized chat formats for team/global messages in ConnectionManager::processChat - Use CHAT:TeamFormat and CHAT:GlobalFormat via FETCH_OR_SUBSTITUTE_FORMAT - Keep team detection with isTeamChat helper - Applied to both GeneralsMD and Generals versions --- .../Source/GameNetwork/ConnectionManager.cpp | 151 ++++++++++-------- .../Source/GameNetwork/ConnectionManager.cpp | 113 ++++++++----- 2 files changed, 154 insertions(+), 110 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index ef01caa9ff..c7376c0313 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -631,76 +631,89 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) } // ============================================================= -// ConnectionManager::processChat() -// Refactored by TheSuperHackers @feature - 31/10/2025 -// Simplified team chat detection, clean formatting logic. -// ============================================================= + // ConnectionManager::processChat() + // Refactored by TheSuperHackers @feature - 31/10/2025 + // Simplified team chat detection + localizable format via FETCH_OR_SUBSTITUTE_FORMAT() + // ============================================================= -static Bool isTeamChat(const Player* sender, UInt32 mask) -{ - Int allies = 0; - Int recipients = 0; - - for (Int i = 0; i < MAX_SLOTS; ++i) - { - if ((1 << i) & mask) - { - const Player* receiver = ThePlayerList->getConstSlot(i); - if (receiver && receiver->isPlayerActive()) - { - recipients++; - if (sender->getRelationship(receiver->getDefaultTeam()) == ALLIES) - allies++; - } - } - } - - // Team message if all recipients are allies and at least one exists - return (recipients > 0 && recipients == allies); -} - -void ConnectionManager::processChat(Int playerID, ChatMessage* msg) -{ - const Player* player = ThePlayerList->getConstSlot(playerID); - if (!player) return; - - const Player* localPlayer = ThePlayerList->getLocalPlayer(); - Bool fromObserver = !player->isPlayerActive(); - - UnicodeString name(player->getDisplayName()); - UnicodeString unitext; - - // Determine whether this is a team message - Bool isTeamMessage = FALSE; - if (player->isPlayerActive()) - { - isTeamMessage = isTeamChat(player, msg->getPlayerMask()); - } - - // Format the message string - if (isTeamMessage) - { - // Format: (TEAM) [Player Name] Message - UnicodeString teamPrefix = TheGameText->FETCH_OR_SUBSTITUTE("GUI:Team", L"TEAM"); - unitext.format(L"(%ls) [%ls] %ls", teamPrefix.str(), name.str(), msg->getText().str()); - } - else - { - // Format: [Player Name] Message (no prefix for global/observer chat) - unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); - } - - // DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", - // playerID, msg->getPlayerMask(), unitext.str())); - - Bool amIObserver = !localPlayer->isPlayerActive(); - Bool canSeeChat = (amIObserver || !fromObserver) && !TheGameInfo->getConstSlot(playerID)->isMuted(); - - if (((1 << m_localSlot) & msg->getPlayerMask()) && canSeeChat) - { - TheInGameUI->message(UnicodeString(L"%ls"), unitext.str()); - } -} + static Bool isTeamChat(const Player* sender, UInt32 mask) + { + Int allies = 0; + Int recipients = 0; + + for (Int i = 0; i < MAX_SLOTS; ++i) + { + if ((1 << i) & mask) + { + const Player* receiver = ThePlayerList->getConstSlot(i); + if (receiver && receiver->isPlayerActive()) + { + recipients++; + if (sender->getRelationship(receiver->getDefaultTeam()) == ALLIES) + allies++; + } + } + } + + // Team message if all recipients are allies and at least one exists + return (recipients > 0 && recipients == allies); + } + + void ConnectionManager::processChat(Int playerID, ChatMessage* msg) + { + const Player* player = ThePlayerList->getConstSlot(playerID); + if (!player) return; + + const Player* localPlayer = ThePlayerList->getLocalPlayer(); + Bool fromObserver = !player->isPlayerActive(); + + UnicodeString name(player->getDisplayName()); + UnicodeString unitext; + + // Determine whether this is a team message + Bool isTeamMessage = FALSE; + if (player->isPlayerActive()) + { + isTeamMessage = isTeamChat(player, msg->getPlayerMask()); + } + + // Use localized formatted strings (via FETCH_OR_SUBSTITUTE_FORMAT) + if (isTeamMessage) + { + // In your .csf file, define: + // CHAT:TeamFormat = (%s) [%s] %s + UnicodeString teamPrefix = TheGameText->FETCH_OR_SUBSTITUTE("GUI:Team", L"TEAM"); + unitext = TheGameText->FETCH_OR_SUBSTITUTE_FORMAT( + "CHAT:TeamFormat", + L"(%ls) [%ls] %ls", + teamPrefix.str(), + name.str(), + msg->getText().str() + ); + } + else + { + // In your .csf file, define: + // CHAT:GlobalFormat = [%s] %s + unitext = TheGameText->FETCH_OR_SUBSTITUTE_FORMAT( + "CHAT:GlobalFormat", + L"[%ls] %ls", + name.str(), + msg->getText().str() + ); + } + + // DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", + // playerID, msg->getPlayerMask(), unitext.str())); + + Bool amIObserver = !localPlayer->isPlayerActive(); + Bool canSeeChat = (amIObserver || !fromObserver) && !TheGameInfo->getConstSlot(playerID)->isMuted(); + + if (((1 << m_localSlot) & msg->getPlayerMask()) && canSeeChat) + { + TheInGameUI->message(UnicodeString(L"%ls"), unitext.str()); + } + } ; if ( ((1<getPlayerMask() ) && canSeeChat ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 675593b0d1..cf13127e24 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -630,59 +630,90 @@ void ConnectionManager::processChat(NetChatCommandMsg *msg) return; } - // TheSuperHackers @feature TheSuperHackers 31/10/2025 Add team chat prefix to distinguish from global messages - // Global chat has no prefix (default), team messages are prefixed with (TEAM) - Bool isTeamMessage = FALSE; - Bool fromObserver = !player->isPlayerActive(); - const Player *localPlayer = ThePlayerList->getLocalPlayer(); - - if (player->isPlayerActive()) + // ============================================================= + // ConnectionManager::processChat() + // Refactored by TheSuperHackers @feature - 31/10/2025 + // Simplified team chat detection + localizable format via FETCH_OR_SUBSTITUTE_FORMAT() + // ============================================================= + + static Bool isTeamChat(const Player* sender, UInt32 mask) { - // Count how many active (non-observer) players receive this message - Int activePlayers = 0; - Int alliesCount = 0; - + Int allies = 0; + Int recipients = 0; + for (Int i = 0; i < MAX_SLOTS; ++i) { - if ((1 << i) & msg->getPlayerMask()) + if ((1 << i) & mask) { - AsciiString checkPlayerName; - checkPlayerName.format("player%d", i); - const Player *checkPlayer = ThePlayerList->findPlayerWithNameKey(TheNameKeyGenerator->nameToKey(checkPlayerName)); - - if (checkPlayer && checkPlayer->isPlayerActive()) + const Player* receiver = ThePlayerList->getConstSlot(i); + if (receiver && receiver->isPlayerActive()) { - activePlayers++; - - // Check if this recipient is an ally of the sender - if (player->getRelationship(checkPlayer->getDefaultTeam()) == ALLIES && - checkPlayer->getRelationship(player->getDefaultTeam()) == ALLIES) - { - alliesCount++; - } + recipients++; + if (sender->getRelationship(receiver->getDefaultTeam()) == ALLIES) + allies++; } } } - - // Team message: sent to allies only (not to all active players) - isTeamMessage = (activePlayers == alliesCount && alliesCount > 0); - } - if (isTeamMessage) - { - // Format: (TEAM) [Player Name] Message - UnicodeString teamPrefix = TheGameText->FETCH_OR_SUBSTITUTE("GUI:Team", L"TEAM"); - unitext.format(L"(%ls) [%ls] %ls", teamPrefix.str(), name.str(), msg->getText().str()); + // Team message if all recipients are allies and at least one exists + return (recipients > 0 && recipients == allies); } - else + + void ConnectionManager::processChat(Int playerID, ChatMessage* msg) { - // Format: [Player Name] Message (no prefix for global/observer chat) - unitext.format(L"[%ls] %ls", name.str(), msg->getText().str()); - } -// DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", playerID, msg->getPlayerMask(), unitext.str())); + const Player* player = ThePlayerList->getConstSlot(playerID); + if (!player) return; + + const Player* localPlayer = ThePlayerList->getLocalPlayer(); + Bool fromObserver = !player->isPlayerActive(); - Bool amIObserver = !localPlayer->isPlayerActive(); - Bool canSeeChat = (amIObserver || !fromObserver) && !TheGameInfo->getConstSlot(playerID)->isMuted(); + UnicodeString name(player->getDisplayName()); + UnicodeString unitext; + + // Determine whether this is a team message + Bool isTeamMessage = FALSE; + if (player->isPlayerActive()) + { + isTeamMessage = isTeamChat(player, msg->getPlayerMask()); + } + + // Use localized formatted strings (via FETCH_OR_SUBSTITUTE_FORMAT) + if (isTeamMessage) + { + // In your .csf file, define: + // CHAT:TeamFormat = (%s) [%s] %s + UnicodeString teamPrefix = TheGameText->FETCH_OR_SUBSTITUTE("GUI:Team", L"TEAM"); + unitext = TheGameText->FETCH_OR_SUBSTITUTE_FORMAT( + "CHAT:TeamFormat", + L"(%ls) [%ls] %ls", + teamPrefix.str(), + name.str(), + msg->getText().str() + ); + } + else + { + // In your .csf file, define: + // CHAT:GlobalFormat = [%s] %s + unitext = TheGameText->FETCH_OR_SUBSTITUTE_FORMAT( + "CHAT:GlobalFormat", + L"[%ls] %ls", + name.str(), + msg->getText().str() + ); + } + + // DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls", + // playerID, msg->getPlayerMask(), unitext.str())); + + Bool amIObserver = !localPlayer->isPlayerActive(); + Bool canSeeChat = (amIObserver || !fromObserver) && !TheGameInfo->getConstSlot(playerID)->isMuted(); + + if (((1 << m_localSlot) & msg->getPlayerMask()) && canSeeChat) + { + TheInGameUI->message(UnicodeString(L"%ls"), unitext.str()); + } + } if ( ((1<getPlayerMask() ) && canSeeChat ) {