From 8588af1001826a37e4d0c610109ab1c3f48720a8 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 20 Sep 2021 22:34:42 +0100 Subject: [PATCH] Disable chat for the first 5s (configurable) after joining a server. --- OpenRA.Game/Network/UnitOrders.cs | 15 ++++++++ OpenRA.Game/Server/Connection.cs | 1 + OpenRA.Game/Server/Server.cs | 19 ++++++++-- OpenRA.Game/Settings.cs | 3 ++ OpenRA.Game/TextNotificationsManager.cs | 2 ++ .../Widgets/Logic/Ingame/IngameChatLogic.cs | 36 +++++++++++++++---- .../Widgets/Logic/Lobby/LobbyLogic.cs | 29 +++++++++++++-- launch-dedicated.cmd | 4 ++- launch-dedicated.sh | 3 ++ 9 files changed, 99 insertions(+), 13 deletions(-) diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index bd9fea2d3b..bb91b15179 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -34,6 +34,21 @@ namespace OpenRA.Network TextNotificationsManager.AddSystemLine(order.TargetString); break; + case "DisableChatEntry": + { + // Order must originate from the server + if (clientId != 0) + break; + + // Server may send MaxValue to indicate that it is disabled until further notice + if (order.ExtraData == uint.MaxValue) + TextNotificationsManager.ChatDisabledUntil = uint.MaxValue; + else + TextNotificationsManager.ChatDisabledUntil = Game.RunTime + order.ExtraData; + + break; + } + case "Chat": { var client = orderManager.LobbyInfo.ClientWithIndex(clientId); diff --git a/OpenRA.Game/Server/Connection.cs b/OpenRA.Game/Server/Connection.cs index 5ced0c3c96..79d47fb0b4 100644 --- a/OpenRA.Game/Server/Connection.cs +++ b/OpenRA.Game/Server/Connection.cs @@ -31,6 +31,7 @@ namespace OpenRA.Server public readonly int PlayerIndex; public readonly string AuthToken; public readonly EndPoint EndPoint; + public readonly Stopwatch ConnectionTimer = Stopwatch.StartNew(); public long TimeSinceLastResponse => Game.RunTime - lastReceivedTime; diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 1682946d76..d972a5f08a 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -476,6 +476,10 @@ namespace OpenRA.Server LobbyInfo.Clients.Add(client); newConn.Validated = true; + // Disable chat UI to stop the client sending messages that we know we will reject + if (!client.IsAdmin && Settings.JoinChatDelay > 0) + DispatchOrdersToClient(newConn, 0, 0, new Order("DisableChatEntry", null, false) { ExtraData = (uint)Settings.JoinChatDelay }.Serialize()); + Log.Write("server", "Client {0}: Accepted connection from {1}.", newConn.PlayerIndex, newConn.EndPoint); if (client.Fingerprint != null) @@ -892,8 +896,19 @@ namespace OpenRA.Server } case "Chat": - DispatchOrdersToClients(conn, 0, o.Serialize()); - break; + { + var isAdmin = GetClient(conn)?.IsAdmin ?? false; + var connected = conn.ConnectionTimer.ElapsedMilliseconds; + if (!isAdmin && connected < Settings.JoinChatDelay) + { + var remaining = (Settings.JoinChatDelay - connected + 999) / 1000; + SendOrderTo(conn, "Message", "Chat is disabled. Please try again in {0} seconds".F(remaining)); + } + else + DispatchOrdersToClients(conn, 0, o.Serialize()); + + break; + } case "GameSaveTraitData": { diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index 1b84007e2d..e128ba9237 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -101,6 +101,9 @@ namespace OpenRA [Desc("For dedicated servers only, treat maps that fail the lint checks as invalid.")] public bool EnableLintChecks = true; + [Desc("Delay in milliseconds before newly joined players can send chat messages.")] + public int JoinChatDelay = 5000; + public ServerSettings Clone() { return (ServerSettings)MemberwiseClone(); diff --git a/OpenRA.Game/TextNotificationsManager.cs b/OpenRA.Game/TextNotificationsManager.cs index b25f7ed15e..00500776fb 100644 --- a/OpenRA.Game/TextNotificationsManager.cs +++ b/OpenRA.Game/TextNotificationsManager.cs @@ -20,6 +20,8 @@ namespace OpenRA static Color chatMessageColor = Color.White; static string systemMessageLabel; + public static long ChatDisabledUntil { get; internal set; } + static TextNotificationsManager() { ChromeMetrics.TryGet("ChatMessageColor", out chatMessageColor); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs index af1ae51e84..a39fdf2bbc 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs @@ -34,8 +34,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly ScrollPanelWidget chatScrollPanel; readonly ContainerWidget chatTemplate; readonly TextFieldWidget chatText; - - readonly INotifyChat[] chatTraits; + readonly CachedTransform chatDisabledLabel; readonly TabCompletionLogic tabCompletion = new TabCompletionLogic(); @@ -43,6 +42,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic TextNotification lastLine; int repetitions; + bool chatEnabled = true; [ObjectCreator.UseCtor] public IngameChatLogic(Widget widget, OrderManager orderManager, World world, ModData modData, bool isMenuChat, Dictionary logicArgs) @@ -50,8 +50,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic this.orderManager = orderManager; modRules = modData.DefaultRules; - chatTraits = world.WorldActor.TraitsImplementing().ToArray(); - + var chatTraits = world.WorldActor.TraitsImplementing().ToArray(); var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot); var isObserver = orderManager.LocalClient != null && orderManager.LocalClient.IsObserver; var alwaysDisabled = world.IsReplay || world.LobbyInfo.NonBotClients.Count() == 1; @@ -82,7 +81,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { chatMode.IsDisabled = () => { - if (world.IsGameOver) + if (world.IsGameOver || !chatEnabled) return true; // The game is over for us, join spectator team chat @@ -100,7 +99,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic }; } else - chatMode.IsDisabled = () => disableTeamChat; + chatMode.IsDisabled = () => disableTeamChat || !chatEnabled; // Disable team chat after the game ended world.GameOver += () => disableTeamChat = true; @@ -163,6 +162,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic return true; }; + chatDisabledLabel = new CachedTransform(x => x > 0 ? $"Chat available in {x} seconds..." : "Chat Disabled"); + if (!isMenuChat) { var openTeamChatKey = new HotkeyReference(); @@ -203,7 +204,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic orderManager.AddTextNotification += AddChatLineWrapper; - chatText.IsDisabled = () => world.IsReplay && !Game.Settings.Debug.EnableDebugCommandsInReplays; + chatText.IsDisabled = () => !chatEnabled || (world.IsReplay && !Game.Settings.Debug.EnableDebugCommandsInReplays); if (!isMenuChat) { @@ -317,6 +318,27 @@ namespace OpenRA.Mods.Common.Widgets.Logic Game.Sound.PlayNotification(modRules, null, "Sounds", chatLineSound, null); } + public override void Tick() + { + var chatWasEnabled = chatEnabled; + chatEnabled = Game.RunTime >= TextNotificationsManager.ChatDisabledUntil && TextNotificationsManager.ChatDisabledUntil != uint.MaxValue; + + if (chatEnabled && !chatWasEnabled) + { + chatText.Text = ""; + if (Ui.KeyboardFocusWidget == null) + chatText.TakeKeyboardFocus(); + } + else if (!chatEnabled) + { + var remaining = 0; + if (TextNotificationsManager.ChatDisabledUntil != uint.MaxValue) + remaining = (int)(TextNotificationsManager.ChatDisabledUntil - Game.RunTime + 999) / 1000; + + chatText.Text = chatDisabledLabel.Update(remaining); + } + } + bool disposed = false; protected override void Dispose(bool disposing) { diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs index f48da4ff52..a282f04fad 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs @@ -48,6 +48,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly ScrollPanelWidget lobbyChatPanel; readonly Widget chatTemplate; + readonly TextFieldWidget chatTextField; + readonly CachedTransform chatDisabledLabel; readonly ScrollPanelWidget players; @@ -60,6 +62,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic MapPreview map; Session.MapStatus mapStatus; + bool chatEnabled = true; bool addBotOnMapLoad; bool disableTeamChat; bool insufficientPlayerSpawns; @@ -400,12 +403,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic var chatMode = lobby.Get("CHAT_MODE"); chatMode.GetText = () => teamChat ? "Team" : "All"; chatMode.OnClick = () => teamChat ^= true; - chatMode.IsDisabled = () => disableTeamChat; + chatMode.IsDisabled = () => disableTeamChat || !chatEnabled; - var chatTextField = lobby.Get("CHAT_TEXTFIELD"); + chatTextField = lobby.Get("CHAT_TEXTFIELD"); + chatTextField.IsDisabled = () => !chatEnabled; chatTextField.MaxLength = UnitOrders.ChatMessageMaxLength; - chatTextField.TakeKeyboardFocus(); chatTextField.OnEnterKey = _ => { if (chatTextField.Text.Length == 0) @@ -438,6 +441,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic chatTextField.OnEscKey = _ => chatTextField.YieldKeyboardFocus(); + chatDisabledLabel = new CachedTransform(x => x > 0 ? $"Chat available in {x} seconds..." : "Chat Disabled"); + lobbyChatPanel = lobby.Get("CHAT_DISPLAY"); chatTemplate = lobbyChatPanel.Get("CHAT_TEMPLATE"); lobbyChatPanel.RemoveChildren(); @@ -487,6 +492,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic { if (panel == PanelType.Options && OptionsTabDisabled()) panel = PanelType.Players; + + var chatWasEnabled = chatEnabled; + chatEnabled = Game.RunTime >= TextNotificationsManager.ChatDisabledUntil && TextNotificationsManager.ChatDisabledUntil != uint.MaxValue; + + if (chatEnabled && !chatWasEnabled) + { + chatTextField.Text = ""; + if (Ui.KeyboardFocusWidget == null) + chatTextField.TakeKeyboardFocus(); + } + else if (!chatEnabled) + { + var remaining = 0; + if (TextNotificationsManager.ChatDisabledUntil != uint.MaxValue) + remaining = (int)(TextNotificationsManager.ChatDisabledUntil - Game.RunTime + 999) / 1000; + + chatTextField.Text = chatDisabledLabel.Update(remaining); + } } void AddChatLine(TextNotification chatLine) diff --git a/launch-dedicated.cmd b/launch-dedicated.cmd index ad943bc332..585353236f 100644 --- a/launch-dedicated.cmd +++ b/launch-dedicated.cmd @@ -19,10 +19,12 @@ set EnableGeoIP=True set EnableLintChecks=True set ShareAnonymizedIPs=True +set JoinChatDelay=5000 + set SupportDir="" :loop -bin\OpenRA.Server.exe Engine.EngineDir=".." Game.Mod=%Mod% Server.Name=%Name% Server.ListenPort=%ListenPort% Server.AdvertiseOnline=%AdvertiseOnline% Server.EnableSingleplayer=%EnableSingleplayer% Server.Password=%Password% Server.RecordReplays=%RecordReplays% Server.RequireAuthentication=%RequireAuthentication% Server.ProfileIDBlacklist=%ProfileIDBlacklist% Server.ProfileIDWhitelist=%ProfileIDWhitelist% Server.EnableSyncReports=%EnableSyncReports% Server.EnableGeoIP=%EnableGeoIP% Server.EnableLintChecks=%EnableLintChecks% Server.ShareAnonymizedIPs=%ShareAnonymizedIPs% Engine.SupportDir=%SupportDir% +bin\OpenRA.Server.exe Engine.EngineDir=".." Game.Mod=%Mod% Server.Name=%Name% Server.ListenPort=%ListenPort% Server.AdvertiseOnline=%AdvertiseOnline% Server.EnableSingleplayer=%EnableSingleplayer% Server.Password=%Password% Server.RecordReplays=%RecordReplays% Server.RequireAuthentication=%RequireAuthentication% Server.ProfileIDBlacklist=%ProfileIDBlacklist% Server.ProfileIDWhitelist=%ProfileIDWhitelist% Server.EnableSyncReports=%EnableSyncReports% Server.EnableGeoIP=%EnableGeoIP% Server.EnableLintChecks=%EnableLintChecks% Server.ShareAnonymizedIPs=%ShareAnonymizedIPs% Server.JoinChatDelay=%JoinChatDelay% Engine.SupportDir=%SupportDir% goto loop diff --git a/launch-dedicated.sh b/launch-dedicated.sh index 4cb632a605..f594852ebf 100755 --- a/launch-dedicated.sh +++ b/launch-dedicated.sh @@ -29,6 +29,8 @@ EnableGeoIP="${EnableGeoIP:-"True"}" EnableLintChecks="${EnableLintChecks:-"True"}" ShareAnonymizedIPs="${ShareAnonymizedIPs:-"True"}" +JoinChatDelay="${JoinChatDelay:-"5000"}" + SupportDir="${SupportDir:-""}" while true; do @@ -46,5 +48,6 @@ while true; do Server.EnableGeoIP="$EnableGeoIP" \ Server.EnableLintChecks="$EnableLintChecks" \ Server.ShareAnonymizedIPs="$ShareAnonymizedIPs" \ + Server.JoinChatDelay="$JoinChatDelay" \ Engine.SupportDir="$SupportDir" done