From bf00577d33b108689e956ff520ddd8744d234170 Mon Sep 17 00:00:00 2001 From: Gustas <37534529+PunkPun@users.noreply.github.com> Date: Tue, 6 Dec 2022 15:48:04 +0200 Subject: [PATCH] Allow kicking dead players --- OpenRA.Game/Server/Server.cs | 60 +++++++++++-------- .../ServerTraits/LobbyCommands.cs | 11 +++- .../Logic/Ingame/GameInfoStatsLogic.cs | 36 ++++++----- mods/cnc/chrome/ingame-infostats.yaml | 13 ++++ mods/common/chrome/ingame-infostats.yaml | 13 ++++ mods/common/languages/en.ftl | 3 +- mods/d2k/chrome/ingame-infostats.yaml | 13 ++++ 7 files changed, 106 insertions(+), 43 deletions(-) diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 12eaa25b39..6fe2655b29 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -791,9 +791,12 @@ namespace OpenRA.Server // Make sure the written file is not valid // TODO: storing a serverside replay on desync would be extremely useful - recorder.Metadata = null; + if (recorder != null) + { + recorder.Metadata = null; - recorder.Dispose(); + recorder.Dispose(); + } // Stop the recording recorder = null; @@ -856,17 +859,14 @@ namespace OpenRA.Server void RecordOrder(int frame, byte[] data, int from) { - if (recorder != null) - { - recorder.ReceiveFrame(from, frame, data); + recorder?.ReceiveFrame(from, frame, data); - if (data.Length > 0 && data[0] == (byte)OrderType.SyncHash) - { - if (data.Length == Order.SyncHashOrderLength) - HandleSyncOrder(frame, data); - else - Log.Write("server", $"Dropped sync order with length {data.Length} from client {from}. Expected length {Order.SyncHashOrderLength}."); - } + if (data.Length > 0 && data[0] == (byte)OrderType.SyncHash) + { + if (data.Length == Order.SyncHashOrderLength) + HandleSyncOrder(frame, data); + else + Log.Write("server", $"Dropped sync order with length {data.Length} from client {from}. Expected length {Order.SyncHashOrderLength}."); } } @@ -1163,6 +1163,16 @@ namespace OpenRA.Server return LobbyInfo.ClientWithIndex(conn.PlayerIndex); } + /// Does not check if client is admin + public bool CanKickClient(Session.Client kickee) + { + if (State != ServerState.GameStarted || kickee.IsObserver) + return true; + + var player = worldPlayers.FirstOrDefault(p => p?.ClientIndex == kickee.Index); + return player != null && player.Outcome != WinState.Undefined; + } + public void DropClient(Connection toDrop) { lock (LobbyInfo) @@ -1323,24 +1333,22 @@ namespace OpenRA.Server foreach (var cmpi in Map.WorldActorInfo.TraitInfos()) cmpi.CreateServerPlayers(Map, LobbyInfo, worldPlayers, playerRandom); - if (recorder != null) + gameInfo = new GameInformation { - gameInfo = new GameInformation - { - Mod = Game.ModData.Manifest.Id, - Version = Game.ModData.Manifest.Metadata.Version, - MapUid = Map.Uid, - MapTitle = Map.Title, - StartTimeUtc = DateTime.UtcNow, - }; + Mod = Game.ModData.Manifest.Id, + Version = Game.ModData.Manifest.Metadata.Version, + MapUid = Map.Uid, + MapTitle = Map.Title, + StartTimeUtc = DateTime.UtcNow, + }; - // Replay metadata should only include the playable players - foreach (var p in worldPlayers) - if (p != null) - gameInfo.Players.Add(p); + // Replay metadata should only include the playable players + foreach (var p in worldPlayers) + if (p != null) + gameInfo.Players.Add(p); + if (recorder != null) recorder.Metadata = new ReplayMetadata(gameInfo); - } SyncLobbyInfo(); diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index dc71e0aba9..d21330d948 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -48,6 +48,9 @@ namespace OpenRA.Mods.Common.Server [TranslationReference] const string KickNone = "notification-kick-none"; + [TranslationReference] + const string NoKickSelf = "notification-kick-self"; + [TranslationReference] const string NoKickGameStarted = "notification-no-kick-game-started"; @@ -794,7 +797,13 @@ namespace OpenRA.Mods.Common.Server } var kickClient = server.GetClient(kickConn); - if (server.State == ServerState.GameStarted && !kickClient.IsObserver) + if (client == kickClient) + { + server.SendLocalizedMessageTo(conn, NoKickSelf); + return true; + } + + if (!server.CanKickClient(kickClient)) { server.SendLocalizedMessageTo(conn, NoKickGameStarted); return true; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs index 64179c2735..e045464f4b 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs @@ -114,6 +114,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic .GroupBy(p => (world.LobbyInfo.ClientWithIndex(p.Player.ClientIndex) ?? new Session.Client()).Team) .OrderByDescending(g => g.Sum(gg => gg.PlayerStatistics?.Experience ?? 0)); + void KickAction(Session.Client client) + { + hideMenu(true); + ConfirmationDialogs.ButtonPrompt(modData, + title: KickTitle, + titleArguments: Translation.Arguments("player", client.Name), + text: KickPrompt, + onConfirm: () => + { + orderManager.IssueOrder(Order.Command($"kick {client.Index} {false}")); + hideMenu(false); + }, + onCancel: () => hideMenu(false), + confirmText: KickAccept); + } + foreach (var t in teams) { if (teams.Count() > 1) @@ -165,6 +181,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic muteCheckbox.IsVisible = () => !pp.IsBot && client.State != Session.ClientState.Disconnected && pp.ClientIndex != orderManager.LocalClient?.Index; muteCheckbox.GetTooltipText = () => muteCheckbox.IsChecked() ? unmuteTooltip : muteTooltip; + var kickButton = item.Get("KICK"); + kickButton.IsVisible = () => Game.IsHost && client.Index != orderManager.LocalClient?.Index && client.State != Session.ClientState.Disconnected && pp.WinState != WinState.Undefined && !pp.IsBot; + kickButton.OnClick = () => KickAction(client); + playerPanel.AddChild(item); } } @@ -198,21 +218,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var kickButton = item.Get("KICK"); kickButton.IsVisible = () => Game.IsHost && client.Index != orderManager.LocalClient?.Index && client.State != Session.ClientState.Disconnected; - kickButton.OnClick = () => - { - hideMenu(true); - ConfirmationDialogs.ButtonPrompt(modData, - title: KickTitle, - titleArguments: Translation.Arguments("player", client.Name), - text: KickPrompt, - onConfirm: () => - { - orderManager.IssueOrder(Order.Command($"kick {client.Index} {false}")); - hideMenu(false); - }, - onCancel: () => hideMenu(false), - confirmText: KickAccept); - }; + kickButton.OnClick = () => KickAction(client); var muteCheckbox = item.Get("MUTE"); muteCheckbox.IsChecked = () => TextNotificationsManager.MutedPlayers[client.Index]; diff --git a/mods/cnc/chrome/ingame-infostats.yaml b/mods/cnc/chrome/ingame-infostats.yaml index 0c444d41f9..9b103d4203 100644 --- a/mods/cnc/chrome/ingame-infostats.yaml +++ b/mods/cnc/chrome/ingame-infostats.yaml @@ -133,6 +133,19 @@ Container@SKIRMISH_STATS: Checkmark: mute Background: checkbox-toggle TooltipContainer: TOOLTIP_CONTAINER + Button@KICK: + X: 485 + Width: 25 + Height: 25 + Background: checkbox-toggle + TooltipContainer: TOOLTIP_CONTAINER + TooltipText: Kick this player + Children: + Image: + ImageCollection: lobby-bits + ImageName: kick + X: 7 + Y: 7 Container@SPECTATOR_TEMPLATE: Width: PARENT_RIGHT - 27 Height: 25 diff --git a/mods/common/chrome/ingame-infostats.yaml b/mods/common/chrome/ingame-infostats.yaml index 6769210296..eb911296b8 100644 --- a/mods/common/chrome/ingame-infostats.yaml +++ b/mods/common/chrome/ingame-infostats.yaml @@ -130,6 +130,19 @@ Container@SKIRMISH_STATS: Checkmark: mute Background: checkbox-toggle TooltipContainer: TOOLTIP_CONTAINER + Button@KICK: + X: 485 + Width: 25 + Height: 25 + Background: checkbox-toggle + TooltipContainer: TOOLTIP_CONTAINER + TooltipText: Kick this player + Children: + Image: + ImageCollection: lobby-bits + ImageName: kick + X: 7 + Y: 7 Container@SPECTATOR_TEMPLATE: Width: PARENT_RIGHT - 26 Height: 25 diff --git a/mods/common/languages/en.ftl b/mods/common/languages/en.ftl index 05043f5d6b..1d52a62864 100644 --- a/mods/common/languages/en.ftl +++ b/mods/common/languages/en.ftl @@ -64,8 +64,9 @@ notification-invalid-configuration-command = Invalid configuration command. notification-admin-option = Only the host can set that option. notification-error-number-teams = Number of teams could not be parsed: { $raw } notification-admin-kick = Only the host can kick players. +notification-kick-self = The host is not allowed to kick themselves. notification-kick-none = No-one in that slot. -notification-no-kick-game-started = Only spectators can be kicked after the game has started. +notification-no-kick-game-started = Only spectators and defeated players can be kicked after the game has started. notification-admin-clear-spawn = Only admins can clear spawn points. notification-spawn-occupied = You cannot occupy the same spawn point as another player. notification-spawn-locked = The spawn point is locked to another player slot. diff --git a/mods/d2k/chrome/ingame-infostats.yaml b/mods/d2k/chrome/ingame-infostats.yaml index 1043d9bb76..1f5310011d 100644 --- a/mods/d2k/chrome/ingame-infostats.yaml +++ b/mods/d2k/chrome/ingame-infostats.yaml @@ -132,6 +132,19 @@ Container@SKIRMISH_STATS: Checkmark: mute Background: checkbox-toggle TooltipContainer: TOOLTIP_CONTAINER + Button@KICK: + X: 485 + Width: 25 + Height: 25 + Background: checkbox-toggle + TooltipContainer: TOOLTIP_CONTAINER + TooltipText: Kick this player + Children: + Image: + ImageCollection: lobby-bits + ImageName: kick + X: 7 + Y: 7 Container@SPECTATOR_TEMPLATE: Width: PARENT_RIGHT - 27 Height: 25