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