From ee4403f923eddff711bb58f7dc5611a40b56dbc2 Mon Sep 17 00:00:00 2001 From: Gustas Date: Sun, 2 Nov 2025 02:57:04 +0000 Subject: [PATCH] Add vote kick (cherry picked from commit 144e716cdfa2c9b51be3b0c304d76267b756b7c1) --- OpenRA.Game/Network/UnitOrders.cs | 50 ++++++++----- OpenRA.Game/Server/Server.cs | 2 +- OpenRA.Game/Server/VoteKickTracker.cs | 28 +++---- .../ServerTraits/LobbyCommands.cs | 74 +++++++++---------- .../Logic/Ingame/GameInfoStatsLogic.cs | 47 ++++++------ 5 files changed, 103 insertions(+), 98 deletions(-) diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index 419d1a3fe3..9558347c07 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -20,24 +20,6 @@ namespace OpenRA.Network { public const int ChatMessageMaxLength = 2500; - [FluentReference("player")] - const string Joined = "notification-joined"; - - [FluentReference("player")] - const string Left = "notification-lobby-disconnected"; - - [FluentReference] - const string GameStarted = "notification-game-has-started"; - - [FluentReference] - const string GameSaved = "notification-game-saved"; - - [FluentReference("player")] - const string GamePaused = "notification-game-paused"; - - [FluentReference("player")] - const string GameUnpaused = "notification-game-unpaused"; - public static int? KickVoteTarget { get; internal set; } static Player FindPlayerByClient(this World world, Session.Client c) @@ -78,8 +60,16 @@ namespace OpenRA.Network } case "DisableChatEntry": - { - if (OrderNotFromServerOrWorldIsReplay(clientId, world)) + { + if (OrderNotFromServerOrWorldIsReplay(clientId, world)) + 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; // Server may send MaxValue to indicate that it is disabled until further notice @@ -111,6 +101,26 @@ namespace OpenRA.Network break; } + case "StartKickVote": + { + if (OrderNotFromServerOrWorldIsReplay(clientId, world)) + break; + + KickVoteTarget = (int)order.ExtraData; + break; + } + + case "EndKickVote": + { + if (OrderNotFromServerOrWorldIsReplay(clientId, world)) + break; + + if (KickVoteTarget == (int)order.ExtraData) + KickVoteTarget = null; + + break; + } + case "Chat": { var client = orderManager.LobbyInfo.ClientWithIndex(clientId); diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index c4b8e42241..8113a8427a 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -319,7 +319,7 @@ namespace OpenRA.Server MapStatusCache = new MapStatusCache(modData, MapStatusChanged, type == ServerType.Dedicated && settings.EnableLintChecks); - playerMessageTracker = new PlayerMessageTracker(this, DispatchOrdersToClient, SendFluentMessageTo); + playerMessageTracker = new PlayerMessageTracker(this, DispatchOrdersToClient, SendLocalizedMessageTo); VoteKickTracker = new VoteKickTracker(this); LobbyInfo = new Session diff --git a/OpenRA.Game/Server/VoteKickTracker.cs b/OpenRA.Game/Server/VoteKickTracker.cs index ab3947f77f..32717d77b3 100644 --- a/OpenRA.Game/Server/VoteKickTracker.cs +++ b/OpenRA.Game/Server/VoteKickTracker.cs @@ -17,22 +17,22 @@ namespace OpenRA.Server { public sealed class VoteKickTracker { - [FluentReference("kickee")] + [TranslationReference("kickee")] const string InsufficientVotes = "notification-insufficient-votes-to-kick"; - [FluentReference] + [TranslationReference] const string AlreadyVoted = "notification-kick-already-voted"; - [FluentReference("kicker", "kickee")] + [TranslationReference("kicker", "kickee")] const string VoteKickStarted = "notification-vote-kick-started"; - [FluentReference] + [TranslationReference] const string UnableToStartAVote = "notification-unable-to-start-a-vote"; - [FluentReference("kickee", "percentage")] + [TranslationReference("kickee", "percentage")] const string VoteKickProgress = "notification-vote-kick-in-progress"; - [FluentReference("kickee")] + [TranslationReference("kickee")] const string VoteKickEnded = "notification-vote-kick-ended"; readonly Dictionary voteTracker = new(); @@ -76,7 +76,7 @@ namespace OpenRA.Server || (voteInProgress && this.kickee.Client != kickee) // Disallow starting new votes when one is already ongoing. || !ClientHasPower(kicker)) { - server.SendFluentMessageTo(conn, UnableToStartAVote); + server.SendLocalizedMessageTo(conn, UnableToStartAVote); return false; } @@ -107,7 +107,7 @@ namespace OpenRA.Server if (!kickee.IsObserver && !server.HasClientWonOrLost(kickee)) { // Vote kick cannot be the sole deciding factor for a game. - server.SendFluentMessageTo(conn, InsufficientVotes, new object[] { "kickee", kickee.Name }); + server.SendLocalizedMessageTo(conn, InsufficientVotes, Translation.Arguments("kickee", kickee.Name)); EndKickVote(); return false; } @@ -126,7 +126,7 @@ namespace OpenRA.Server { if (time + server.Settings.VoteKickerCooldown > kickeeConn.ConnectionTimer.ElapsedMilliseconds) { - server.SendFluentMessageTo(conn, UnableToStartAVote); + server.SendLocalizedMessageTo(conn, UnableToStartAVote); return false; } else @@ -135,7 +135,7 @@ namespace OpenRA.Server Log.Write("server", $"Vote kick started on {kickeeID}."); voteKickTimer = Stopwatch.StartNew(); - server.SendFluentMessage(VoteKickStarted, "kicker", kicker.Name, "kickee", kickee.Name); + server.SendLocalizedMessage(VoteKickStarted, Translation.Arguments("kicker", kicker.Name, "kickee", kickee.Name)); server.DispatchServerOrdersToClients(new Order("StartKickVote", null, false) { ExtraData = (uint)kickeeID }.Serialize()); this.kickee = (kickee, kickeeConn); voteKickerStarter = (kicker, conn); @@ -145,7 +145,7 @@ namespace OpenRA.Server voteTracker[conn.PlayerIndex] = vote; else { - server.SendFluentMessageTo(conn, AlreadyVoted); + server.SendLocalizedMessageTo(conn, AlreadyVoted, null); return false; } @@ -168,9 +168,9 @@ namespace OpenRA.Server } var votesNeeded = eligiblePlayers / 2 + 1; - server.SendFluentMessage(VoteKickProgress, + server.SendLocalizedMessage(VoteKickProgress, Translation.Arguments( "kickee", kickee.Name, - "percentage", votesFor * 100 / eligiblePlayers); + "percentage", votesFor * 100 / eligiblePlayers)); // If a player or players during a vote lose or disconnect, it is possible that a downvote will // kick a client. Guard against that situation. @@ -210,7 +210,7 @@ namespace OpenRA.Server return; if (sendMessage) - server.SendFluentMessage(VoteKickEnded, "kickee", kickee.Client.Name); + server.SendLocalizedMessage(VoteKickEnded, Translation.Arguments("kickee", kickee.Client.Name)); server.DispatchServerOrdersToClients(new Order("EndKickVote", null, false) { ExtraData = (uint)kickee.Client.Index }.Serialize()); diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index b07d439946..0ca445c332 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -57,10 +57,10 @@ namespace OpenRA.Mods.Common.Server [FluentReference] const string NoKickGameStarted = "notification-no-kick-game-started"; - [FluentReference("admin", "player")] + [TranslationReference("admin", "player")] const string AdminKicked = "notification-admin-kicked"; - [FluentReference("player")] + [TranslationReference("player")] const string Kicked = "notification-kicked"; [FluentReference("admin", "player")] @@ -159,37 +159,35 @@ namespace OpenRA.Mods.Common.Server [FluentReference] const string YouWereKicked = "notification-you-were-kicked"; - [FluentReference] + [TranslationReference] const string VoteKickDisabled = "notification-vote-kick-disabled"; - readonly IDictionary> commandHandlers = - new Dictionary> - { - { "state", State }, - { "startgame", StartGame }, - { "slot", Slot }, - { "allow_spectators", AllowSpectators }, - { "spectate", Specate }, - { "slot_close", SlotClose }, - { "slot_open", SlotOpen }, - { "slot_bot", SlotBot }, - { "map", Map }, - { "option", Option }, - { "reset_options", ResetOptions }, - { "assignteams", AssignTeams }, - { "kick", Kick }, - { "vote_kick", VoteKick }, - { "make_admin", MakeAdmin }, - { "make_spectator", MakeSpectator }, - { "name", Name }, - { "faction", Faction }, - { "team", Team }, - { "handicap", Handicap }, - { "spawn", Spawn }, - { "clear_spawn", ClearPlayerSpawn }, - { "color", PlayerColor }, - { "sync_lobby", SyncLobby } - }; + readonly IDictionary> commandHandlers = new Dictionary> + { + { "state", State }, + { "startgame", StartGame }, + { "slot", Slot }, + { "allow_spectators", AllowSpectators }, + { "spectate", Specate }, + { "slot_close", SlotClose }, + { "slot_open", SlotOpen }, + { "slot_bot", SlotBot }, + { "map", Map }, + { "option", Option }, + { "assignteams", AssignTeams }, + { "kick", Kick }, + { "vote_kick", VoteKick }, + { "make_admin", MakeAdmin }, + { "make_spectator", MakeSpectator }, + { "name", Name }, + { "faction", Faction }, + { "team", Team }, + { "handicap", Handicap }, + { "spawn", Spawn }, + { "clear_spawn", ClearPlayerSpawn }, + { "color", PlayerColor }, + { "sync_lobby", SyncLobby } + }; static bool ValidateSlotCommand(S server, Connection conn, Session.Client client, string arg, bool requiresHost) { @@ -873,7 +871,7 @@ namespace OpenRA.Mods.Common.Server } Log.Write("server", $"Kicking client {kickClientID}."); - server.SendFluentMessage(AdminKicked, "admin", client.Name, "player", kickClient.Name); + server.SendLocalizedMessage(AdminKicked, Translation.Arguments("admin", client.Name, "player", kickClient.Name)); server.SendOrderTo(kickConn, "ServerError", YouWereKicked); server.DropClient(kickConn); @@ -898,13 +896,13 @@ namespace OpenRA.Mods.Common.Server var split = s.Split(' '); if (split.Length != 2) { - server.SendFluentMessageTo(conn, MalformedCommand, new object[] { "command", "vote_kick" }); + server.SendLocalizedMessageTo(conn, MalformedCommand, Translation.Arguments("command", "vote_kick")); return true; } if (!server.Settings.EnableVoteKick) { - server.SendFluentMessageTo(conn, VoteKickDisabled); + server.SendLocalizedMessageTo(conn, VoteKickDisabled); return true; } @@ -913,27 +911,27 @@ namespace OpenRA.Mods.Common.Server if (kickConn == null) { - server.SendFluentMessageTo(conn, KickNone); + server.SendLocalizedMessageTo(conn, KickNone); return true; } var kickClient = server.GetClient(kickConn); if (client == kickClient) { - server.SendFluentMessageTo(conn, NoKickSelf); + server.SendLocalizedMessageTo(conn, NoKickSelf); return true; } if (!bool.TryParse(split[1], out var vote)) { - server.SendFluentMessageTo(conn, MalformedCommand, new object[] { "command", "vote_kick" }); + server.SendLocalizedMessageTo(conn, MalformedCommand, Translation.Arguments("command", "vote_kick")); return true; } if (server.VoteKickTracker.VoteKick(conn, client, kickConn, kickClient, kickClientID, vote)) { Log.Write("server", $"Kicking client {kickClientID}."); - server.SendFluentMessage(Kicked, "player", kickClient.Name); + server.SendLocalizedMessage(Kicked, Translation.Arguments("player", kickClient.Name)); server.SendOrderTo(kickConn, "ServerError", YouWereKicked); server.DropClient(kickConn); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs index 7a6abb3830..a151cef5de 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs @@ -50,10 +50,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic [FluentReference] const string Gone = "label-client-state-disconnected"; - [FluentReference] + [TranslationReference] const string KickTooltip = "button-kick-player"; - [FluentReference("player")] + [TranslationReference("player")] const string KickTitle = "dialog-kick.title"; [FluentReference] @@ -62,28 +62,28 @@ namespace OpenRA.Mods.Common.Widgets.Logic [FluentReference] const string KickAccept = "dialog-kick.confirm"; - [FluentReference] + [TranslationReference] const string KickVoteTooltip = "button-vote-kick-player"; - [FluentReference("player")] + [TranslationReference("player")] const string VoteKickTitle = "dialog-vote-kick.title"; - [FluentReference] + [TranslationReference] const string VoteKickPrompt = "dialog-vote-kick.prompt"; - [FluentReference("bots")] + [TranslationReference("bots")] const string VoteKickPromptBreakBots = "dialog-vote-kick.prompt-break-bots"; - [FluentReference] + [TranslationReference] const string VoteKickVoteStart = "dialog-vote-kick.vote-start"; - [FluentReference] + [TranslationReference] const string VoteKickVoteFor = "dialog-vote-kick.vote-for"; - [FluentReference] + [TranslationReference] const string VoteKickVoteAgainst = "dialog-vote-kick.vote-against"; - [FluentReference] + [TranslationReference] const string VoteKickVoteCancel = "dialog-vote-kick.vote-cancel"; [ObjectCreator.UseCtor] @@ -133,10 +133,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic var teamTemplate = playerPanel.Get("TEAM_TEMPLATE"); var playerTemplate = playerPanel.Get("PLAYER_TEMPLATE"); var spectatorTemplate = playerPanel.Get("SPECTATOR_TEMPLATE"); - var unmuteTooltip = FluentProvider.GetMessage(Unmute); - var muteTooltip = FluentProvider.GetMessage(Mute); - var kickTooltip = FluentProvider.GetMessage(KickTooltip); - var voteKickTooltip = FluentProvider.GetMessage(KickVoteTooltip); + var unmuteTooltip = TranslationProvider.GetString(Unmute); + var muteTooltip = TranslationProvider.GetString(Mute); + var kickTooltip = TranslationProvider.GetString(KickTooltip); + var voteKickTooltip = TranslationProvider.GetString(KickVoteTooltip); playerPanel.RemoveChildren(); var teams = world.Players.Where(p => !p.NonCombatant && p.Playable) @@ -159,14 +159,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic { ConfirmationDialogs.ButtonPrompt(modData, title: VoteKickTitle, + titleArguments: Translation.Arguments("player", client.Name), text: botsCount > 0 ? VoteKickPromptBreakBots : VoteKickPrompt, - titleArguments: new object[] { "player", client.Name }, - textArguments: new object[] { "bots", botsCount }, + textArguments: Translation.Arguments("bots", botsCount), onConfirm: () => { orderManager.IssueOrder(Order.Command($"vote_kick {client.Index} {true}")); hideMenu(false); - closeMenu(); }, confirmText: VoteKickVoteStart, onCancel: () => hideMenu(false)); @@ -175,33 +174,31 @@ namespace OpenRA.Mods.Common.Widgets.Logic ConfirmationDialogs.ButtonPrompt(modData, title: VoteKickTitle, + titleArguments: Translation.Arguments("player", client.Name), text: botsCount > 0 ? VoteKickPromptBreakBots : VoteKickPrompt, - titleArguments: new object[] { "player", client.Name }, - textArguments: new object[] { "bots", botsCount }, + textArguments: Translation.Arguments("bots", botsCount), onConfirm: () => { orderManager.IssueOrder(Order.Command($"vote_kick {client.Index} {true}")); hideMenu(false); - closeMenu(); }, confirmText: VoteKickVoteFor, - onCancel: () => hideMenu(false), - cancelText: VoteKickVoteCancel, onOther: () => { Ui.CloseWindow(); orderManager.IssueOrder(Order.Command($"vote_kick {client.Index} {false}")); hideMenu(false); - closeMenu(); }, - otherText: VoteKickVoteAgainst); + otherText: VoteKickVoteAgainst, + onCancel: () => hideMenu(false), + cancelText: VoteKickVoteCancel); } else { ConfirmationDialogs.ButtonPrompt(modData, title: KickTitle, + titleArguments: Translation.Arguments("player", client.Name), text: KickPrompt, - titleArguments: new object[] { "player", client.Name }, onConfirm: () => { orderManager.IssueOrder(Order.Command($"kick {client.Index} {false}"));