diff --git a/AUTHORS b/AUTHORS index d6e0facd88..47f94b22f3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -49,6 +49,7 @@ Also thanks to: * Maarten Meuris (Nyerguds) * Mark Olson (markolson) * Matthew Gatland (mgatland) + * Matthew Uzzell (MUzzell) * Max621 * Max Ugrumov (katzsmile) * Nukem diff --git a/CHANGELOG b/CHANGELOG index 6c42fb1658..6e1ec18a43 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ NEW: Fixed units staying selected and contributing to control groups when becoming cloaked or hidden in fog. Swapped the cursors used for sabotaging and capturing buildings/units. Added Ctrl+T shortcut for selection of all units matching the types of the currently selected ones across the screen and map. + Added a toggle spectators to multiplayer. Dune 2000: Added the Atreides grenadier from the 1.06 patch. Added randomized tiles for Sand and Rock terrain. diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index ca635729bb..c032e45340 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -127,6 +127,7 @@ namespace OpenRA.Network public int RandomSeed = 0; public bool FragileAlliances = false; // Allow diplomatic stance changes after game start. public bool AllowCheats = false; + public bool AllowSpectators = true; public bool Dedicated; public string Difficulty; public bool Crates = true; diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 8e5781d691..38ec7eaef2 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -276,6 +276,13 @@ namespace OpenRA.Server IsAdmin = !LobbyInfo.Clients.Any(c1 => c1.IsAdmin) }; + if (client.IsObserver && !LobbyInfo.GlobalSettings.AllowSpectators) + { + SendOrderTo(newConn, "ServerError", "The game is full"); + DropClient(newConn); + return; + } + if (client.Slot != null) SyncClientToPlayerReference(client, Map.Players[client.Slot]); else diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index e76aa1ef5c..5cf28264a5 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -390,6 +390,7 @@ + diff --git a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs index 1746fd7211..37ef9d3b4f 100644 --- a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs @@ -132,14 +132,33 @@ namespace OpenRA.Mods.RA.Server return true; }}, + { "allow_spectators", + s => + { + if (bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllowSpectators)) + { + server.SyncLobbyInfo(); + return true; + } + else + { + server.SendOrderTo(conn, "Message", "Malformed allow_spectate command"); + return true; + } + }}, { "spectate", s => { - client.Slot = null; - client.SpawnPoint = 0; - client.Color = HSLColor.FromRGB(255, 255, 255); - server.SyncLobbyInfo(); - return true; + if (server.LobbyInfo.GlobalSettings.AllowSpectators || client.IsAdmin) + { + client.Slot = null; + client.SpawnPoint = 0; + client.Color = HSLColor.FromRGB(255, 255, 255); + server.SyncLobbyInfo(); + return true; + } + else + return false; }}, { "slot_close", s => diff --git a/OpenRA.Mods.RA/Widgets/Logic/KickSpectatorsLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/KickSpectatorsLogic.cs new file mode 100644 index 0000000000..77f8f3995e --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/KickSpectatorsLogic.cs @@ -0,0 +1,36 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using OpenRA.Widgets; + +namespace OpenRA.Mods.RA.Widgets.Logic +{ + class KickSpectatorsLogic + { + [ObjectCreator.UseCtor] + public KickSpectatorsLogic(Widget widget, string clientCount, Action okPressed, Action cancelPressed) + { + widget.Get("TEXT").GetText = () => "Are you sure you want to kick {0} spectators?".F(clientCount); + + widget.Get("OK_BUTTON").OnClick = () => + { + widget.Parent.RemoveChild(widget); + okPressed(); + }; + + widget.Get("CANCEL_BUTTON").OnClick = () => + { + widget.Parent.RemoveChild(widget); + cancelPressed(); + }; + } + } +} diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 3d75b073f0..bd39887338 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -38,7 +38,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic readonly Action OnGameStart; readonly Action onExit; - readonly OrderManager orderManager; + readonly OrderManager orderManager; + readonly bool skirmishMode; // Listen for connection failures void ConnectionStateChanged(OrderManager om) @@ -54,7 +55,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { { "onExit", onExit }, { "onStart", OnGameStart }, - { "addBots", false } + { "skirmishMode", false } }); }; @@ -85,12 +86,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic [ObjectCreator.UseCtor] internal LobbyLogic(Widget widget, World world, OrderManager orderManager, - Action onExit, Action onStart, bool addBots) + Action onExit, Action onStart, bool skirmishMode) { lobby = widget; this.orderManager = orderManager; this.OnGameStart = () => { CloseWindow(); onStart(); }; - this.onExit = onExit; + this.onExit = onExit; + this.skirmishMode = skirmishMode; Game.LobbyInfoChanged += UpdateCurrentMap; Game.LobbyInfoChanged += UpdatePlayerList; @@ -470,7 +472,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { { "onExit", () => {} } }); // Add a bot on the first lobbyinfo update - if (addBots) + if (this.skirmishMode) Game.LobbyInfoChanged += WidgetUtils.Once(() => { var slot = orderManager.LobbyInfo.FirstEmptySlot(); @@ -662,11 +664,17 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (spec == null || spec.Id != NewSpectatorTemplate.Id) spec = NewSpectatorTemplate.Clone(); + LobbyUtils.SetupKickSpectatorsWidget(spec, orderManager, lobby, + () => panel = PanelType.Kick, () => panel = PanelType.Players, this.skirmishMode); + var btn = spec.Get("SPECTATE"); btn.OnClick = () => orderManager.IssueOrder(Order.Command("spectate")); btn.IsDisabled = () => orderManager.LocalClient.IsReady; - spec.IsVisible = () => true; + btn.IsVisible = () => orderManager.LobbyInfo.GlobalSettings.AllowSpectators + || orderManager.LocalClient.IsAdmin; + spec.IsVisible = () => true; + if (idx >= Players.Children.Count) Players.AddChild(spec); else if (Players.Children[idx].Id != spec.Id) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs index 6b58d45abc..76f3e5fa13 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs @@ -293,6 +293,47 @@ namespace OpenRA.Mods.RA.Widgets.Logic }; } + public static void SetupKickSpectatorsWidget(Widget parent, OrderManager orderManager, Widget lobby, Action before, Action after, bool skirmishMode) + { + var checkBox = parent.Get("TOGGLE_SPECTATORS"); + checkBox.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllowSpectators; + checkBox.IsVisible = () => orderManager.LocalClient.IsAdmin && !skirmishMode; + checkBox.IsDisabled = () => false; + + Action okPressed = () => + { + orderManager.IssueOrder(Order.Command("allow_spectators {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllowSpectators))); + orderManager.IssueOrders( + orderManager.LobbyInfo.Clients.Where( + c => c.IsObserver && !c.IsAdmin).Select( + client => Order.Command(String.Format("kick {0} {1}", client.Index, client.Name + ))).ToArray()); + + after(); + }; + + checkBox.OnClick = () => + { + before(); + + int spectatorCount = orderManager.LobbyInfo.Clients.Count(c => c.IsObserver); + if (spectatorCount > 0) + { + Game.LoadWidget(null, "KICK_SPECTATORS_DIALOG", lobby, new WidgetArgs + { + { "clientCount", "{0}".F(spectatorCount) }, + { "okPressed", okPressed }, + { "cancelPressed", after } + }); + } + else + { + orderManager.IssueOrder(Order.Command("allow_spectators {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllowSpectators))); + after(); + } + }; + } + public static void SetupEditableColorWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, ColorPreviewManagerWidget colorPreview) { var color = parent.Get("COLOR"); diff --git a/OpenRA.Mods.RA/Widgets/Logic/MainMenuLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/MainMenuLogic.cs index 9d000a3463..14d5d65ecb 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/MainMenuLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/MainMenuLogic.cs @@ -123,7 +123,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { { "onExit", () => { Game.Disconnect(); menuType = MenuType.Main; } }, { "onStart", RemoveShellmapUI }, - { "addBots", true } + { "skirmishMode", true } }); } diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index b8abe157ea..7787ea071d 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -117,7 +117,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { { "onExit", Game.Disconnect }, { "onStart", onStart }, - { "addBots", false } + { "skirmishMode", false } }); } diff --git a/mods/cnc/chrome/dialogs.yaml b/mods/cnc/chrome/dialogs.yaml index ea64b03ece..1ac590a989 100644 --- a/mods/cnc/chrome/dialogs.yaml +++ b/mods/cnc/chrome/dialogs.yaml @@ -199,6 +199,44 @@ Background@KICK_CLIENT_DIALOG: Height:25 Text:Cancel Font:Bold + +Background@KICK_SPECTATORS_DIALOG: + X:15 + Y:30 + Width:501 + Height:219 + Logic:KickSpectatorsLogic + Background:scrollpanel-bg + Children: + Label@TITLE: + X:0 + Y:40 + Width:PARENT_RIGHT + Height:25 + Font:Bold + Align:Center + Text:Kick Spectators + Label@TEXT: + X:0 + Y:85 + Width:PARENT_RIGHT + Height:25 + Font:Regular + Align:Center + Button@OK_BUTTON: + X:(PARENT_RIGHT - WIDTH)/2 + 75 + Y:155 + Width:120 + Height:25 + Text:Ok + Font:Bold + Button@CANCEL_BUTTON: + X:(PARENT_RIGHT - WIDTH)/2 - 75 + Y:155 + Width:120 + Height:25 + Text:Cancel + Font:Bold Background@LOBBY_OPTIONS_BIN: X:15 diff --git a/mods/cnc/chrome/lobby-playerbin.yaml b/mods/cnc/chrome/lobby-playerbin.yaml index 83c877e61d..37ef7aba82 100644 --- a/mods/cnc/chrome/lobby-playerbin.yaml +++ b/mods/cnc/chrome/lobby-playerbin.yaml @@ -307,10 +307,17 @@ ScrollPanel@LOBBY_PLAYER_BIN: Height:25 Visible:false Children: + Checkbox@TOGGLE_SPECTATORS: + Font:Regular + Width:190 + Height:20 + X:15 + Y:0 + Text:Allow Spectators? Button@SPECTATE: Text:Spectate Font:Regular - Width:453 + Width:257 Height:25 - X:15 + X:210 Y:0 \ No newline at end of file diff --git a/mods/d2k/chrome/lobby-playerbin.yaml b/mods/d2k/chrome/lobby-playerbin.yaml index f64d6ed24f..81e08b6a75 100644 --- a/mods/d2k/chrome/lobby-playerbin.yaml +++ b/mods/d2k/chrome/lobby-playerbin.yaml @@ -298,6 +298,13 @@ ScrollPanel@LOBBY_PLAYER_BIN: Height:25 Visible:false Children: + Checkbox@TOGGLE_SPECTATORS: + Font:Regular + Width:165 + Height:20 + X:15 + Y:0 + Text:Allow Spectators? Button@SPECTATE: Text:Spectate Font:Regular diff --git a/mods/ra/chrome/lobby-dialogs.yaml b/mods/ra/chrome/lobby-dialogs.yaml index ae7b4e0c74..cb266f2f7d 100644 --- a/mods/ra/chrome/lobby-dialogs.yaml +++ b/mods/ra/chrome/lobby-dialogs.yaml @@ -50,6 +50,44 @@ Background@KICK_CLIENT_DIALOG: Text:Cancel Font:Bold +Background@KICK_SPECTATORS_DIALOG: + X:20 + Y:67 + Width:535 + Height:235 + Logic:KickSpectatorsLogic + Background:dialog3 + Children: + Label@TITLE: + X:0 + Y:40 + Width:PARENT_RIGHT + Height:25 + Font:Bold + Align:Center + Text:Kick Spectators + Label@TEXT: + X:0 + Y:85 + Width:PARENT_RIGHT + Height:25 + Font:Regular + Align:Center + Button@OK_BUTTON: + X:(PARENT_RIGHT - WIDTH)/2 + 75 + Y:155 + Width:120 + Height:25 + Text:Ok + Font:Bold + Button@CANCEL_BUTTON: + X:(PARENT_RIGHT - WIDTH)/2 - 75 + Y:155 + Width:120 + Height:25 + Text:Cancel + Font:Bold + Background@LOBBY_OPTIONS_BIN: X:20 Y:67 diff --git a/mods/ra/chrome/lobby-playerbin.yaml b/mods/ra/chrome/lobby-playerbin.yaml index c6d2c6b88c..cb2285c67c 100644 --- a/mods/ra/chrome/lobby-playerbin.yaml +++ b/mods/ra/chrome/lobby-playerbin.yaml @@ -298,6 +298,13 @@ ScrollPanel@LOBBY_PLAYER_BIN: Height:25 Visible:false Children: + Checkbox@TOGGLE_SPECTATORS: + Font:Regular + Width:165 + Height:20 + X:15 + Y:0 + Text:Allow Spectators? Button@SPECTATE: Text:Spectate Font:Regular