/FileExtraction/Java_unseen/Wind-Development_WindSpigot/WindSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
package org.bukkit.craftbukkit;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;

import org.apache.commons.lang.Validate;
import org.bukkit.BanList;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.UnsafeValues;
import org.bukkit.Warning.WarningState;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.WorldCreator;
import org.bukkit.command.Command;
import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.conversations.Conversable;
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.generator.CraftChunkData;
import org.bukkit.craftbukkit.help.SimpleHelpMap;
import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe;
import org.bukkit.craftbukkit.inventory.CraftInventoryCustom;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.craftbukkit.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.inventory.CraftRecipe;
import org.bukkit.craftbukkit.inventory.CraftShapedRecipe;
import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe;
import org.bukkit.craftbukkit.inventory.RecipeIterator;
import org.bukkit.craftbukkit.map.CraftMapView;
import org.bukkit.craftbukkit.metadata.EntityMetadataStore;
import org.bukkit.craftbukkit.metadata.PlayerMetadataStore;
import org.bukkit.craftbukkit.metadata.WorldMetadataStore;
import org.bukkit.craftbukkit.potion.CraftPotionBrewer;
import org.bukkit.craftbukkit.scheduler.CraftScheduler;
import org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager;
import org.bukkit.craftbukkit.util.CraftIconCache;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.util.DatFileFilter;
import org.bukkit.craftbukkit.util.Versioning;
import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerChatTabCompleteEvent;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.help.HelpMap;
import org.bukkit.inventory.FurnaceRecipe;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginLoadOrder;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicesManager;
import org.bukkit.plugin.SimplePluginManager;
import org.bukkit.plugin.SimpleServicesManager;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.bukkit.plugin.messaging.Messenger;
import org.bukkit.plugin.messaging.StandardMessenger;
import org.bukkit.potion.Potion;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitWorker;
import org.bukkit.util.StringUtil;
import org.bukkit.util.permissions.DefaultPermissions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.MarkedYAMLException;

import com.avaje.ebean.config.DataSourceConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.SQLitePlatform;
import com.avaje.ebeaninternal.server.lib.sql.TransactionIsolation;
import com.eatthepath.uuid.FastUUID;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.mojang.authlib.GameProfile;

import ga.windpvp.windspigot.async.entitytracker.AsyncEntityTracker;
import ga.windpvp.windspigot.commons.PluginUtils;
import ga.windpvp.windspigot.config.WindSpigotConfig;
import ga.windpvp.windspigot.knockback.KnockbackConfig;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import jline.console.ConsoleReader;
import me.elier.nachospigot.config.NachoConfig;
import net.md_5.bungee.api.chat.BaseComponent;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.ChunkProviderServer;
import net.minecraft.server.ChunkRegionLoader;
import net.minecraft.server.CommandAbstract;
import net.minecraft.server.CommandDispatcher;
import net.minecraft.server.ContainerWorkbench;
import net.minecraft.server.Convertable;
import net.minecraft.server.CraftingManager;
import net.minecraft.server.DedicatedPlayerList;
import net.minecraft.server.DedicatedServer;
import net.minecraft.server.Enchantment;
import net.minecraft.server.EntityPlayer;
import net.minecraft.server.EntityTracker;
import net.minecraft.server.EnumDifficulty;
import net.minecraft.server.ExceptionWorldConflict;
import net.minecraft.server.FileIOThread;
import net.minecraft.server.ICommand;
import net.minecraft.server.IDataManager;
import net.minecraft.server.IProgressUpdate;
import net.minecraft.server.Items;
import net.minecraft.server.JsonListEntry;
import net.minecraft.server.LocaleI18n;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.MobEffectList;
import net.minecraft.server.PersistentCollection;
import net.minecraft.server.PlayerList;
import net.minecraft.server.PropertyManager;
import net.minecraft.server.RecipesFurnace;
import net.minecraft.server.RegionFile;
import net.minecraft.server.RegionFileCache;
import net.minecraft.server.ServerCommand;
import net.minecraft.server.ServerNBTManager;
import net.minecraft.server.WorldData;
import net.minecraft.server.WorldLoaderServer;
import net.minecraft.server.WorldManager;
import net.minecraft.server.WorldMap;
import net.minecraft.server.WorldNBTStorage;
import net.minecraft.server.WorldServer;
import net.minecraft.server.WorldSettings;
import net.minecraft.server.WorldType;
import xyz.sculas.nacho.malware.AntiMalware;

public final class CraftServer implements Server {
	private static final Player[] EMPTY_PLAYER_ARRAY = new Player[0];
	private String serverName = "WindSpigot";
	private final String serverVersion;
	private final String bukkitVersion = Versioning.getBukkitVersion();
	private final Logger logger = Logger.getLogger("Minecraft");
	private final ServicesManager servicesManager = new SimpleServicesManager();
	private final CraftScheduler scheduler = new CraftScheduler();
	private final SimpleCommandMap commandMap = new SimpleCommandMap(this);
	private final SimpleHelpMap helpMap = new SimpleHelpMap(this);
	private final StandardMessenger messenger = new StandardMessenger();
	private final PluginManager pluginManager = new SimplePluginManager(this, commandMap);
	protected final MinecraftServer console;
	protected final DedicatedPlayerList playerList;
	private final Map<String, World> worlds = new LinkedHashMap<>();
	private YamlConfiguration configuration;
	private YamlConfiguration commandsConfiguration;
	private final Yaml yaml = new Yaml(new SafeConstructor());
	private final Map<UUID, OfflinePlayer> offlinePlayers = new MapMaker().softValues().makeMap();
	private final EntityMetadataStore entityMetadata = new EntityMetadataStore();
	private final PlayerMetadataStore playerMetadata = new PlayerMetadataStore();
	private final WorldMetadataStore worldMetadata = new WorldMetadataStore();
	private int monsterSpawn = -1;
	private int animalSpawn = -1;
	private int waterAnimalSpawn = -1;
	private int ambientSpawn = -1;
	public int chunkGCPeriod = -1;
	public int chunkGCLoadThresh = 0;
	private File container;
	private WarningState warningState = WarningState.DEFAULT;
	private final BooleanWrapper online = new org.bukkit.craftbukkit.CraftServer.BooleanWrapper();
	public CraftScoreboardManager scoreboardManager;
	public boolean playerCommandState;
	private boolean printSaveWarning;
	private CraftIconCache icon;
	private boolean overrideAllCommandBlockCommands = false;
	private final Pattern validUserPattern = Pattern.compile("^[a-zA-Z0-9_]{2,16}$");
	private final UUID invalidUserUUID = UUID.nameUUIDFromBytes("InvalidUsername".getBytes(Charsets.UTF_8));
	private final List<CraftPlayer> playerView;
	public int reloadCount;

	private static final class BooleanWrapper {
		private boolean value = true;
	}

	static {
		ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
		CraftItemFactory.instance();
	}

