context: #region Copyright & License Information
  /*
   * Copyright 2007-2015 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.Drawing;
  using System.Linq;
  using OpenRA.Graphics;
  using OpenRA.Mods.Common.Traits;
  using OpenRA.Network;
  using OpenRA.Server;
  using S = OpenRA.Server.Server;
  
  namespace OpenRA.Mods.Common.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.StartsWith("state") || cmd == "startgame"))
  			{
  				server.SendOrderTo(conn, "Message", "Cannot change state when marked as ready.");
  				return false;
  			}
  
  			return true;
  		}
  
  		static void CheckAutoStart(S server)
  		{
  			var playerClients = server.LobbyInfo.Clients.Where(c => c.Bot == null && c.Slot != null);
  
  			// Are all players ready?
  			if (!playerClients.Any() || 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 (server == null || conn == null || client == null || !ValidateCommand(server, conn, client, cmd))
  				return false;
  
  			var dict = new Dictionary<string, Func<string, bool>>
  			{
  				{ "state",
  					s =>
  					{
  						var state = Session.ClientState.Invalid;
  						if (!Enum<Session.ClientState>.TryParse(s, false, out state))
  						{
  							server.SendOrderTo(conn, "Message", "Malformed state command");
  							return true;
  						}
  
  						client.State = state;
  
  						Log.Write("server", "Player @{0} is {1}",
  							conn.Socket.RemoteEndPoint, client.State);
  
  						server.SyncLobbyClients();
  
  						CheckAutoStart(server);
  
  						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.MapPlayers.Players[s]);
  						server.SyncLobbyClients();
  						CheckAutoStart(server);
  
  						return true;
  					}
  				},
  				{ "allow_spectators",
  					s =>
  					{
  						if (bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllowSpectators))
  						{
  							server.SyncLobbyGlobalSettings();
  							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.SyncLobbyClients();
  							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);
  								server.SyncLobbyClients();
  								var ping = server.LobbyInfo.PingFromClient(occupant);
  								if (ping != null)
  								{
  									server.LobbyInfo.ClientPings.Remove(ping);
  									server.SyncClientPing();
  								}
  							}
  							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.SyncLobbySlots();
  						return true;
  					}
  				},
  				{ "slot_open",
  					s =>
  					{
  						if (!ValidateSlotCommand(server, conn, client, s, true))
  							return false;
  
  						var slot = server.LobbyInfo.Slots[s];
  						slot.Closed = false;
  						server.SyncLobbySlots();
  
  						// Slot may have a bot in it
  						var occupant = server.LobbyInfo.ClientInSlot(s);
  						if (occupant != null && occupant.Bot != null)
  						{
  							server.LobbyInfo.Clients.Remove(occupant);
  							var ping = server.LobbyInfo.PingFromClient(occupant);
  							if (ping != null)
  							{
  								server.LobbyInfo.ClientPings.Remove(ping);
  								server.SyncClientPing();
  							}
  						}
  
  						server.SyncLobbyClients();
  						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 (!Exts.TryParseIntegerInvariant(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],
  								Race = "Random",
  								SpawnPoint = 0,
  								Team = 0,
  								State = Session.ClientState.NotReady,
  								BotControllerClientIndex = controllerClientIndex
  							};
  
  							// Pick a random color for the bot
  							HSLColor botColor;
  							do
  							{
  								var hue = (byte)server.Random.Next(255);
  								var sat = (byte)server.Random.Next(255);
  								var lum = (byte)server.Random.Next(51, 255);
  								botColor = new HSLColor(hue, sat, lum);
  							} while (!ColorValidator.ValidatePlayerNewColor(server, botColor.RGB, bot.Index));
  
  							bot.Color = bot.PreferredColor = botColor;
  
  							server.LobbyInfo.Clients.Add(bot);
  						}
  						else
  						{
  							// Change the type of the existing bot
  							bot.Name = botType;
  							bot.Bot = botType;
  						}
  
  						S.SyncClientToPlayerReference(bot, server.MapPlayers.Players[parts[0]]);
  						server.SyncLobbyClients();
  						server.SyncLobbySlots();
  						return true;
  					}
  				},
  				{ "map",
  					s =>
  					{
  						if (!client.IsAdmin)
  						{
  							server.SendOrderTo(conn, "Message", "Only the host can change the map.");
  							return true;
  						}
  
  						if (server.ModData.MapCache[s].Status != MapStatus.Available)
  						{
  							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);
  
  						// Reset client states
  						foreach (var c in server.LobbyInfo.Clients)
  							c.State = Session.ClientState.Invalid;
  
  						// 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();
  						var i = 0;
  						foreach (var os in oldSlots)
  						{
  							var c = server.LobbyInfo.ClientInSlot(os);
  							if (c == null)
  								continue;
  
  							c.SpawnPoint = 0;
  							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.MapPlayers.Players[c.Slot].AllowBots)
  									server.LobbyInfo.Clients.Remove(c);
  								S.SyncClientToPlayerReference(c, server.MapPlayers.Players[c.Slot]);
  							}
  							else if (c.Bot != null)
  								server.LobbyInfo.Clients.Remove(c);
  						}
  
  						foreach (var c in server.LobbyInfo.Clients)
  						{
  							// Validate if color is allowed and get an alternative it it isn't
  							c.Color = c.PreferredColor = ColorValidator.ValidatePlayerColorAndGetAlternative(server, c.Color, c.Index, conn);
  						}
  
  						server.SyncLobbyInfo();
  
  						server.SendMessage("{0} changed the map to {1}.".F(client.Name, server.Map.Title));
  
  						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.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} {1} Diplomacy Changes."
  							.F(client.Name, server.LobbyInfo.GlobalSettings.FragileAlliances ? "enabled" : "disabled"));
  
  						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.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} {1} Developer Cheats."
  							.F(client.Name, server.LobbyInfo.GlobalSettings.AllowCheats ? "allowed" : "disallowed"));
  
  						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.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} {1} Shroud."
  							.F(client.Name, server.LobbyInfo.GlobalSettings.Shroud ? "enabled" : "disabled"));
  
  						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.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} {1} Fog of War."
  							.F(client.Name, server.LobbyInfo.GlobalSettings.Fog ? "enabled" : "disabled"));
  
  						return true;
  					}
  				},
  				{ "assignteams",
  					s =>
  					{
  						if (!client.IsAdmin)
  						{
  							server.SendOrderTo(conn, "Message", "Only the host can set that option.");
  							return true;
  						}
  
  						int teamCount;
  						if (!Exts.TryParseIntegerInvariant(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 clients = server.LobbyInfo.Slots
  							.Select(slot => server.LobbyInfo.ClientInSlot(slot.Key))
  							.Where(c => c != null && !server.LobbyInfo.Slots[c.Slot].LockTeam);
  
  						var assigned = 0;
  						var clientCount = clients.Count();
  						foreach (var player in clients)
  						{
  							// 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 / clientCount + 1;
  						}
  
  						server.SyncLobbyClients();
  						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.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} {1} Crates."
  							.F(client.Name, server.LobbyInfo.GlobalSettings.Crates ? "enabled" : "disabled"));
  
  						return true;
  					}
  				},
  				{ "creeps",
  					s =>
  					{
  						if (!client.IsAdmin)
  						{
  							server.SendOrderTo(conn, "Message", "Only the host can set that option.");
  							return true;
  						}
  
  						if (server.Map.Options.Creeps.HasValue)
  						{
  							server.SendOrderTo(conn, "Message", "Map has disabled Creeps spawning configuration.");
  							return true;
  						}
  
  						bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Creeps);
  						server.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} {1} Creeps spawning."
  							.F(client.Name, server.LobbyInfo.GlobalSettings.Creeps ? "enabled" : "disabled"));
  
  						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.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} {1} Build off Allies' ConYards."
  							.F(client.Name, server.LobbyInfo.GlobalSettings.AllyBuildRadius ? "enabled" : "disabled"));
  
  						return true;
  					}
  				},
  				{ "difficulty",
  					s =>
  					{
  						if (!server.Map.Options.Difficulties.Any())
  							return true;
  
  						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.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} changed difficulty to {1}.".F(client.Name, s));
  
  						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;
  						}
  
  						var startUnitsInfo = server.Map.Rules.Actors["world"].Traits.WithInterface<MPStartUnitsInfo>();
  						var selectedClass = startUnitsInfo.Where(u => u.Class == s).Select(u => u.ClassName).FirstOrDefault();
  						var className = selectedClass != null ? selectedClass : s;
  
  						server.LobbyInfo.GlobalSettings.StartingUnitsClass = s;
  						server.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} changed Starting Units to {1}.".F(client.Name, className));
  
  						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 = Exts.ParseIntegerInvariant(s);
  						server.SyncLobbyGlobalSettings();
  						server.SendMessage("{0} changed Starting Cash to ${1}.".F(client.Name, s));
  
  						return true;
  					}
  				},
  				{ "techlevel",
  					s =>
  					{
  						if (!client.IsAdmin)
  						{
  							server.SendOrderTo(conn, "Message", "Only the host can set that option.");
  							return true;
  						}
  
  						if (server.Map.Options.TechLevel != null)
  						{
  							server.SendOrderTo(conn, "Message", "Map has disabled Tech configuration.");
  							return true;
  						}
  
  						server.LobbyInfo.GlobalSettings.TechLevel = s;
  						server.SyncLobbyInfo();
  						server.SendMessage("{0} changed Tech Level to {1}.".F(client.Name, s));
  
  						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;
  						Exts.TryParseIntegerInvariant(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 kickClient = server.GetClient(kickConn);
  
  						Log.Write("server", "Kicking client {0}.", kickClientID);
  						server.SendMessage("{0} kicked {1} from the server.".F(client.Name, kickClient.Name));
  						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}).", kickClientID, kickClient.IpAddress);
  							server.SendMessage("{0} temporarily banned {1} from the server.".F(client.Name, kickClient.Name));
  							server.TempBans.Add(kickClient.IpAddress);
  						}
  
  						server.SyncLobbyClients();
  						server.SyncLobbySlots();
  
  						return true;
  					}
  				},
  				{ "name",
  					s =>
  					{
  						var sanitizedName = OpenRA.Settings.SanitizedPlayerName(s);
  						if (sanitizedName == client.Name)
  							return true;
  
  						Log.Write("server", "Player@{0} is now known as {1}.", conn.Socket.RemoteEndPoint, sanitizedName);
  						server.SendMessage("{0} is now known as {1}.".F(client.Name, sanitizedName));
  						client.Name = sanitizedName;
  						server.SyncLobbyClients();
  						return true;
  					}
  				},
  				{ "race",
  					s =>
  					{
  						var parts = s.Split(' ');
  						var targetClient = server.LobbyInfo.ClientWithIndex(Exts.ParseIntegerInvariant(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.Race = parts[1];
  						server.SyncLobbyClients();
  						return true;
  					}
  				},
  				{ "team",
  					s =>
  					{
  						var parts = s.Split(' ');
  						var targetClient = server.LobbyInfo.ClientWithIndex(Exts.ParseIntegerInvariant(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 (!Exts.TryParseIntegerInvariant(parts[1], out team))
  						{
  							Log.Write("server", "Invalid team: {0}", s);
  							return false;
  						}
  
  						targetClient.Team = team;
  						server.SyncLobbyClients();
  						return true;
  					}
  				},
  				{ "spawn",
  					s =>
  					{
  						var parts = s.Split(' ');
  						var targetClient = server.LobbyInfo.ClientWithIndex(Exts.ParseIntegerInvariant(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
  
gt: 						if (targetClient.Slot == null)