Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ enum class EOnlineModerationAction
};

void ShowLoginBanDialog(const std::string& reason);
void ShowChatRateLimitNotice(const std::string& reason, const std::string& scopeType);
void HandleModerationDisconnect(EOnlineModerationAction action, const std::string& reason);
void HandleModerationNotice(const std::string& actionType, const std::string& reason, const std::string& scopeType);
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ enum class EChatMessageType
CHAT_MESSAGE_TYPE_NETWORK_ROOM,
CHAT_MESSAGE_TYPE_LOBBY
};
static Color DetermineSystemNoticeColor(bool bWarning = false, bool bError = false)
{
if (bError)
{
return GameMakeColor(255, 94, 94, 255);
}
if (bWarning)
{
return GameMakeColor(255, 194, 15, 255);
}
return GameMakeColor(192, 192, 192, 255);
}

static Color DetermineColorForChatMessage(EChatMessageType chatMessageType, Bool isPublic, bool bAction, bool bAdmin, bool bIsNameChange, int lobbySlot = -1)
{
Color style = GameMakeColor(255, 255, 255, 255);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
#include "GameNetwork/GameSpy/LobbyUtils.h"
#include "GameNetwork/RankPointValue.h"
#include "GameNetwork/GeneralsOnline/NGMP_interfaces.h"
#include "GameNetwork/GeneralsOnline/OnlineServices_Moderation.h"

#include <deque>

Expand Down Expand Up @@ -132,10 +133,8 @@ static Int groupRoomToJoin = 0;
static Int initialGadgetDelay = 2;
static Bool justEntered = FALSE;

// TODO: Remove this client-side limit after server-side rate limiting is deployed.
// Preserve rejected messages while the server enforces the limit.
static std::deque<std::chrono::steady_clock::time_point> s_lobbyChatMessageTimes;
static std::chrono::steady_clock::time_point s_lastLobbyChatRateLimitNotice;
static Bool s_hasShownLobbyChatRateLimitNotice = FALSE;

static bool LobbyChatRateLimitAllowsSend()
{
Expand All @@ -150,17 +149,9 @@ static bool LobbyChatRateLimitAllowsSend()

if (s_lobbyChatMessageTimes.size() >= 3)
{
if (!s_hasShownLobbyChatRateLimitNotice || now - s_lastLobbyChatRateLimitNotice >= window)
{
GadgetListBoxAddEntryText(
listboxLobbyChat,
UnicodeString(L"Rate limit: Please wait before sending another message."),
GameMakeColor(255, 194, 15, 255),
-1,
-1);
s_lastLobbyChatRateLimitNotice = now;
s_hasShownLobbyChatRateLimitNotice = TRUE;
}
ShowChatRateLimitNotice(
"Rate limit: Please wait before sending another message.",
"room");
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ static Int minPoints = 0;
static Int matchFoundTimeoutStart = 0;
static const Int lobbyTimeoutMs = 10000;
static Int matchFoundTimeoutDurationMs = lobbyTimeoutMs;
static const Int matchStartCountdownDurationMs = 5000;
static Int matchStartCountdownLastSecond = 0;

static const LadderInfo * getLadderInfo();

Expand Down Expand Up @@ -911,6 +913,9 @@ void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
buttonPushed = false;
isShuttingDown = false;
raiseMessageBoxes = true;
matchFoundTimeoutStart = 0;
matchFoundTimeoutDurationMs = lobbyTimeoutMs;
matchStartCountdownLastSecond = 0;

delete TheNAT;
TheNAT = nullptr;
Expand Down Expand Up @@ -1222,6 +1227,7 @@ void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
buttonStop->winEnable(FALSE);
matchFoundTimeoutDurationMs = lobbyTimeoutMs;
matchFoundTimeoutStart = timeGetTime();
matchStartCountdownLastSecond = 0;
if (TheAudio)
{
AudioEventRTS evt("GUICommunicatorOpen");
Expand All @@ -1233,6 +1239,7 @@ void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
{
matchFoundTimeoutStart = 0;
matchFoundTimeoutDurationMs = lobbyTimeoutMs;
matchStartCountdownLastSecond = 0;
buttonBack->winEnable(TRUE);
buttonStop->winEnable(TRUE);
buttonWiden->winEnable(TRUE);
Expand All @@ -1242,11 +1249,17 @@ void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
{
matchFoundTimeoutDurationMs = timeoutMs;
matchFoundTimeoutStart = timeGetTime();

// Mirror the service-owned countdown for UI feedback.
matchStartCountdownLastSecond = timeoutMs < lobbyTimeoutMs
? (matchStartCountdownDurationMs + 999) / 1000
: 0;
});

pLobbyInterface->RegisterForMatchmakingStartGameCallback([]()
{
matchFoundTimeoutStart = 0;
matchStartCountdownLastSecond = 0;
NetworkLog(ELogVerbosity::LOG_DEBUG, "[QUICKMATCH] GOT START GAME EVENT");

// Check if TheNGMPGame is initialized before dereferencing it
Expand Down Expand Up @@ -1490,6 +1503,8 @@ void WOLQuickMatchMenuShutdown( WindowLayout *layout, void *userData )
buttonBack = nullptr;
quickmatchTextWindow = nullptr;
selectedImage = unselectedImage = nullptr;
matchFoundTimeoutStart = 0;
matchStartCountdownLastSecond = 0;

isShuttingDown = true;

Expand Down Expand Up @@ -1597,7 +1612,33 @@ void WOLQuickMatchMenuUpdate( WindowLayout * layout, void *userData)
HandleBuddyResponses();
#endif

if (matchFoundTimeoutStart != 0 && timeGetTime() - matchFoundTimeoutStart >= matchFoundTimeoutDurationMs)
if (matchStartCountdownLastSecond > 0)
{
Int elapsedMs = timeGetTime() - matchFoundTimeoutStart;
Int remainingMs = matchStartCountdownDurationMs - elapsedMs;
Int secondsRemaining = remainingMs > 0 ? (remainingMs + 999) / 1000 : 0;

if (secondsRemaining > 0 && secondsRemaining < matchStartCountdownLastSecond)
{
UnicodeString countdownMessage;
if (secondsRemaining == 1)
{
countdownMessage.format(TheGameText->fetch("LAN:GameStartTimerSingular"), secondsRemaining);
}
else
{
countdownMessage.format(TheGameText->fetch("LAN:GameStartTimerPlural"), secondsRemaining);
}

Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, countdownMessage, GameMakeColor(192, 192, 192, 255), -1, -1);
GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
matchStartCountdownLastSecond = secondsRemaining;
}
}

// Leave time for the server's START_GAME event to arrive.
Int effectiveTimeoutMs = matchFoundTimeoutDurationMs < lobbyTimeoutMs ? lobbyTimeoutMs : matchFoundTimeoutDurationMs;
if (matchFoundTimeoutStart != 0 && timeGetTime() - matchFoundTimeoutStart >= effectiveTimeoutMs)
{
matchFoundTimeoutStart = 0;
buttonBack->winEnable(TRUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include "GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h"
#include "GameNetwork/GameSpyOverlay.h"

#include <chrono>
#include <map>
#include <stdexcept>
#include <windows.h>
#include <shellapi.h>
Expand Down Expand Up @@ -100,40 +102,66 @@ namespace
reason,
EModerationDialogContext::ACTIVE_SESSION);
}
}

void ShowLoginBanDialog(const std::string& reason)
{
ShowBanDialog(reason, EModerationDialogContext::LOGIN);
}

void ShowChatRateLimitNotice(const std::string& reason, const std::string& scopeType)
{
using namespace std::chrono;

void ShowChatNotice(const std::string& reason, const std::string& scopeType)
const std::wstring normalizedReason = GetNormalizedReason(reason);
if (normalizedReason.empty())
{
const std::wstring normalizedReason = GetNormalizedReason(reason);
if (normalizedReason.empty())
return;
}

using NoticeKey = std::pair<std::string, std::wstring>;
static std::map<NoticeKey, steady_clock::time_point> lastNotices;

const auto now = steady_clock::now();
const auto window = seconds(9);
for (auto it = lastNotices.begin(); it != lastNotices.end();)
{
if (now - it->second >= window)
{
return;
it = lastNotices.erase(it);
}

UnicodeString message(normalizedReason.c_str());
const Color color = GameMakeColor(255, 194, 15, 255);
if (scopeType == "lobby")
else
{
NGMP_OnlineServices_LobbyInterface* lobbyInterface =
NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_LobbyInterface>();
if (lobbyInterface != nullptr && lobbyInterface->m_OnChatCallback != nullptr)
{
lobbyInterface->m_OnChatCallback(message, color);
}
return;
++it;
}
}

NGMP_OnlineServices_RoomsInterface* roomsInterface =
NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_RoomsInterface>();
if (roomsInterface != nullptr && roomsInterface->m_OnChatCallback != nullptr)
const NoticeKey key(scopeType, normalizedReason);
if (lastNotices.find(key) != lastNotices.end())
{
return;
}
lastNotices[key] = now;

UnicodeString message(normalizedReason.c_str());
const Color color = DetermineSystemNoticeColor(true, false);
if (scopeType == "lobby")
{
NGMP_OnlineServices_LobbyInterface* lobbyInterface =
NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_LobbyInterface>();
if (lobbyInterface != nullptr && lobbyInterface->m_OnChatCallback != nullptr)
{
roomsInterface->m_OnChatCallback(message, color);
lobbyInterface->m_OnChatCallback(message, color);
}
return;
}
}

void ShowLoginBanDialog(const std::string& reason)
{
ShowBanDialog(reason, EModerationDialogContext::LOGIN);
NGMP_OnlineServices_RoomsInterface* roomsInterface =
NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_RoomsInterface>();
if (roomsInterface != nullptr && roomsInterface->m_OnChatCallback != nullptr)
{
roomsInterface->m_OnChatCallback(message, color);
}
}

void HandleModerationDisconnect(EOnlineModerationAction action, const std::string& reason)
Expand Down Expand Up @@ -170,7 +198,7 @@ void HandleModerationNotice(const std::string& actionType, const std::string& re
}
else if (actionType == "rate_limit")
{
ShowChatNotice(reason, scopeType);
ShowChatRateLimitNotice(reason, scopeType);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1304,8 +1304,12 @@ void WebSocket::Tick()
}
}

// no admin chat in lobby
Color color = DetermineColorForChatMessage(EChatMessageType::CHAT_MESSAGE_TYPE_LOBBY, true, chatData.action, false, false, lobbySlot);
// Match local setup notice colors.
// System announcements stay neutral even when they come from the host;
// player chat keeps its per-slot color.
Color color = chatData.announcement
? DetermineSystemNoticeColor(false, false)
: DetermineColorForChatMessage(EChatMessageType::CHAT_MESSAGE_TYPE_LOBBY, true, chatData.action, false, false, lobbySlot);

if (pLobbyInterface->m_OnChatCallback != nullptr)
{
Expand Down
Loading