	public CraftServer(MinecraftServer console, PlayerList playerList) {
		this.console = console;
		this.playerList = (DedicatedPlayerList) playerList;
		this.playerView = Collections.unmodifiableList(
				Lists.transform(playerList.players, net.minecraft.server.EntityPlayer::getBukkitEntity));
		// WindSpigot start - manual versioning
		if (serverName.equalsIgnoreCase("WindSpigot")) {
			this.serverVersion = serverName + " Release v2.1.3"; // Bump this every release
		} else {
			this.serverVersion = serverName; // Only put the release version if the server version is default
		}
		// WindSpigot end
		//CraftServer.class.getPackage().getImplementationVersion();
		online.value = console.getPropertyManager().getBoolean("online-mode", true);

		Bukkit.setServer(this);

		// Register all the Enchantments and PotionTypes now so we can stop new
		// registration immediately after
		Enchantment.DAMAGE_ALL.getClass();
		org.bukkit.enchantments.Enchantment.stopAcceptingRegistrations();

		Potion.setPotionBrewer(new CraftPotionBrewer());
		MobEffectList.BLINDNESS.getClass();
		PotionEffectType.stopAcceptingRegistrations();
		// Ugly hack :(

		if (!Main.useConsole) {
			getLogger().info("Console input is disabled due to --noconsole command argument");
		}

		configuration = YamlConfiguration.loadConfiguration(getConfigFile());
		configuration.options().copyDefaults(true);
		configuration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(
				getClass().getClassLoader().getResourceAsStream("configurations/bukkit.yml"), Charsets.UTF_8)));
		ConfigurationSection legacyAlias = null;
		if (!configuration.isString("aliases")) {
			legacyAlias = configuration.getConfigurationSection("aliases");
			configuration.set("aliases", "now-in-commands.yml");
		}
		saveConfig();
		if (getCommandsConfigFile().isFile()) {
			legacyAlias = null;
		}
		commandsConfiguration = YamlConfiguration.loadConfiguration(getCommandsConfigFile());
		commandsConfiguration.options().copyDefaults(true);
		commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(
				getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)));
		saveCommandsConfig();

		// Migrate aliases from old file and add previously implicit $1- to pass all
		// arguments
		if (legacyAlias != null) {
			ConfigurationSection aliases = commandsConfiguration.createSection("aliases");
			for (String key : legacyAlias.getKeys(false)) {
				ArrayList<String> commands = new ArrayList<String>();

				if (legacyAlias.isList(key)) {
					for (String command : legacyAlias.getStringList(key)) {
						commands.add(command + " $1-");
					}
				} else {
					commands.add(legacyAlias.getString(key) + " $1-");
				}

				aliases.set(key, commands);
			}
		}

		saveCommandsConfig();
		overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*");
		((SimplePluginManager) pluginManager).useTimings(configuration.getBoolean("settings.plugin-profiling"));
		monsterSpawn = configuration.getInt("spawn-limits.monsters");
		animalSpawn = configuration.getInt("spawn-limits.animals");
		waterAnimalSpawn = configuration.getInt("spawn-limits.water-animals");
		ambientSpawn = configuration.getInt("spawn-limits.ambient");
		console.autosavePeriod = configuration.getInt("ticks-per.autosave");
		warningState = WarningState.value(configuration.getString("settings.deprecated-verbose"));
		chunkGCPeriod = configuration.getInt("chunk-gc.period-in-ticks");
		chunkGCLoadThresh = configuration.getInt("chunk-gc.load-threshold");
		loadIcon();

		// Spigot Start - Moved to old location of new DedicatedPlayerList in
		// DedicatedServer
		// loadPlugins();
		// enablePlugins(PluginLoadOrder.STARTUP);
		// Spigot End

		this.serverName = WindSpigotConfig.serverBrandName;
	}

	public boolean getCommandBlockOverride(String command) {
		return overrideAllCommandBlockCommands
				|| commandsConfiguration.getStringList("command-block-overrides").contains(command);
	}

	private File getConfigFile() {
		return (File) console.options.valueOf("bukkit-settings");
	}

	private File getCommandsConfigFile() {
		return (File) console.options.valueOf("commands-settings");
	}

	private void saveConfig() {
		try {
			configuration.save(getConfigFile());
		} catch (IOException ex) {
			Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, "Could not save " + getConfigFile(), ex);
		}
	}

	private void saveCommandsConfig() {
		try {
			commandsConfiguration.save(getCommandsConfigFile());
		} catch (IOException ex) {
			Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, "Could not save " + getCommandsConfigFile(),
					ex);
		}
	}

	public void loadPlugins() {
		pluginManager.registerInterface(JavaPluginLoader.class);

		File pluginFolder = (File) console.options.valueOf("plugins");

		if (pluginFolder.exists()) {
			Plugin[] plugins = pluginManager.loadPlugins(pluginFolder);
			for (Plugin plugin : plugins) {
				try {
					// Nacho start - [Nacho-0047] Little anti-malware
					if (WindSpigotConfig.checkForMalware) {
						AntiMalware.find(plugin);
					}
					// Nacho end

					// Nacho start - Add notice for older ProtocolLib versions
					if (plugin.getDescription().getFullName().contains("ProtocolLib")) {
						String[] tmp = plugin.getDescription().getVersion().split("\\.");
						if (Integer.parseInt(tmp[0]) <= 4 && Integer.parseInt(tmp[1]) <= 6) {
							logger.warning("Please update to ProtocolLib version 4.7.0 or higher!\n"
									+ "In version 4.6.0 and lower, ProtocolLib does not work as expected due to a netty update.\n"
									+ "So.. once again, please update!\n"
									+ "You can download the latest version with this link: "
									+ "https://github.com/dmulloy2/ProtocolLib/releases/latest\n"
									+ "Sleeping for 10s so this message can be read.");
							Thread.sleep(10000);
						}
					}
					// Nacho end

					// Nacho start - Add notice for older Citizens versions
					else if (plugin.getDescription().getFullName().contains("Citizens")) {
						if (PluginUtils.getCitizensBuild(plugin) < 2396) {
							logger.warning("Please update to Citizens 2.0.28 #7 or higher!\n"
									+ "Previously, there was a fix for older versions, but that has been removed.\n"
									+ "So, if you want Citizens to work, please update!\n"
									+ "You can download the latest version with this link: "
									+ "https://ci.citizensnpcs.co/job/Citizens2/\n"
									+ "Sleeping for 10s so this message can be read.");
							Thread.sleep(10000);
						}
					}
					// Nacho end

					plugin.getLogger().info(String.format("Loading %s", plugin.getDescription().getFullName()));
					plugin.onLoad();
				} catch (Throwable ex) {
					Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, ex.getMessage() + " initializing "
							+ plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
				}
			}
		} else {
			pluginFolder.mkdir();
		}
	}

	public void enablePlugins(PluginLoadOrder type) {
		if (type == PluginLoadOrder.STARTUP) {
			helpMap.clear();
			helpMap.initializeGeneralTopics();
		}

		Plugin[] plugins = pluginManager.getPlugins();

		for (Plugin plugin : plugins) {
			if ((!plugin.isEnabled()) && (plugin.getDescription().getLoad() == type)) {
				loadPlugin(plugin);
			}
		}

		if (type == PluginLoadOrder.POSTWORLD) {
			// Spigot start - Allow vanilla commands to be forced to be the main command
			setVanillaCommands(true);
			commandMap.setFallbackCommands();
			setVanillaCommands(false);
			// Spigot end
			commandMap.registerServerAliases();
			loadCustomPermissions();
			DefaultPermissions.registerCorePermissions();
			CraftDefaultPermissions.registerCorePermissions();
			helpMap.initializeCommands();
			co.aikar.timings.Timings.reset(); // Spigot
		}
	}

	public void disablePlugins() {
		pluginManager.disablePlugins();
	}

	private void setVanillaCommands(boolean first) { // Spigot
		Map<String, ICommand> commands = new CommandDispatcher().getCommands();
		for (ICommand cmd : commands.values()) {
			// Spigot start
			VanillaCommandWrapper wrapper = new VanillaCommandWrapper((CommandAbstract) cmd,
					LocaleI18n.get(cmd.getUsage(null)));
			if (org.spigotmc.SpigotConfig.replaceCommands.contains(wrapper.getName())) {
				if (first) {
					commandMap.register("minecraft", wrapper);
				}
			} else if (!first) {
				commandMap.register("minecraft", wrapper);
			}
			// Spigot end
		}
	}

	private void loadPlugin(Plugin plugin) {
		try {
			pluginManager.enablePlugin(plugin);

			List<Permission> perms = plugin.getDescription().getPermissions();

			for (Permission perm : perms) {
				try {
					pluginManager.addPermission(perm);
				} catch (IllegalArgumentException ex) {
					getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName()
							+ " tried to register permission '" + perm.getName() + "' but it's already registered", ex);
				}
			}
		} catch (Throwable ex) {
			Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE,
					ex.getMessage() + " loading " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
		}
	}

	@Override
	public String getName() {
		return serverName;
	}

	@Override
	public String getVersion() {
		return serverVersion + " (MC: " + console.getVersion() + ")";
	}

	@Override
	public String getBukkitVersion() {
		return bukkitVersion;
	}

	@Override
	@Deprecated
	@SuppressWarnings("unchecked")
	public Player[] _INVALID_getOnlinePlayers() {
		return getOnlinePlayers().toArray(EMPTY_PLAYER_ARRAY);
	}

	@Override
	public List<CraftPlayer> getOnlinePlayers() {
		return this.playerView;
	}

	@Override
	@Deprecated
	public Player getPlayer(final String name) {
		Validate.notNull(name, "Name cannot be null");

		Player found = getPlayerExact(name);
		// Try for an exact match first.
		if (found != null) {
			return found;
		}

		String lowerName = name.toLowerCase();
		int delta = Integer.MAX_VALUE;
		for (Player player : getOnlinePlayers()) {
			if (player.getName().toLowerCase().startsWith(lowerName)) {
				int curDelta = Math.abs(player.getName().length() - lowerName.length());
				if (curDelta < delta) {
					found = player;
					delta = curDelta;
				}
				if (curDelta == 0) {
					break;
				}
			}
		}
		return found;
	}

	@Override
	@Deprecated
	public Player getPlayerExact(String name) {
		Validate.notNull(name, "Name cannot be null");

		EntityPlayer player = playerList.playerMap.get(name);
		return (player != null) ? player.getBukkitEntity() : null;
	}

	@Override
	public Player getPlayer(UUID id) {
		EntityPlayer player = playerList.a(id);

		if (player != null) {
			return player.getBukkitEntity();
		}

		return null;
	}
	
	// WindSpigot start - backport some modern API
	@Override
    public Entity getEntity(UUID uuid) {
        Validate.notNull(uuid, "UUID cannot be null");

        for (WorldServer world : this.getServer().worlds) {
            net.minecraft.server.Entity entity = world.getEntity(uuid);
            if (entity != null) {
                return entity.getBukkitEntity();
            }
        }

        return null;
    }
    // WindSpigot end

	@Override
	public int broadcastMessage(String message) {
		return broadcast(message, BROADCAST_CHANNEL_USERS);
	}

	public Player getPlayer(final EntityPlayer entity) {
		return entity.getBukkitEntity();
	}

	@Override
	@Deprecated
	public List<Player> matchPlayer(String partialName) {
		Validate.notNull(partialName, "PartialName cannot be null");

		List<Player> matchedPlayers = new ArrayList<Player>();

		for (Player iterPlayer : this.getOnlinePlayers()) {
			String iterPlayerName = iterPlayer.getName();

			if (partialName.equalsIgnoreCase(iterPlayerName)) {
				// Exact match
				matchedPlayers.clear();
				matchedPlayers.add(iterPlayer);
				break;
			}
			if (iterPlayerName.toLowerCase().contains(partialName.toLowerCase())) {
				// Partial match
				matchedPlayers.add(iterPlayer);
			}
		}

		return matchedPlayers;
	}

	@Override
	public int getMaxPlayers() {
		return playerList.getMaxPlayers();
	}

	@Override
	public void setMaxPlayers(int maxPlayers) {
		this.playerList.setMaxPlayers(maxPlayers);
	}

	// NOTE: These are dependent on the corrisponding call in MinecraftServer
	// so if that changes this will need to as well
	@Override
	public int getPort() {
		return this.getConfigInt("server-port", 25565);
	}

	@Override
	public int getViewDistance() {
		// WindSpigot - optimize config defaults 10 -> 6
		return this.getConfigInt("view-distance", 6);
	}

	@Override
	public String getIp() {
		return this.getConfigString("server-ip", "");
	}

	@Override
	public String getServerName() {
		return this.getConfigString("server-name", "Unknown Server");
	}

	@Override
	public String getServerId() {
		return this.getConfigString("server-id", "unnamed");
	}

	@Override
	public String getWorldType() {
		return this.getConfigString("level-type", "DEFAULT");
	}

	@Override
	public boolean getGenerateStructures() {
		return this.getConfigBoolean("generate-structures", true);
	}

	@Override
	public boolean getAllowEnd() {
		return this.configuration.getBoolean("settings.allow-end");
	}

	@Override
	public boolean getAllowNether() {
		return this.getConfigBoolean("allow-nether", true);
	}

	public boolean getWarnOnOverload() {
		return this.configuration.getBoolean("settings.warn-on-overload");
	}

	public boolean getQueryPlugins() {
		return this.configuration.getBoolean("settings.query-plugins");
	}

	@Override
	public boolean hasWhitelist() {
		return this.getConfigBoolean("white-list", false);
	}

	// NOTE: Temporary calls through to server.properies until its replaced
	private String getConfigString(String variable, String defaultValue) {
		return this.console.getPropertyManager().getString(variable, defaultValue);
	}

	private int getConfigInt(String variable, int defaultValue) {
		return this.console.getPropertyManager().getInt(variable, defaultValue);
	}

	private boolean getConfigBoolean(String variable, boolean defaultValue) {
		return this.console.getPropertyManager().getBoolean(variable, defaultValue);
	}

	// End Temporary calls

	@Override
	public String getUpdateFolder() {
		return this.configuration.getString("settings.update-folder", "update");
	}

	@Override
	public File getUpdateFolderFile() {
		return new File((File) console.options.valueOf("plugins"),
				this.configuration.getString("settings.update-folder", "update"));
	}

	@Override
	public long getConnectionThrottle() {
		// Spigot Start - Automatically set connection throttle for bungee
		// configurations
		if (org.spigotmc.SpigotConfig.bungee) {
			return -1;
		} else {
			return this.configuration.getInt("settings.connection-throttle");
		}
		// Spigot End
	}

	@Override
	public int getTicksPerAnimalSpawns() {
		return this.configuration.getInt("ticks-per.animal-spawns");
	}

	@Override
	public int getTicksPerMonsterSpawns() {
		return this.configuration.getInt("ticks-per.monster-spawns");
	}

	@Override
	public PluginManager getPluginManager() {
		return pluginManager;
	}

	@Override
	public CraftScheduler getScheduler() {
		return scheduler;
	}

	@Override
	public ServicesManager getServicesManager() {
		return servicesManager;
	}

	@Override
	public List<World> getWorlds() {
		return new ArrayList<World>(worlds.values());
	}

	public DedicatedPlayerList getHandle() {
		return playerList;
	}

	// NOTE: Should only be called from DedicatedServer.ah()
	public boolean dispatchServerCommand(CommandSender sender, ServerCommand serverCommand) {
		if (sender instanceof Conversable) {
			Conversable conversable = (Conversable) sender;

			if (conversable.isConversing()) {
				conversable.acceptConversationInput(serverCommand.command);
				return true;
			}
		}
		try {
			this.playerCommandState = true;
			return dispatchCommand(sender, serverCommand.command);
		} catch (Exception ex) {
			getLogger().log(Level.WARNING,
					"Unexpected exception while parsing console command \"" + serverCommand.command + '"', ex);
			return false;
		} finally {
			this.playerCommandState = false;
		}
	}

	@Override
	public boolean dispatchCommand(CommandSender sender, String commandLine) {
		Validate.notNull(sender, "Sender cannot be null");
		Validate.notNull(commandLine, "CommandLine cannot be null");

		// PaperSpigot Start
		if (!Bukkit.isPrimaryThread()) {
			final CommandSender fSender = sender;
			final String fCommandLine = commandLine;
			Bukkit.getLogger().log(Level.SEVERE, "Command Dispatched Async: " + commandLine);
			Bukkit.getLogger().log(Level.SEVERE,
					"Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C",
					new Throwable());
			org.bukkit.craftbukkit.util.Waitable<Boolean> wait = new org.bukkit.craftbukkit.util.Waitable<Boolean>() {
				@Override
				protected Boolean evaluate() {
					return dispatchCommand(fSender, fCommandLine);
				}
			};
			net.minecraft.server.MinecraftServer.getServer().processQueue.add(wait);
			try {
				return wait.get();
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it
													// on!
			} catch (Exception e) {
				throw new RuntimeException("Exception processing dispatch command", e.getCause());
			}
		}
		// PaperSpigot End

		if (commandMap.dispatch(sender, commandLine)) {
			return true;
		}

		sender.sendMessage(org.spigotmc.SpigotConfig.unknownCommandMessage);

		return false;
	}

	@Override
	public void reload() {
		reloadCount++;
		configuration = YamlConfiguration.loadConfiguration(getConfigFile());
		commandsConfiguration = YamlConfiguration.loadConfiguration(getCommandsConfigFile());
		PropertyManager config = new PropertyManager(console.options);

		((DedicatedServer) console).propertyManager = config;

		boolean animals = config.getBoolean("spawn-animals", console.getSpawnAnimals());
		boolean monsters = config.getBoolean("spawn-monsters",
				console.worlds.get(0).getDifficulty() != EnumDifficulty.PEACEFUL);
		EnumDifficulty difficulty = EnumDifficulty
				.getById(config.getInt("difficulty", console.worlds.get(0).getDifficulty().ordinal()));

		online.value = config.getBoolean("online-mode", console.getOnlineMode());
		console.setSpawnAnimals(config.getBoolean("spawn-animals", console.getSpawnAnimals()));
		console.setPVP(config.getBoolean("pvp", console.getPVP()));
		console.setAllowFlight(config.getBoolean("allow-flight", console.getAllowFlight()));
		console.setMotd(config.getString("motd", console.getMotd()));
		monsterSpawn = configuration.getInt("spawn-limits.monsters");
		animalSpawn = configuration.getInt("spawn-limits.animals");
		waterAnimalSpawn = configuration.getInt("spawn-limits.water-animals");
		ambientSpawn = configuration.getInt("spawn-limits.ambient");
		warningState = WarningState.value(configuration.getString("settings.deprecated-verbose"));
		printSaveWarning = false;
		console.autosavePeriod = configuration.getInt("ticks-per.autosave");
		chunkGCPeriod = configuration.getInt("chunk-gc.period-in-ticks");
		chunkGCLoadThresh = configuration.getInt("chunk-gc.load-threshold");
		loadIcon();

		try {
			playerList.getIPBans().load();
		} catch (IOException ex) {
			logger.log(Level.WARNING, "Failed to load banned-ips.json, " + ex.getMessage());
		}
		try {
			playerList.getProfileBans().load();
		} catch (IOException ex) {
			logger.log(Level.WARNING, "Failed to load banned-players.json, " + ex.getMessage());
		}

		org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
		org.github.paperspigot.PaperSpigotConfig.init((File) console.options.valueOf("paper-settings")); // PaperSpigot
		net.techcable.tacospigot.TacoSpigotConfig.init((File) console.options.valueOf("taco-settings")); // TacoSpigot
		NachoConfig.init((File) console.options.valueOf("nacho-settings")); // NachoSpigot
		KnockbackConfig.init((File) console.options.valueOf("knockback-settings"));
		
		// WindSpigot start - config
		WindSpigotConfig.init((File) console.options.valueOf("windspigot-settings"));
		// WindSpigot end
		
		for (WorldServer world : console.worlds) {
			world.worldData.setDifficulty(difficulty);
			world.setSpawnFlags(monsters, animals);
			if (this.getTicksPerAnimalSpawns() < 0) {
				world.ticksPerAnimalSpawns = 400;
			} else {
				world.ticksPerAnimalSpawns = this.getTicksPerAnimalSpawns();
			}

			if (this.getTicksPerMonsterSpawns() < 0) {
				world.ticksPerMonsterSpawns = 1;
			} else {
				world.ticksPerMonsterSpawns = this.getTicksPerMonsterSpawns();
			}
			world.spigotConfig.init(); // Spigot
			world.paperSpigotConfig.init(); // PaperSpigot
			world.tacoSpigotConfig.init(); // TacoSpigot
		}

		pluginManager.clearPlugins();
		commandMap.clearCommands();
		resetRecipes();
		org.spigotmc.SpigotConfig.registerCommands(); // Spigot
		org.github.paperspigot.PaperSpigotConfig.registerCommands(); // PaperSpigot
		
		MinecraftServer.getServer().getWindSpigot().reload(); // WindSpigot - reload

		overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*");

		int pollCount = 0;

		// Wait for at most 2.5 seconds for plugins to close their threads
		while (pollCount < 50 && getScheduler().getActiveWorkers().size() > 0) {
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
			}
			pollCount++;
		}

		List<BukkitWorker> overdueWorkers = getScheduler().getActiveWorkers();
		for (BukkitWorker worker : overdueWorkers) {
			Plugin plugin = worker.getOwner();
			String author = "<NoAuthorGiven>";
			if (plugin.getDescription().getAuthors().size() > 0) {
				author = plugin.getDescription().getAuthors().get(0);
			}
			getLogger().log(Level.SEVERE, String.format("Nag author: '%s' of '%s' about the following: %s", author,
					plugin.getDescription().getName(),
					"This plugin is not properly shutting down its async tasks when it is being reloaded.  This may cause conflicts with the newly loaded version of the plugin"));
		}
		loadPlugins();
		enablePlugins(PluginLoadOrder.STARTUP);
		enablePlugins(PluginLoadOrder.POSTWORLD);
	}

	private void loadIcon() {
		icon = new CraftIconCache(null);
		try {
			final File file = new File(new File("."), "server-icon.png");
			if (file.isFile()) {
				icon = loadServerIcon0(file);
			}
		} catch (Exception ex) {
			getLogger().log(Level.WARNING, "Couldn't load server icon", ex);
		}
	}

	@SuppressWarnings({ "unchecked", "finally" })
	private void loadCustomPermissions() {
		File file = new File(configuration.getString("settings.permissions-file"));
		FileInputStream stream;

		try {
			stream = new FileInputStream(file);
		} catch (FileNotFoundException ex) {
			try {
				file.createNewFile();
			} finally {
				return;
			}
		}

		Map<String, Map<String, Object>> perms;

		try {
			perms = (Map<String, Map<String, Object>>) yaml.load(stream);
		} catch (MarkedYAMLException ex) {
			getLogger().log(Level.WARNING, "Server permissions file " + file + " is not valid YAML: " + ex.toString());
			return;
		} catch (Throwable ex) {
			getLogger().log(Level.WARNING, "Server permissions file " + file + " is not valid YAML.", ex);
			return;
		} finally {
			try {
				stream.close();
			} catch (IOException ex) {
			}
		}

		if (perms == null) {
			getLogger().log(Level.INFO, "Server permissions file " + file + " is empty, ignoring it");
			return;
		}

		List<Permission> permsList = Permission.loadPermissions(perms,
				"Permission node '%s' in " + file + " is invalid", Permission.DEFAULT_PERMISSION);

		for (Permission perm : permsList) {
			try {
				pluginManager.addPermission(perm);
			} catch (IllegalArgumentException ex) {
				getLogger().log(Level.SEVERE, "Permission in " + file + " was already defined", ex);
			}
		}
	}

	@Override
	public String toString() {
		return "CraftServer{" + "serverName=" + serverName + ",serverVersion=" + serverVersion + ",minecraftVersion="
				+ console.getVersion() + '}';
	}

	public World createWorld(String name, World.Environment environment) {
		return WorldCreator.name(name).environment(environment).createWorld();
	}

	public World createWorld(String name, World.Environment environment, long seed) {
		return WorldCreator.name(name).environment(environment).seed(seed).createWorld();
	}

	public World createWorld(String name, Environment environment, ChunkGenerator generator) {
		return WorldCreator.name(name).environment(environment).generator(generator).createWorld();
	}

	public World createWorld(String name, Environment environment, long seed, ChunkGenerator generator) {
		return WorldCreator.name(name).environment(environment).seed(seed).generator(generator).createWorld();
	}

	@Override
	public World createWorld(WorldCreator creator) {
		Validate.notNull(creator, "Creator may not be null");

		String name = creator.name();
		ChunkGenerator generator = creator.generator();
		File folder = new File(getWorldContainer(), name);
		World world = getWorld(name);
		WorldType type = WorldType.getType(creator.type().getName());
		boolean generateStructures = creator.generateStructures();

		if (world != null) {
			return world;
		}

		if ((folder.exists()) && (!folder.isDirectory())) {
			throw new IllegalArgumentException("File exists with the name '" + name + "' and isn't a folder");
		}

		if (generator == null) {
			generator = getGenerator(name);
		}

		Convertable converter = new WorldLoaderServer(getWorldContainer());
		if (converter.isConvertable(name)) {
			getLogger().info("Converting world '" + name + "'");
			converter.convert(name, new IProgressUpdate() {
				private long b = System.currentTimeMillis();

				@Override
				public void a(String s) {
				}

				@Override
				public void a(int i) {
					if (System.currentTimeMillis() - this.b >= 1000L) {
						this.b = System.currentTimeMillis();
						MinecraftServer.LOGGER.info("Converting... " + i + "%");
					}

				}

				@Override
				public void c(String s) {
				}
			});
		}

		int dimension = CraftWorld.CUSTOM_DIMENSION_OFFSET + console.worlds.size();
		boolean used = false;
		do {
			for (WorldServer server : console.worlds) {
				used = server.dimension == dimension;
				if (used) {
					dimension++;
					break;
				}
			}
		} while (used);
		boolean hardcore = false;

		IDataManager sdm = new ServerNBTManager(getWorldContainer(), name, true);
		WorldData worlddata = sdm.getWorldData();
		if (worlddata == null) {
			WorldSettings worldSettings = new WorldSettings(creator.seed(),
					WorldSettings.EnumGamemode.getById(getDefaultGameMode().getValue()), generateStructures, hardcore,
					type);
			worldSettings.setGeneratorSettings(creator.generatorSettings());
			worlddata = new WorldData(worldSettings, name);
		}
		worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to
									// take the last loaded world as respawn (in this case the end)
		WorldServer internal = (WorldServer) new WorldServer(console, sdm, worlddata, dimension, console.methodProfiler,
				creator.environment(), generator).b();

		if (!(worlds.containsKey(name.toLowerCase()))) {
			return null;
		}

		internal.scoreboard = getScoreboardManager().getMainScoreboard().getHandle();

		// WindSpigot start
		if (WindSpigotConfig.disableTracking) {
			internal.tracker = new EntityTracker(internal);
		} else {
			internal.tracker = new AsyncEntityTracker(internal);
		}
		// WindSpigot end
		internal.addIWorldAccess(new WorldManager(console, internal));
		internal.worldData.setDifficulty(EnumDifficulty.EASY);
		internal.setSpawnFlags(true, true);
		console.worlds.add(internal);

		if (generator != null) {
			internal.getWorld().getPopulators().addAll(generator.getDefaultPopulators(internal.getWorld()));
		}

		pluginManager.callEvent(new WorldInitEvent(internal.getWorld()));
		System.out.print("Preparing start region for level " + (console.worlds.size() - 1) + " (Seed: "
				+ internal.getSeed() + ")");

		if (internal.getWorld().getKeepSpawnInMemory()) {
			short short1 = 196;
			long i = System.currentTimeMillis();
			for (int j = -short1; j <= short1; j += 16) {
				for (int k = -short1; k <= short1; k += 16) {
					long l = System.currentTimeMillis();

					if (l < i) {
						i = l;
					}

					if (l > i + 1000L) {
						int i1 = (short1 * 2 + 1) * (short1 * 2 + 1);
						int j1 = (j + short1) * (short1 * 2 + 1) + k + 1;

						System.out.println("Preparing spawn area for " + name + ", " + (j1 * 100 / i1) + "%");
						i = l;
					}

					BlockPosition chunkcoordinates = internal.getSpawn();
					internal.chunkProviderServer.getChunkAt(chunkcoordinates.getX() + j >> 4,
							chunkcoordinates.getZ() + k >> 4);
				}
			}
		}
		pluginManager.callEvent(new WorldLoadEvent(internal.getWorld()));
		return internal.getWorld();
	}

	@Override
	public boolean unloadWorld(String name, boolean save) {
		return unloadWorld(getWorld(name), save);
	}

	@Override
	public boolean unloadWorld(World world, boolean save) {
		if (world == null) {
			return false;
		}

		WorldServer handle = ((CraftWorld) world).getHandle();

		if (!(console.worlds.contains(handle))) {
			return false;
		}

		if (!(handle.dimension > 1)) {
			return false;
		}

		if (handle.players.size() > 0) {
			return false;
		}

		WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld());
		pluginManager.callEvent(e);

		if (e.isCancelled()) {
			return false;
		}

		if (save) {
			try {
				handle.save(true, null);
				handle.saveLevel();
			} catch (ExceptionWorldConflict ex) {
				getLogger().log(Level.SEVERE, null, ex);
			}
		} else if (handle.chunkProviderServer.chunkLoader instanceof ChunkRegionLoader) { // Nacho - allow custom chunk
																							// loaders // FlamePaper -
																							// Fix chunk memory leak
			ChunkProviderServer chunkProviderServer = handle.chunkProviderServer;
			ChunkRegionLoader regionLoader = (ChunkRegionLoader) chunkProviderServer.chunkLoader;

			regionLoader.b.clear();
			regionLoader.c.clear();

			try {
				FileIOThread.a().b();
			} catch (InterruptedException ex) {
				Thread.currentThread().interrupt();
			}

			chunkProviderServer.chunkLoader = null;
			chunkProviderServer.chunkProvider = null;
			chunkProviderServer.chunks.clear();
		}

		worlds.remove(world.getName().toLowerCase());
		console.worlds.remove(handle);

		// KigPaper start - fix memory leak
		CraftingManager craftingManager = CraftingManager.getInstance();
		CraftInventoryView lastView = (CraftInventoryView) craftingManager.lastCraftView;
		if (lastView != null && lastView.getHandle() instanceof ContainerWorkbench
				&& ((ContainerWorkbench) lastView.getHandle()).g == handle) {
			craftingManager.lastCraftView = null;
			// KigPaper end
		}

		File parentFolder = world.getWorldFolder().getAbsoluteFile();

		// Synchronized because access to RegionFileCache.a is guarded by this lock.
		synchronized (RegionFileCache.class) {
			// RegionFileCache.a should be RegionFileCache.cache
			Iterator<Map.Entry<File, RegionFile>> i = RegionFileCache.a.entrySet().iterator();
			while (i.hasNext()) {
				Map.Entry<File, RegionFile> entry = i.next();
				File child = entry.getKey().getAbsoluteFile();
				while (child != null) {
					if (child.equals(parentFolder)) {
						i.remove();
						try {
							entry.getValue().c(); // Should be RegionFile.close();
						} catch (IOException ex) {
							getLogger().log(Level.SEVERE, null, ex);
						}
						break;
					}
					child = child.getParentFile();
				}
			}
		}

		return true;
	}

	public MinecraftServer getServer() {
		return console;
	}

	@Override
	public World getWorld(String name) {
		Validate.notNull(name, "Name cannot be null");

		return worlds.get(name.toLowerCase());
	}

	@Override
	public World getWorld(UUID uid) {
		for (World world : worlds.values()) {
			if (world.getUID().equals(uid)) {
				return world;
			}
		}
		return null;
	}

	public void addWorld(World world) {
		// Check if a World already exists with the UID.
		if (getWorld(world.getUID()) != null) {
			System.out.println("World " + world.getName()
					+ " is a duplicate of another world and has been prevented from loading. Please delete the uid.dat file from "
					+ world.getName() + "'s world directory if you want to be able to load the duplicate world.");
			return;
		}
		worlds.put(world.getName().toLowerCase(), world);
	}

	@Override
	public Logger getLogger() {
		return logger;
	}

	public ConsoleReader getReader() {
		return console.reader;
	}

	@Override
	public PluginCommand getPluginCommand(String name) {
		Command command = commandMap.getCommand(name);

		if (command instanceof PluginCommand) {
			return (PluginCommand) command;
		} else {
			return null;
		}
	}

	@Override
	public void savePlayers() {
		checkSaveState();
		playerList.savePlayers();
	}

	@Override
	public void configureDbConfig(ServerConfig config) {
		Validate.notNull(config, "Config cannot be null");

		DataSourceConfig ds = new DataSourceConfig();
		ds.setDriver(configuration.getString("database.driver"));
		ds.setUrl(configuration.getString("database.url"));
		ds.setUsername(configuration.getString("database.username"));
		ds.setPassword(configuration.getString("database.password"));
		ds.setIsolationLevel(TransactionIsolation.getLevel(configuration.getString("database.isolation")));

		if (ds.getDriver().contains("sqlite")) {
			config.setDatabasePlatform(new SQLitePlatform());
			config.getDatabasePlatform().getDbDdlSyntax().setIdentity("");
		}

		config.setDataSourceConfig(ds);
	}

	@Override
	public boolean addRecipe(Recipe recipe) {
		CraftRecipe toAdd;
		if (recipe instanceof CraftRecipe) {
			toAdd = (CraftRecipe) recipe;
		} else if (recipe instanceof ShapedRecipe) {
			toAdd = CraftShapedRecipe.fromBukkitRecipe((ShapedRecipe) recipe);
		} else if (recipe instanceof ShapelessRecipe) {
			toAdd = CraftShapelessRecipe.fromBukkitRecipe((ShapelessRecipe) recipe);
		} else if (recipe instanceof FurnaceRecipe) {
			toAdd = CraftFurnaceRecipe.fromBukkitRecipe((FurnaceRecipe) recipe);
		} else {
			return false;
		}
		toAdd.addToCraftingManager();
		CraftingManager.getInstance().sort();
		return true;
	}

	@Override
	public List<Recipe> getRecipesFor(ItemStack result) {
		Validate.notNull(result, "Result cannot be null");

		List<Recipe> results = new ArrayList<Recipe>();
		Iterator<Recipe> iter = recipeIterator();
		while (iter.hasNext()) {
			Recipe recipe = iter.next();
			ItemStack stack = recipe.getResult();
			if (stack.getType() != result.getType()) {
				continue;
			}
			if (result.getDurability() == -1 || result.getDurability() == stack.getDurability()) {
				results.add(recipe);
			}
		}
		return results;
	}

	@Override
	public Iterator<Recipe> recipeIterator() {
		return new RecipeIterator();
	}

	@Override
	public void clearRecipes() {
		CraftingManager.getInstance().recipes.clear();
		RecipesFurnace.getInstance().recipes.clear();
		RecipesFurnace.getInstance().customRecipes.clear();
	}

	@Override
	public void resetRecipes() {
		CraftingManager.getInstance().recipes = new CraftingManager().recipes;
		RecipesFurnace.getInstance().recipes = new RecipesFurnace().recipes;
		RecipesFurnace.getInstance().customRecipes.clear();
	}

	@Override
	public Map<String, String[]> getCommandAliases() {
		ConfigurationSection section = commandsConfiguration.getConfigurationSection("aliases");
		Map<String, String[]> result = new LinkedHashMap<String, String[]>();

		if (section != null) {
			for (String key : section.getKeys(false)) {
				List<String> commands;

				if (section.isList(key)) {
					commands = section.getStringList(key);
				} else {
					commands = ImmutableList.of(section.getString(key));
				}

				result.put(key, commands.toArray(new String[commands.size()]));
			}
		}

		return result;
	}

	public void removeBukkitSpawnRadius() {
		configuration.set("settings.spawn-radius", null);
		saveConfig();
	}

	public int getBukkitSpawnRadius() {
		return configuration.getInt("settings.spawn-radius", -1);
	}

	@Override
	public String getShutdownMessage() {
		return configuration.getString("settings.shutdown-message");
	}

	@Override
	public int getSpawnRadius() {
		return ((DedicatedServer) console).propertyManager.getInt("spawn-protection", 16);
	}

	@Override
	public void setSpawnRadius(int value) {
		configuration.set("settings.spawn-radius", value);
		saveConfig();
	}

	@Override
	public boolean getOnlineMode() {
		return online.value;
	}

	@Override
	public boolean getAllowFlight() {
		return console.getAllowFlight();
	}

	@Override
	public boolean isHardcore() {
		return console.isHardcore();
	}

	@Override
	public boolean useExactLoginLocation() {
		return configuration.getBoolean("settings.use-exact-login-location");
	}

	public ChunkGenerator getGenerator(String world) {
		ConfigurationSection section = configuration.getConfigurationSection("worlds");
		ChunkGenerator result = null;

		if (section != null) {
			section = section.getConfigurationSection(world);

			if (section != null) {
				String name = section.getString("generator");

				if ((name != null) && (!name.isEmpty())) {
					String[] split = name.split(":", 2);
					String id = (split.length > 1) ? split[1] : null;
					Plugin plugin = pluginManager.getPlugin(split[0]);

					if (plugin == null) {
						getLogger().severe("Could not set generator for default world '" + world + "': Plugin '"
								+ split[0] + "' does not exist");
					} else if (!plugin.isEnabled()) {
						getLogger().severe("Could not set generator for default world '" + world + "': Plugin '"
								+ plugin.getDescription().getFullName() + "' is not enabled yet (is it load:STARTUP?)");
					} else {
						try {
							result = plugin.getDefaultWorldGenerator(world, id);
							if (result == null) {
								getLogger().severe("Could not set generator for default world '" + world + "': Plugin '"
										+ plugin.getDescription().getFullName() + "' lacks a default world generator");
							}
						} catch (Throwable t) {
							plugin.getLogger().log(Level.SEVERE, "Could not set generator for default world '" + world
									+ "': Plugin '" + plugin.getDescription().getFullName(), t);
						}
					}
				}
			}
		}

		return result;
	}

	@Override
	@Deprecated
	public CraftMapView getMap(short id) {
		PersistentCollection collection = console.worlds.get(0).worldMaps;
		WorldMap worldmap = (WorldMap) collection.get(WorldMap.class, "map_" + id);
		if (worldmap == null) {
			return null;
		}
		return worldmap.mapView;
	}

	@Override
	public CraftMapView createMap(World world) {
		Validate.notNull(world, "World cannot be null");

		net.minecraft.server.ItemStack stack = new net.minecraft.server.ItemStack(Items.MAP, 1, -1);
		WorldMap worldmap = Items.FILLED_MAP.getSavedMap(stack, ((CraftWorld) world).getHandle());
		return worldmap.mapView;
	}

	@Override
	public void shutdown() {
		console.safeShutdown();
	}

	@Override
	public int broadcast(String message, String permission) {
		int count = 0;
		Set<Permissible> permissibles = getPluginManager().getPermissionSubscriptions(permission);

		for (Permissible permissible : permissibles) {
			if (permissible instanceof CommandSender && permissible.hasPermission(permission)) {
				CommandSender user = (CommandSender) permissible;
				user.sendMessage(message);
				count++;
			}
		}

		return count;
	}

	// Paper start
	@Override
	public void broadcast(BaseComponent component) {
		for (Player player : getOnlinePlayers()) {
			player.sendMessage(component);
		}
	}

	@Override
	public void broadcast(BaseComponent... components) {
		for (Player player : getOnlinePlayers()) {
			player.sendMessage(components);
		}
	}
	// Paper end

	@Override
	@Deprecated
	public OfflinePlayer getOfflinePlayer(String name) {
		Validate.notNull(name, "Name cannot be null");
		com.google.common.base.Preconditions.checkArgument(!org.apache.commons.lang.StringUtils.isBlank(name),
				"Name cannot be blank"); // Spigot

		OfflinePlayer result = getPlayerExact(name);
		if (result == null) {
			// Spigot Start
			GameProfile profile = null;
			// Only fetch an online UUID in online mode
			if (MinecraftServer.getServer().getOnlineMode() || org.spigotmc.SpigotConfig.bungee) {
				profile = MinecraftServer.getServer().getUserCache().getProfile(name);
			}
			// Spigot end
			if (profile == null) {
				// Make an OfflinePlayer using an offline mode UUID since the name has no
				// profile
				result = getOfflinePlayer(new GameProfile(
						UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name));
			} else {
				// Use the GameProfile even when we get a UUID so we ensure we still have a name
				result = getOfflinePlayer(profile);
			}
		} else {
			offlinePlayers.remove(result.getUniqueId());
		}

		return result;
	}

	@Override
	public OfflinePlayer getOfflinePlayer(UUID id) {
		Validate.notNull(id, "UUID cannot be null");

		OfflinePlayer result = getPlayer(id);
		if (result == null) {
			result = offlinePlayers.get(id);
			if (result == null) {
				result = new CraftOfflinePlayer(this, new GameProfile(id, null));
				offlinePlayers.put(id, result);
			}
		} else {
			offlinePlayers.remove(id);
		}

		return result;
	}

	public OfflinePlayer getOfflinePlayer(GameProfile profile) {
		OfflinePlayer player = new CraftOfflinePlayer(this, profile);
		offlinePlayers.put(profile.getId(), player);
		return player;
	}

	@Override
	@SuppressWarnings("unchecked")
	public Set<String> getIPBans() {
		return new HashSet<String>(Arrays.asList(playerList.getIPBans().getEntries()));
	}

	@Override
	public void banIP(String address) {
		Validate.notNull(address, "Address cannot be null.");

		this.getBanList(org.bukkit.BanList.Type.IP).addBan(address, null, null, null);
	}

	@Override
	public void unbanIP(String address) {
		Validate.notNull(address, "Address cannot be null.");

		this.getBanList(org.bukkit.BanList.Type.IP).pardon(address);
	}

	@Override
	public Set<OfflinePlayer> getBannedPlayers() {
		Set<OfflinePlayer> result = new HashSet<OfflinePlayer>();

		for (JsonListEntry entry : playerList.getProfileBans().getValues()) {
			result.add(getOfflinePlayer((GameProfile) entry.getKey()));
		}

		return result;
	}

	@Override
	public BanList getBanList(BanList.Type type) {
		Validate.notNull(type, "Type cannot be null");

		switch (type) {
		case IP:
			return new CraftIpBanList(playerList.getIPBans());
		case NAME:
		default:
			return new CraftProfileBanList(playerList.getProfileBans());
		}
	}

	@Override
	public void setWhitelist(boolean value) {
		playerList.setHasWhitelist(value);
		console.getPropertyManager().setProperty("white-list", value);
	}

	@Override
	public Set<OfflinePlayer> getWhitelistedPlayers() {
		Set<OfflinePlayer> result = new LinkedHashSet<OfflinePlayer>();

		for (JsonListEntry entry : playerList.getWhitelist().getValues()) {
			result.add(getOfflinePlayer((GameProfile) entry.getKey()));
		}

		return result;
	}

	@Override
	public Set<OfflinePlayer> getOperators() {
		Set<OfflinePlayer> result = new HashSet<OfflinePlayer>();

		for (JsonListEntry entry : playerList.getOPs().getValues()) {
			result.add(getOfflinePlayer((GameProfile) entry.getKey()));
		}

		return result;
	}

	@Override
	public void reloadWhitelist() {
		playerList.reloadWhitelist();
	}

	@Override
	public GameMode getDefaultGameMode() {
		return GameMode.getByValue(console.worlds.get(0).getWorldData().getGameType().getId());
	}

	@Override
	public void setDefaultGameMode(GameMode mode) {
		Validate.notNull(mode, "Mode cannot be null");

		for (World world : getWorlds()) {
			((CraftWorld) world).getHandle().worldData.setGameType(WorldSettings.EnumGamemode.getById(mode.getValue()));
		}
	}

	@Override
	public ConsoleCommandSender getConsoleSender() {
		return console.console;
	}

	@Override
	public boolean versionCommandEnabled() {
		return WindSpigotConfig.enableVersionCommand;
	}

	@Override
	public boolean reloadCommandEnabled() {
		return WindSpigotConfig.enableReloadCommand;
	}

	@Override
	public boolean pluginsCommandEnabled() {
		return WindSpigotConfig.enablePluginsCommand;
	}

	public EntityMetadataStore getEntityMetadata() {
		return entityMetadata;
	}

	public PlayerMetadataStore getPlayerMetadata() {
		return playerMetadata;
	}

	public WorldMetadataStore getWorldMetadata() {
		return worldMetadata;
	}

	@Override
	public File getWorldContainer() {
		if (this.getServer().universe != null) {
			return this.getServer().universe;
		}

		if (container == null) {
			container = new File(configuration.getString("settings.world-container", "."));
		}

		return container;
	}

	@Override
	public OfflinePlayer[] getOfflinePlayers() {
		WorldNBTStorage storage = (WorldNBTStorage) console.worlds.get(0).getDataManager();
		String[] files = storage.getPlayerDir().list(new DatFileFilter());
		Set<OfflinePlayer> players = new HashSet<OfflinePlayer>();

		for (String file : files) {
			try {
				players.add(getOfflinePlayer(FastUUID.parseUUID(file.substring(0, file.length() - 4))));
			} catch (IllegalArgumentException ex) {
				// Who knows what is in this directory, just ignore invalid files
			}
		}

		players.addAll(getOnlinePlayers());

		return players.toArray(new OfflinePlayer[players.size()]);
	}

	@Override
	public Messenger getMessenger() {
		return messenger;
	}

	@Override
	public void sendPluginMessage(Plugin source, String channel, byte[] message) {
		StandardMessenger.validatePluginMessage(getMessenger(), source, channel, message);

		for (Player player : getOnlinePlayers()) {
			player.sendPluginMessage(source, channel, message);
		}
	}

	@Override
	public Set<String> getListeningPluginChannels() {
		Set<String> result = new HashSet<String>();

		for (Player player : getOnlinePlayers()) {
			result.addAll(player.getListeningPluginChannels());
		}

		return result;
	}

	@Override
	public Inventory createInventory(InventoryHolder owner, InventoryType type) {
		// TODO: Create the appropriate type, rather than Custom?
		return new CraftInventoryCustom(owner, type);
	}

	@Override
	public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) {
		return new CraftInventoryCustom(owner, type, title);
	}

	@Override
	public Inventory createInventory(InventoryHolder owner, int size) throws IllegalArgumentException {
		Validate.isTrue(size % 9 == 0, "Chests must have a size that is a multiple of 9!");
		return new CraftInventoryCustom(owner, size);
	}

	@Override
	public Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException {
		Validate.isTrue(size % 9 == 0, "Chests must have a size that is a multiple of 9!");
		return new CraftInventoryCustom(owner, size, title);
	}

	@Override
	public HelpMap getHelpMap() {
		return helpMap;
	}

	@Override // Paper - add override
	public SimpleCommandMap getCommandMap() {
		return commandMap;
	}

	@Override
	public int getMonsterSpawnLimit() {
		return monsterSpawn;
	}

	@Override
	public int getAnimalSpawnLimit() {
		return animalSpawn;
	}

	@Override
	public int getWaterAnimalSpawnLimit() {
		return waterAnimalSpawn;
	}

	@Override
	public int getAmbientSpawnLimit() {
		return ambientSpawn;
	}

	@Override
	public boolean isPrimaryThread() {
		Thread current = Thread.currentThread();
		
		if (current.getName().indexOf("WindSpigot Parallel World Thread") != -1) {
			return true;
		}
		
		return current.equals(console.primaryThread);
	}

	@Override
	public String getMotd() {
		return console.getMotd();
	}

	@Override
	public WarningState getWarningState() {
		return warningState;
	}

	public List<String> tabComplete(net.minecraft.server.ICommandListener sender, String message) {
		return tabComplete(sender, message, null); // PaperSpigot - location tab-completes. Original code here moved
													// below
	}

	// PaperSpigot start - add BlockPosition support
	/*
	 * this code is copied, except for the noted change, from the original
	 * tabComplete(net.minecraft.server.ICommandListener sender, String message)
	 * method
	 */
	public List<String> tabComplete(net.minecraft.server.ICommandListener sender, String message,
			BlockPosition blockPosition) {
		if (!(sender instanceof EntityPlayer)) {
			return ImmutableList.of();
		}

		Player player = ((EntityPlayer) sender).getBukkitEntity();
		if (message.startsWith("/")) {
			return tabCompleteCommand(player, message, blockPosition);
		} else {
			return tabCompleteChat(player, message);
		}
	}
	// PaperSpigot end

	public List<String> tabCompleteCommand(Player player, String message) {
		return tabCompleteCommand(player, message, null); // PaperSpigot - location tab-completes. Original code here
															// moved below
	}

	// PaperSpigot start - add BlockPosition support
	/*
	 * this code is copied, except for the noted change, from the original
	 * tabCompleteCommand(Player player, String message) method
	 */
	public List<String> tabCompleteCommand(Player player, String message, BlockPosition blockPosition) {
		// Spigot Start
		if ((org.spigotmc.SpigotConfig.tabComplete < 0 || message.length() <= org.spigotmc.SpigotConfig.tabComplete)
				&& !message.contains(" ")) {
			return ImmutableList.of();
		}
		// Spigot End

		List<String> completions = null;
		try {
			// send location info if present
			// completions = getCommandMap().tabComplete(player, message.substring(1));
			if (blockPosition == null || !((CraftWorld) player.getWorld())
					.getHandle().paperSpigotConfig.allowBlockLocationTabCompletion) {
				completions = getCommandMap().tabComplete(player, message.substring(1));
			} else {
				completions = getCommandMap().tabComplete(player, message.substring(1), new Location(player.getWorld(),
						blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()));
			}
		} catch (CommandException ex) {
			player.sendMessage(
					ChatColor.RED + "An internal error occurred while attempting to tab-complete this command");
			getLogger().log(Level.SEVERE,
					"Exception when " + player.getName() + " attempted to tab complete " + message, ex);
		}

		return completions == null ? ImmutableList.<String>of() : completions;
	}
	// PaperSpigot end

	public List<String> tabCompleteChat(Player player, String message) {
		List<String> completions = new ArrayList<String>();
		PlayerChatTabCompleteEvent event = new PlayerChatTabCompleteEvent(player, message, completions);
		String token = event.getLastToken();
		for (Player p : getOnlinePlayers()) {
			if (player.canSee(p) && StringUtil.startsWithIgnoreCase(p.getName(), token)) {
				completions.add(p.getName());
			}
		}
		pluginManager.callEvent(event);

		Iterator<?> it = completions.iterator();
		while (it.hasNext()) {
			Object current = it.next();
			if (!(current instanceof String)) {
				// Sanity
				it.remove();
			}
		}
		Collections.sort(completions, String.CASE_INSENSITIVE_ORDER);
		return completions;
	}

	@Override
	public CraftItemFactory getItemFactory() {
		return CraftItemFactory.instance();
	}

	@Override
	public CraftScoreboardManager getScoreboardManager() {
		return scoreboardManager;
	}

	public void checkSaveState() {
		if (this.playerCommandState || this.printSaveWarning || this.console.autosavePeriod <= 0) {
			return;
		}
		this.printSaveWarning = true;
		getLogger().log(Level.WARNING,
				"A manual (plugin-induced) save has been detected while server is configured to auto-save. This may affect performance.",
				warningState == WarningState.ON ? new Throwable() : null);
	}

	@Override
	public CraftIconCache getServerIcon() {
		return icon;
	}

	@Override
	public CraftIconCache loadServerIcon(File file) throws Exception {
		Validate.notNull(file, "File cannot be null");
		if (!file.isFile()) {
			throw new IllegalArgumentException(file + " is not a file");
		}
		return loadServerIcon0(file);
	}

	static CraftIconCache loadServerIcon0(File file) throws Exception {
		return loadServerIcon0(ImageIO.read(file));
	}

	@Override
	public CraftIconCache loadServerIcon(BufferedImage image) throws Exception {
		Validate.notNull(image, "Image cannot be null");
		return loadServerIcon0(image);
	}

	static CraftIconCache loadServerIcon0(BufferedImage image) throws Exception {
		ByteBuf bytebuf = Unpooled.buffer();

		Validate.isTrue(image.getWidth() == 64, "Must be 64 pixels wide");
		Validate.isTrue(image.getHeight() == 64, "Must be 64 pixels high");
		ImageIO.write(image, "PNG", new ByteBufOutputStream(bytebuf));
		ByteBuf bytebuf1 = Base64.encode(bytebuf);

		return new CraftIconCache("data:image/png;base64," + bytebuf1.toString(Charsets.UTF_8));
	}

	@Override
	public void setIdleTimeout(int threshold) {
		console.setIdleTimeout(threshold);
	}

	@Override
	public int getIdleTimeout() {
		return console.getIdleTimeout();
	}

	@Override
	public ChunkGenerator.ChunkData createChunkData(World world) {
		return new CraftChunkData(world);
	}

	@Deprecated
	@Override
	public UnsafeValues getUnsafe() {
		return CraftMagicNumbers.INSTANCE;
	}

	private final Spigot spigot = new Spigot() {

		// PaperSpigot start - Add getTPS (Further improve tick loop)
		@Override
		public double[] getTPS() {
			return new double[] { MinecraftServer.getServer().tps1.getAverage(),
					MinecraftServer.getServer().tps5.getAverage(), MinecraftServer.getServer().tps15.getAverage() };
		}
		// PaperSpigot end

		@Deprecated
		@Override
		public YamlConfiguration getConfig() {
			return getBukkitConfig();
		}

		@Override
		public YamlConfiguration getBukkitConfig() {
			return configuration;
		}

		@Override
		public YamlConfiguration getSpigotConfig() {
			return org.spigotmc.SpigotConfig.config;
		}

		@Override
		public YamlConfiguration getPaperSpigotConfig() {
			return org.github.paperspigot.PaperSpigotConfig.config;
		}
		
		// WindSpigot start
		@Override
		public YamlConfiguration getWindSpigotConfig() {
			return ga.windpvp.windspigot.config.WindSpigotConfig.config;
		}
		// WindSpigot end

		@Override
		public void restart() {
			org.spigotmc.RestartCommand.restart();
		}

		@Override
		public void broadcast(BaseComponent component) {
			for (Player player : getOnlinePlayers()) {
				player.spigot().sendMessage(component);
			}
		}

		@Override
		public void broadcast(BaseComponent... components) {
			for (Player player : getOnlinePlayers()) {
				player.spigot().sendMessage(components);
			}
		}
	};

	@Override
	public Spigot spigot() {
		return spigot;
	}
}
