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 0756da7fed..c032e45340 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -127,7 +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 AllowSpectate = true; + 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 276540470b..38ec7eaef2 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -276,7 +276,7 @@ namespace OpenRA.Server IsAdmin = !LobbyInfo.Clients.Any(c1 => c1.IsAdmin) }; - if (client.IsObserver && !LobbyInfo.GlobalSettings.AllowSpectate) + if (client.IsObserver && !LobbyInfo.GlobalSettings.AllowSpectators) { SendOrderTo(newConn, "ServerError", "The game is full"); DropClient(newConn); @@ -324,7 +324,7 @@ namespace OpenRA.Server LobbyInfo.Clients.Add(client); Log.Write("server", "Client {0}: Accepted connection from {1}.", - newConn.PlayerIndex, newConn.socket.RemoteEndPoint); + newConn.PlayerIndex, newConn.socket.RemoteEndPoint); foreach (var t in serverTraits.WithInterface()) t.ClientJoined(this, newConn); 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 8252e4346a..37ef9d3b4f 100644 --- a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs @@ -1,743 +1,744 @@ -#region Copyright & License Information -/* - * Copyright 2007-2011 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 System.Collections.Generic; -using System.Linq; -using OpenRA.Network; -using OpenRA.FileFormats; -using OpenRA.Server; -using S = OpenRA.Server.Server; - -namespace OpenRA.Mods.RA.Server -{ - public class LobbyCommands : ServerTrait, IInterpretCommand, INotifyServerStart - { - static bool ValidateSlotCommand(S server, Connection conn, Session.Client client, string arg, bool requiresHost) - { - if (!server.LobbyInfo.Slots.ContainsKey(arg)) - { - Log.Write("server", "Invalid slot: {0}", arg); - return false; - } - - if (requiresHost && !client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can do that"); - return false; - } - - return true; - } - - public static bool ValidateCommand(S server, Connection conn, Session.Client client, string cmd) - { - if (server.State == ServerState.GameStarted) - { - server.SendOrderTo(conn, "Message", "Cannot change state when game started. ({0})".F(cmd)); - return false; - } - else if (client.State == Session.ClientState.Ready && !(cmd == "ready" || cmd == "startgame")) - { - server.SendOrderTo(conn, "Message", "Cannot change state when marked as ready."); - return false; - } - - return true; - } - - void CheckAutoStart(S server, Connection conn, Session.Client client) - { - var playerClients = server.LobbyInfo.Clients.Where(c => c.Bot == null && c.Slot != null); - - // Are all players ready? - if (playerClients.Count() == 0 || playerClients.Any(c => c.State != Session.ClientState.Ready)) - return; - - // Are the map conditions satisfied? - if (server.LobbyInfo.Slots.Any(sl => sl.Value.Required && server.LobbyInfo.ClientInSlot(sl.Key) == null)) - return; - - server.StartGame(); - } - - public bool InterpretCommand(S server, Connection conn, Session.Client client, string cmd) - { - if (!ValidateCommand(server, conn, client, cmd)) - return false; - - var dict = new Dictionary> - { - { "ready", - s => - { - // if we're downloading, we can't ready up. - if (client.State == Session.ClientState.NotReady) - client.State = Session.ClientState.Ready; - else if (client.State == Session.ClientState.Ready) - client.State = Session.ClientState.NotReady; - - Log.Write("server", "Player @{0} is {1}", - conn.socket.RemoteEndPoint, client.State); - - server.SyncLobbyInfo(); - - CheckAutoStart(server, conn, client); - - return true; - }}, - { "startgame", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can start the game"); - return true; - } - - if (server.LobbyInfo.Slots.Any(sl => sl.Value.Required && - server.LobbyInfo.ClientInSlot(sl.Key) == null)) - { - server.SendOrderTo(conn, "Message", "Unable to start the game until required slots are full."); - return true; - } - server.StartGame(); - return true; - }}, - { "slot", - s => - { - if (!server.LobbyInfo.Slots.ContainsKey(s)) - { - Log.Write("server", "Invalid slot: {0}", s ); - return false; - } - var slot = server.LobbyInfo.Slots[s]; - - if (slot.Closed || server.LobbyInfo.ClientInSlot(s) != null) - return false; - - client.Slot = s; - S.SyncClientToPlayerReference(client, server.Map.Players[s]); - - server.SyncLobbyInfo(); - CheckAutoStart(server, conn, client); - - return true; - }}, - { "allow_spectate", - s => - { - s = s.Trim(); - if(s.Equals("True") || s.Equals("False")){ - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllowSpectate); - - server.SyncLobbyInfo(); - return true; - }else{ - server.SendOrderTo(conn, "Message", "Malformed allow_spectate command"); - return true; - } - }}, - { "spectate", - s => - { - if(server.LobbyInfo.GlobalSettings.AllowSpectate){ - client.Slot = null; - client.SpawnPoint = 0; - client.Color = HSLColor.FromRGB(255, 255, 255); - server.SyncLobbyInfo(); - return true; - }else{ - return false; - } - }}, - { "slot_close", - s => - { - if (!ValidateSlotCommand( server, conn, client, s, true )) - return false; - - // kick any player that's in the slot - var occupant = server.LobbyInfo.ClientInSlot(s); - if (occupant != null) - { - if (occupant.Bot != null) - server.LobbyInfo.Clients.Remove(occupant); - else - { - var occupantConn = server.Conns.FirstOrDefault( c => c.PlayerIndex == occupant.Index ); - if (occupantConn != null) - { - server.SendOrderTo(occupantConn, "ServerError", "Your slot was closed by the host"); - server.DropClient(occupantConn); - } - } - } - - server.LobbyInfo.Slots[s].Closed = true; - server.SyncLobbyInfo(); - return true; - }}, - { "slot_open", - s => - { - if (!ValidateSlotCommand( server, conn, client, s, true )) - return false; - - var slot = server.LobbyInfo.Slots[s]; - slot.Closed = false; - - // Slot may have a bot in it - var occupant = server.LobbyInfo.ClientInSlot(s); - if (occupant != null && occupant.Bot != null) - server.LobbyInfo.Clients.Remove(occupant); - - server.SyncLobbyInfo(); - return true; - }}, - { "slot_bot", - s => - { - var parts = s.Split(' '); - - if (parts.Length < 3) - { - server.SendOrderTo(conn, "Message", "Malformed slot_bot command"); - return true; - } - - if (!ValidateSlotCommand(server, conn, client, parts[0], true)) - return false; - - var slot = server.LobbyInfo.Slots[parts[0]]; - var bot = server.LobbyInfo.ClientInSlot(parts[0]); - int controllerClientIndex; - if (!int.TryParse(parts[1], out controllerClientIndex)) - { - Log.Write("server", "Invalid bot controller client index: {0}", parts[1]); - return false; - } - var botType = parts.Skip(2).JoinWith(" "); - - // Invalid slot - if (bot != null && bot.Bot == null) - { - server.SendOrderTo(conn, "Message", "Can't add bots to a slot with another client"); - return true; - } - - slot.Closed = false; - if (bot == null) - { - // Create a new bot - bot = new Session.Client() - { - Index = server.ChooseFreePlayerIndex(), - Name = botType, - Bot = botType, - Slot = parts[0], - Country = "random", - SpawnPoint = 0, - Team = 0, - State = Session.ClientState.NotReady, - BotControllerClientIndex = controllerClientIndex - }; - - // pick a random color for the bot - var hue = (byte)server.Random.Next(255); - var sat = (byte)server.Random.Next(255); - var lum = (byte)server.Random.Next(51,255); - bot.Color = bot.PreferredColor = new HSLColor(hue, sat, lum); - - server.LobbyInfo.Clients.Add(bot); - } - else - { - // Change the type of the existing bot - bot.Name = botType; - bot.Bot = botType; - } - - S.SyncClientToPlayerReference(bot, server.Map.Players[parts[0]]); - server.SyncLobbyInfo(); - return true; - }}, - { "map", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can change the map"); - return true; - } - - if (!server.ModData.AvailableMaps.ContainsKey(s)) - { - server.SendOrderTo(conn, "Message", "Map was not found on server"); - return true; - } - server.LobbyInfo.GlobalSettings.Map = s; - var oldSlots = server.LobbyInfo.Slots.Keys.ToArray(); - LoadMap(server); - SetDefaultDifficulty(server); - - // Reassign players into new slots based on their old slots: - // - Observers remain as observers - // - Players who now lack a slot are made observers - // - Bots who now lack a slot are dropped - var slots = server.LobbyInfo.Slots.Keys.ToArray(); - int i = 0; - foreach (var os in oldSlots) - { - var c = server.LobbyInfo.ClientInSlot(os); - if (c == null) - continue; - - c.SpawnPoint = 0; - c.State = Session.ClientState.NotReady; - c.Slot = i < slots.Length ? slots[i++] : null; - if (c.Slot != null) - { - // Remove Bot from slot if slot forbids bots - if (c.Bot != null && !server.Map.Players[c.Slot].AllowBots) - server.LobbyInfo.Clients.Remove(c); - S.SyncClientToPlayerReference(c, server.Map.Players[c.Slot]); - } - else if (c.Bot != null) - server.LobbyInfo.Clients.Remove(c); - } - - server.SyncLobbyInfo(); - return true; - }}, - { "fragilealliance", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (server.Map.Options.FragileAlliances.HasValue) - { - server.SendOrderTo(conn, "Message", "Map has disabled alliance configuration"); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.FragileAlliances); - server.SyncLobbyInfo(); - return true; - }}, - { "allowcheats", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (server.Map.Options.Cheats.HasValue) - { - server.SendOrderTo(conn, "Message", "Map has disabled cheat configuration"); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllowCheats); - server.SyncLobbyInfo(); - return true; - }}, - { "shroud", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (server.Map.Options.Shroud.HasValue) - { - server.SendOrderTo(conn, "Message", "Map has disabled shroud configuration"); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Shroud); - server.SyncLobbyInfo(); - return true; - }}, - { "fog", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (server.Map.Options.Fog.HasValue) - { - server.SendOrderTo(conn, "Message", "Map has disabled fog configuration"); - return true; - } - - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Fog); - server.SyncLobbyInfo(); - return true; - }}, - { "assignteams", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - int teamCount; - if (!int.TryParse(s, out teamCount)) - { - server.SendOrderTo(conn, "Message", "Number of teams could not be parsed: {0}".F(s)); - return true; - } - - var maxTeams = (server.LobbyInfo.Clients.Count(c => c.Slot != null) + 1) / 2; - teamCount = teamCount.Clamp(0, maxTeams); - var players = server.LobbyInfo.Slots - .Select(slot => server.LobbyInfo.ClientInSlot(slot.Key)) - .Where(c => c != null && !server.LobbyInfo.Slots[c.Slot].LockTeam); - - var assigned = 0; - var playerCount = players.Count(); - foreach (var player in players) - { - // Free for all - if (teamCount == 0) - player.Team = 0; - - // Humans vs Bots - else if (teamCount == 1) - player.Team = player.Bot == null ? 1 : 2; - - else - player.Team = assigned++ * teamCount / playerCount + 1; - } - - server.SyncLobbyInfo(); - return true; - }}, - { "crates", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (server.Map.Options.Crates.HasValue) - { - server.SendOrderTo(conn, "Message", "Map has disabled crate configuration"); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Crates); - server.SyncLobbyInfo(); - return true; - }}, - { "allybuildradius", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (server.Map.Options.AllyBuildRadius.HasValue) - { - server.SendOrderTo(conn, "Message", "Map has disabled ally build radius configuration"); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllyBuildRadius); - server.SyncLobbyInfo(); - return true; - }}, - { "difficulty", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (s != null && !server.Map.Options.Difficulties.Contains(s)) - { - server.SendOrderTo(conn, "Message", "Unsupported difficulty selected: {0}".F(s)); - server.SendOrderTo(conn, "Message", "Supported difficulties: {0}".F(server.Map.Options.Difficulties.JoinWith(","))); - return true; - } - - server.LobbyInfo.GlobalSettings.Difficulty = s; - server.SyncLobbyInfo(); - return true; - }}, - { "startingunits", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (!server.Map.Options.ConfigurableStartingUnits) - { - server.SendOrderTo(conn, "Message", "Map has disabled start unit configuration"); - return true; - } - - server.LobbyInfo.GlobalSettings.StartingUnitsClass = s; - server.SyncLobbyInfo(); - return true; - }}, - { "startingcash", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option"); - return true; - } - - if (server.Map.Options.StartingCash.HasValue) - { - server.SendOrderTo(conn, "Message", "Map has disabled cash configuration"); - return true; - } - - server.LobbyInfo.GlobalSettings.StartingCash = int.Parse(s); - server.SyncLobbyInfo(); - return true; - }}, - { "kick", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can kick players"); - return true; - } - - var split = s.Split(' '); - if (split.Length < 2) - { - server.SendOrderTo(conn, "Message", "Malformed kick command"); - return true; - } - - int kickClientID; - int.TryParse(split[0], out kickClientID); - - var kickConn = server.Conns.SingleOrDefault(c => server.GetClient(c) != null && server.GetClient(c).Index == kickClientID); - if (kickConn == null) - { - server.SendOrderTo(conn, "Message", "Noone in that slot."); - return true; - } - - var kickConnIP = server.GetClient(kickConn).IpAddress; - - Log.Write("server", "Kicking client {0} as requested", kickClientID); - server.SendOrderTo(kickConn, "ServerError", "You have been kicked from the server"); - server.DropClient(kickConn); - - bool tempBan; - bool.TryParse(split[1], out tempBan); - - if (tempBan) - { - Log.Write("server", "Temporarily banning client {0} ({1}) as requested", kickClientID, kickConnIP); - server.TempBans.Add(kickConnIP); - } - - server.SyncLobbyInfo(); - return true; - }}, - { "name", - s => - { - Log.Write("server", "Player@{0} is now known as {1}", conn.socket.RemoteEndPoint, s); - client.Name = s; - server.SyncLobbyInfo(); - return true; - }}, - { "race", - s => - { - var parts = s.Split(' '); - var targetClient = server.LobbyInfo.ClientWithIndex(int.Parse(parts[0])); - - // Only the host can change other client's info - if (targetClient.Index != client.Index && !client.IsAdmin) - return true; - - // Map has disabled race changes - if (server.LobbyInfo.Slots[targetClient.Slot].LockRace) - return true; - - targetClient.Country = parts[1]; - server.SyncLobbyInfo(); - return true; - }}, - { "team", - s => - { - var parts = s.Split(' '); - var targetClient = server.LobbyInfo.ClientWithIndex(int.Parse(parts[0])); - - // Only the host can change other client's info - if (targetClient.Index != client.Index && !client.IsAdmin) - return true; - - // Map has disabled team changes - if (server.LobbyInfo.Slots[targetClient.Slot].LockTeam) - return true; - - int team; - if (!int.TryParse(parts[1], out team)) - { - Log.Write("server", "Invalid team: {0}", s ); - return false; - } - - targetClient.Team = team; - server.SyncLobbyInfo(); - return true; - }}, - { "spawn", - s => - { - var parts = s.Split(' '); - var targetClient = server.LobbyInfo.ClientWithIndex(int.Parse(parts[0])); - - // Only the host can change other client's info - if (targetClient.Index != client.Index && !client.IsAdmin) - return true; - - // Spectators don't need a spawnpoint - if (targetClient.Slot == null) - return true; - - // Map has disabled spawn changes - if (server.LobbyInfo.Slots[targetClient.Slot].LockSpawn) - return true; - - int spawnPoint; - if (!int.TryParse(parts[1], out spawnPoint) || spawnPoint < 0 || spawnPoint > server.Map.GetSpawnPoints().Length) - { - Log.Write("server", "Invalid spawn point: {0}", parts[1]); - return true; - } - - if (server.LobbyInfo.Clients.Where( cc => cc != client ).Any( cc => (cc.SpawnPoint == spawnPoint) && (cc.SpawnPoint != 0) )) - { - server.SendOrderTo(conn, "Message", "You can't be at the same spawn point as another player"); - return true; - } - - targetClient.SpawnPoint = spawnPoint; - server.SyncLobbyInfo(); - return true; - }}, - { "color", - s => - { - var parts = s.Split(' '); - var targetClient = server.LobbyInfo.ClientWithIndex(int.Parse(parts[0])); - - // Only the host can change other client's info - if (targetClient.Index != client.Index && !client.IsAdmin) - return true; - - // Spectator or map has disabled color changes - if (targetClient.Slot == null || server.LobbyInfo.Slots[targetClient.Slot].LockColor) - return true; - - var ci = parts[1].Split(',').Select(cc => int.Parse(cc)).ToArray(); - targetClient.Color = targetClient.PreferredColor = new HSLColor((byte)ci[0], (byte)ci[1], (byte)ci[2]); - server.SyncLobbyInfo(); - return true; - }} - }; - - var cmdName = cmd.Split(' ').First(); - var cmdValue = cmd.Split(' ').Skip(1).JoinWith(" "); - - Func a; - if (!dict.TryGetValue(cmdName, out a)) - return false; - - return a(cmdValue); - } - - public void ServerStarted(S server) - { - LoadMap(server); - SetDefaultDifficulty(server); - } - - static Session.Slot MakeSlotFromPlayerReference(PlayerReference pr) - { - if (!pr.Playable) return null; - if (Game.Settings.Server.LockBots) - pr.AllowBots = false; - return new Session.Slot - { - PlayerReference = pr.Name, - Closed = false, - AllowBots = pr.AllowBots, - LockRace = pr.LockRace, - LockColor = pr.LockColor, - LockTeam = pr.LockTeam, - LockSpawn = pr.LockSpawn, - Required = pr.Required, - }; - } - - static void LoadMap(S server) - { - server.Map = new Map(server.ModData.AvailableMaps[server.LobbyInfo.GlobalSettings.Map].Path); - server.LobbyInfo.Slots = server.Map.Players - .Select(p => MakeSlotFromPlayerReference(p.Value)) - .Where(s => s != null) - .ToDictionary(s => s.PlayerReference, s => s); - - server.Map.Options.UpdateServerSettings(server.LobbyInfo.GlobalSettings); - } - - static void SetDefaultDifficulty(S server) - { - if (!server.Map.Options.Difficulties.Any()) - { - server.LobbyInfo.GlobalSettings.Difficulty = null; - return; - } - - if (!server.Map.Options.Difficulties.Contains(server.LobbyInfo.GlobalSettings.Difficulty)) - server.LobbyInfo.GlobalSettings.Difficulty = server.Map.Options.Difficulties.First(); - } - } -} +#region Copyright & License Information +/* + * Copyright 2007-2011 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 System.Collections.Generic; +using System.Linq; +using OpenRA.Network; +using OpenRA.FileFormats; +using OpenRA.Server; +using S = OpenRA.Server.Server; + +namespace OpenRA.Mods.RA.Server +{ + public class LobbyCommands : ServerTrait, IInterpretCommand, INotifyServerStart + { + static bool ValidateSlotCommand(S server, Connection conn, Session.Client client, string arg, bool requiresHost) + { + if (!server.LobbyInfo.Slots.ContainsKey(arg)) + { + Log.Write("server", "Invalid slot: {0}", arg); + return false; + } + + if (requiresHost && !client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can do that"); + return false; + } + + return true; + } + + public static bool ValidateCommand(S server, Connection conn, Session.Client client, string cmd) + { + if (server.State == ServerState.GameStarted) + { + server.SendOrderTo(conn, "Message", "Cannot change state when game started. ({0})".F(cmd)); + return false; + } + else if (client.State == Session.ClientState.Ready && !(cmd == "ready" || cmd == "startgame")) + { + server.SendOrderTo(conn, "Message", "Cannot change state when marked as ready."); + return false; + } + + return true; + } + + void CheckAutoStart(S server, Connection conn, Session.Client client) + { + var playerClients = server.LobbyInfo.Clients.Where(c => c.Bot == null && c.Slot != null); + + // Are all players ready? + if (playerClients.Count() == 0 || playerClients.Any(c => c.State != Session.ClientState.Ready)) + return; + + // Are the map conditions satisfied? + if (server.LobbyInfo.Slots.Any(sl => sl.Value.Required && server.LobbyInfo.ClientInSlot(sl.Key) == null)) + return; + + server.StartGame(); + } + + public bool InterpretCommand(S server, Connection conn, Session.Client client, string cmd) + { + if (!ValidateCommand(server, conn, client, cmd)) + return false; + + var dict = new Dictionary> + { + { "ready", + s => + { + // if we're downloading, we can't ready up. + if (client.State == Session.ClientState.NotReady) + client.State = Session.ClientState.Ready; + else if (client.State == Session.ClientState.Ready) + client.State = Session.ClientState.NotReady; + + Log.Write("server", "Player @{0} is {1}", + conn.socket.RemoteEndPoint, client.State); + + server.SyncLobbyInfo(); + + CheckAutoStart(server, conn, client); + + return true; + }}, + { "startgame", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can start the game"); + return true; + } + + if (server.LobbyInfo.Slots.Any(sl => sl.Value.Required && + server.LobbyInfo.ClientInSlot(sl.Key) == null)) + { + server.SendOrderTo(conn, "Message", "Unable to start the game until required slots are full."); + return true; + } + server.StartGame(); + return true; + }}, + { "slot", + s => + { + if (!server.LobbyInfo.Slots.ContainsKey(s)) + { + Log.Write("server", "Invalid slot: {0}", s ); + return false; + } + var slot = server.LobbyInfo.Slots[s]; + + if (slot.Closed || server.LobbyInfo.ClientInSlot(s) != null) + return false; + + client.Slot = s; + S.SyncClientToPlayerReference(client, server.Map.Players[s]); + + server.SyncLobbyInfo(); + CheckAutoStart(server, conn, client); + + 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 => + { + 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 => + { + if (!ValidateSlotCommand( server, conn, client, s, true )) + return false; + + // kick any player that's in the slot + var occupant = server.LobbyInfo.ClientInSlot(s); + if (occupant != null) + { + if (occupant.Bot != null) + server.LobbyInfo.Clients.Remove(occupant); + else + { + var occupantConn = server.Conns.FirstOrDefault( c => c.PlayerIndex == occupant.Index ); + if (occupantConn != null) + { + server.SendOrderTo(occupantConn, "ServerError", "Your slot was closed by the host"); + server.DropClient(occupantConn); + } + } + } + + server.LobbyInfo.Slots[s].Closed = true; + server.SyncLobbyInfo(); + return true; + }}, + { "slot_open", + s => + { + if (!ValidateSlotCommand( server, conn, client, s, true )) + return false; + + var slot = server.LobbyInfo.Slots[s]; + slot.Closed = false; + + // Slot may have a bot in it + var occupant = server.LobbyInfo.ClientInSlot(s); + if (occupant != null && occupant.Bot != null) + server.LobbyInfo.Clients.Remove(occupant); + + server.SyncLobbyInfo(); + return true; + }}, + { "slot_bot", + s => + { + var parts = s.Split(' '); + + if (parts.Length < 3) + { + server.SendOrderTo(conn, "Message", "Malformed slot_bot command"); + return true; + } + + if (!ValidateSlotCommand(server, conn, client, parts[0], true)) + return false; + + var slot = server.LobbyInfo.Slots[parts[0]]; + var bot = server.LobbyInfo.ClientInSlot(parts[0]); + int controllerClientIndex; + if (!int.TryParse(parts[1], out controllerClientIndex)) + { + Log.Write("server", "Invalid bot controller client index: {0}", parts[1]); + return false; + } + var botType = parts.Skip(2).JoinWith(" "); + + // Invalid slot + if (bot != null && bot.Bot == null) + { + server.SendOrderTo(conn, "Message", "Can't add bots to a slot with another client"); + return true; + } + + slot.Closed = false; + if (bot == null) + { + // Create a new bot + bot = new Session.Client() + { + Index = server.ChooseFreePlayerIndex(), + Name = botType, + Bot = botType, + Slot = parts[0], + Country = "random", + SpawnPoint = 0, + Team = 0, + State = Session.ClientState.NotReady, + BotControllerClientIndex = controllerClientIndex + }; + + // pick a random color for the bot + var hue = (byte)server.Random.Next(255); + var sat = (byte)server.Random.Next(255); + var lum = (byte)server.Random.Next(51,255); + bot.Color = bot.PreferredColor = new HSLColor(hue, sat, lum); + + server.LobbyInfo.Clients.Add(bot); + } + else + { + // Change the type of the existing bot + bot.Name = botType; + bot.Bot = botType; + } + + S.SyncClientToPlayerReference(bot, server.Map.Players[parts[0]]); + server.SyncLobbyInfo(); + return true; + }}, + { "map", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can change the map"); + return true; + } + + if (!server.ModData.AvailableMaps.ContainsKey(s)) + { + server.SendOrderTo(conn, "Message", "Map was not found on server"); + return true; + } + server.LobbyInfo.GlobalSettings.Map = s; + var oldSlots = server.LobbyInfo.Slots.Keys.ToArray(); + LoadMap(server); + SetDefaultDifficulty(server); + + // Reassign players into new slots based on their old slots: + // - Observers remain as observers + // - Players who now lack a slot are made observers + // - Bots who now lack a slot are dropped + var slots = server.LobbyInfo.Slots.Keys.ToArray(); + int i = 0; + foreach (var os in oldSlots) + { + var c = server.LobbyInfo.ClientInSlot(os); + if (c == null) + continue; + + c.SpawnPoint = 0; + c.State = Session.ClientState.NotReady; + c.Slot = i < slots.Length ? slots[i++] : null; + if (c.Slot != null) + { + // Remove Bot from slot if slot forbids bots + if (c.Bot != null && !server.Map.Players[c.Slot].AllowBots) + server.LobbyInfo.Clients.Remove(c); + S.SyncClientToPlayerReference(c, server.Map.Players[c.Slot]); + } + else if (c.Bot != null) + server.LobbyInfo.Clients.Remove(c); + } + + server.SyncLobbyInfo(); + return true; + }}, + { "fragilealliance", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (server.Map.Options.FragileAlliances.HasValue) + { + server.SendOrderTo(conn, "Message", "Map has disabled alliance configuration"); + return true; + } + + bool.TryParse(s, out server.LobbyInfo.GlobalSettings.FragileAlliances); + server.SyncLobbyInfo(); + return true; + }}, + { "allowcheats", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (server.Map.Options.Cheats.HasValue) + { + server.SendOrderTo(conn, "Message", "Map has disabled cheat configuration"); + return true; + } + + bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllowCheats); + server.SyncLobbyInfo(); + return true; + }}, + { "shroud", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (server.Map.Options.Shroud.HasValue) + { + server.SendOrderTo(conn, "Message", "Map has disabled shroud configuration"); + return true; + } + + bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Shroud); + server.SyncLobbyInfo(); + return true; + }}, + { "fog", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (server.Map.Options.Fog.HasValue) + { + server.SendOrderTo(conn, "Message", "Map has disabled fog configuration"); + return true; + } + + + bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Fog); + server.SyncLobbyInfo(); + return true; + }}, + { "assignteams", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + int teamCount; + if (!int.TryParse(s, out teamCount)) + { + server.SendOrderTo(conn, "Message", "Number of teams could not be parsed: {0}".F(s)); + return true; + } + + var maxTeams = (server.LobbyInfo.Clients.Count(c => c.Slot != null) + 1) / 2; + teamCount = teamCount.Clamp(0, maxTeams); + var players = server.LobbyInfo.Slots + .Select(slot => server.LobbyInfo.ClientInSlot(slot.Key)) + .Where(c => c != null && !server.LobbyInfo.Slots[c.Slot].LockTeam); + + var assigned = 0; + var playerCount = players.Count(); + foreach (var player in players) + { + // Free for all + if (teamCount == 0) + player.Team = 0; + + // Humans vs Bots + else if (teamCount == 1) + player.Team = player.Bot == null ? 1 : 2; + + else + player.Team = assigned++ * teamCount / playerCount + 1; + } + + server.SyncLobbyInfo(); + return true; + }}, + { "crates", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (server.Map.Options.Crates.HasValue) + { + server.SendOrderTo(conn, "Message", "Map has disabled crate configuration"); + return true; + } + + bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Crates); + server.SyncLobbyInfo(); + return true; + }}, + { "allybuildradius", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (server.Map.Options.AllyBuildRadius.HasValue) + { + server.SendOrderTo(conn, "Message", "Map has disabled ally build radius configuration"); + return true; + } + + bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllyBuildRadius); + server.SyncLobbyInfo(); + return true; + }}, + { "difficulty", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (s != null && !server.Map.Options.Difficulties.Contains(s)) + { + server.SendOrderTo(conn, "Message", "Unsupported difficulty selected: {0}".F(s)); + server.SendOrderTo(conn, "Message", "Supported difficulties: {0}".F(server.Map.Options.Difficulties.JoinWith(","))); + return true; + } + + server.LobbyInfo.GlobalSettings.Difficulty = s; + server.SyncLobbyInfo(); + return true; + }}, + { "startingunits", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (!server.Map.Options.ConfigurableStartingUnits) + { + server.SendOrderTo(conn, "Message", "Map has disabled start unit configuration"); + return true; + } + + server.LobbyInfo.GlobalSettings.StartingUnitsClass = s; + server.SyncLobbyInfo(); + return true; + }}, + { "startingcash", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can set that option"); + return true; + } + + if (server.Map.Options.StartingCash.HasValue) + { + server.SendOrderTo(conn, "Message", "Map has disabled cash configuration"); + return true; + } + + server.LobbyInfo.GlobalSettings.StartingCash = int.Parse(s); + server.SyncLobbyInfo(); + return true; + }}, + { "kick", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can kick players"); + return true; + } + + var split = s.Split(' '); + if (split.Length < 2) + { + server.SendOrderTo(conn, "Message", "Malformed kick command"); + return true; + } + + int kickClientID; + int.TryParse(split[0], out kickClientID); + + var kickConn = server.Conns.SingleOrDefault(c => server.GetClient(c) != null && server.GetClient(c).Index == kickClientID); + if (kickConn == null) + { + server.SendOrderTo(conn, "Message", "Noone in that slot."); + return true; + } + + var kickConnIP = server.GetClient(kickConn).IpAddress; + + Log.Write("server", "Kicking client {0} as requested", kickClientID); + server.SendOrderTo(kickConn, "ServerError", "You have been kicked from the server"); + server.DropClient(kickConn); + + bool tempBan; + bool.TryParse(split[1], out tempBan); + + if (tempBan) + { + Log.Write("server", "Temporarily banning client {0} ({1}) as requested", kickClientID, kickConnIP); + server.TempBans.Add(kickConnIP); + } + + server.SyncLobbyInfo(); + return true; + }}, + { "name", + s => + { + Log.Write("server", "Player@{0} is now known as {1}", conn.socket.RemoteEndPoint, s); + client.Name = s; + server.SyncLobbyInfo(); + return true; + }}, + { "race", + s => + { + var parts = s.Split(' '); + var targetClient = server.LobbyInfo.ClientWithIndex(int.Parse(parts[0])); + + // Only the host can change other client's info + if (targetClient.Index != client.Index && !client.IsAdmin) + return true; + + // Map has disabled race changes + if (server.LobbyInfo.Slots[targetClient.Slot].LockRace) + return true; + + targetClient.Country = parts[1]; + server.SyncLobbyInfo(); + return true; + }}, + { "team", + s => + { + var parts = s.Split(' '); + var targetClient = server.LobbyInfo.ClientWithIndex(int.Parse(parts[0])); + + // Only the host can change other client's info + if (targetClient.Index != client.Index && !client.IsAdmin) + return true; + + // Map has disabled team changes + if (server.LobbyInfo.Slots[targetClient.Slot].LockTeam) + return true; + + int team; + if (!int.TryParse(parts[1], out team)) + { + Log.Write("server", "Invalid team: {0}", s ); + return false; + } + + targetClient.Team = team; + server.SyncLobbyInfo(); + return true; + }}, + { "spawn", + s => + { + var parts = s.Split(' '); + var targetClient = server.LobbyInfo.ClientWithIndex(int.Parse(parts[0])); + + // Only the host can change other client's info + if (targetClient.Index != client.Index && !client.IsAdmin) + return true; + + // Spectators don't need a spawnpoint + if (targetClient.Slot == null) + return true; + + // Map has disabled spawn changes + if (server.LobbyInfo.Slots[targetClient.Slot].LockSpawn) + return true; + + int spawnPoint; + if (!int.TryParse(parts[1], out spawnPoint) || spawnPoint < 0 || spawnPoint > server.Map.GetSpawnPoints().Length) + { + Log.Write("server", "Invalid spawn point: {0}", parts[1]); + return true; + } + + if (server.LobbyInfo.Clients.Where( cc => cc != client ).Any( cc => (cc.SpawnPoint == spawnPoint) && (cc.SpawnPoint != 0) )) + { + server.SendOrderTo(conn, "Message", "You can't be at the same spawn point as another player"); + return true; + } + + targetClient.SpawnPoint = spawnPoint; + server.SyncLobbyInfo(); + return true; + }}, + { "color", + s => + { + var parts = s.Split(' '); + var targetClient = server.LobbyInfo.ClientWithIndex(int.Parse(parts[0])); + + // Only the host can change other client's info + if (targetClient.Index != client.Index && !client.IsAdmin) + return true; + + // Spectator or map has disabled color changes + if (targetClient.Slot == null || server.LobbyInfo.Slots[targetClient.Slot].LockColor) + return true; + + var ci = parts[1].Split(',').Select(cc => int.Parse(cc)).ToArray(); + targetClient.Color = targetClient.PreferredColor = new HSLColor((byte)ci[0], (byte)ci[1], (byte)ci[2]); + server.SyncLobbyInfo(); + return true; + }} + }; + + var cmdName = cmd.Split(' ').First(); + var cmdValue = cmd.Split(' ').Skip(1).JoinWith(" "); + + Func a; + if (!dict.TryGetValue(cmdName, out a)) + return false; + + return a(cmdValue); + } + + public void ServerStarted(S server) + { + LoadMap(server); + SetDefaultDifficulty(server); + } + + static Session.Slot MakeSlotFromPlayerReference(PlayerReference pr) + { + if (!pr.Playable) return null; + if (Game.Settings.Server.LockBots) + pr.AllowBots = false; + return new Session.Slot + { + PlayerReference = pr.Name, + Closed = false, + AllowBots = pr.AllowBots, + LockRace = pr.LockRace, + LockColor = pr.LockColor, + LockTeam = pr.LockTeam, + LockSpawn = pr.LockSpawn, + Required = pr.Required, + }; + } + + static void LoadMap(S server) + { + server.Map = new Map(server.ModData.AvailableMaps[server.LobbyInfo.GlobalSettings.Map].Path); + server.LobbyInfo.Slots = server.Map.Players + .Select(p => MakeSlotFromPlayerReference(p.Value)) + .Where(s => s != null) + .ToDictionary(s => s.PlayerReference, s => s); + + server.Map.Options.UpdateServerSettings(server.LobbyInfo.GlobalSettings); + } + + static void SetDefaultDifficulty(S server) + { + if (!server.Map.Options.Difficulties.Any()) + { + server.LobbyInfo.GlobalSettings.Difficulty = null; + return; + } + + if (!server.Map.Options.Difficulties.Contains(server.LobbyInfo.GlobalSettings.Difficulty)) + server.LobbyInfo.GlobalSettings.Difficulty = server.Map.Options.Difficulties.First(); + } + } +} diff --git a/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs b/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs index 35381d3ce5..227be96376 100644 --- a/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs +++ b/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs @@ -75,8 +75,7 @@ namespace OpenRA.Mods.RA.Server numBots, "{0}@{1}".F(mod.Id, mod.Version), server.LobbyInfo.GlobalSettings.Map, - server.Map.PlayerCount, - server.LobbyInfo.GlobalSettings.AllowSpectate)); + server.Map.PlayerCount)); if (isInitialPing) { 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 681efd37f9..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; @@ -173,7 +175,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic }; } - var slotsButton = lobby.GetOrNull("SLOTS_DROPDOWNBUTTON"); if (slotsButton != null) { @@ -471,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(); @@ -614,7 +615,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic idx++; } - // Add spectators foreach (var client in orderManager.LobbyInfo.Clients.Where(client => client.Slot == null)) { @@ -655,48 +655,33 @@ namespace OpenRA.Mods.RA.Widgets.Logic idx++; } - // Spectate button - if (orderManager.LocalClient.Slot != null) - { + // Spectate button + if (orderManager.LocalClient.Slot != null) + { + Widget spec = null; + if (idx < Players.Children.Count) + spec = Players.Children[idx]; + if (spec == null || spec.Id != NewSpectatorTemplate.Id) + spec = NewSpectatorTemplate.Clone(); - Widget spec = null; - if (idx < Players.Children.Count) - spec = Players.Children[idx]; - if (spec == null || spec.Id != NewSpectatorTemplate.Id) - spec = NewSpectatorTemplate.Clone(); - - var block = spec.Get("BLOCK_SPECTATE"); - block.OnClick = () => - { - orderManager.IssueOrder(Order.Command("allow_spectate False")); - 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()); - }; - block.IsVisible = () => orderManager.LocalClient.IsAdmin && orderManager.LobbyInfo.GlobalSettings.AllowSpectate; - block.IsDisabled = () => !orderManager.LobbyInfo.GlobalSettings.AllowSpectate; - - var allow = spec.Get("ALLOW_SPECTATE"); - allow.OnClick = () => orderManager.IssueOrder(Order.Command("allow_spectate True")); - allow.IsVisible = () => orderManager.LocalClient.IsAdmin && !orderManager.LobbyInfo.GlobalSettings.AllowSpectate; - allow.IsDisabled = () => orderManager.LobbyInfo.GlobalSettings.AllowSpectate; + 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; - btn.IsVisible = () => orderManager.LobbyInfo.GlobalSettings.AllowSpectate; - spec.IsVisible = () => true; + var btn = spec.Get("SPECTATE"); + btn.OnClick = () => orderManager.IssueOrder(Order.Command("spectate")); + btn.IsDisabled = () => orderManager.LocalClient.IsReady; + 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) - Players.ReplaceChild(Players.Children[idx], spec); - - idx++; - } + if (idx >= Players.Children.Count) + Players.AddChild(spec); + else if (Players.Children[idx].Id != spec.Id) + Players.ReplaceChild(Players.Children[idx], spec); + idx++; + } while (Players.Children.Count > idx) Players.RemoveChild(Players.Children[idx]); 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 451fcfa9f9..37ef7aba82 100644 --- a/mods/cnc/chrome/lobby-playerbin.yaml +++ b/mods/cnc/chrome/lobby-playerbin.yaml @@ -307,20 +307,13 @@ ScrollPanel@LOBBY_PLAYER_BIN: Height:25 Visible:false Children: - Button@ALLOW_SPECTATE: - Text:Allow Spectators + Checkbox@TOGGLE_SPECTATORS: Font:Regular Width:190 - Height:25 - Y:0 + Height:20 X:15 - Button@BLOCK_SPECTATE: - Text:Block Spectators - Font:Regular - Width:190 - Height:25 Y:0 - X:15 + Text:Allow Spectators? Button@SPECTATE: Text:Spectate Font:Regular diff --git a/mods/d2k/chrome/lobby-playerbin.yaml b/mods/d2k/chrome/lobby-playerbin.yaml index 2d37501d56..81e08b6a75 100644 --- a/mods/d2k/chrome/lobby-playerbin.yaml +++ b/mods/d2k/chrome/lobby-playerbin.yaml @@ -298,20 +298,13 @@ ScrollPanel@LOBBY_PLAYER_BIN: Height:25 Visible:false Children: - Button@ALLOW_SPECTATE: - Text:Allow Spectators + Checkbox@TOGGLE_SPECTATORS: Font:Regular Width:165 - Height:25 - X:15 - Y:0 - Button@BLOCK_SPECTATE: - Text:Block Spectators - Font:Regular - Width:165 - Height:25 + 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 2396bb125c..cb2285c67c 100644 --- a/mods/ra/chrome/lobby-playerbin.yaml +++ b/mods/ra/chrome/lobby-playerbin.yaml @@ -298,20 +298,13 @@ ScrollPanel@LOBBY_PLAYER_BIN: Height:25 Visible:false Children: - Button@ALLOW_SPECTATE: - Text:Allow Spectators + Checkbox@TOGGLE_SPECTATORS: Font:Regular Width:165 - Height:25 - X:15 - Y:0 - Button@BLOCK_SPECTATE: - Text:Block Spectators - Font:Regular - Width:165 - Height:25 + Height:20 X:15 Y:0 + Text:Allow Spectators? Button@SPECTATE: Text:Spectate Font:Regular