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.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 javax.imageio.ImageIO; import net.minecraft.server.BanEntry; import net.minecraft.server.ChunkCoordinates; import net.minecraft.server.Convertable; import net.minecraft.server.ConvertProgressUpdater; 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.EnumGamemode; import net.minecraft.server.ExceptionWorldConflict; import net.minecraft.server.Items; import net.minecraft.server.PlayerList; import net.minecraft.server.RecipesFurnace; import net.minecraft.server.MinecraftServer; import net.minecraft.server.MobEffectList; import net.minecraft.server.PropertyManager; import net.minecraft.server.ServerCommand; import net.minecraft.server.ServerNBTManager; import net.minecraft.server.WorldLoaderServer; import net.minecraft.server.WorldManager; import net.minecraft.server.WorldMap; import net.minecraft.server.PersistentCollection; import net.minecraft.server.WorldNBTStorage; import net.minecraft.server.WorldServer; import net.minecraft.server.WorldSettings; import net.minecraft.server.WorldType; import net.minecraft.util.com.google.common.base.Charsets; import net.minecraft.util.io.netty.buffer.ByteBuf; import net.minecraft.util.io.netty.buffer.ByteBufOutputStream; import net.minecraft.util.io.netty.buffer.Unpooled; import net.minecraft.util.io.netty.handler.codec.base64.Base64; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.GameMode; import org.bukkit.OfflinePlayer; import org.bukkit.Server; 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.help.SimpleHelpMap; import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe; import org.bukkit.craftbukkit.inventory.CraftInventoryCustom; 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.updater.AutoUpdater; import org.bukkit.craftbukkit.updater.BukkitDLUpdaterService; import org.bukkit.craftbukkit.util.CraftIconCache; import org.bukkit.craftbukkit.util.DatFileFilter; import org.bukkit.craftbukkit.util.Versioning; 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.WorldSaveEvent; 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.ItemStack; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; 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.potion.Potion; import org.bukkit.potion.PotionEffectType; import org.bukkit.plugin.messaging.StandardMessenger; 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 org.apache.commons.lang.Validate; 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.google.common.collect.ImmutableList; import com.google.common.collect.MapMaker; import jline.console.ConsoleReader; public final class CraftServer implements Server { private final String serverName = "CraftBukkit"; private final String serverVersion; private final String bukkitVersion = Versioning.getBukkitVersion(); 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 worlds = new LinkedHashMap(); private YamlConfiguration configuration; private final Yaml yaml = new Yaml(new SafeConstructor()); private final Map offlinePlayers = new MapMaker().softValues().makeMap(); private final AutoUpdater updater; 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 BooleanWrapper(); public CraftScoreboardManager scoreboardManager; public boolean playerCommandState; private boolean printSaveWarning; private Logger logger; private CraftIconCache icon; private final class BooleanWrapper { private boolean value = true; } static { ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); CraftItemFactory.instance(); } public CraftServer(MinecraftServer console, PlayerList playerList) { this.logger = Logger.getLogger("Minecraft"); this.console = console; this.playerList = (DedicatedPlayerList) playerList; this.serverVersion = 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(getClass().getClassLoader().getResourceAsStream("configurations/bukkit.yml"))); saveConfig(); ((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(); updater = new AutoUpdater(new BukkitDLUpdaterService(configuration.getString("auto-updater.host")), getLogger(), configuration.getString("auto-updater.preferred-channel")); updater.setEnabled(configuration.getBoolean("auto-updater.enabled")); updater.setSuggestChannels(configuration.getBoolean("auto-updater.suggest-channels")); updater.getOnBroken().addAll(configuration.getStringList("auto-updater.on-broken")); updater.getOnUpdate().addAll(configuration.getStringList("auto-updater.on-update")); updater.check(serverVersion); loadPlugins(); enablePlugins(PluginLoadOrder.STARTUP); } private File getConfigFile() { return (File) console.options.valueOf("bukkit-settings"); } private void saveConfig() { try { configuration.save(getConfigFile()); } catch (IOException ex) { Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, "Could not save " + getConfigFile(), 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 { String message = String.format("Loading %s", plugin.getDescription().getFullName()); plugin.getLogger().info(message); 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) { commandMap.registerServerAliases(); loadCustomPermissions(); DefaultPermissions.registerCorePermissions(); helpMap.initializeCommands(); } } public void disablePlugins() { pluginManager.disablePlugins(); } private void loadPlugin(Plugin plugin) { try { pluginManager.enablePlugin(plugin); List 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); } } public String getName() { return serverName; } public String getVersion() { return serverVersion + " (MC: " + console.getVersion() + ")"; } public String getBukkitVersion() { return bukkitVersion; } @SuppressWarnings("unchecked") public Player[] getOnlinePlayers() { List online = playerList.players; Player[] players = new Player[online.size()]; for (int i = 0; i < players.length; i++) { players[i] = online.get(i).playerConnection.getPlayer(); } return players; } public Player getPlayer(final String name) { Validate.notNull(name, "Name cannot be null"); Player[] players = getOnlinePlayers(); Player found = null; String lowerName = name.toLowerCase(); int delta = Integer.MAX_VALUE; for (Player player : players) { if (player.getName().toLowerCase().startsWith(lowerName)) { int curDelta = player.getName().length() - lowerName.length(); if (curDelta < delta) { found = player; delta = curDelta; } if (curDelta == 0) break; } } return found; } public Player getPlayerExact(String name) { Validate.notNull(name, "Name cannot be null"); String lname = name.toLowerCase(); for (Player player : getOnlinePlayers()) { if (player.getName().equalsIgnoreCase(lname)) { return player; } } return null; } public int broadcastMessage(String message) { return broadcast(message, BROADCAST_CHANNEL_USERS); } public Player getPlayer(final EntityPlayer entity) { return entity.playerConnection.getPlayer(); } public List matchPlayer(String partialName) { Validate.notNull(partialName, "PartialName cannot be null"); List matchedPlayers = new ArrayList(); 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; } public int getMaxPlayers() { return playerList.getMaxPlayers(); } // NOTE: These are dependent on the corrisponding call in MinecraftServer // so if that changes this will need to as well public int getPort() { return this.getConfigInt("server-port", 25565); } public int getViewDistance() { return this.getConfigInt("view-distance", 10); } public String getIp() { return this.getConfigString("server-ip", ""); } public String getServerName() { return this.getConfigString("server-name", "Unknown Server"); } public String getServerId() { return this.getConfigString("server-id", "unnamed"); } public String getWorldType() { return this.getConfigString("level-type", "DEFAULT"); } public boolean getGenerateStructures() { return this.getConfigBoolean("generate-structures", true); } public boolean getAllowEnd() { return this.configuration.getBoolean("settings.allow-end"); } 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"); } 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 public String getUpdateFolder() { return this.configuration.getString("settings.update-folder", "update"); } public File getUpdateFolderFile() { return new File((File) console.options.valueOf("plugins"), this.configuration.getString("settings.update-folder", "update")); } public int getPingPacketLimit() { return this.configuration.getInt("settings.ping-packet-limit", 100); } public long getConnectionThrottle() { return this.configuration.getInt("settings.connection-throttle"); } public int getTicksPerAnimalSpawns() { return this.configuration.getInt("ticks-per.animal-spawns"); } public int getTicksPerMonsterSpawns() { return this.configuration.getInt("ticks-per.monster-spawns"); } public PluginManager getPluginManager() { return pluginManager; } public CraftScheduler getScheduler() { return scheduler; } public ServicesManager getServicesManager() { return servicesManager; } public List getWorlds() { return new ArrayList(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; } } public boolean dispatchCommand(CommandSender sender, String commandLine) { Validate.notNull(sender, "Sender cannot be null"); Validate.notNull(commandLine, "CommandLine cannot be null"); if (commandMap.dispatch(sender, commandLine)) { return true; } if (sender instanceof Player) { sender.sendMessage("Unknown command. Type \"/help\" for help."); } else { sender.sendMessage("Unknown command. Type \"help\" for help."); } return false; } public void reload() { configuration = YamlConfiguration.loadConfiguration(getConfigFile()); 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).difficulty != EnumDifficulty.PEACEFUL); EnumDifficulty difficulty = EnumDifficulty.a(config.getInt("difficulty", console.worlds.get(0).difficulty.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(); playerList.getIPBans().load(); playerList.getNameBans().load(); for (WorldServer world : console.worlds) { world.difficulty = 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(); } } pluginManager.clearPlugins(); commandMap.clearCommands(); resetRecipes(); 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 overdueWorkers = getScheduler().getActiveWorkers(); for (BukkitWorker worker : overdueWorkers) { Plugin plugin = worker.getOwner(); String author = ""; 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> perms; try { perms = (Map>) 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 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(); } 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 ConvertProgressUpdater(console)); } 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; WorldServer internal = new WorldServer(console, new ServerNBTManager(getWorldContainer(), name, true), name, dimension, new WorldSettings(creator.seed(), EnumGamemode.a(getDefaultGameMode().getValue()), generateStructures, hardcore, type), console.methodProfiler, creator.environment(), generator); if (!(worlds.containsKey(name.toLowerCase()))) { return null; } internal.scoreboard = getScoreboardManager().getMainScoreboard().getHandle(); internal.tracker = new EntityTracker(internal); internal.addIWorldAccess(new WorldManager(console, internal)); internal.difficulty = 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; } ChunkCoordinates chunkcoordinates = internal.getSpawn(); internal.chunkProviderServer.getChunkAt(chunkcoordinates.x + j >> 4, chunkcoordinates.z + k >> 4); } } } pluginManager.callEvent(new WorldLoadEvent(internal.getWorld())); return internal.getWorld(); } public boolean unloadWorld(String name, boolean save) { return unloadWorld(getWorld(name), save); } 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(); WorldSaveEvent event = new WorldSaveEvent(handle.getWorld()); getPluginManager().callEvent(event); } catch (ExceptionWorldConflict ex) { getLogger().log(Level.SEVERE, null, ex); } } worlds.remove(world.getName().toLowerCase()); console.worlds.remove(console.worlds.indexOf(handle)); return true; } public MinecraftServer getServer() { return console; } public World getWorld(String name) { Validate.notNull(name, "Name cannot be null"); return worlds.get(name.toLowerCase()); } 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); } public Logger getLogger() { return logger; } public ConsoleReader getReader() { return console.reader; } public PluginCommand getPluginCommand(String name) { Command command = commandMap.getCommand(name); if (command instanceof PluginCommand) { return (PluginCommand) command; } else { return null; } } public void savePlayers() { checkSaveState(); playerList.savePlayers(); } 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); } 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; } public List getRecipesFor(ItemStack result) { Validate.notNull(result, "Result cannot be null"); List results = new ArrayList(); Iterator 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; } public Iterator recipeIterator() { return new RecipeIterator(); } public void clearRecipes() { CraftingManager.getInstance().recipes.clear(); RecipesFurnace.getInstance().recipes.clear(); } public void resetRecipes() { CraftingManager.getInstance().recipes = new CraftingManager().recipes; RecipesFurnace.getInstance().recipes = new RecipesFurnace().recipes; } public Map getCommandAliases() { ConfigurationSection section = configuration.getConfigurationSection("aliases"); Map result = new LinkedHashMap(); if (section != null) { for (String key : section.getKeys(false)) { List 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); } public String getShutdownMessage() { return configuration.getString("settings.shutdown-message"); } public int getSpawnRadius() { return ((DedicatedServer) console).propertyManager.getInt("spawn-protection", 16); } public void setSpawnRadius(int value) { configuration.set("settings.spawn-radius", value); saveConfig(); } public boolean getOnlineMode() { return online.value; } public boolean getAllowFlight() { return console.getAllowFlight(); } public boolean isHardcore() { return console.isHardcore(); } 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.equals(""))) { 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; } 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; } 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.MAP.getSavedMap(stack, ((CraftWorld) world).getHandle()); return worldmap.mapView; } public void shutdown() { console.safeShutdown(); } public int broadcast(String message, String permission) { int count = 0; Set 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; } public OfflinePlayer getOfflinePlayer(String name) { return getOfflinePlayer(name, true); } public OfflinePlayer getOfflinePlayer(String name, boolean search) { Validate.notNull(name, "Name cannot be null"); OfflinePlayer result = getPlayerExact(name); String lname = name.toLowerCase(); if (result == null) { result = offlinePlayers.get(lname); if (result == null) { if (search) { WorldNBTStorage storage = (WorldNBTStorage) console.worlds.get(0).getDataManager(); for (String dat : storage.getPlayerDir().list(new DatFileFilter())) { String datName = dat.substring(0, dat.length() - 4); if (datName.equalsIgnoreCase(name)) { name = datName; break; } } } result = new CraftOfflinePlayer(this, name); offlinePlayers.put(lname, result); } } else { offlinePlayers.remove(lname); } return result; } @SuppressWarnings("unchecked") public Set getIPBans() { return playerList.getIPBans().getEntries().keySet(); } public void banIP(String address) { Validate.notNull(address, "Address cannot be null."); BanEntry entry = new BanEntry(address); playerList.getIPBans().add(entry); playerList.getIPBans().save(); } public void unbanIP(String address) { playerList.getIPBans().remove(address); playerList.getIPBans().save(); } public Set getBannedPlayers() { Set result = new HashSet(); for (Object name : playerList.getNameBans().getEntries().keySet()) { result.add(getOfflinePlayer((String) name)); } return result; } public void setWhitelist(boolean value) { playerList.hasWhitelist = value; console.getPropertyManager().a("white-list", value); } public Set getWhitelistedPlayers() { Set result = new LinkedHashSet(); for (Object name : playerList.getWhitelisted()) { if (((String)name).length() == 0 || ((String)name).startsWith("#")) { continue; } result.add(getOfflinePlayer((String) name)); } return result; } public Set getOperators() { Set result = new HashSet(); for (Object name : playerList.getOPs()) { result.add(getOfflinePlayer((String) name)); } return result; } public void reloadWhitelist() { playerList.reloadWhitelist(); } public GameMode getDefaultGameMode() { return GameMode.getByValue(console.worlds.get(0).getWorldData().getGameType().a()); } public void setDefaultGameMode(GameMode mode) { Validate.notNull(mode, "Mode cannot be null"); for (World world : getWorlds()) { ((CraftWorld) world).getHandle().worldData.setGameType(EnumGamemode.a(mode.getValue())); } } public ConsoleCommandSender getConsoleSender() { return console.console; } public EntityMetadataStore getEntityMetadata() { return entityMetadata; } public PlayerMetadataStore getPlayerMetadata() { return playerMetadata; } public WorldMetadataStore getWorldMetadata() { return worldMetadata; } public void detectListNameConflict(EntityPlayer entityPlayer) { // Collisions will make for invisible people for (int i = 0; i < getHandle().players.size(); ++i) { EntityPlayer testEntityPlayer = (EntityPlayer) getHandle().players.get(i); // We have a problem! if (testEntityPlayer != entityPlayer && testEntityPlayer.listName.equals(entityPlayer.listName)) { String oldName = entityPlayer.listName; int spaceLeft = 16 - oldName.length(); if (spaceLeft <= 1) { // We also hit the list name length limit! entityPlayer.listName = oldName.subSequence(0, oldName.length() - 2 - spaceLeft) + String.valueOf(System.currentTimeMillis() % 99); } else { entityPlayer.listName = oldName + String.valueOf(System.currentTimeMillis() % 99); } return; } } } 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; } public OfflinePlayer[] getOfflinePlayers() { WorldNBTStorage storage = (WorldNBTStorage) console.worlds.get(0).getDataManager(); String[] files = storage.getPlayerDir().list(new DatFileFilter()); Set players = new HashSet(); for (String file : files) { players.add(getOfflinePlayer(file.substring(0, file.length() - 4), false)); } players.addAll(Arrays.asList(getOnlinePlayers())); return players.toArray(new OfflinePlayer[players.size()]); } public Messenger getMessenger() { return messenger; } public void sendPluginMessage(Plugin source, String channel, byte[] message) { StandardMessenger.validatePluginMessage(getMessenger(), source, channel, message); for (Player player : getOnlinePlayers()) { player.sendPluginMessage(source, channel, message); } } public Set getListeningPluginChannels() { Set result = new HashSet(); for (Player player : getOnlinePlayers()) { result.addAll(player.getListeningPluginChannels()); } return result; } public void onPlayerJoin(Player player) { if ((updater.isEnabled()) && (updater.getCurrent() != null) && (player.hasPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE))) { if ((updater.getCurrent().isBroken()) && (updater.getOnBroken().contains(AutoUpdater.WARN_OPERATORS))) { player.sendMessage(ChatColor.DARK_RED + "The version of CraftBukkit that this server is running is known to be broken. Please consider updating to the latest version at dl.bukkit.org."); } else if ((updater.isUpdateAvailable()) && (updater.getOnUpdate().contains(AutoUpdater.WARN_OPERATORS))) { player.sendMessage(ChatColor.DARK_PURPLE + "The version of CraftBukkit that this server is running is out of date. Please consider updating to the latest version at dl.bukkit.org."); } } } public Inventory createInventory(InventoryHolder owner, InventoryType type) { // TODO: Create the appropriate type, rather than Custom? return new CraftInventoryCustom(owner, type); } 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); } 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); } public HelpMap getHelpMap() { return helpMap; } public SimpleCommandMap getCommandMap() { return commandMap; } public int getMonsterSpawnLimit() { return monsterSpawn; } public int getAnimalSpawnLimit() { return animalSpawn; } public int getWaterAnimalSpawnLimit() { return waterAnimalSpawn; } public int getAmbientSpawnLimit() { return ambientSpawn; } public boolean isPrimaryThread() { return Thread.currentThread().equals(console.primaryThread); } public String getMotd() { return console.getMotd(); } public WarningState getWarningState() { return warningState; } public List tabComplete(net.minecraft.server.ICommandListener sender, String message) { if (!(sender instanceof EntityPlayer)) { return ImmutableList.of(); } Player player = ((EntityPlayer) sender).getBukkitEntity(); if (message.startsWith("/")) { return tabCompleteCommand(player, message); } else { return tabCompleteChat(player, message); } } public List tabCompleteCommand(Player player, String message) { List completions = null; try { completions = getCommandMap().tabComplete(player, message.substring(1)); } 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.of() : completions; } public List tabCompleteChat(Player player, String message) { Player[] players = getOnlinePlayers(); List completions = new ArrayList(); PlayerChatTabCompleteEvent event = new PlayerChatTabCompleteEvent(player, message, completions); String token = event.getLastToken(); for (Player p : players) { 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; } public CraftItemFactory getItemFactory() { return CraftItemFactory.instance(); } 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)); } }