diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 7b5861938e..718339d158 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -90,9 +90,14 @@ namespace OpenRA.Network return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null && s.Value.AllowBots).Key; } - public bool IsSinglePlayer + public IEnumerable NonBotClients { - get { return Clients.Count(c => c.Bot == null) == 1; } + get { return Clients.Where(c => c.Bot == null); } + } + + public IEnumerable NonBotPlayers + { + get { return Clients.Where(c => c.Bot == null && c.Slot != null); } } public enum ClientState { NotReady, Invalid, Ready, Disconnected = 1000 } diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index 4eb2af26f3..a698430674 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -118,7 +118,7 @@ namespace OpenRA.Network if (client != null) { var pause = order.TargetString == "Pause"; - if (orderManager.World.Paused != pause && world != null && !world.LobbyInfo.IsSinglePlayer) + if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1) { var pausetext = "The game is {0} by {1}".F(pause ? "paused" : "un-paused", client.Name); Game.AddChatLine(Color.White, ServerChatName, pausetext); diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 6a67af4f4a..6c7d1e7648 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -375,7 +375,7 @@ namespace OpenRA.Server Log.Write("server", "{0} ({1}) has joined the game.", client.Name, newConn.Socket.RemoteEndPoint); - if (Dedicated || !LobbyInfo.IsSinglePlayer) + if (LobbyInfo.NonBotClients.Count() > 1) SendMessage("{0} has joined the game.".F(client.Name)); // Send initial ping @@ -392,7 +392,7 @@ namespace OpenRA.Server SendOrderTo(newConn, "Message", motd); } - if (!LobbyInfo.IsSinglePlayer && Map.DefinesUnsafeCustomRules) + if (Map.DefinesUnsafeCustomRules) SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change."); if (!LobbyInfo.GlobalSettings.EnableSingleplayer) @@ -679,7 +679,7 @@ namespace OpenRA.Server } // HACK: Turn down the latency if there is only one real player - if (LobbyInfo.IsSinglePlayer) + if (LobbyInfo.NonBotClients.Count() == 1) LobbyInfo.GlobalSettings.OrderLatency = 1; SyncLobbyInfo(); diff --git a/OpenRA.Game/Traits/Player/DeveloperMode.cs b/OpenRA.Game/Traits/Player/DeveloperMode.cs index f53cbb2f6b..0f044447b5 100644 --- a/OpenRA.Game/Traits/Player/DeveloperMode.cs +++ b/OpenRA.Game/Traits/Player/DeveloperMode.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Linq; namespace OpenRA.Traits { @@ -113,7 +114,7 @@ namespace OpenRA.Traits void INotifyCreated.Created(Actor self) { - Enabled = self.World.LobbyInfo.IsSinglePlayer || self.World.LobbyInfo.GlobalSettings + Enabled = self.World.LobbyInfo.NonBotPlayers.Count() == 1 || self.World.LobbyInfo.GlobalSettings .OptionOrDefault("cheats", info.Enabled); } diff --git a/OpenRA.Mods.Common/Scripting/Global/MapGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/MapGlobal.cs index 6ba54ea926..01de5f2215 100644 --- a/OpenRA.Mods.Common/Scripting/Global/MapGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/MapGlobal.cs @@ -108,7 +108,7 @@ namespace OpenRA.Mods.Common.Scripting } [Desc("Returns true if there is only one human player.")] - public bool IsSinglePlayer { get { return Context.World.LobbyInfo.IsSinglePlayer; } } + public bool IsSinglePlayer { get { return Context.World.LobbyInfo.NonBotPlayers.Count() == 1; } } [Desc("Returns the difficulty selected by the player before starting the mission.")] public string Difficulty diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index 9fffa25a2b..016258ab58 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -58,21 +58,21 @@ namespace OpenRA.Mods.Common.Server static void CheckAutoStart(S server) { - // A spectating admin is included for checking these rules - var playerClients = server.LobbyInfo.Clients.Where(c => (c.Bot == null && c.Slot != null) || c.IsAdmin); + var nonBotPlayers = server.LobbyInfo.NonBotPlayers; - // Are all players ready? - if (!playerClients.Any() || playerClients.Any(c => c.State != Session.ClientState.Ready)) + // Are all players and admin (could be spectating) ready? + if (nonBotPlayers.Any(c => c.State != Session.ClientState.Ready) || + server.LobbyInfo.Clients.First(c => c.IsAdmin).State != Session.ClientState.Ready) + return; + + // Does server have at least 2 human players? + if (!server.LobbyInfo.GlobalSettings.EnableSingleplayer && nonBotPlayers.Count() < 2) return; // Are the map conditions satisfied? if (server.LobbyInfo.Slots.Any(sl => sl.Value.Required && server.LobbyInfo.ClientInSlot(sl.Key) == null)) return; - // Does server have only one player? - if (!server.LobbyInfo.GlobalSettings.EnableSingleplayer && playerClients.Count() == 1) - return; - server.StartGame(); } @@ -121,8 +121,7 @@ namespace OpenRA.Mods.Common.Server return true; } - if (!server.LobbyInfo.GlobalSettings.EnableSingleplayer && - server.LobbyInfo.Clients.Where(c => c.Bot == null && c.Slot != null).Count() == 1) + if (!server.LobbyInfo.GlobalSettings.EnableSingleplayer && server.LobbyInfo.NonBotPlayers.Count() < 2) { server.SendOrderTo(conn, "Message", server.TwoHumansRequiredText); return true; diff --git a/OpenRA.Mods.Common/ServerTraits/PlayerPinger.cs b/OpenRA.Mods.Common/ServerTraits/PlayerPinger.cs index 623fec9710..5cbd256473 100644 --- a/OpenRA.Mods.Common/ServerTraits/PlayerPinger.cs +++ b/OpenRA.Mods.Common/ServerTraits/PlayerPinger.cs @@ -36,7 +36,7 @@ namespace OpenRA.Mods.Common.Server lastPing = Game.RunTime; // Ignore client timeout in singleplayer games to make debugging easier - if (server.LobbyInfo.IsSinglePlayer && !server.Dedicated) + if (server.LobbyInfo.NonBotClients.Count() < 2 && !server.Dedicated) foreach (var c in server.Conns.ToList()) server.SendOrderTo(c, "Ping", Game.RunTime.ToString()); else diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs index 08ed0b8e7f..ac618eafca 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs @@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic chatTraits = world.WorldActor.TraitsImplementing().ToArray(); var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot); - disableTeamChat = world.IsReplay || world.LobbyInfo.IsSinglePlayer || (world.LocalPlayer != null && !players.Any(p => p.IsAlliedWith(world.LocalPlayer))); + disableTeamChat = world.IsReplay || world.LobbyInfo.NonBotClients.Count() == 1 || (world.LocalPlayer != null && !players.Any(p => p.IsAlliedWith(world.LocalPlayer))); teamChat = !disableTeamChat; tabCompletion.Commands = chatTraits.OfType().SelectMany(x => x.Commands.Keys).ToList(); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs index d6a11c67aa..32f6503522 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs @@ -97,7 +97,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var iop = world.WorldActor.TraitsImplementing().FirstOrDefault(); var exitDelay = iop != null ? iop.ExitDelay : 0; - if (world.LobbyInfo.IsSinglePlayer) + if (world.LobbyInfo.NonBotClients.Count() == 1) { restartAction = () => { diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/MenuButtonsChromeLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/MenuButtonsChromeLogic.cs index 49005ea520..24ef636aca 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/MenuButtonsChromeLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/MenuButtonsChromeLogic.cs @@ -102,7 +102,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic worldRoot.IsVisible = () => false; } - if (button.Pause && world.LobbyInfo.IsSinglePlayer) + if (button.Pause && world.LobbyInfo.NonBotClients.Count() == 1) world.SetPauseState(true); var cachedDisableWorldSounds = Game.Sound.DisableWorldSounds; @@ -118,7 +118,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (button.DisableWorldSounds) Game.Sound.DisableWorldSounds = cachedDisableWorldSounds; - if (button.Pause && world.LobbyInfo.IsSinglePlayer) + if (button.Pause && world.LobbyInfo.NonBotClients.Count() == 1) world.SetPauseState(cachedPause); menuRoot.RemoveChild(currentWidget); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs index cdbefa99aa..c2bccd7d6e 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs @@ -322,7 +322,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { startGameButton.IsDisabled = () => configurationDisabled() || map.Status != MapStatus.Available || orderManager.LobbyInfo.Slots.Any(sl => sl.Value.Required && orderManager.LobbyInfo.ClientInSlot(sl.Key) == null) || - (!orderManager.LobbyInfo.GlobalSettings.EnableSingleplayer && orderManager.LobbyInfo.IsSinglePlayer); + (!orderManager.LobbyInfo.GlobalSettings.EnableSingleplayer && orderManager.LobbyInfo.NonBotPlayers.Count() < 2); startGameButton.OnClick = () => {