context: #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.SendChatTo( conn, "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.GameStarted)
  			{
  				server.SendChatTo(conn, "Cannot change state when game started. ({0})".F(cmd));
  				return false;
  			}
  			else if (client.State == Session.ClientState.Ready && !(cmd == "ready" || cmd == "normalstart"))
  			{
  				server.SendChatTo(conn, "Cannot change state when marked as ready.");
  				return false;
  			}
  
  			return true;
  		}
  
  		public bool InterpretCommand(S server, Connection conn, Session.Client client, string cmd)
  		{
  			if (!ValidateCommand(server, conn, client, cmd))
  				return false;
  
  			var dict = new Dictionary<string, Func<string, bool>>
  			{
  				{ "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();
  
  						if (server.conns.Count > 0 && server.conns.All(c => server.GetClient(c).State == Session.ClientState.Ready))
  							InterpretCommand(server, conn, client, "normalstart");
  
  						return true;
  					}},
  				{ "normalstart",
  					s =>
  					{
  						server.StartGame();
  						return true;	
  					}},
  				{ "startgame",
  					s =>
  					{
  						server.SendChatTo(conn, "No rights! To start the game, everyone must tick Ready!");
  						return true;	
  					}},
  				{ "lag",
  					s =>
  					{
  						int lag;
  						if (!int.TryParse(s, out lag)) { Log.Write("server", "Invalid order lag: {0}", s); return false; }
  
  						Log.Write("server", "Order lag is now {0} frames.", lag);
  
  						server.lobbyInfo.GlobalSettings.OrderLatency = lag;
  						server.SyncLobbyInfo();
  						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();
  						return true;
  					}},
  				{ "spectate",
  					s =>
  					{
  						client.Slot = null;
  						client.SpawnPoint = 0;
  						server.SyncLobbyInfo();
  						return true;
  					}},
  				{ "slot_close",
  					s =>
  					{
  						server.SendChatTo(conn, "No rights!");
  						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 < 2)
  						{
  							server.SendChatTo( conn, "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]);
  						var botType = parts.Skip(1).JoinWith(" ");
  
  						// Invalid slot
  						if (bot != null && bot.Bot == null)
  						{
  							server.SendChatTo( conn, "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
  							};
  
  							// 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.ColorRamp = new ColorRamp(hue, sat, lum, 10);
  
  							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.SendChatTo( conn, "Only the host can change the map" );
  							return true;
  						}
  						if(!server.ModData.AvailableMaps.ContainsKey(s))
  						{
  							server.SendChatTo( conn, "Map not found");
  							return true;
  						}
  						server.lobbyInfo.GlobalSettings.Map = s;
  						var oldSlots = server.lobbyInfo.Slots.Keys.ToArray();
  						LoadMap(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)
  								S.SyncClientToPlayerReference(c, server.Map.Players[c.Slot]);
  
gt: 							else if (c.Bot != null)