context: /*
  TShock, a server mod for Terraria
  Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
  
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Data;
  using System.Diagnostics;
  using System.Diagnostics.CodeAnalysis;
  using System.Globalization;
  using System.IO;
  using System.Linq;
  using System.Net;
  using System.Reflection;
  using MaxMind;
  using Mono.Data.Sqlite;
  using MySql.Data.MySqlClient;
  using Newtonsoft.Json;
  using Rests;
  using Terraria;
  using Terraria.ID;
  using Terraria.Localization;
  using TerrariaApi.Server;
  using TShockAPI.DB;
  using TShockAPI.Hooks;
  using TShockAPI.ServerSideCharacters;
  using Terraria.Utilities;
  using Microsoft.Xna.Framework;
  using TShockAPI.Sockets;
  using TShockAPI.CLI;
  using TShockAPI.Localization;
  
  namespace TShockAPI
  {
  	/// <summary>
  	/// This is the TShock main class. TShock is a plugin on the TerrariaServerAPI, so it extends the base TerrariaPlugin.
  	/// TShock also complies with the API versioning system, and defines its required API version here.
  	/// </summary>
  	[ApiVersion(2, 1)]
  	public class TShock : TerrariaPlugin
  	{
  		/// <summary>VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.</summary>
  		public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
  		/// <summary>VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.</summary>
  		public static readonly string VersionCodename = "Mintaka";
  
  		/// <summary>SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).</summary>
  		public static string SavePath = "tshock";
  		/// <summary>LogFormatDefault - This is the default log file naming format. Actually, this is the only log format, because it never gets set again.</summary>
  		private const string LogFormatDefault = "yyyy-MM-dd_HH-mm-ss";
  		//TODO: Set the log path in the config file.
  		/// <summary>LogFormat - This is the log format, which is never set again.</summary>
  		private static string LogFormat = LogFormatDefault;
  		/// <summary>LogPathDefault - The default log path.</summary>
  		private const string LogPathDefault = "tshock";
  		/// <summary>This is the log path, which is initially set to the default log path, and then to the config file log path later.</summary>
  		private static string LogPath = LogPathDefault;
  		/// <summary>LogClear - Determines whether or not the log file should be cleared on initialization.</summary>
  		private static bool LogClear;
  
  		/// <summary>
  		/// Set by the command line, disables the '/restart' command.
  		/// </summary>
  		internal static bool NoRestart;
  
  		/// <summary>Will be set to true once Utils.StopServer() is called.</summary>
  		public static bool ShuttingDown;
  
  		/// <summary>Players - Contains all TSPlayer objects for accessing TSPlayers currently on the server</summary>
  		public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers];
  		/// <summary>Bans - Static reference to the ban manager for accessing bans & related functions.</summary>
  		public static BanManager Bans;
  		/// <summary>Warps - Static reference to the warp manager for accessing the warp system.</summary>
  		public static WarpManager Warps;
  		/// <summary>Regions - Static reference to the region manager for accessing the region system.</summary>
  		public static RegionManager Regions;
  		/// <summary>Backups - Static reference to the backup manager for accessing the backup system.</summary>
  		public static BackupManager Backups;
  		/// <summary>Groups - Static reference to the group manager for accessing the group system.</summary>
  		public static GroupManager Groups;
  		/// <summary>Users - Static reference to the user manager for accessing the user database system.</summary>
  		public static UserManager Users;
  		/// <summary>Itembans - Static reference to the item ban system.</summary>
  		public static ItemManager Itembans;
  		/// <summary>ProjectileBans - Static reference to the projectile ban system.</summary>
  		public static ProjectileManagager ProjectileBans;
  		/// <summary>TileBans - Static reference to the tile ban system.</summary>
  		public static TileManager TileBans;
  		/// <summary>RememberedPos - Static reference to the remembered position manager.</summary>
  		public static RememberedPosManager RememberedPos;
  		/// <summary>CharacterDB - Static reference to the SSC character manager.</summary>
  		public static CharacterManager CharacterDB;
  		/// <summary>Config - Static reference to the config system, for accessing values set in users' config files.</summary>
  		public static ConfigFile Config { get; set; }
  		/// <summary>ServerSideCharacterConfig - Static reference to the server side character config, for accessing values set by users to modify SSC.</summary>
  		public static ServerSideConfig ServerSideCharacterConfig;
  		/// <summary>DB - Static reference to the database.</summary>
  		public static IDbConnection DB;
  		/// <summary>OverridePort - Determines if TShock should override the server port.</summary>
  		public static bool OverridePort;
  		/// <summary>Geo - Static reference to the GeoIP system which determines the location of an IP address.</summary>
  		public static GeoIPCountry Geo;
  		/// <summary>RestApi - Static reference to the Rest API authentication manager.</summary>
  		public static SecureRest RestApi;
  		/// <summary>RestManager - Static reference to the Rest API manager.</summary>
  		public static RestManager RestManager;
  		/// <summary>Utils - Static reference to the utilities class, which contains a variety of utility functions.</summary>
  		public static Utils Utils = Utils.Instance;
  		/// <summary>StatTracker - Static reference to the stat tracker, which sends some server metrics every 5 minutes.</summary>
  		public static StatTracker StatTracker = new StatTracker();
  		/// <summary>UpdateManager - Static reference to the update checker, which checks for updates and notifies server admins of updates.</summary>
  		public static UpdateManager UpdateManager;
  		/// <summary>Log - Static reference to the log system, which outputs to either SQL or a text file, depending on user config.</summary>
  		public static ILog Log;
  		/// <summary>instance - Static reference to the TerrariaPlugin instance.</summary>
  		public static TerrariaPlugin instance;
  		/// <summary>
  		/// Static reference to a <see cref="CommandLineParser"/> used for simple command-line parsing
  		/// </summary>
  		public static CommandLineParser CliParser { get; } = new CommandLineParser();
  		/// <summary>
  		/// Used for implementing REST Tokens prior to the REST system starting up.
  		/// </summary>
  		public static Dictionary<string, SecureRest.TokenData> RESTStartupTokens = new Dictionary<string, SecureRest.TokenData>();
  
  		/// <summary>
  		/// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded.
  		/// </summary>
  		public static event Action Initialized;
  
  		/// <summary>Version - The version required by the TerrariaAPI to be passed back for checking & loading the plugin.</summary>
  		/// <value>value - The version number specified in the Assembly, based on the VersionNum variable set in this class.</value>
  		public override Version Version
  		{
  			get { return VersionNum; }
  		}
  
  		/// <summary>Name - The plugin name.</summary>
  		/// <value>value - "TShock"</value>
  		public override string Name
  		{
  			get { return "TShock"; }
  		}
  
  		/// <summary>Author - The author of the plugin.</summary>
  		/// <value>value - "The TShock Team"</value>
  		public override string Author
  		{
  			get { return "The TShock Team"; }
  		}
  
  		/// <summary>Description - The plugin description.</summary>
  		/// <value>value - "The administration modification of the future."</value>
  		public override string Description
  		{
  			get { return "The administration modification of the future."; }
  		}
  
  		/// <summary>TShock - The constructor for the TShock plugin.</summary>
  		/// <param name="game">game - The Terraria main game.</param>
  		public TShock(Main game)
  			: base(game)
  		{
  			Config = new ConfigFile();
  			ServerSideCharacterConfig = new ServerSideConfig();
  			ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-15, 1, 0));
  			ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-13, 1, 0));
  			ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-16, 1, 0));
  			Order = 0;
  			instance = this;
  		}
  
  		/// <summary>Initialize - Called by the TerrariaServerAPI during initialization.</summary>
  		[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
  		public override void Initialize()
  		{
  			string logFilename;
  			string logPathSetupWarning;
  
  			OTAPI.Hooks.Net.Socket.Create = () =>
  			{
  				//Console.WriteLine($"Creating socket {nameof(LinuxTcpSocket)}");
  				return new LinuxTcpSocket();
  				//return new OTAPI.Sockets.PoolSocket();
  				//return new Terraria.Net.Sockets.TcpSocket();
  			};
  			OTAPI.Hooks.Player.Announce = (int playerId) =>
  			{
  				//TShock handles this
  				return OTAPI.HookResult.Cancel;
  			};
  
  			Main.SettingsUnlock_WorldEvil = true;
  
  			TerrariaApi.Reporting.CrashReporter.HeapshotRequesting += CrashReporter_HeapshotRequesting;
  
  			try
  			{
  				CliParser.Reset();
  				HandleCommandLine(Environment.GetCommandLineArgs());
  
  				if (!Directory.Exists(SavePath))
  					Directory.CreateDirectory(SavePath);
  
  				ConfigFile.ConfigRead += OnConfigRead;
  				FileTools.SetupConfig();
  
  				Main.ServerSideCharacter = ServerSideCharacterConfig.Enabled;
  
  				//TSAPI previously would do this automatically, but the vanilla server wont
  				if (Netplay.ServerIP == null)
  					Netplay.ServerIP = IPAddress.Any;
  
  				DateTime now = DateTime.Now;
  				// Log path was not already set by the command line parameter?
  				if (LogPath == LogPathDefault)
  					LogPath = Config.LogPath;
  				try
  				{
  					logFilename = Path.Combine(LogPath, now.ToString(LogFormat) + ".log");
  					if (!Directory.Exists(LogPath))
  						Directory.CreateDirectory(LogPath);
  				}
  				catch (Exception ex)
  				{
  					logPathSetupWarning =
  						"Could not apply the given log path / log format, defaults will be used. Exception details:\n" + ex;
  
  					ServerApi.LogWriter.PluginWriteLine(this, logPathSetupWarning, TraceLevel.Error);
  
  					// Problem with the log path or format use the default
  					logFilename = Path.Combine(LogPathDefault, now.ToString(LogFormatDefault) + ".log");
  				}
  
  				AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
  			}
  			catch (Exception ex)
  			{
  				// Will be handled by the server api and written to its crashlog.txt.
  				throw new Exception("Fatal TShock initialization exception. See inner exception for details.", ex);
  			}
  
  			// Further exceptions are written to TShock's log from now on.
  			try
  			{
  				if (Config.StorageType.ToLower() == "sqlite")
  				{
  					string sql = Path.Combine(SavePath, "tshock.sqlite");
  					DB = new SqliteConnection(string.Format("uri=file://{0},Version=3", sql));
  				}
  				else if (Config.StorageType.ToLower() == "mysql")
  				{
  					try
  					{
  						var hostport = Config.MySqlHost.Split(':');
  						DB = new MySqlConnection();
  						DB.ConnectionString =
  							String.Format("Server={0}; Port={1}; Database={2}; Uid={3}; Pwd={4};",
  								hostport[0],
  								hostport.Length > 1 ? hostport[1] : "3306",
  								Config.MySqlDbName,
  								Config.MySqlUsername,
  								Config.MySqlPassword
  								);
  					}
  					catch (MySqlException ex)
  					{
  						ServerApi.LogWriter.PluginWriteLine(this, ex.ToString(), TraceLevel.Error);
  						throw new Exception("MySql not setup correctly");
  					}
  				}
  				else
  				{
  					throw new Exception("Invalid storage type");
  				}
  
  				if (Config.UseSqlLogs)
  					Log = new SqlLog(DB, logFilename, LogClear);
  				else
  					Log = new TextLog(logFilename, LogClear);
  
  				if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
  				{
  					Log.ConsoleInfo(
  						"TShock was improperly shut down. Please use the exit command in the future to prevent this.");
  					File.Delete(Path.Combine(SavePath, "tshock.pid"));
  				}
  				File.WriteAllText(Path.Combine(SavePath, "tshock.pid"),
  					Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture));
  
  				CliParser.Reset();
  				HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs());
  
  				Backups = new BackupManager(Path.Combine(SavePath, "backups"));
  				Backups.KeepFor = Config.BackupKeepFor;
  				Backups.Interval = Config.BackupInterval;
  				Bans = new BanManager(DB);
  				Warps = new WarpManager(DB);
  				Regions = new RegionManager(DB);
  				Users = new UserManager(DB);
  				Groups = new GroupManager(DB);
  				Itembans = new ItemManager(DB);
  				ProjectileBans = new ProjectileManagager(DB);
  				TileBans = new TileManager(DB);
  				RememberedPos = new RememberedPosManager(DB);
  				CharacterDB = new CharacterManager(DB);
  				RestApi = new SecureRest(Netplay.ServerIP, Config.RestApiPort);
  				RestManager = new RestManager(RestApi);
  				RestManager.RegisterRestfulCommands();
  
  				var geoippath = "GeoIP.dat";
  				if (Config.EnableGeoIP && File.Exists(geoippath))
  					Geo = new GeoIPCountry(geoippath);
  
  				Log.ConsoleInfo("TShock {0} ({1}) now running.", Version, VersionCodename);
  
  				ServerApi.Hooks.GamePostInitialize.Register(this, OnPostInit);
  				ServerApi.Hooks.GameUpdate.Register(this, OnUpdate);
  				ServerApi.Hooks.GameHardmodeTileUpdate.Register(this, OnHardUpdate);
  				ServerApi.Hooks.GameStatueSpawn.Register(this, OnStatueSpawn);
  				ServerApi.Hooks.ServerConnect.Register(this, OnConnect);
  				ServerApi.Hooks.ServerJoin.Register(this, OnJoin);
  				ServerApi.Hooks.ServerLeave.Register(this, OnLeave);
  				ServerApi.Hooks.ServerChat.Register(this, OnChat);
  				ServerApi.Hooks.ServerCommand.Register(this, ServerHooks_OnCommand);
  				ServerApi.Hooks.NetGetData.Register(this, OnGetData);
  				ServerApi.Hooks.NetSendData.Register(this, NetHooks_SendData);
  				ServerApi.Hooks.NetGreetPlayer.Register(this, OnGreetPlayer);
  				ServerApi.Hooks.NpcStrike.Register(this, NpcHooks_OnStrikeNpc);
  				ServerApi.Hooks.ProjectileSetDefaults.Register(this, OnProjectileSetDefaults);
  				ServerApi.Hooks.WorldStartHardMode.Register(this, OnStartHardMode);
  				ServerApi.Hooks.WorldSave.Register(this, SaveManager.Instance.OnSaveWorld);
  				ServerApi.Hooks.WorldChristmasCheck.Register(this, OnXmasCheck);
  				ServerApi.Hooks.WorldHalloweenCheck.Register(this, OnHalloweenCheck);
  				ServerApi.Hooks.NetNameCollision.Register(this, NetHooks_NameCollision);
  				ServerApi.Hooks.ItemForceIntoChest.Register(this, OnItemForceIntoChest);
  				ServerApi.Hooks.WorldGrassSpread.Register(this, OnWorldGrassSpread);
  				Hooks.PlayerHooks.PlayerPreLogin += OnPlayerPreLogin;
  				Hooks.PlayerHooks.PlayerPostLogin += OnPlayerLogin;
  				Hooks.AccountHooks.AccountDelete += OnAccountDelete;
  				Hooks.AccountHooks.AccountCreate += OnAccountCreate;
  
  				GetDataHandlers.InitGetDataHandler();
  				Commands.InitCommands();
  
  				EnglishLanguage.Initialize();
  
  				if (Config.RestApiEnabled)
  					RestApi.Start();
  
  				Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled"));
  				Log.ConsoleInfo("Backups " + (Backups.Interval > 0 ? "Enabled" : "Disabled"));
  
  				if (Initialized != null)
  					Initialized();
  
  				Log.ConsoleInfo("Welcome to TShock for Terraria. Initialization complete.");
  			}
  			catch (Exception ex)
  			{
  				Log.Error("Fatal Startup Exception");
  				Log.Error(ex.ToString());
  				Environment.Exit(1);
  			}
  		}
  
  		protected void CrashReporter_HeapshotRequesting(object sender, EventArgs e)
  		{
  			foreach (TSPlayer player in TShock.Players)
  			{
  				player.User = null;
  			}
  		}
  
  		/// <summary>Dispose - Called when disposing.</summary>
  		/// <param name="disposing">disposing - If set, disposes of all hooks and other systems.</param>
  		protected override void Dispose(bool disposing)
  		{
  			if (disposing)
  			{
  				// NOTE: order is important here
  				if (Geo != null)
  				{
  					Geo.Dispose();
  				}
  				SaveManager.Instance.Dispose();
  
  				ServerApi.Hooks.GamePostInitialize.Deregister(this, OnPostInit);
  				ServerApi.Hooks.GameUpdate.Deregister(this, OnUpdate);
  				ServerApi.Hooks.GameHardmodeTileUpdate.Deregister(this, OnHardUpdate);
  				ServerApi.Hooks.GameStatueSpawn.Deregister(this, OnStatueSpawn);
  				ServerApi.Hooks.ServerConnect.Deregister(this, OnConnect);
  				ServerApi.Hooks.ServerJoin.Deregister(this, OnJoin);
  				ServerApi.Hooks.ServerLeave.Deregister(this, OnLeave);
  				ServerApi.Hooks.ServerChat.Deregister(this, OnChat);
  				ServerApi.Hooks.ServerCommand.Deregister(this, ServerHooks_OnCommand);
  				ServerApi.Hooks.NetGetData.Deregister(this, OnGetData);
  				ServerApi.Hooks.NetSendData.Deregister(this, NetHooks_SendData);
  				ServerApi.Hooks.NetGreetPlayer.Deregister(this, OnGreetPlayer);
  				ServerApi.Hooks.NpcStrike.Deregister(this, NpcHooks_OnStrikeNpc);
  				ServerApi.Hooks.ProjectileSetDefaults.Deregister(this, OnProjectileSetDefaults);
  				ServerApi.Hooks.WorldStartHardMode.Deregister(this, OnStartHardMode);
  				ServerApi.Hooks.WorldSave.Deregister(this, SaveManager.Instance.OnSaveWorld);
  				ServerApi.Hooks.WorldChristmasCheck.Deregister(this, OnXmasCheck);
  				ServerApi.Hooks.WorldHalloweenCheck.Deregister(this, OnHalloweenCheck);
  				ServerApi.Hooks.NetNameCollision.Deregister(this, NetHooks_NameCollision);
  				ServerApi.Hooks.ItemForceIntoChest.Deregister(this, OnItemForceIntoChest);
  				ServerApi.Hooks.WorldGrassSpread.Deregister(this, OnWorldGrassSpread);
  				TShockAPI.Hooks.PlayerHooks.PlayerPostLogin -= OnPlayerLogin;
  
  				if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
  				{
  					File.Delete(Path.Combine(SavePath, "tshock.pid"));
  				}
  
  				RestApi.Dispose();
  				Log.Dispose();
  			}
  			base.Dispose(disposing);
  		}
  
  		/// <summary>OnPlayerLogin - Fires the PlayerLogin hook to listening plugins.</summary>
  		/// <param name="args">args - The PlayerPostLoginEventArgs object.</param>
  		private void OnPlayerLogin(PlayerPostLoginEventArgs args)
  		{
  			List<String> KnownIps = new List<string>();
  			if (!string.IsNullOrWhiteSpace(args.Player.User.KnownIps))
  			{
  				KnownIps = JsonConvert.DeserializeObject<List<String>>(args.Player.User.KnownIps);
  			}
  
  			if (KnownIps.Count == 0)
  			{
  				KnownIps.Add(args.Player.IP);
  			}
  			else
  			{
  				bool last = KnownIps.Last() == args.Player.IP;
  				if (!last)
  				{
  					if (KnownIps.Count == 100)
  					{
  						KnownIps.RemoveAt(0);
  					}
  
  					KnownIps.Add(args.Player.IP);
  				}
  			}
  
  			args.Player.User.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented);
  			Users.UpdateLogin(args.Player.User);
  		}
  
  		/// <summary>OnAccountDelete - Internal hook fired on account delete.</summary>
  		/// <param name="args">args - The AccountDeleteEventArgs object.</param>
  		private void OnAccountDelete(Hooks.AccountDeleteEventArgs args)
  		{
  			CharacterDB.RemovePlayer(args.User.ID);
  		}
  
  		/// <summary>OnAccountCreate - Internal hook fired on account creation.</summary>
  		/// <param name="args">args - The AccountCreateEventArgs object.</param>
  		private void OnAccountCreate(Hooks.AccountCreateEventArgs args)
  		{
  			CharacterDB.SeedInitialData(Users.GetUser(args.User));
  		}
  
  		/// <summary>OnPlayerPreLogin - Internal hook fired when on player pre login.</summary>
  		/// <param name="args">args - The PlayerPreLoginEventArgs object.</param>
  		private void OnPlayerPreLogin(Hooks.PlayerPreLoginEventArgs args)
  		{
  			if (args.Player.IsLoggedIn)
  				args.Player.SaveServerCharacter();
  		}
  
  		/// <summary>NetHooks_NameCollision - Internal hook fired when a name collision happens.</summary>
  		/// <param name="args">args - The NameCollisionEventArgs object.</param>
  		private void NetHooks_NameCollision(NameCollisionEventArgs args)
  		{
  			string ip = Utils.GetRealIP(Netplay.Clients[args.Who].Socket.GetRemoteAddress().ToString());
  
  			var player = Players.First(p => p != null && p.Name == args.Name && p.Index != args.Who);
  			if (player != null)
  			{
  				if (player.IP == ip)
  				{
  					Netplay.Clients[player.Index].PendingTermination = true;
  					args.Handled = true;
  					return;
  				}
  				if (player.IsLoggedIn)
  				{
  					var ips = JsonConvert.DeserializeObject<List<string>>(player.User.KnownIps);
  					if (ips.Contains(ip))
  					{
  						Netplay.Clients[player.Index].PendingTermination = true;
  						args.Handled = true;
  					}
  				}
  			}
  		}
  
  		/// <summary>OnItemForceIntoChest - Internal hook fired when a player quick stacks items into a chest.</summary>
  		/// <param name="args">The <see cref="ForceItemIntoChestEventArgs"/> object.</param>
  		private void OnItemForceIntoChest(ForceItemIntoChestEventArgs args)
  		{
  			if (args.Handled)
  			{
  				return;
  			}
  
  			if (args.Player == null)
  			{
  				args.Handled = true;
  				return;
  			}
  
  			TSPlayer tsplr = Players[args.Player.whoAmI];
  			if (tsplr == null)
  			{
  				args.Handled = true;
  				return;
  			}
  
  			if (args.Chest != null)
  			{
  				if (Config.RegionProtectChests && !Regions.CanBuild((int)args.WorldPosition.X, (int)args.WorldPosition.Y, tsplr))
  				{
  					args.Handled = true;
  					return;
  				}
  
  				if (CheckRangePermission(tsplr, args.Chest.x, args.Chest.y))
  				{
  					args.Handled = true;
  					return;
  				}
  			}
  		}
  
  		/// <summary>OnXmasCheck - Internal hook fired when the XMasCheck happens.</summary>
  		/// <param name="args">args - The ChristmasCheckEventArgs object.</param>
  		private void OnXmasCheck(ChristmasCheckEventArgs args)
  		{
  			if (args.Handled)
  				return;
  
  			if (Config.ForceXmas)
  			{
  				args.Xmas = true;
  				args.Handled = true;
  			}
  		}
  
  		/// <summary>OnHalloweenCheck - Internal hook fired when the HalloweenCheck happens.</summary>
  		/// <param name="args">args - The HalloweenCheckEventArgs object.</param>
  		private void OnHalloweenCheck(HalloweenCheckEventArgs args)
  		{
  			if (args.Handled)
  				return;
  
  			if (Config.ForceHalloween)
  			{
  				args.Halloween = true;
  				args.Handled = true;
  			}
  		}
  
  		/// <summary>
  		/// Handles exceptions that we didn't catch earlier in the code, or in Terraria.
  		/// </summary>
  		/// <param name="sender">sender - The object that sent the exception.</param>
  		/// <param name="e">e - The UnhandledExceptionEventArgs object.</param>
  		private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
  		{
  			Log.Error(e.ExceptionObject.ToString());
  
  			if (e.ExceptionObject.ToString().Contains("Terraria.Netplay.ListenForClients") ||
  				e.ExceptionObject.ToString().Contains("Terraria.Netplay.ServerLoop"))
  			{
  				var sb = new List<string>();
  				for (int i = 0; i < Netplay.Clients.Length; i++)
  				{
  					if (Netplay.Clients[i] == null)
  					{
  						sb.Add("Client[" + i + "]");
  					}
  					else if (Netplay.Clients[i].Socket == null)
  					{
  						sb.Add("Tcp[" + i + "]");
  					}
  				}
  				Log.Error(string.Join(", ", sb));
  			}
  
  			if (e.IsTerminating)
  			{
  				if (Main.worldPathName != null && Config.SaveWorldOnCrash)
  				{
  					Main.ActiveWorldFileData._path += ".crash";
  					SaveManager.Instance.SaveWorld();
  				}
  			}
  		}
  
  		/// <summary>HandleCommandLine - Handles the command line parameters passed to the server.</summary>
  		/// <param name="parms">parms - The array of arguments passed in through the command line.</param>
  		private void HandleCommandLine(string[] parms)
  		{
  			string path = null;
  
  			//Generic method for doing a path sanity check
  			Action<string> pathChecker = (p) =>
  			{
  				if (!string.IsNullOrWhiteSpace(p) && p.IndexOfAny(Path.GetInvalidPathChars()) == -1)
  				{
  					path = p;
  				}
  			};
  
  			//Prepare the parser with all the flags available
  			CliParser
  				.AddFlag("-configpath", pathChecker)
  					//The .After Action is run after the pathChecker Action
  					.After(() =>
  					{
  						SavePath = path ?? "tshock";
  						if (path != null)
  						{
  							ServerApi.LogWriter.PluginWriteLine(this, "Config path has been set to " + path, TraceLevel.Info);
  						}
  					})
  
  				.AddFlag("-worldpath", pathChecker)
  					.After(() =>
  					{
  						if (path != null)
  						{
  							Main.WorldPath = path;
  							ServerApi.LogWriter.PluginWriteLine(this, "World path has been set to " + path, TraceLevel.Info);
  						}
  					})
  
  				.AddFlag("-logpath", pathChecker)
  					.After(() =>
  					{
  						if (path != null)
  						{
  							LogPath = path;
  							ServerApi.LogWriter.PluginWriteLine(this, "Log path has been set to " + path, TraceLevel.Info);
  						}
  					})
  
  				.AddFlag("-logformat", (format) =>
  					{
  						if (!string.IsNullOrWhiteSpace(format)) { LogFormat = format; }
  					})
  
  				.AddFlag("-config", (cfg) =>
  					{
  						if (!string.IsNullOrWhiteSpace(cfg))
  						{
  							ServerApi.LogWriter.PluginWriteLine(this, string.Format("Loading dedicated config file: {0}", cfg), TraceLevel.Verbose);
  							Main.instance.LoadDedConfig(cfg);
  						}
  					})
  
  				.AddFlag("-port", (p) =>
  					{
  						int port;
  						if (int.TryParse(p, out port))
  						{
  							Netplay.ListenPort = port;
  							ServerApi.LogWriter.PluginWriteLine(this, string.Format("Listening on port {0}.", port), TraceLevel.Verbose);
  						}
  					})
  
  				.AddFlag("-worldname", (world) =>
  					{
  						if (!string.IsNullOrWhiteSpace(world))
  						{
  							Main.instance.SetWorldName(world);
  							ServerApi.LogWriter.PluginWriteLine(this, string.Format("World name will be overridden by: {0}", world), TraceLevel.Verbose);
  						}
  					})
  
  				.AddFlag("-ip", (ip) =>
  					{
  						IPAddress addr;
  						if (IPAddress.TryParse(ip, out addr))
  						{
  							Netplay.ServerIP = addr;
  							ServerApi.LogWriter.PluginWriteLine(this, string.Format("Listening on IP {0}.", addr), TraceLevel.Verbose);
  						}
  						else
  						{
  							// The server should not start up if this argument is invalid.
  							throw new InvalidOperationException("Invalid value given for command line argument \"-ip\".");
  						}
  					})
  
  				.AddFlag("-autocreate", (size) =>
  					{
  						if (!string.IsNullOrWhiteSpace(size))
  						{
  							Main.instance.autoCreate(size);
  						}
  					})
  
  				.AddFlag("--provider-token", (token) => StatTracker.ProviderToken = token)
  
  				//Flags without arguments
  				.AddFlag("-logclear", () => LogClear = true)
  				.AddFlag("-autoshutdown", () => Main.instance.EnableAutoShutdown())
  				.AddFlag("-dump", () => Utils.Dump())
  				.AddFlag("--stats-optout", () => StatTracker.OptOut = true)
  				.AddFlag("--no-restart", () => NoRestart = true);
  
  			CliParser.ParseFromSource(parms);
  
  			/*"-connperip": Todo - Requires an OTAPI modification
  			{
  				int limit;
  				if (int.TryParse(parms[++i], out limit))
  				{
  					//Netplay.MaxConnections = limit;
  					//ServerApi.LogWriter.PluginWriteLine(this, string.Format(
  					//	"Connections per IP have been limited to {0} connections.", limit), TraceLevel.Verbose);
  					ServerApi.LogWriter.PluginWriteLine(this, "\"-connperip\" is not supported in this version of TShock.", TraceLevel.Verbose);
  				}
  				else
  					ServerApi.LogWriter.PluginWriteLine(this, "Invalid value given for command line argument \"-connperip\".", TraceLevel.Warning);
  			}*/
  		}
  
  		/// <summary>HandleCommandLinePostConfigLoad - Handles additional command line options after the config file is read.</summary>
  		/// <param name="parms">parms - The array of arguments passed in through the command line.</param>
  		public static void HandleCommandLinePostConfigLoad(string[] parms)
  		{
  			FlagSet portSet = new FlagSet("-port");
  			FlagSet playerSet = new FlagSet("-maxplayers", "-players");
  			FlagSet restTokenSet = new FlagSet("--rest-token", "-rest-token");
  			FlagSet restEnableSet = new FlagSet("--rest-enabled", "-rest-enabled");
  			FlagSet restPortSet = new FlagSet("--rest-port", "-rest-port");
  
  			CliParser
  				.AddFlags(portSet, (p) =>
  					{
  						int port;
  						if (int.TryParse(p, out port))
  						{
  							Netplay.ListenPort = port;
  							Config.ServerPort = port;
  							OverridePort = true;
  							Log.ConsoleInfo("Port overridden by startup argument. Set to " + port);
  						}
  					})
  				.AddFlags(restTokenSet, (token) =>
  					{
  						RESTStartupTokens.Add(token, new SecureRest.TokenData { Username = "null", UserGroupName = "superadmin" });
  						Console.WriteLine("Startup parameter overrode REST token.");
  					})
  				.AddFlags(restEnableSet, (e) =>
  					{
  						bool enabled;
  						if (bool.TryParse(e, out enabled))
  						{
  							Config.RestApiEnabled = enabled;
  							Console.WriteLine("Startup parameter overrode REST enable.");
  						}
  					})
  				.AddFlags(restPortSet, (p) =>
  				{
  					int restPort;
  					if (int.TryParse(p, out restPort))
  					{
  						Config.RestApiPort = restPort;
  						Console.WriteLine("Startup parameter overrode REST port.");
  					}
  				})
  				.AddFlags(playerSet, (p)=>
  					{
  						int slots;
  						if (int.TryParse(p, out slots))
  						{
  							Config.MaxSlots = slots;
  							Console.WriteLine("Startup parameter overrode maximum player slot configuration value.");
  						}
  					});
  
  			CliParser.ParseFromSource(parms);
  		}
  
  		/// <summary>AuthToken - The auth token used by the /auth system to grant temporary superadmin access to new admins.</summary>
  		public static int AuthToken = -1;
  		private string _cliPassword = null;
  
  		/// <summary>OnPostInit - Fired when the server loads a map, to perform world specific operations.</summary>
  		/// <param name="args">args - The EventArgs object.</param>
  		private void OnPostInit(EventArgs args)
  		{
  			SetConsoleTitle(false);
  
  			//This is to prevent a bug where a CLI-defined password causes packets to be
  			//sent in an unexpected order, resulting in clients being unable to connect
  			if (!string.IsNullOrEmpty(Netplay.ServerPassword))
  			{
  				//CLI defined password overrides a config password
  				_cliPassword = Netplay.ServerPassword;
  				Netplay.ServerPassword = "";
  				Config.ServerPassword = _cliPassword;
  			}
  
  			// Disable the auth system if "auth.lck" is present or a superadmin exists
  			if (File.Exists(Path.Combine(SavePath, "auth.lck")) || Users.GetUsers().Exists(u => u.Group == new SuperAdminGroup().Name))
  			{
  				AuthToken = 0;
  
  				if (File.Exists(Path.Combine(SavePath, "authcode.txt")))
  				{
  					Log.ConsoleInfo("A superadmin account has been detected in the user database, but authcode.txt is still present.");
  					Log.ConsoleInfo("TShock will now disable the auth system and remove authcode.txt as it is no longer needed.");
  					File.Delete(Path.Combine(SavePath, "authcode.txt"));
  				}
  
  				if (!File.Exists(Path.Combine(SavePath, "auth.lck")))
  				{
  					// This avoids unnecessary database work, which can get ridiculously high on old servers as all users need to be fetched
  					File.Create(Path.Combine(SavePath, "auth.lck"));
  				}
  			}
  			else if (!File.Exists(Path.Combine(SavePath, "authcode.txt")))
  			{
  				var r = new Random((int)DateTime.Now.ToBinary());
  				AuthToken = r.Next(100000, 10000000);
  				Console.ForegroundColor = ConsoleColor.Yellow;
  				Console.WriteLine("TShock Notice: To become SuperAdmin, join the game and type {0}auth {1}", Commands.Specifier, AuthToken);
  				Console.WriteLine("This token will display until disabled by verification. ({0}auth)", Commands.Specifier);
  				Console.ResetColor();
  				File.WriteAllText(Path.Combine(SavePath, "authcode.txt"), AuthToken.ToString());
  			}
  			else
  			{
  				AuthToken = Convert.ToInt32(File.ReadAllText(Path.Combine(SavePath, "authcode.txt")));
  				Console.ForegroundColor = ConsoleColor.Yellow;
  				Console.WriteLine("TShock Notice: authcode.txt is still present, and the AuthToken located in that file will be used.");
  				Console.WriteLine("To become superadmin, join the game and type {0}auth {1}", Commands.Specifier, AuthToken);
  				Console.WriteLine("This token will display until disabled by verification. ({0}auth)", Commands.Specifier);
  				Console.ResetColor();
  			}
  
  			Regions.Reload();
  			Warps.ReloadWarps();
  
  			ComputeMaxStyles();
  			FixChestStacks();
  
  			Utils.UpgradeMotD();
  
  			if (Config.UseServerName)
  			{
  				Main.worldName = Config.ServerName;
  			}
  
  			UpdateManager = new UpdateManager();
  			StatTracker.Start();
  		}
  
  		/// <summary>ComputeMaxStyles - Computes the max styles...</summary>
  		private void ComputeMaxStyles()
  		{
  			var item = new Item();
  			for (int i = 0; i < Main.maxItemTypes; i++)
  			{
  				item.netDefaults(i);
  				if (item.placeStyle > 0)
  				{
  					if (GetDataHandlers.MaxPlaceStyles.ContainsKey(item.createTile))
  					{
  						if (item.placeStyle > GetDataHandlers.MaxPlaceStyles[item.createTile])
  							GetDataHandlers.MaxPlaceStyles[item.createTile] = item.placeStyle;
  					}
  					else
  						GetDataHandlers.MaxPlaceStyles.Add(item.createTile, item.placeStyle);
  				}
  			}
  		}
  
  		/// <summary>FixChestStacks - Verifies that each stack in each chest is valid and not over the max stack count.</summary>
  		private void FixChestStacks()
  		{
  			if (Config.IgnoreChestStacksOnLoad)
  				return;
  
  			foreach (Chest chest in Main.chest)
  			{
  				if (chest != null)
  				{
  					foreach (Item item in chest.item)
  					{
  						if (item != null && item.stack > item.maxStack)
  							item.stack = item.maxStack;
  					}
  				}
  			}
  		}
  
  		/// <summary>LastCheck - Used to keep track of the last check for basically all time based checks.</summary>
  		private DateTime LastCheck = DateTime.UtcNow;
  
  		/// <summary>LastSave - Used to keep track of SSC save intervals.</summary>
  		private DateTime LastSave = DateTime.UtcNow;
  
  		/// <summary>OnUpdate - Called when ever the server ticks.</summary>
  		/// <param name="args">args - EventArgs args</param>
  		private void OnUpdate(EventArgs args)
  		{
  			if (Backups.IsBackupTime)
  				Backups.Backup();
  			//call these every second, not every update
  			if ((DateTime.UtcNow - LastCheck).TotalSeconds >= 1)
  			{
  				OnSecondUpdate();
  				LastCheck = DateTime.UtcNow;
  			}
  
  			if (Main.ServerSideCharacter && (DateTime.UtcNow - LastSave).TotalMinutes >= ServerSideCharacterConfig.ServerSideCharacterSave)
  			{
  				foreach (TSPlayer player in Players)
  				{
  					// prevent null point exceptions
  					if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
  					{
  
  						CharacterDB.InsertPlayerData(player);
  					}
  				}
  				LastSave = DateTime.UtcNow;
  			}
  		}
  
  		/// <summary>OnSecondUpdate - Called effectively every second for all time based checks.</summary>
  		private void OnSecondUpdate()
  		{
  			DisableFlags flags = Config.DisableSecondUpdateLogs ? DisableFlags.WriteToConsole : DisableFlags.WriteToLogAndConsole;
  
  			if (Config.ForceTime != "normal")
  			{
  				switch (Config.ForceTime)
  				{
  					case "day":
  						TSPlayer.Server.SetTime(true, 27000.0);
  						break;
  					case "night":
  						TSPlayer.Server.SetTime(false, 16200.0);
  						break;
  				}
  			}
  
  			foreach (TSPlayer player in Players)
  			{
  				if (player != null && player.Active)
  				{
  					if (player.TilesDestroyed != null)
  					{
  						if (player.TileKillThreshold >= Config.TileKillThreshold)
  						{
  							player.Disable("Reached TileKill threshold.", flags);
  							TSPlayer.Server.RevertTiles(player.TilesDestroyed);
  							player.TilesDestroyed.Clear();
  						}
  					}
  					if (player.TileKillThreshold > 0)
  					{
  						player.TileKillThreshold = 0;
  						//We don't want to revert the entire map in case of a disable.
  						lock (player.TilesDestroyed)
  							player.TilesDestroyed.Clear();
  					}
  
  					if (player.TilesCreated != null)
  					{
  						if (player.TilePlaceThreshold >= Config.TilePlaceThreshold)
  						{
  							player.Disable("Reached TilePlace threshold", flags);
  							lock (player.TilesCreated)
  							{
  								TSPlayer.Server.RevertTiles(player.TilesCreated);
  								player.TilesCreated.Clear();
  							}
  						}
  					}
  					if (player.TilePlaceThreshold > 0)
  					{
  						player.TilePlaceThreshold = 0;
  					}
  
  					if (player.RecentFuse > 0)
  						player.RecentFuse--;
  
  					if ((Main.ServerSideCharacter) && (player.TPlayer.SpawnX > 0) && (player.sX != player.TPlayer.SpawnX))
  					{
  						player.sX = player.TPlayer.SpawnX;
  						player.sY = player.TPlayer.SpawnY;
  					}
  
  					if ((Main.ServerSideCharacter) && (player.sX > 0) && (player.sY > 0) && (player.TPlayer.SpawnX < 0))
  					{
  						player.TPlayer.SpawnX = player.sX;
  						player.TPlayer.SpawnY = player.sY;
  					}
  
  					if (player.RPPending > 0)
  					{
  						if (player.RPPending == 1)
  						{
  							var pos = RememberedPos.GetLeavePos(player.Name, player.IP);
  							player.Teleport(pos.X * 16, pos.Y * 16);
  							player.RPPending = 0;
  						}
  						else
  						{
  							player.RPPending--;
  						}
  					}
  
  					if (player.TileLiquidThreshold >= Config.TileLiquidThreshold)
  					{
  						player.Disable("Reached TileLiquid threshold", flags);
  					}
  					if (player.TileLiquidThreshold > 0)
  					{
  						player.TileLiquidThreshold = 0;
  					}
  
  					if (player.ProjectileThreshold >= Config.ProjectileThreshold)
  					{
  						player.Disable("Reached projectile threshold", flags);
  					}
  					if (player.ProjectileThreshold > 0)
  					{
  						player.ProjectileThreshold = 0;
  					}
  
  					if (player.PaintThreshold >= Config.TilePaintThreshold)
  					{
  						player.Disable("Reached paint threshold", flags);
  					}
  					if (player.PaintThreshold > 0)
  					{
  						player.PaintThreshold = 0;
  					}
  
  					if (player.HealOtherThreshold >= TShock.Config.HealOtherThreshold)
  					{
  						player.Disable("Reached HealOtherPlayer threshold", flags);
  					}
  					if (player.HealOtherThreshold > 0)
  					{
  						player.HealOtherThreshold = 0;
  					}
  
  					if (player.RespawnTimer > 0 && --player.RespawnTimer == 0 && player.Difficulty != 2)
  					{
  						player.Spawn();
  					}
  
  					if (Main.ServerSideCharacter && !player.IsLoggedIn)
  					{
  						if (CheckIgnores(player))
  						{
  							player.Disable(flags: flags);
  						}
  						else if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[player.TPlayer.selectedItem].netID), player))
  						{
  							player.Disable($"holding banned item: {player.TPlayer.inventory[player.TPlayer.selectedItem].Name}", flags);
  							player.SendErrorMessage($"You are holding a banned item: {player.TPlayer.inventory[player.TPlayer.selectedItem].Name}");
  						}
  					}
  					else if (!Main.ServerSideCharacter || (Main.ServerSideCharacter && player.IsLoggedIn))
  					{
  						string check = "none";
  						foreach (Item item in player.TPlayer.inventory)
  						{
  							if (!player.HasPermission(Permissions.ignorestackhackdetection) && (item.stack > item.maxStack || item.stack < 0) &&
  								item.type != 0)
  							{
  								check = "Remove item " + item.Name + " (" + item.stack + ") exceeds max stack of " + item.maxStack;
  								player.SendErrorMessage(check);
  								break;
  							}
  						}
  						player.IgnoreActionsForCheating = check;
  						check = "none";
  						// Please don't remove this for the time being; without it, players wearing banned equipment will only get debuffed once
  						foreach (Item item in player.TPlayer.armor)
  						{
  							if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player))
  							{
  								player.SetBuff(BuffID.Frozen, 330, true);
  								player.SetBuff(BuffID.Stoned, 330, true);
  								player.SetBuff(BuffID.Webbed, 330, true);
  								check = "Remove armor/accessory " + item.Name;
  
  								player.SendErrorMessage("You are wearing banned equipment. {0}", check);
  								break;
  							}
  						}
  						foreach (Item item in player.TPlayer.dye)
  						{
  							if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player))
  							{
  								player.SetBuff(BuffID.Frozen, 330, true);
  								player.SetBuff(BuffID.Stoned, 330, true);
  								player.SetBuff(BuffID.Webbed, 330, true);
  								check = "Remove dye " + item.Name;
  
  								player.SendErrorMessage("You are wearing banned equipment. {0}", check);
  								break;
  							}
  						}
  						foreach (Item item in player.TPlayer.miscEquips)
  						{
  							if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player))
  							{
  								player.SetBuff(BuffID.Frozen, 330, true);
  								player.SetBuff(BuffID.Stoned, 330, true);
  								player.SetBuff(BuffID.Webbed, 330, true);
  								check = "Remove misc equip " + item.Name;
  
  								player.SendErrorMessage("You are wearing banned equipment. {0}", check);
  								break;
  							}
  						}
  						foreach (Item item in player.TPlayer.miscDyes)
  						{
  							if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), player))
  							{
  								player.SetBuff(BuffID.Frozen, 330, true);
  								player.SetBuff(BuffID.Stoned, 330, true);
  								player.SetBuff(BuffID.Webbed, 330, true);
  								check = "Remove misc dye " + item.Name;
  
  								player.SendErrorMessage("You are wearing banned equipment. {0}", check);
  								break;
  							}
  						}
  						player.IgnoreActionsForDisabledArmor = check;
  
  						if (CheckIgnores(player))
  						{
  							player.Disable(flags: flags);
  						}
  						else if (Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(player.TPlayer.inventory[player.TPlayer.selectedItem].netID), player))
  						{
  							player.Disable($"holding banned item: {player.TPlayer.inventory[player.TPlayer.selectedItem].Name}", flags);
  							player.SendErrorMessage($"You are holding a banned item: {player.TPlayer.inventory[player.TPlayer.selectedItem].Name}");
  						}
  					}
  
  					var oldRegion = player.CurrentRegion;
  					player.CurrentRegion = Regions.GetTopRegion(Regions.InAreaRegion(player.TileX, player.TileY));
  
  					if (oldRegion != player.CurrentRegion)
  					{
  						if (oldRegion != null)
  						{
  							RegionHooks.OnRegionLeft(player, oldRegion);
  						}
  
  						if (player.CurrentRegion != null)
  						{
  							RegionHooks.OnRegionEntered(player, player.CurrentRegion);
  						}
  					}
  				}
  			}
  			SetConsoleTitle(false);
  		}
  
  		/// <summary>SetConsoleTitle - Updates the console title with some pertinent information.</summary>
  		/// <param name="empty">empty - True/false if the server is empty; determines if we should use Utils.ActivePlayers() for player count or 0.</param>
  		private void SetConsoleTitle(bool empty)
  		{
  			Console.Title = string.Format("{0}{1}/{2} on {3} @ {4}:{5} (TShock for Terraria v{6})",
  					!string.IsNullOrWhiteSpace(Config.ServerName) ? Config.ServerName + " - " : "",
  					empty ? 0 : Utils.ActivePlayers(),
  					Config.MaxSlots, Main.worldName, Netplay.ServerIP.ToString(), Netplay.ListenPort, Version);
  		}
  
  		/// <summary>OnHardUpdate - Fired when a hardmode tile update event happens.</summary>
  		/// <param name="args">args - The HardmodeTileUpdateEventArgs object.</param>
  		private void OnHardUpdate(HardmodeTileUpdateEventArgs args)
  		{
  			if (args.Handled)
  				return;
  
  			if (!OnCreep(args.Type))
  			{
  				args.Handled = true;
  			}
  		}
  
  		/// <summary>OnWorldGrassSpread - Fired when grass is attempting to spread.</summary>
  		/// <param name="args">args - The GrassSpreadEventArgs object.</param>
  		private void OnWorldGrassSpread(GrassSpreadEventArgs args)
  		{
  			if (args.Handled)
  				return;
  
  			if(!OnCreep(args.Grass))
  			{
  				args.Handled = true;
  			}
  		}
  
  		/// <summary>
  		/// Checks if the tile type is allowed to creep
  		/// </summary>
  		/// <param name="tileType">Tile id</param>
  		/// <returns>True if allowed, otherwise false</returns>
  		private bool OnCreep(int tileType)
  		{
  			if (!Config.AllowCrimsonCreep && (tileType == TileID.Dirt || tileType == TileID.FleshWeeds
  				|| TileID.Sets.Crimson[tileType]))
  			{
  				return false;
  			}
  
  			if (!Config.AllowCorruptionCreep && (tileType == TileID.Dirt || tileType == TileID.CorruptThorns
  				|| TileID.Sets.Corrupt[tileType]))
  			{
  				return false;
  			}
  
  			if (!Config.AllowHallowCreep && (TileID.Sets.Hallow[tileType]))
  			{
  				return false;
  			}
  
  			return true;
  		}
  
  		/// <summary>OnStatueSpawn - Fired when a statue spawns.</summary>
  		/// <param name="args">args - The StatueSpawnEventArgs object.</param>
  		private void OnStatueSpawn(StatueSpawnEventArgs args)
  		{
  			if (args.Within200 < Config.StatueSpawn200 && args.Within600 < Config.StatueSpawn600 && args.WorldWide < Config.StatueSpawnWorld)
  			{
  				args.Handled = true;
  			}
  			else
  			{
  				args.Handled = false;
  			}
  		}
  
  		/// <summary>OnConnect - Fired when a player connects to the server.</summary>
  		/// <param name="args">args - The ConnectEventArgs object.</param>
  		private void OnConnect(ConnectEventArgs args)
  		{
  			if (ShuttingDown)
  			{
  				NetMessage.SendData((int)PacketTypes.Disconnect, args.Who, -1, NetworkText.FromLiteral("Server is shutting down..."));
  				args.Handled = true;
  				return;
  			}
  
  			var player = new TSPlayer(args.Who);
  
  			if (Utils.ActivePlayers() + 1 > Config.MaxSlots + Config.ReservedSlots)
  			{
  				Utils.ForceKick(player, Config.ServerFullNoReservedReason, true, false);
  				args.Handled = true;
  				return;
  			}
  
  			if (!FileTools.OnWhitelist(player.IP))
  			{
  				Utils.ForceKick(player, Config.WhitelistKickReason, true, false);
  				args.Handled = true;
  				return;
  			}
  
  			if (Geo != null)
  			{
  				var code = Geo.TryGetCountryCode(IPAddress.Parse(player.IP));
  				player.Country = code == null ? "N/A" : GeoIPCountry.GetCountryNameByCode(code);
  				if (code == "A1")
  				{
  					if (Config.KickProxyUsers)
  					{
  						Utils.ForceKick(player, "Proxies are not allowed.", true, false);
  						args.Handled = true;
  						return;
  					}
  				}
  			}
  			Players[args.Who] = player;
  		}
  
  		/// <summary>OnJoin - Internal hook called when a player joins. This is called after OnConnect.</summary>
  		/// <param name="args">args - The JoinEventArgs object.</param>
  		private void OnJoin(JoinEventArgs args)
  		{
  			var player = Players[args.Who];
  			if (player == null)
  			{
  				args.Handled = true;
  				return;
  			}
  
  			if (Config.KickEmptyUUID && String.IsNullOrWhiteSpace(player.UUID))
  			{
  				Utils.ForceKick(player, "Your client did not send a UUID, this server is not configured to accept such a client.", true);
  				args.Handled = true;
  				return;
  			}
  
  			Ban ban = null;
  			if (Config.EnableBanOnUsernames)
  			{
  				var newban = Bans.GetBanByName(player.Name);
  				if (null != newban)
  					ban = newban;
  			}
  
  			if (Config.EnableIPBans && null == ban)
  			{
  				ban = Bans.GetBanByIp(player.IP);
  			}
  
  			if (Config.EnableUUIDBans && null == ban && !String.IsNullOrWhiteSpace(player.UUID))
  			{
  				ban = Bans.GetBanByUUID(player.UUID);
  			}
  
  			if (ban != null)
  			{
  				if (!Utils.HasBanExpired(ban))
  				{
  					DateTime exp;
  					if (!DateTime.TryParse(ban.Expiration, out exp))
  					{
  						player.Disconnect("You are banned forever: " + ban.Reason);
  					}
  					else
  					{
  						TimeSpan ts = exp - DateTime.UtcNow;
  						int months = ts.Days / 30;
  						if (months > 0)
  						{
  							player.Disconnect(String.Format("You are banned for {0} month{1} and {2} day{3}: {4}",
  								months, months == 1 ? "" : "s", ts.Days, ts.Days == 1 ? "" : "s", ban.Reason));
  						}
  						else if (ts.Days > 0)
  						{
  							player.Disconnect(String.Format("You are banned for {0} day{1} and {2} hour{3}: {4}",
  								ts.Days, ts.Days == 1 ? "" : "s", ts.Hours, ts.Hours == 1 ? "" : "s", ban.Reason));
  						}
  						else if (ts.Hours > 0)
  						{
  							player.Disconnect(String.Format("You are banned for {0} hour{1} and {2} minute{3}: {4}",
  								ts.Hours, ts.Hours == 1 ? "" : "s", ts.Minutes, ts.Minutes == 1 ? "" : "s", ban.Reason));
  						}
  						else if (ts.Minutes > 0)
  						{
  							player.Disconnect(String.Format("You are banned for {0} minute{1} and {2} second{3}: {4}",
  								ts.Minutes, ts.Minutes == 1 ? "" : "s", ts.Seconds, ts.Seconds == 1 ? "" : "s", ban.Reason));
  						}
  						else
  						{
  							player.Disconnect(String.Format("You are banned for {0} second{1}: {2}",
  								ts.Seconds, ts.Seconds == 1 ? "" : "s", ban.Reason));
  						}
  					}
  					args.Handled = true;
  				}
  			}
  		}
  
  		/// <summary>OnLeave - Called when a player leaves the server.</summary>
  		/// <param name="args">args - The LeaveEventArgs object.</param>
  		private void OnLeave(LeaveEventArgs args)
  		{
  			if (args.Who >= Players.Length || args.Who < 0)
  			{
  				//Something not right has happened
  				return;
  			}
  
  			var tsplr = Players[args.Who];
  			if (tsplr == null)
  			{
  				return;
  			}
  
  			Players[args.Who] = null;
  
  			if (tsplr.ReceivedInfo)
  			{
  				if (!tsplr.SilentKickInProgress && tsplr.State >= 3)
  					Utils.Broadcast(tsplr.Name + " has left.", Color.Yellow);
  				Log.Info("{0} disconnected.", tsplr.Name);
  
  				if (tsplr.IsLoggedIn && !tsplr.IgnoreActionsForClearingTrashCan && Main.ServerSideCharacter && (!tsplr.Dead || tsplr.TPlayer.difficulty != 2))
  				{
  					tsplr.PlayerData.CopyCharacter(tsplr);
  					CharacterDB.InsertPlayerData(tsplr);
  				}
  
  				if (Config.RememberLeavePos && !tsplr.LoginHarassed)
  				{
  					RememberedPos.InsertLeavePos(tsplr.Name, tsplr.IP, (int)(tsplr.X / 16), (int)(tsplr.Y / 16));
  				}
  
  				if (tsplr.tempGroupTimer != null)
  				{
  					tsplr.tempGroupTimer.Stop();
  				}
  			}
  
  			// Fire the OnPlayerLogout hook too, if the player was logged in and they have a TSPlayer object.
  			if (tsplr.IsLoggedIn)
  			{
  				Hooks.PlayerHooks.OnPlayerLogout(tsplr);
  			}
  
  			// The last player will leave after this hook is executed.
  			if (Utils.ActivePlayers() == 1)
  			{
  				if (Config.SaveWorldOnLastPlayerExit)
  					SaveManager.Instance.SaveWorld();
  				SetConsoleTitle(true);
  			}
  		}
  
  		/// <summary>OnChat - Fired when a player chats. Used for handling chat and commands.</summary>
  		/// <param name="args">args - The ServerChatEventArgs object.</param>
  		private void OnChat(ServerChatEventArgs args)
  		{
  			if (args.Handled)
  				return;
  
  			var tsplr = Players[args.Who];
  			if (tsplr == null)
  			{
  				args.Handled = true;
  				return;
  			}
  
  			if (args.Text.Length > 500)
  			{
  				Utils.Kick(tsplr, "Crash attempt via long chat packet.", true);
  				args.Handled = true;
  				return;
  			}
  
  			string text = args.Text;
  
  			// Terraria now has chat commands on the client side.
  			// These commands remove the commands prefix (e.g. /me /playing) and send the command id instead
  			// In order for us to keep legacy code we must reverse this and get the prefix using the command id
  			foreach (var item in Terraria.UI.Chat.ChatManager.Commands._localizedCommands)
  			{
  				if (item.Value._name == args.CommandId._name)
  				{
  					if (!String.IsNullOrEmpty(text))
  					{
  						text = item.Key.Value + ' ' + text;
  					}
  					else
  					{
  						text = item.Key.Value;
  					}
  					break;
  				}
  			}
  
  			if ((text.StartsWith(Config.CommandSpecifier) || text.StartsWith(Config.CommandSilentSpecifier))
  				&& !string.IsNullOrWhiteSpace(text.Substring(1)))
  			{
  				try
  				{
  					args.Handled = true;
  					if (!Commands.HandleCommand(tsplr, text))
  					{
  						// This is required in case anyone makes HandleCommand return false again
  						tsplr.SendErrorMessage("Unable to parse command. Please contact an administrator for assistance.");
  						Log.ConsoleError("Unable to parse command '{0}' from player {1}.", text, tsplr.Name);
  					}
  				}
  				catch (Exception ex)
  				{
  					Log.ConsoleError("An exception occurred executing a command.");
  					Log.Error(ex.ToString());
  				}
  			}
  			else
  			{
  				if (!tsplr.HasPermission(Permissions.canchat))
  				{
  					args.Handled = true;
  				}
  				else if (tsplr.mute)
  				{
  					tsplr.SendErrorMessage("You are muted!");
  					args.Handled = true;
  				}
  				else if (!TShock.Config.EnableChatAboveHeads)
  				{
  					text = String.Format(Config.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix,
  											 args.Text);
  					Hooks.PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text);
  					Utils.Broadcast(text, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B);
  					args.Handled = true;
  				}
  				else
  				{
  					Player ply = Main.player[args.Who];
  					string name = ply.name;
  					ply.name = String.Format(Config.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix);
  					//Update the player's name to format text nicely. This needs to be done because Terraria automatically formats messages against our will
  					NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(ply.name), args.Who, 0, 0, 0, 0);
  
  					//Give that poor player their name back :'c
  					ply.name = name;
  					PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text);
  
  					//This netpacket is used to send chat text from the server to clients, in this case on behalf of a client
  					Terraria.Net.NetPacket packet = Terraria.GameContent.NetModules.NetTextModule.SerializeServerMessage(
  						NetworkText.FromLiteral(text), new Color(tsplr.Group.R, tsplr.Group.G, tsplr.Group.B), (byte)args.Who
  					);
  					//Broadcast to everyone except the player who sent the message.
  					//This is so that we can send them the same nicely formatted message that everyone else gets
  					Terraria.Net.NetManager.Instance.Broadcast(packet, args.Who);
  
  					//Reset their name
  					NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(name), args.Who, 0, 0, 0, 0);
  
  					string msg = String.Format("<{0}> {1}",
  						String.Format(Config.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix),
  						text
  					);
  
  					//Send the original sender their nicely formatted message, and do all the loggy things
  					tsplr.SendMessage(msg, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B);
  					TSPlayer.Server.SendMessage(msg, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B);
  					Log.Info("Broadcast: {0}", msg);
  					args.Handled = true;
  				}
  			}
  		}
  
  		/// <summary>
  		/// Called when a command is issued from the server console.
  		/// </summary>
  		/// <param name="args">The CommandEventArgs object</param>
  		private void ServerHooks_OnCommand(CommandEventArgs args)
  		{
  			if (args.Handled)
  				return;
  
  			if (string.IsNullOrWhiteSpace(args.Command))
  			{
  				args.Handled = true;
  				return;
  			}
  
  			// Damn you ThreadStatic and Redigit
  			if (Main.rand == null)
  			{
  				Main.rand = new UnifiedRandom();
  			}
  
  			if (args.Command == "autosave")
  			{
  				Main.autoSave = Config.AutoSave = !Config.AutoSave;
  				Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled"));
  			}
  			else if (args.Command.StartsWith(Commands.Specifier) || args.Command.StartsWith(Commands.SilentSpecifier))
  			{
  				Commands.HandleCommand(TSPlayer.Server, args.Command);
  			}
  			else
  			{
  				Commands.HandleCommand(TSPlayer.Server, "/" + args.Command);
  			}
  			args.Handled = true;
  		}
  
  		/// <summary>OnGetData - Called when the server gets raw data packets.</summary>
  		/// <param name="e">e - The GetDataEventArgs object.</param>
  		private void OnGetData(GetDataEventArgs e)
  		{
  			if (e.Handled)
  				return;
  
  			PacketTypes type = e.MsgID;
  
  			Debug.WriteLine("Recv: {0:X}: {2} ({1:XX})", e.Msg.whoAmI, (byte)type, type);
  
  			var player = Players[e.Msg.whoAmI];
  			if (player == null || !player.ConnectionAlive)
  			{
  				e.Handled = true;
  				return;
  			}
  
  			if (player.RequiresPassword && type != PacketTypes.PasswordSend)
  			{
  				e.Handled = true;
  				return;
  			}
  
  			if ((player.State < 10 || player.Dead) && (int)type > 12 && (int)type != 16 && (int)type != 42 && (int)type != 50 &&
  				(int)type != 38 && (int)type != 21 && (int)type != 22)
  			{
  				e.Handled = true;
  				return;
  			}
  
  			int length = e.Length - 1;
  			if (length < 0)
  			{
  				length = 0;
  			}
  			using (var data = new MemoryStream(e.Msg.readBuffer, e.Index, e.Length - 1))
  			{
  				// Exceptions are already handled
  				e.Handled = GetDataHandlers.HandlerGetData(type, player, data);
  			}
  		}
  
  		/// <summary>OnGreetPlayer - Fired when a player is greeted by the server. Handles things like the MOTD, join messages, etc.</summary>
  		/// <param name="args">args - The GreetPlayerEventArgs object.</param>
  		private void OnGreetPlayer(GreetPlayerEventArgs args)
  		{
  			var player = Players[args.Who];
  			if (player == null)
  			{
  				args.Handled = true;
  				return;
  			}
  
  			player.LoginMS = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  
  			if (Config.EnableGeoIP && TShock.Geo != null)
  			{
  				Log.Info("{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})", player.Name, player.IP,
  									   player.Group.Name, player.Country, TShock.Utils.ActivePlayers(),
  									   TShock.Config.MaxSlots);
  				if (!player.SilentJoinInProgress)
  					Utils.Broadcast(string.Format("{0} ({1}) has joined.", player.Name, player.Country), Color.Yellow);
  			}
  			else
  			{
  				Log.Info("{0} ({1}) from '{2}' group joined. ({3}/{4})", player.Name, player.IP,
  									   player.Group.Name, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots);
  				if (!player.SilentJoinInProgress)
  					Utils.Broadcast(player.Name + " has joined.", Color.Yellow);
  			}
  
  			if (Config.DisplayIPToAdmins)
  				Utils.SendLogs(string.Format("{0} has joined. IP: {1}", player.Name, player.IP), Color.Blue);
  
  			Utils.ShowFileToUser(player, FileTools.MotdPath);
  
  			string pvpMode = Config.PvPMode.ToLowerInvariant();
  			if (pvpMode == "always")
  			{
  				player.TPlayer.hostile = true;
  				player.SendData(PacketTypes.TogglePvp, "", player.Index);
  				TSPlayer.All.SendData(PacketTypes.TogglePvp, "", player.Index);
  			}
  
  			if (!player.IsLoggedIn)
  			{
  				if (Main.ServerSideCharacter)
  				{
  					player.SendErrorMessage(
  						player.IgnoreActionsForInventory = String.Format("Server side characters is enabled! Please {0}register or {0}login to play!", Commands.Specifier));
  					player.LoginHarassed = true;
  				}
  				else if (Config.RequireLogin)
  				{
  					player.SendErrorMessage("Please {0}register or {0}login to play!", Commands.Specifier);
  					player.LoginHarassed = true;
  				}
  			}
  
  			player.LastNetPosition = new Vector2(Main.spawnTileX * 16f, Main.spawnTileY * 16f);
  
  			if (Config.RememberLeavePos && (RememberedPos.GetLeavePos(player.Name, player.IP) != Vector2.Zero) && !player.LoginHarassed)
  			{
  				player.RPPending = 3;
  				player.SendInfoMessage("You will be teleported to your last known location...");
  			}
  
  			args.Handled = true;
  		}
  
  		/// <summary>NpcHooks_OnStrikeNpc - Fired when an NPC strike packet happens.</summary>
  		/// <param name="e">e - The NpcStrikeEventArgs object.</param>
  		private void NpcHooks_OnStrikeNpc(NpcStrikeEventArgs e)
  		{
  			if (Config.InfiniteInvasion)
  			{
  				if (Main.invasionSize < 10)
  				{
  					Main.invasionSize = 20000000;
  				}
  			}
  		}
  
  		/// <summary>OnProjectileSetDefaults - Called when a projectile sets the default attributes for itself.</summary>
  		/// <param name="e">e - The SetDefaultsEventArgs object praameterized with Projectile and int.</param>
  		private void OnProjectileSetDefaults(SetDefaultsEventArgs<Projectile, int> e)
  		{
  			//tombstone fix.
  			if (e.Info == 43 || (e.Info >= 201 && e.Info <= 205) || (e.Info >= 527 && e.Info <= 531))
  				if (Config.DisableTombstones)
  					e.Object.SetDefaults(0);
  			if (e.Info == 75)
  				if (Config.DisableClownBombs)
  					e.Object.SetDefaults(0);
  			if (e.Info == 109)
  				if (Config.DisableSnowBalls)
  					e.Object.SetDefaults(0);
  		}
  
  		/// <summary>NetHooks_SendData - Fired when the server sends data.</summary>
  		/// <param name="e">e - The SendDataEventArgs object.</param>
  		private void NetHooks_SendData(SendDataEventArgs e)
  		{
  			if (e.MsgId == PacketTypes.PlayerHp)
  			{
  				if (Main.player[(byte)e.number].statLife <= 0)
  				{
  					e.Handled = true;
  					return;
  				}
  			}
  		}
  
  		/// <summary>OnStartHardMode - Fired when hard mode is started.</summary>
  		/// <param name="e">e - The HandledEventArgs object.</param>
  		private void OnStartHardMode(HandledEventArgs e)
  		{
  			if (Config.DisableHardmode)
  				e.Handled = true;
  		}
  
  
  		/// <summary>StartInvasion - Starts an invasion on the server.</summary>
  		/// <param name="type">type - The invasion type id.</param>
  		//TODO: Why is this in TShock's main class?
  		public static void StartInvasion(int type)
  		{
  			int invasionSize = 0;
  
  			if (Config.InfiniteInvasion)
  			{
  				invasionSize = 20000000;
  			}
  			else
  			{
  				invasionSize = 100 + (Config.InvasionMultiplier * Utils.ActivePlayers());
  			}
  
  			// Note: This is a workaround to previously providing the size as a parameter in StartInvasion
  			Main.invasionSize = invasionSize;
  
  			Main.StartInvasion(type);
  		}
  
  		/// <summary>CheckProjectilePermission - Checks if a projectile is banned.</summary>
  		/// <param name="player">player - The TSPlayer object that created the projectile.</param>
  		/// <param name="index">index - The projectile index.</param>
  		/// <param name="type">type - The projectile type.</param>
  		/// <returns>bool - True if the player does not have permission to use a projectile.</returns>
  		public static bool CheckProjectilePermission(TSPlayer player, int index, int type)
  		{
  			if (type == 43)
  			{
  				return true;
  			}
  
  			if (type == 17 && Itembans.ItemIsBanned("Dirt Rod", player))
  			//Dirt Rod Projectile
  			{
  				return true;
  			}
  
  			if ((type == 42 || type == 65 || type == 68) && Itembans.ItemIsBanned("Sandgun", player)) //Sandgun Projectiles
  			{
  				return true;
  			}
  
  			Projectile proj = new Projectile();
  			proj.SetDefaults(type);
  
  			if (Main.projHostile[type])
  			{
  				//player.SendMessage( proj.name, Color.Yellow);
  				return true;
  			}
  
  			return false;
  		}
  
  		/// <summary>CheckRangePermission - Checks if a player has permission to modify a tile dependent on range checks.</summary>
  		/// <param name="player">player - The TSPlayer object.</param>
  		/// <param name="x">x - The x coordinate of the tile.</param>
  		/// <param name="y">y - The y coordinate of the tile.</param>
  		/// <param name="range">range - The range to check for.</param>
  		/// <returns>bool - True if the player should not be able to place the tile. False if they can, or if range checks are off.</returns>
  		public static bool CheckRangePermission(TSPlayer player, int x, int y, int range = 32)
  		{
  			if (Config.RangeChecks && ((Math.Abs(player.TileX - x) > range) || (Math.Abs(player.TileY - y) > range)))
  			{
  				return true;
  			}
  			return false;
  		}
  
  		/// <summary>CheckTilePermission - Checks to see if a player has permission to modify a tile in general.</summary>
  		/// <param name="player">player - The TSPlayer object.</param>
  		/// <param name="tileX">tileX - The x coordinate of the tile.</param>
  		/// <param name="tileY">tileY - The y coordinate of the tile.</param>
  		/// <param name="tileType">tileType - The tile type.</param>
  		/// <param name="actionType">actionType - The type of edit that took place.</param>
  		/// <returns>bool - True if the player should not be able to modify a tile.</returns>
  		public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY, short tileType, GetDataHandlers.EditAction actionType)
  		{
  			if (!player.HasPermission(Permissions.canbuild))
  			{
  				if (TShock.Config.AllowIce && actionType != GetDataHandlers.EditAction.PlaceTile)
  				{
  					foreach (Point p in player.IceTiles)
  					{
  						if (p.X == tileX && p.Y == tileY && (Main.tile[p.X, p.Y].type == 0 || Main.tile[p.X, p.Y].type == 127))
  						{
  							player.IceTiles.Remove(p);
  							return false;
  						}
  					}
  
  					if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000)
  					{
  						player.SendErrorMessage("You do not have permission to build!");
  						player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  					}
  					return true;
  				}
  
  				if (TShock.Config.AllowIce && actionType == GetDataHandlers.EditAction.PlaceTile && tileType == 127)
  				{
  					player.IceTiles.Add(new Point(tileX, tileY));
  					return false;
  				}
  
  				if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000)
  				{
  					player.SendErrorMessage("You do not have permission to build!");
  					player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  				}
  				return true;
  			}
  
  			if (!Regions.CanBuild(tileX, tileY, player))
  			{
  				if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000)
  				{
  					player.SendErrorMessage("This region is protected from changes.");
  					player.RPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  				}
  				return true;
  			}
  
  			if (Config.DisableBuild)
  			{
  				if (!player.HasPermission(Permissions.antibuild))
  				{
  					if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000)
  					{
  						player.SendErrorMessage("The world is protected from changes.");
  						player.WPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  					}
  					return true;
  				}
  			}
  
  			if (Config.SpawnProtection)
  			{
  				if (!player.HasPermission(Permissions.editspawn))
  				{
  					if (CheckSpawn(tileX, tileY))
  					{
  						if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 2000)
  						{
  							player.SendErrorMessage("Spawn is protected from changes.");
  							player.SPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  						}
  						return true;
  					}
  				}
  			}
  			return false;
  		}
  
  		/// <summary>CheckTilePermission - Checks to see if a player has the ability to modify a tile at a given position.</summary>
  		/// <param name="player">player - The TSPlayer object.</param>
  		/// <param name="tileX">tileX - The x coordinate of the tile.</param>
  		/// <param name="tileY">tileY - The y coordinate of the tile.</param>
  		/// <param name="paint">paint - Whether or not the tile is paint.</param>
  		/// <returns>bool - True if the player should not be able to modify the tile.</returns>
  		public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY, bool paint = false)
  		{
  			if ((!paint && !player.HasPermission(Permissions.canbuild)) ||
  				(paint && !player.HasPermission(Permissions.canpaint)))
  			{
  				if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000)
  				{
  					if (paint)
  					{
  						player.SendErrorMessage("You do not have permission to paint!");
  					}
  					else
  					{
  						player.SendErrorMessage("You do not have permission to build!");
  					}
  					player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  				}
  				return true;
  			}
  
  			if (!Regions.CanBuild(tileX, tileY, player))
  			{
  				if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000)
  				{
  					player.SendErrorMessage("This region is protected from changes.");
  					player.RPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  				}
  				return true;
  			}
  
  			if (Config.DisableBuild)
  			{
  				if (!player.HasPermission(Permissions.antibuild))
  				{
  					if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000)
  					{
  						player.SendErrorMessage("The world is protected from changes.");
  						player.WPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  					}
  					return true;
  				}
  			}
  
  			if (Config.SpawnProtection)
  			{
  				if (!player.HasPermission(Permissions.editspawn))
  				{
  					if (CheckSpawn(tileX, tileY))
  					{
  						if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 1000)
  						{
  							player.SendErrorMessage("Spawn is protected from changes.");
  							player.SPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  						}
  						return true;
  					}
  				}
  			}
  			return false;
  		}
  
  		/// <summary>CheckSpawn - Checks to see if a location is inside the spawn protection zone.</summary>
  		/// <param name="x">x - The x coordinate to check.</param>
  		/// <param name="y">y - The y coordinate to check.</param>
  		/// <returns>bool - True if the location is inside the spawn protection zone.</returns>
  		public static bool CheckSpawn(int x, int y)
  		{
  			Vector2 tile = new Vector2(x, y);
  			Vector2 spawn = new Vector2(Main.spawnTileX, Main.spawnTileY);
  			return Distance(spawn, tile) <= Config.SpawnProtectionRadius;
  		}
  
  		/// <summary>Distance - Determines the distance between two vectors.</summary>
  		/// <param name="value1">value1 - The first vector location.</param>
  		/// <param name="value2">value2 - The second vector location.</param>
  		/// <returns>float - The distance between the two vectors.</returns>
  		public static float Distance(Vector2 value1, Vector2 value2)
  		{
  			float num2 = value1.X - value2.X;
  			float num = value1.Y - value2.Y;
  			float num3 = (num2 * num2) + (num * num);
  			return (float)Math.Sqrt(num3);
  		}
  
  		/// <summary>HackedInventory - Checks to see if a user has a hacked inventory. In addition, messages players the result.</summary>
  		/// <param name="player">player - The TSPlayer object.</param>
  		/// <returns>bool - True if the player has a hacked inventory.</returns>
  		public static bool HackedInventory(TSPlayer player)
  		{
  			bool check = false;
  
  			Item[] inventory = player.TPlayer.inventory;
  			Item[] armor = player.TPlayer.armor;
  			Item[] dye = player.TPlayer.dye;
  			Item[] miscEquips = player.TPlayer.miscEquips;
  			Item[] miscDyes = player.TPlayer.miscDyes;
  			Item[] piggy = player.TPlayer.bank.item;
  			Item[] safe = player.TPlayer.bank2.item;
  			Item[] forge = player.TPlayer.bank3.item;
  			Item trash = player.TPlayer.trashItem;
  			for (int i = 0; i < NetItem.MaxInventory; i++)
  			{
  				if (i < NetItem.InventoryIndex.Item2)
  				{
  					//0-58
  					Item item = new Item();
  					if (inventory[i] != null && inventory[i].netID != 0)
  					{
  						item.netDefaults(inventory[i].netID);
  						item.Prefix(inventory[i].prefix);
  						item.AffixName();
  						if (inventory[i].stack > item.maxStack)
  						{
  							check = true;
  							player.SendMessage(
  								String.Format("Stack cheat detected. Remove item {0} ({1}) and then rejoin", item.Name, inventory[i].stack),
  								Color.Cyan);
  						}
  					}
  				}
  				else if (i < NetItem.ArmorIndex.Item2)
  				{
  					//59-78
  					var index = i - NetItem.ArmorIndex.Item1;
  					Item item = new Item();
  					if (armor[index] != null && armor[index].netID != 0)
  					{
  						item.netDefaults(armor[index].netID);
  						item.Prefix(armor[index].prefix);
  						item.AffixName();
  						if (armor[index].stack > item.maxStack)
  						{
  							check = true;
  							player.SendMessage(
  								String.Format("Stack cheat detected. Remove armor {0} ({1}) and then rejoin", item.Name, armor[index].stack),
  								Color.Cyan);
  						}
  					}
  				}
  				else if (i < NetItem.DyeIndex.Item2)
  				{
  					//79-88
  					var index = i - NetItem.DyeIndex.Item1;
  					Item item = new Item();
  					if (dye[index] != null && dye[index].netID != 0)
  					{
  						item.netDefaults(dye[index].netID);
  						item.Prefix(dye[index].prefix);
  						item.AffixName();
  						if (dye[index].stack > item.maxStack)
  						{
  							check = true;
  							player.SendMessage(
  								String.Format("Stack cheat detected. Remove dye {0} ({1}) and then rejoin", item.Name, dye[index].stack),
  								Color.Cyan);
  						}
  					}
  				}
  				else if (i < NetItem.MiscEquipIndex.Item2)
  				{
  					//89-93
  					var index = i - NetItem.MiscEquipIndex.Item1;
  					Item item = new Item();
  					if (miscEquips[index] != null && miscEquips[index].netID != 0)
  					{
  						item.netDefaults(miscEquips[index].netID);
  						item.Prefix(miscEquips[index].prefix);
  						item.AffixName();
  						if (miscEquips[index].stack > item.maxStack)
  						{
  							check = true;
  							player.SendMessage(
  								String.Format("Stack cheat detected. Remove item {0} ({1}) and then rejoin", item.Name, miscEquips[index].stack),
  								Color.Cyan);
  						}
  					}
  				}
  				else if (i < NetItem.MiscDyeIndex.Item2)
  				{
  					//93-98
  					var index = i - NetItem.MiscDyeIndex.Item1;
  					Item item = new Item();
  					if (miscDyes[index] != null && miscDyes[index].netID != 0)
  					{
  						item.netDefaults(miscDyes[index].netID);
  						item.Prefix(miscDyes[index].prefix);
  						item.AffixName();
  						if (miscDyes[index].stack > item.maxStack)
  						{
  							check = true;
  							player.SendMessage(
  								String.Format("Stack cheat detected. Remove item dye {0} ({1}) and then rejoin", item.Name, miscDyes[index].stack),
  								Color.Cyan);
  						}
  					}
  				}
  				else if (i < NetItem.PiggyIndex.Item2)
  				{
  					//98-138
  					var index = i - NetItem.PiggyIndex.Item1;
  					Item item = new Item();
  					if (piggy[index] != null && piggy[index].netID != 0)
  					{
  						item.netDefaults(piggy[index].netID);
  						item.Prefix(piggy[index].prefix);
  						item.AffixName();
  
  						if (piggy[index].stack > item.maxStack)
  						{
  							check = true;
  							player.SendMessage(
  								String.Format("Stack cheat detected. Remove Piggy-bank item {0} ({1}) and then rejoin", item.Name, piggy[index].stack),
  								Color.Cyan);
  						}
  					}
  				}
  				else if (i < NetItem.SafeIndex.Item2)
  				{
  					//138-178
  					var index = i - NetItem.SafeIndex.Item1;
  					Item item = new Item();
  					if (safe[index] != null && safe[index].netID != 0)
  					{
  						item.netDefaults(safe[index].netID);
  						item.Prefix(safe[index].prefix);
  						item.AffixName();
  
  						if (safe[index].stack > item.maxStack)
  						{
  							check = true;
  							player.SendMessage(
  								String.Format("Stack cheat detected. Remove Safe item {0} ({1}) and then rejoin", item.Name, safe[index].stack),
  								Color.Cyan);
  						}
  					}
  				}
  				else if (i < NetItem.TrashIndex.Item2)
  				{
  					//179-219
  					Item item = new Item();
  					if (trash != null && trash.netID != 0)
  					{
  						item.netDefaults(trash.netID);
  						item.Prefix(trash.prefix);
  						item.AffixName();
  
  						if (trash.stack > item.maxStack)
  						{
  							check = true;
  							player.SendMessage(
  								String.Format("Stack cheat detected. Remove trash item {0} ({1}) and then rejoin", item.Name, trash.stack),
  								Color.Cyan);
  						}
  					}
  				}
  				else
  				{
  					//220
  					var index = i - NetItem.ForgeIndex.Item1;
  					Item item = new Item();
  
gt: 					if (forge[index] != null && forge[index].netID != 0)