diff --git a/OpenRA.Game/Server/ProtocolVersion.cs b/OpenRA.Game/Server/ProtocolVersion.cs index ab52e9794d..0a491f1829 100644 --- a/OpenRA.Game/Server/ProtocolVersion.cs +++ b/OpenRA.Game/Server/ProtocolVersion.cs @@ -66,6 +66,6 @@ namespace OpenRA.Server // The protocol for server and world orders // This applies after the handshake has completed, and is provided to support // alternative server implementations that wish to support multiple versions in parallel - public const int Orders = 9; + public const int Orders = 10; } } diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index e5b849d47c..6c779f3513 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -67,6 +67,10 @@ namespace OpenRA.Mods.Common.Server public static bool ValidateCommand(S server, Connection conn, Session.Client client, string cmd) { + // Kick command is always valid for the host + if (cmd.StartsWith("kick ")) + return true; + if (server.State == ServerState.GameStarted) { server.SendOrderTo(conn, "Message", "Cannot change state when game started. ({0})".F(cmd)); @@ -584,6 +588,11 @@ namespace OpenRA.Mods.Common.Server } var kickClient = server.GetClient(kickConn); + if (server.State == ServerState.GameStarted && !kickClient.IsObserver) + { + server.SendOrderTo(conn, "Message", "Only spectators can be kicked after the game has started."); + return true; + } Log.Write("server", "Kicking client {0}.", kickClientID); server.SendMessage("{0} kicked {1} from the server.".F(client.Name, kickClient.Name)); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs index e232806ed0..c8e908ee36 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs @@ -9,6 +9,7 @@ */ #endregion +using System; using System.Linq; using OpenRA.Mods.Common.Scripting; using OpenRA.Mods.Common.Traits; @@ -21,7 +22,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic class GameInfoLogic : ChromeLogic { [ObjectCreator.UseCtor] - public GameInfoLogic(Widget widget, World world, IngameInfoPanel activePanel) + public GameInfoLogic(Widget widget, World world, IngameInfoPanel activePanel, Action hideMenu) { var lp = world.LocalPlayer; var numTabs = 0; @@ -47,7 +48,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic var objectivesPanel = widget.Get("OBJECTIVES_PANEL"); objectivesPanel.IsVisible = () => activePanel == IngameInfoPanel.Objectives; - Game.LoadWidget(world, panel, objectivesPanel, new WidgetArgs()); + Game.LoadWidget(world, panel, objectivesPanel, new WidgetArgs() + { + { "hideMenu", hideMenu } + }); if (activePanel == IngameInfoPanel.AutoSelect) activePanel = IngameInfoPanel.Objectives; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs index cc85dcdcc7..13f440d4b6 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoStatsLogic.cs @@ -9,6 +9,7 @@ */ #endregion +using System; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; @@ -22,7 +23,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic class GameInfoStatsLogic : ChromeLogic { [ObjectCreator.UseCtor] - public GameInfoStatsLogic(Widget widget, World world, OrderManager orderManager, WorldRenderer worldRenderer) + public GameInfoStatsLogic(Widget widget, World world, OrderManager orderManager, WorldRenderer worldRenderer, Action hideMenu) { var player = world.LocalPlayer; var playerPanel = widget.Get("PLAYER_LIST"); @@ -61,6 +62,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var teamTemplate = playerPanel.Get("TEAM_TEMPLATE"); var playerTemplate = playerPanel.Get("PLAYER_TEMPLATE"); + var spectatorTemplate = playerPanel.Get("SPECTATOR_TEMPLATE"); playerPanel.RemoveChildren(); var teams = world.Players.Where(p => !p.NonCombatant && p.Playable) @@ -137,7 +139,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic foreach (var client in spectators) { - var item = playerTemplate.Clone(); + var item = spectatorTemplate.Clone(); LobbyUtils.SetupProfileWidget(item, client, orderManager, worldRenderer); var nameLabel = item.Get("NAME"); @@ -153,7 +155,23 @@ namespace OpenRA.Mods.Common.Widgets.Logic return name.Update(Pair.New(client.Name, suffix)); }; - item.Get("FACTIONFLAG").IsVisible = () => false; + 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( + title: "Kick {0}?".F(client.Name), + text: "They will not be able to rejoin this game.", + onConfirm: () => + { + orderManager.IssueOrder(Order.Command("kick {0} {1}".F(client.Index, false))); + hideMenu(false); + }, + onCancel: () => hideMenu(false), + confirmText: "Kick"); + }; + playerPanel.AddChild(item); } } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs index 214c3cda45..e49719928a 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs @@ -104,9 +104,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic var panelRoot = widget.GetOrNull("PANEL_ROOT"); if (panelRoot != null && world.Type != WorldType.Editor) { + Action requestHideMenu = h => hideMenu = h; var gameInfoPanel = Game.LoadWidget(world, "GAME_INFO_PANEL", panelRoot, new WidgetArgs() { - { "activePanel", activePanel } + { "activePanel", activePanel }, + { "hideMenu", requestHideMenu } }); gameInfoPanel.IsVisible = () => !hideMenu; diff --git a/mods/cnc/chrome/ingame-infostats.yaml b/mods/cnc/chrome/ingame-infostats.yaml index 0abeb731d3..1600001b84 100644 --- a/mods/cnc/chrome/ingame-infostats.yaml +++ b/mods/cnc/chrome/ingame-infostats.yaml @@ -120,3 +120,36 @@ Container@SKIRMISH_STATS: Height: 25 Align: Right Shadow: True + Container@SPECTATOR_TEMPLATE: + Width: PARENT_RIGHT - 27 + Height: 25 + X: 2 + Children: + Image@PROFILE: + ImageCollection: lobby-bits + X: 8 + Y: 4 + Visible: false + ClientTooltipRegion@PROFILE_TOOLTIP: + X: 8 + Y: 4 + Width: 16 + Height: 16 + Visible: false + TooltipContainer: TOOLTIP_CONTAINER + Template: ANONYMOUS_PLAYER_TOOLTIP + Label@NAME: + X: 29 + Width: 166 + Height: 25 + Shadow: True + Button@KICK: + X: 195 + Width: 24 + Height: 25 + Children: + Image: + ImageCollection: lobby-bits + ImageName: kick + X: 6 + Y: 8 diff --git a/mods/common/chrome/ingame-infostats.yaml b/mods/common/chrome/ingame-infostats.yaml index cd5f7f8beb..f357155a39 100644 --- a/mods/common/chrome/ingame-infostats.yaml +++ b/mods/common/chrome/ingame-infostats.yaml @@ -118,3 +118,36 @@ Container@SKIRMISH_STATS: Height: 25 Align: Right Shadow: True + Container@SPECTATOR_TEMPLATE: + Width: PARENT_RIGHT - 27 + Height: 25 + X: 2 + Children: + Image@PROFILE: + ImageCollection: lobby-bits + X: 8 + Y: 4 + Visible: false + ClientTooltipRegion@PROFILE_TOOLTIP: + X: 8 + Y: 4 + Width: 16 + Height: 16 + Visible: false + TooltipContainer: TOOLTIP_CONTAINER + Template: ANONYMOUS_PLAYER_TOOLTIP + Label@NAME: + X: 29 + Width: 166 + Height: 25 + Shadow: True + Button@KICK: + X: 195 + Width: 24 + Height: 25 + Children: + Image: + ImageCollection: lobby-bits + ImageName: kick + X: 6 + Y: 8 diff --git a/mods/d2k/chrome/ingame-infostats.yaml b/mods/d2k/chrome/ingame-infostats.yaml index 80c9d7bed4..d6aa6d72f9 100644 --- a/mods/d2k/chrome/ingame-infostats.yaml +++ b/mods/d2k/chrome/ingame-infostats.yaml @@ -119,3 +119,36 @@ Container@SKIRMISH_STATS: Height: 25 Align: Right Shadow: True + Container@SPECTATOR_TEMPLATE: + Width: PARENT_RIGHT - 27 + Height: 25 + X: 2 + Children: + Image@PROFILE: + ImageCollection: lobby-bits + X: 8 + Y: 4 + Visible: false + ClientTooltipRegion@PROFILE_TOOLTIP: + X: 8 + Y: 4 + Width: 16 + Height: 16 + Visible: false + TooltipContainer: TOOLTIP_CONTAINER + Template: ANONYMOUS_PLAYER_TOOLTIP + Label@NAME: + X: 29 + Width: 166 + Height: 25 + Shadow: True + Button@KICK: + X: 195 + Width: 24 + Height: 25 + Children: + Image: + ImageCollection: lobby-bits + ImageName: kick + X: 6 + Y: 8