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.Globalization;
  using System.IO;
  using System.Linq;
  using System.Net;
  using System.Net.Sockets;
  using System.Threading;
  using OpenRA.Graphics;
  using OpenRA.Network;
  using OpenRA.Primitives;
  using OpenRA.Support;
  
  using XTimer = System.Timers.Timer;
  
  namespace OpenRA.Server
  {
  	public enum ServerState
  	{
  		WaitingPlayers = 1,
  		GameStarted = 2,
  		ShuttingDown = 3
  	}
  
  	public class Server
  	{
  		public readonly IPAddress Ip;
  		public readonly int Port;
  		public readonly MersenneTwister Random = new MersenneTwister();
  
  		// Valid player connections
  		public List<Connection> Conns = new List<Connection>();
  
  		// Pre-verified player connections
  		public List<Connection> PreConns = new List<Connection>();
  
  		public Session LobbyInfo;
  		public ServerSettings Settings;
  		public ModData ModData;
  		public List<string> TempBans = new List<string>();
  
  		// Managed by LobbyCommands
  		public Map Map;
  		public MapPlayers MapPlayers;
  
  		readonly int randomSeed;
  		readonly TcpListener listener;
  		readonly TypeDictionary serverTraits = new TypeDictionary();
  
  		XTimer gameTimeout;
  
  		protected volatile ServerState internalState = ServerState.WaitingPlayers;
  
  		public ServerState State
  		{
  			get { return internalState; }
  			protected set { internalState = value; }
  		}
  
  		public static void SyncClientToPlayerReference(Session.Client c, PlayerReference pr)
  		{
  			if (pr == null)
  				return;
  
  			if (pr.LockFaction)
  				c.Faction = pr.Faction;
  			if (pr.LockSpawn)
  				c.SpawnPoint = pr.Spawn;
  			if (pr.LockTeam)
  				c.Team = pr.Team;
  
  			c.Color = pr.LockColor ? pr.Color : c.PreferredColor;
  		}
  
  		static void SendData(Socket s, byte[] data)
  		{
  			var start = 0;
  			var length = data.Length;
  
  			// Non-blocking sends are free to send only part of the data
  			while (start < length)
  			{
  				SocketError error;
  				var sent = s.Send(data, start, length - start, SocketFlags.None, out error);
  				if (error == SocketError.WouldBlock)
  				{
  					Log.Write("server", "Non-blocking send of {0} bytes failed. Falling back to blocking send.", length - start);
  					s.Blocking = true;
  					sent = s.Send(data, start, length - start, SocketFlags.None);
  					s.Blocking = false;
  				}
  				else if (error != SocketError.Success)
  					throw new SocketException((int)error);
  
  				start += sent;
  			}
  		}
  
  		public void Shutdown()
  		{
  			State = ServerState.ShuttingDown;
  		}
  
  		public void EndGame()
  		{
  			foreach (var t in serverTraits.WithInterface<IEndGame>())
  				t.GameEnded(this);
  		}
  
  		public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData)
  		{
  			Log.AddChannel("server", "server.log");
  
  			listener = new TcpListener(endpoint);
  			listener.Start();
  			var localEndpoint = (IPEndPoint)listener.LocalEndpoint;
  			Ip = localEndpoint.Address;
  			Port = localEndpoint.Port;
  
  			Settings = settings;
  
  			Settings.Name = OpenRA.Settings.SanitizedServerName(Settings.Name);
  
  			ModData = modData;
  
  			randomSeed = (int)DateTime.Now.ToBinary();
  
  			if (Settings.AllowPortForward)
  				UPnP.ForwardPort(3600);
  
  			foreach (var trait in modData.Manifest.ServerTraits)
  				serverTraits.Add(modData.ObjectCreator.CreateObject<ServerTrait>(trait));
  
  			LobbyInfo = new Session
  			{
  				GlobalSettings =
  				{
  					RandomSeed = randomSeed,
  					Map = settings.Map,
  					ServerName = settings.Name,
  					Dedicated = settings.Dedicated
  				}
  			};
  
  			FieldLoader.Load(LobbyInfo.GlobalSettings, modData.Manifest.LobbyDefaults);
  
  			foreach (var t in serverTraits.WithInterface<INotifyServerStart>())
  				t.ServerStarted(this);
  
  			Log.Write("server", "Initial mod: {0}", ModData.Manifest.Mod.Id);
  			Log.Write("server", "Initial map: {0}", LobbyInfo.GlobalSettings.Map);
  
  			new Thread(_ =>
  			{
  				var timeout = serverTraits.WithInterface<ITick>().Min(t => t.TickTimeout);
  				for (;;)
  				{
  					var checkRead = new List<Socket>();
  					if (State == ServerState.WaitingPlayers)
  						checkRead.Add(listener.Server);
  
  					checkRead.AddRange(Conns.Select(c => c.Socket));
  					checkRead.AddRange(PreConns.Select(c => c.Socket));
  
  					if (checkRead.Count > 0)
  						Socket.Select(checkRead, null, null, timeout);
  
  					if (State == ServerState.ShuttingDown)
  					{
  						EndGame();
  						break;
  					}
  
  					foreach (var s in checkRead)
  					{
  						if (s == listener.Server)
  							AcceptConnection();
  						else if (PreConns.Count > 0)
  						{
  							var p = PreConns.SingleOrDefault(c => c.Socket == s);
  							if (p != null) p.ReadData(this);
  						}
  						else if (Conns.Count > 0)
  						{
  							var conn = Conns.SingleOrDefault(c => c.Socket == s);
  							if (conn != null) conn.ReadData(this);
  						}
  					}
  
  					foreach (var t in serverTraits.WithInterface<ITick>())
  						t.Tick(this);
  
  					if (State == ServerState.ShuttingDown)
  					{
  						EndGame();
  						if (Settings.AllowPortForward) UPnP.RemovePortforward();
  						break;
  					}
  				}
  
  				foreach (var t in serverTraits.WithInterface<INotifyServerShutdown>())
  					t.ServerShutdown(this);
  
  				PreConns.Clear();
  				Conns.Clear();
  				try { listener.Stop(); }
  				catch { }
  			}) { IsBackground = true }.Start();
  		}
  
  		/* lobby rework TODO:
  		 *	- "teams together" option for team games -- will eliminate most need
  		 *		for manual spawnpoint choosing.
  		 */
  		int nextPlayerIndex;
  		public int ChooseFreePlayerIndex()
  		{
  			return nextPlayerIndex++;
  		}
  
  		void AcceptConnection()
  		{
  			Socket newSocket;
  
  			try
  			{
  				if (!listener.Server.IsBound)
  					return;
  
  
gt: 				newSocket = listener.AcceptSocket();