package org.bukkit.craftbukkit; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.serialization.DynamicOps; import com.mojang.serialization.Lifecycle; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; 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.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; 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.Objects; import java.util.Optional; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import jline.console.ConsoleReader; import net.minecraft.advancements.Advancement; import net.minecraft.commands.CommandDispatcher; import net.minecraft.commands.CommandListenerWrapper; import net.minecraft.commands.arguments.ArgumentEntity; import net.minecraft.core.BlockPosition; import net.minecraft.core.IRegistry; import net.minecraft.core.RegistryMaterials; import net.minecraft.nbt.DynamicOpsNBT; import net.minecraft.nbt.NBTBase; import net.minecraft.resources.MinecraftKey; import net.minecraft.resources.RegistryReadOps; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerCommand; import net.minecraft.server.bossevents.BossBattleCustom; import net.minecraft.server.commands.CommandReload; import net.minecraft.server.dedicated.DedicatedPlayerList; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.dedicated.DedicatedServerProperties; import net.minecraft.server.dedicated.DedicatedServerSettings; import net.minecraft.server.level.EntityPlayer; import net.minecraft.server.level.TicketType; import net.minecraft.server.level.WorldServer; import net.minecraft.server.players.JsonListEntry; import net.minecraft.server.players.PlayerList; import net.minecraft.tags.Tags; import net.minecraft.tags.TagsBlock; import net.minecraft.tags.TagsEntity; import net.minecraft.tags.TagsFluid; import net.minecraft.tags.TagsItem; import net.minecraft.util.datafix.DataConverterRegistry; import net.minecraft.world.EnumDifficulty; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.entity.ai.village.VillageSiege; import net.minecraft.world.entity.npc.MobSpawnerCat; import net.minecraft.world.entity.npc.MobSpawnerTrader; import net.minecraft.world.entity.player.EntityHuman; import net.minecraft.world.inventory.Container; import net.minecraft.world.inventory.ContainerWorkbench; import net.minecraft.world.inventory.InventoryCraftResult; import net.minecraft.world.inventory.InventoryCrafting; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemWorldMap; import net.minecraft.world.item.crafting.IRecipe; import net.minecraft.world.item.crafting.RecipeCrafting; import net.minecraft.world.item.crafting.RecipeRepair; import net.minecraft.world.item.crafting.Recipes; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.EnumGamemode; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.MobSpawner; import net.minecraft.world.level.WorldSettings; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.WorldChunkManager; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.dimension.DimensionManager; import net.minecraft.world.level.dimension.WorldDimension; import net.minecraft.world.level.levelgen.ChunkGeneratorAbstract; import net.minecraft.world.level.levelgen.GeneratorSettings; import net.minecraft.world.level.levelgen.MobSpawnerPatrol; import net.minecraft.world.level.levelgen.MobSpawnerPhantom; import net.minecraft.world.level.material.FluidType; import net.minecraft.world.level.saveddata.maps.MapIcon; import net.minecraft.world.level.saveddata.maps.WorldMap; import net.minecraft.world.level.storage.Convertable; import net.minecraft.world.level.storage.WorldDataServer; import net.minecraft.world.level.storage.WorldNBTStorage; import net.minecraft.world.level.storage.loot.LootTableRegistry; import net.minecraft.world.phys.Vec3D; 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.Keyed; import org.bukkit.Location; import org.bukkit.NamespacedKey; import org.bukkit.OfflinePlayer; import org.bukkit.Server; import org.bukkit.StructureType; 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.block.data.BlockData; import org.bukkit.boss.BarColor; import org.bukkit.boss.BarFlag; import org.bukkit.boss.BarStyle; import org.bukkit.boss.BossBar; import org.bukkit.boss.KeyedBossBar; 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.block.data.CraftBlockData; import org.bukkit.craftbukkit.boss.CraftBossBar; import org.bukkit.craftbukkit.boss.CraftKeyedBossbar; import org.bukkit.craftbukkit.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.command.CraftCommandMap; import org.bukkit.craftbukkit.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.craftbukkit.event.CraftEventFactory; import org.bukkit.craftbukkit.generator.CraftWorldInfo; import org.bukkit.craftbukkit.generator.CustomWorldChunkManager; import org.bukkit.craftbukkit.generator.OldCraftChunkData; import org.bukkit.craftbukkit.help.SimpleHelpMap; import org.bukkit.craftbukkit.inventory.CraftBlastingRecipe; import org.bukkit.craftbukkit.inventory.CraftCampfireRecipe; import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe; import org.bukkit.craftbukkit.inventory.CraftItemFactory; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.inventory.CraftMerchantCustom; import org.bukkit.craftbukkit.inventory.CraftRecipe; import org.bukkit.craftbukkit.inventory.CraftShapedRecipe; import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe; import org.bukkit.craftbukkit.inventory.CraftSmithingRecipe; import org.bukkit.craftbukkit.inventory.CraftSmokingRecipe; import org.bukkit.craftbukkit.inventory.CraftStonecuttingRecipe; import org.bukkit.craftbukkit.inventory.RecipeIterator; import org.bukkit.craftbukkit.inventory.util.CraftInventoryCreator; 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.tag.CraftBlockTag; import org.bukkit.craftbukkit.tag.CraftEntityTag; import org.bukkit.craftbukkit.tag.CraftFluidTag; import org.bukkit.craftbukkit.tag.CraftItemTag; import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftIconCache; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.craftbukkit.util.CraftNamespacedKey; 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.server.BroadcastMessageEvent; import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.event.server.TabCompleteEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.WorldInfo; import org.bukkit.help.HelpMap; import org.bukkit.inventory.BlastingRecipe; import org.bukkit.inventory.CampfireRecipe; import org.bukkit.inventory.ComplexRecipe; import org.bukkit.inventory.FurnaceRecipe; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Merchant; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapelessRecipe; import org.bukkit.inventory.SmithingRecipe; import org.bukkit.inventory.SmokingRecipe; import org.bukkit.inventory.StonecuttingRecipe; import org.bukkit.loot.LootTable; import org.bukkit.map.MapView; 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; public final class CraftServer implements Server { private final String serverName = "CraftBukkit"; 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 CraftCommandMap commandMap = new CraftCommandMap(this); private final SimpleHelpMap helpMap = new SimpleHelpMap(this); private final StandardMessenger messenger = new StandardMessenger(); private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap); protected final DedicatedServer console; protected final DedicatedPlayerList playerList; private final Map worlds = new LinkedHashMap(); private YamlConfiguration configuration; private YamlConfiguration commandsConfiguration; private final Yaml yaml = new Yaml(new SafeConstructor()); private final Map offlinePlayers = new MapMaker().weakValues().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 waterAmbientSpawn = -1; private int waterUndergroundCreatureSpawn = -1; private int ambientSpawn = -1; private File container; private WarningState warningState = WarningState.DEFAULT; public String minimumAPI; public CraftScoreboardManager scoreboardManager; public boolean playerCommandState; private boolean printSaveWarning; private CraftIconCache icon; private boolean overrideAllCommandBlockCommands = false; public boolean ignoreVanillaPermissions = false; private final List playerView; public int reloadCount; static { ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); CraftItemFactory.instance(); } public CraftServer(DedicatedServer console, PlayerList playerList) { this.console = console; this.playerList = (DedicatedPlayerList) playerList; this.playerView = Collections.unmodifiableList(Lists.transform(playerList.players, new Function() { @Override public CraftPlayer apply(EntityPlayer player) { return player.getBukkitEntity(); } })); this.serverVersion = CraftServer.class.getPackage().getImplementationVersion(); Bukkit.setServer(this); // Register all the Enchantments and PotionTypes now so we can stop new registration immediately after Enchantments.SHARPNESS.getClass(); org.bukkit.enchantments.Enchantment.stopAcceptingRegistrations(); Potion.setPotionBrewer(new CraftPotionBrewer()); MobEffects.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 commands = new ArrayList(); 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("*"); ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions"); 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"); waterAmbientSpawn = configuration.getInt("spawn-limits.water-ambient"); waterUndergroundCreatureSpawn = configuration.getInt("spawn-limits.water-underground-creature"); ambientSpawn = configuration.getInt("spawn-limits.ambient"); console.autosavePeriod = configuration.getInt("ticks-per.autosave"); warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); TicketType.PLUGIN.timeout = configuration.getInt("chunk-gc.period-in-ticks"); minimumAPI = configuration.getString("settings.minimum-api"); loadIcon(); } 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 { 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)) { enablePlugin(plugin); } } if (type == PluginLoadOrder.POSTWORLD) { commandMap.setFallbackCommands(); setVanillaCommands(); commandMap.registerServerAliases(); DefaultPermissions.registerCorePermissions(); CraftDefaultPermissions.registerCorePermissions(); loadCustomPermissions(); helpMap.initializeCommands(); syncCommands(); } } public void disablePlugins() { pluginManager.disablePlugins(); } private void setVanillaCommands() { CommandDispatcher dispatcher = console.vanillaCommandDispatcher; // Build a list of all Vanilla commands and create wrappers for (CommandNode cmd : dispatcher.a().getRoot().getChildren()) { commandMap.register("minecraft", new VanillaCommandWrapper(dispatcher, cmd)); } } public void syncCommands() { // Clear existing commands CommandDispatcher dispatcher = console.resources.commands = new CommandDispatcher(); // Register all commands, vanilla ones will be using the old dispatcher references for (Map.Entry entry : commandMap.getKnownCommands().entrySet()) { String label = entry.getKey(); Command command = entry.getValue(); if (command instanceof VanillaCommandWrapper) { LiteralCommandNode node = (LiteralCommandNode) ((VanillaCommandWrapper) command).vanillaCommand; if (!node.getLiteral().equals(label)) { LiteralCommandNode clone = new LiteralCommandNode(label, node.getCommand(), node.getRequirement(), node.getRedirect(), node.getRedirectModifier(), node.isFork()); for (CommandNode child : node.getChildren()) { clone.addChild(child); } node = clone; } dispatcher.a().getRoot().addChild(node); } else { new BukkitCommandWrapper(this, entry.getValue()).register(dispatcher.a(), label); } } // Refresh commands for (EntityPlayer player : getHandle().players) { dispatcher.a(player); } } private void enablePlugin(Plugin plugin) { try { List perms = plugin.getDescription().getPermissions(); for (Permission perm : perms) { try { pluginManager.addPermission(perm, false); } catch (IllegalArgumentException ex) { getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex); } } pluginManager.dirtyPermissibles(); pluginManager.enablePlugin(plugin); } 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 public List 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(java.util.Locale.ENGLISH); int delta = Integer.MAX_VALUE; for (Player player : getOnlinePlayers()) { if (player.getName().toLowerCase(java.util.Locale.ENGLISH).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.getPlayer(name); return (player != null) ? player.getBukkitEntity() : null; } @Override public Player getPlayer(UUID id) { EntityPlayer player = playerList.getPlayer(id); if (player != null) { return player.getBukkitEntity(); } return null; } @Override public int broadcastMessage(String message) { return broadcast(message, BROADCAST_CHANNEL_USERS); } @Override @Deprecated 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(java.util.Locale.ENGLISH).contains(partialName.toLowerCase(java.util.Locale.ENGLISH))) { // Partial match matchedPlayers.add(iterPlayer); } } return matchedPlayers; } @Override public int getMaxPlayers() { return playerList.getMaxPlayers(); } // NOTE: These are dependent on the corresponding call in MinecraftServer // so if that changes this will need to as well @Override public int getPort() { return this.getServer().getPort(); } @Override public int getViewDistance() { return this.getProperties().viewDistance; } @Override public String getIp() { return this.getServer().getServerIp(); } @Override public String getWorldType() { return this.getProperties().properties.getProperty("level-type"); } @Override public boolean getGenerateStructures() { return this.getProperties().a(this.getServer().getCustomRegistry()).shouldGenerateMapFeatures(); } @Override public int getMaxWorldSize() { return this.getProperties().maxWorldSize; } @Override public boolean getAllowEnd() { return this.configuration.getBoolean("settings.allow-end"); } @Override public boolean getAllowNether() { return this.getServer().getAllowNether(); } 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.getProperties().whiteList.get(); } // NOTE: Temporary calls through to server.properies until its replaced private DedicatedServerProperties getProperties() { return this.console.getDedicatedServerProperties(); } // 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() { return this.configuration.getInt("settings.connection-throttle"); } @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 int getTicksPerWaterSpawns() { return this.configuration.getInt("ticks-per.water-spawns"); } @Override public int getTicksPerWaterAmbientSpawns() { return this.configuration.getInt("ticks-per.water-ambient-spawns"); } @Override public int getTicksPerWaterUndergroundCreatureSpawns() { return this.configuration.getInt("ticks-per.water-underground-creature-spawns"); } @Override public int getTicksPerAmbientSpawns() { return this.configuration.getInt("ticks-per.ambient-spawns"); } @Override public PluginManager getPluginManager() { return pluginManager; } @Override public CraftScheduler getScheduler() { return scheduler; } @Override public ServicesManager getServicesManager() { return servicesManager; } @Override 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.msg); return true; } } try { this.playerCommandState = true; return dispatchCommand(sender, serverCommand.msg); } catch (Exception ex) { getLogger().log(Level.WARNING, "Unexpected exception while parsing console command \"" + serverCommand.msg + '"', 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"); 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; } @Override public void reload() { reloadCount++; configuration = YamlConfiguration.loadConfiguration(getConfigFile()); commandsConfiguration = YamlConfiguration.loadConfiguration(getCommandsConfigFile()); console.settings = new DedicatedServerSettings(console.options); DedicatedServerProperties config = console.settings.getProperties(); console.setPVP(config.pvp); console.setAllowFlight(config.allowFlight); console.setMotd(config.motd); monsterSpawn = configuration.getInt("spawn-limits.monsters"); animalSpawn = configuration.getInt("spawn-limits.animals"); waterAnimalSpawn = configuration.getInt("spawn-limits.water-animals"); waterAmbientSpawn = configuration.getInt("spawn-limits.water-ambient"); waterUndergroundCreatureSpawn = configuration.getInt("spawn-limits.water-underground-creature"); ambientSpawn = configuration.getInt("spawn-limits.ambient"); warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); TicketType.PLUGIN.timeout = configuration.getInt("chunk-gc.period-in-ticks"); minimumAPI = configuration.getString("settings.minimum-api"); printSaveWarning = false; console.autosavePeriod = configuration.getInt("ticks-per.autosave"); 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()); } for (WorldServer world : console.getWorlds()) { world.serverLevelData.setDifficulty(config.difficulty); world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals); if (this.getTicksPerAnimalSpawns() < 0) { world.ticksPerAnimalSpawns = 400; } else { world.ticksPerAnimalSpawns = this.getTicksPerAnimalSpawns(); } if (this.getTicksPerMonsterSpawns() < 0) { world.ticksPerMonsterSpawns = 1; } else { world.ticksPerMonsterSpawns = this.getTicksPerMonsterSpawns(); } if (this.getTicksPerWaterSpawns() < 0) { world.ticksPerWaterSpawns = 1; } else { world.ticksPerWaterSpawns = this.getTicksPerWaterSpawns(); } if (this.getTicksPerWaterAmbientSpawns() < 0) { world.ticksPerWaterAmbientSpawns = 1; } else { world.ticksPerWaterAmbientSpawns = this.getTicksPerWaterAmbientSpawns(); } if (this.getTicksPerWaterUndergroundCreatureSpawns() < 0) { world.ticksPerWaterUndergroundCreatureSpawns = 1; } else { world.ticksPerWaterUndergroundCreatureSpawns = this.getTicksPerWaterUndergroundCreatureSpawns(); } if (this.getTicksPerAmbientSpawns() < 0) { world.ticksPerAmbientSpawns = 1; } else { world.ticksPerAmbientSpawns = this.getTicksPerAmbientSpawns(); } } pluginManager.clearPlugins(); commandMap.clearCommands(); reloadData(); overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*"); ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions"); 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(); getLogger().log(Level.SEVERE, String.format( "Nag author(s): '%s' of '%s' about the following: %s", plugin.getDescription().getAuthors(), plugin.getDescription().getFullName(), "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); getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD)); } @Override public void reloadData() { CommandReload.reload(console); } 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(); } @Override public World createWorld(WorldCreator creator) { Preconditions.checkState(!console.levels.isEmpty(), "Cannot create additional worlds on STARTUP"); Validate.notNull(creator, "Creator may not be null"); String name = creator.name(); ChunkGenerator generator = creator.generator(); BiomeProvider biomeProvider = creator.biomeProvider(); File folder = new File(getWorldContainer(), name); World world = getWorld(name); 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); } if (biomeProvider == null) { biomeProvider = getBiomeProvider(name); } ResourceKey actualDimension; switch (creator.environment()) { case NORMAL: actualDimension = WorldDimension.OVERWORLD; break; case NETHER: actualDimension = WorldDimension.NETHER; break; case THE_END: actualDimension = WorldDimension.END; break; default: throw new IllegalArgumentException("Illegal dimension"); } Convertable.ConversionSession worldSession; try { worldSession = Convertable.a(getWorldContainer().toPath()).c(name, actualDimension); } catch (IOException ex) { throw new RuntimeException(ex); } MinecraftServer.convertWorld(worldSession); // Run conversion now boolean hardcore = creator.hardcore(); RegistryReadOps registryreadops = RegistryReadOps.a((DynamicOps) DynamicOpsNBT.INSTANCE, console.resources.i(), console.registryHolder); WorldDataServer worlddata = (WorldDataServer) worldSession.a((DynamicOps) registryreadops, console.datapackconfiguration); WorldSettings worldSettings; // See MinecraftServer.a(String, String, long, WorldType, JsonElement) if (worlddata == null) { Properties properties = new Properties(); properties.put("generator-settings", Objects.toString(creator.generatorSettings())); properties.put("level-seed", Objects.toString(creator.seed())); properties.put("generate-structures", Objects.toString(creator.generateStructures())); properties.put("level-type", Objects.toString(creator.type().getName())); GeneratorSettings generatorsettings = GeneratorSettings.a(console.getCustomRegistry(), properties); worldSettings = new WorldSettings(name, EnumGamemode.getById(getDefaultGameMode().getValue()), hardcore, EnumDifficulty.EASY, false, new GameRules(), console.datapackconfiguration); worlddata = new WorldDataServer(worldSettings, generatorsettings, Lifecycle.stable()); } worlddata.checkName(name); worlddata.a(console.getServerModName(), console.getModded().isPresent()); if (console.options.has("forceUpgrade")) { net.minecraft.server.Main.convertWorld(worldSession, DataConverterRegistry.a(), console.options.has("eraseCache"), () -> { return true; }, worlddata.getGeneratorSettings().d().d().stream().map((entry) -> { return ResourceKey.a(IRegistry.DIMENSION_TYPE_REGISTRY, ((ResourceKey) entry.getKey()).a()); }).collect(ImmutableSet.toImmutableSet())); } long j = BiomeManager.a(creator.seed()); List list = ImmutableList.of(new MobSpawnerPhantom(), new MobSpawnerPatrol(), new MobSpawnerCat(), new VillageSiege(), new MobSpawnerTrader(worlddata)); RegistryMaterials registrymaterials = worlddata.getGeneratorSettings().d(); WorldDimension worlddimension = (WorldDimension) registrymaterials.a(actualDimension); DimensionManager dimensionmanager; net.minecraft.world.level.chunk.ChunkGenerator chunkgenerator; if (worlddimension == null) { dimensionmanager = (DimensionManager) console.registryHolder.d(IRegistry.DIMENSION_TYPE_REGISTRY).d(DimensionManager.OVERWORLD_LOCATION); chunkgenerator = GeneratorSettings.a(console.registryHolder.d(IRegistry.BIOME_REGISTRY), console.registryHolder.d(IRegistry.NOISE_GENERATOR_SETTINGS_REGISTRY), (new Random()).nextLong()); } else { dimensionmanager = worlddimension.b(); chunkgenerator = worlddimension.c(); } WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), dimensionmanager); if (biomeProvider == null && generator != null) { biomeProvider = generator.getDefaultBiomeProvider(worldInfo); } if (biomeProvider != null) { WorldChunkManager worldChunkManager = new CustomWorldChunkManager(worldInfo, biomeProvider, console.registryHolder.b(IRegistry.BIOME_REGISTRY)); if (chunkgenerator instanceof ChunkGeneratorAbstract) { chunkgenerator = new ChunkGeneratorAbstract(worldChunkManager, chunkgenerator.strongholdSeed, ((ChunkGeneratorAbstract) chunkgenerator).settings); } } ResourceKey worldKey; String levelName = this.getServer().getDedicatedServerProperties().levelName; if (name.equals(levelName + "_nether")) { worldKey = net.minecraft.world.level.World.NETHER; } else if (name.equals(levelName + "_the_end")) { worldKey = net.minecraft.world.level.World.END; } else { worldKey = ResourceKey.a(IRegistry.DIMENSION_REGISTRY, new MinecraftKey(name.toLowerCase(java.util.Locale.ENGLISH))); } WorldServer internal = (WorldServer) new WorldServer(console, console.executor, worldSession, worlddata, worldKey, dimensionmanager, getServer().progressListenerFactory.create(11), chunkgenerator, worlddata.getGeneratorSettings().isDebugWorld(), j, creator.environment() == Environment.NORMAL ? list : ImmutableList.of(), true, creator.environment(), generator, biomeProvider); if (!(worlds.containsKey(name.toLowerCase(java.util.Locale.ENGLISH)))) { return null; } console.initWorld(internal, worlddata, worlddata, worlddata.getGeneratorSettings()); internal.setSpawnFlags(true, true); console.levels.put(internal.getDimensionKey(), internal); getServer().loadSpawn(internal.getChunkProvider().chunkMap.progressListener, internal); internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API 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.levels.containsKey(handle.getDimensionKey()))) { return false; } if (handle.getDimensionKey() == net.minecraft.world.level.World.OVERWORLD) { return false; } if (handle.getPlayers().size() > 0) { return false; } WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld()); pluginManager.callEvent(e); if (e.isCancelled()) { return false; } try { if (save) { handle.save(null, true, true); } handle.getChunkProvider().close(save); handle.entityManager.close(save); // SPIGOT-6722: close entityManager handle.convertable.close(); } catch (Exception ex) { getLogger().log(Level.SEVERE, null, ex); } worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH)); console.levels.remove(handle.getDimensionKey()); return true; } public DedicatedServer getServer() { return console; } @Override public World getWorld(String name) { Validate.notNull(name, "Name cannot be null"); return worlds.get(name.toLowerCase(java.util.Locale.ENGLISH)); } @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(java.util.Locale.ENGLISH), 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 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 if (recipe instanceof BlastingRecipe) { toAdd = CraftBlastingRecipe.fromBukkitRecipe((BlastingRecipe) recipe); } else if (recipe instanceof CampfireRecipe) { toAdd = CraftCampfireRecipe.fromBukkitRecipe((CampfireRecipe) recipe); } else if (recipe instanceof SmokingRecipe) { toAdd = CraftSmokingRecipe.fromBukkitRecipe((SmokingRecipe) recipe); } else if (recipe instanceof StonecuttingRecipe) { toAdd = CraftStonecuttingRecipe.fromBukkitRecipe((StonecuttingRecipe) recipe); } else if (recipe instanceof SmithingRecipe) { toAdd = CraftSmithingRecipe.fromBukkitRecipe((SmithingRecipe) recipe); } else if (recipe instanceof ComplexRecipe) { throw new UnsupportedOperationException("Cannot add custom complex recipe"); } else { return false; } } toAdd.addToCraftingManager(); return true; } @Override 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; } @Override public Recipe getRecipe(NamespacedKey recipeKey) { Preconditions.checkArgument(recipeKey != null, "recipeKey == null"); return getServer().getCraftingManager().getRecipe(CraftNamespacedKey.toMinecraft(recipeKey)).map(IRecipe::toBukkitRecipe).orElse(null); } @Override public Recipe getCraftingRecipe(ItemStack[] craftingMatrix, World world) { // Create a players Crafting Inventory Container container = new Container(null, -1) { @Override public InventoryView getBukkitView() { return null; } @Override public boolean canUse(EntityHuman entityhuman) { return false; } }; InventoryCrafting inventoryCrafting = new InventoryCrafting(container, 3, 3); return getNMSRecipe(craftingMatrix, inventoryCrafting, (CraftWorld) world).map(IRecipe::toBukkitRecipe).orElse(null); } @Override public ItemStack craftItem(ItemStack[] craftingMatrix, World world, Player player) { Preconditions.checkArgument(world != null, "world must not be null"); Preconditions.checkArgument(player != null, "player must not be null"); CraftWorld craftWorld = (CraftWorld) world; CraftPlayer craftPlayer = (CraftPlayer) player; // Create a players Crafting Inventory and get the recipe ContainerWorkbench container = new ContainerWorkbench(-1, craftPlayer.getHandle().getInventory()); InventoryCrafting inventoryCrafting = container.craftSlots; InventoryCraftResult craftResult = container.resultSlots; Optional recipe = getNMSRecipe(craftingMatrix, inventoryCrafting, craftWorld); // Generate the resulting ItemStack from the Crafting Matrix net.minecraft.world.item.ItemStack itemstack = net.minecraft.world.item.ItemStack.EMPTY; if (recipe.isPresent()) { RecipeCrafting recipeCrafting = recipe.get(); if (craftResult.setRecipeUsed(craftWorld.getHandle(), craftPlayer.getHandle(), recipeCrafting)) { itemstack = recipeCrafting.a(inventoryCrafting); } } // Call Bukkit event to check for matrix/result changes. net.minecraft.world.item.ItemStack result = CraftEventFactory.callPreCraftEvent(inventoryCrafting, craftResult, itemstack, container.getBukkitView(), recipe.orElse(null) instanceof RecipeRepair); // Set the resulting matrix items for (int i = 0; i < craftingMatrix.length; i++) { Item remaining = inventoryCrafting.getContents().get(i).getItem().getCraftingRemainingItem(); craftingMatrix[i] = (remaining != null) ? CraftItemStack.asBukkitCopy(remaining.createItemStack()) : null; } return CraftItemStack.asBukkitCopy(result); } private Optional getNMSRecipe(ItemStack[] craftingMatrix, InventoryCrafting inventoryCrafting, CraftWorld world) { Preconditions.checkArgument(craftingMatrix != null, "craftingMatrix must not be null"); Preconditions.checkArgument(craftingMatrix.length == 9, "craftingMatrix must be an array of length 9"); Preconditions.checkArgument(world != null, "world must not be null"); for (int i = 0; i < craftingMatrix.length; i++) { inventoryCrafting.setItem(i, CraftItemStack.asNMSCopy(craftingMatrix[i])); } return getServer().getCraftingManager().craft(Recipes.CRAFTING, inventoryCrafting, world.getHandle()); } @Override public Iterator recipeIterator() { return new RecipeIterator(); } @Override public void clearRecipes() { console.getCraftingManager().clearRecipes(); } @Override public void resetRecipes() { reloadData(); // Not ideal but hard to reload a subset of a resource pack } @Override public boolean removeRecipe(NamespacedKey recipeKey) { Preconditions.checkArgument(recipeKey != null, "recipeKey == null"); MinecraftKey mcKey = CraftNamespacedKey.toMinecraft(recipeKey); for (Object2ObjectLinkedOpenHashMap> recipes : getServer().getCraftingManager().recipes.values()) { if (recipes.remove(mcKey) != null) { return true; } } return false; } @Override public Map getCommandAliases() { ConfigurationSection section = commandsConfiguration.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); } @Override public String getShutdownMessage() { return configuration.getString("settings.shutdown-message"); } @Override public int getSpawnRadius() { return this.getServer().getSpawnProtection(); } @Override public void setSpawnRadius(int value) { configuration.set("settings.spawn-radius", value); saveConfig(); } @Override public boolean getOnlineMode() { return console.getOnlineMode(); } @Override public boolean getAllowFlight() { return console.getAllowFlight(); } @Override public boolean isHardcore() { return console.isHardcore(); } 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 BiomeProvider getBiomeProvider(String world) { ConfigurationSection section = configuration.getConfigurationSection("worlds"); BiomeProvider result = null; if (section != null) { section = section.getConfigurationSection(world); if (section != null) { String name = section.getString("biome-provider"); 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 biome provider for default world '" + world + "': Plugin '" + split[0] + "' does not exist"); } else if (!plugin.isEnabled()) { getLogger().severe("Could not set biome provider for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName() + "' is not enabled yet (is it load:STARTUP?)"); } else { try { result = plugin.getDefaultBiomeProvider(world, id); if (result == null) { getLogger().severe("Could not set biome provider for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName() + "' lacks a default world biome provider"); } } catch (Throwable t) { plugin.getLogger().log(Level.SEVERE, "Could not set biome provider for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName(), t); } } } } } return result; } @Override @Deprecated public CraftMapView getMap(int id) { WorldMap worldmap = console.getWorldServer(net.minecraft.world.level.World.OVERWORLD).a("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.world.level.World minecraftWorld = ((CraftWorld) world).getHandle(); // creates a new map at world spawn with the scale of 3, with out tracking position and unlimited tracking int newId = ItemWorldMap.createNewSavedData(minecraftWorld, minecraftWorld.getWorldData().a(), minecraftWorld.getWorldData().c(), 3, false, false, minecraftWorld.getDimensionKey()); return minecraftWorld.a(ItemWorldMap.a(newId)).mapView; } @Override public ItemStack createExplorerMap(World world, Location location, StructureType structureType) { return this.createExplorerMap(world, location, structureType, 100, true); } @Override public ItemStack createExplorerMap(World world, Location location, StructureType structureType, int radius, boolean findUnexplored) { Validate.notNull(world, "World cannot be null"); Validate.notNull(structureType, "StructureType cannot be null"); Validate.notNull(structureType.getMapIcon(), "Cannot create explorer maps for StructureType " + structureType.getName()); WorldServer worldServer = ((CraftWorld) world).getHandle(); Location structureLocation = world.locateNearestStructure(location, structureType, radius, findUnexplored); BlockPosition structurePosition = new BlockPosition(structureLocation.getBlockX(), structureLocation.getBlockY(), structureLocation.getBlockZ()); // Create map with trackPlayer = true, unlimitedTracking = true net.minecraft.world.item.ItemStack stack = ItemWorldMap.createFilledMapView(worldServer, structurePosition.getX(), structurePosition.getZ(), MapView.Scale.NORMAL.getValue(), true, true); ItemWorldMap.applySepiaFilter(worldServer, stack); // "+" map ID taken from EntityVillager ItemWorldMap.getSavedMap(stack, worldServer).decorateMap(stack, structurePosition, "+", MapIcon.Type.a(structureType.getMapIcon().getValue())); return CraftItemStack.asBukkitCopy(stack); } @Override public void shutdown() { console.safeShutdown(false); } @Override public int broadcast(String message, String permission) { Set recipients = new HashSet<>(); for (Permissible permissible : getPluginManager().getPermissionSubscriptions(permission)) { if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { recipients.add((CommandSender) permissible); } } BroadcastMessageEvent broadcastMessageEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, recipients); getPluginManager().callEvent(broadcastMessageEvent); if (broadcastMessageEvent.isCancelled()) { return 0; } message = broadcastMessageEvent.getMessage(); for (CommandSender recipient : recipients) { recipient.sendMessage(message); } return recipients.size(); } @Override @Deprecated public OfflinePlayer getOfflinePlayer(String name) { Validate.notNull(name, "Name cannot be null"); Validate.notEmpty(name, "Name cannot be empty"); OfflinePlayer result = getPlayerExact(name); if (result == null) { // This is potentially blocking :( GameProfile profile = console.getUserCache().getProfile(name).orElse(null); 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 getIPBans() { return new HashSet(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 getBannedPlayers() { Set result = new HashSet(); 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.setHasWhitelist(value); } @Override public boolean isWhitelistEnforced() { return console.isEnforceWhitelist(); } @Override public void setWhitelistEnforced(boolean value) { console.setEnforceWhitelist(value); } @Override public Set getWhitelistedPlayers() { Set result = new LinkedHashSet(); for (JsonListEntry entry : playerList.getWhitelist().getValues()) { result.add(getOfflinePlayer((GameProfile) entry.getKey())); } return result; } @Override public Set getOperators() { Set result = new HashSet(); 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.getWorldServer(net.minecraft.world.level.World.OVERWORLD).serverLevelData.getGameType().getId()); } @Override public void setDefaultGameMode(GameMode mode) { Validate.notNull(mode, "Mode cannot be null"); for (World world : getWorlds()) { ((CraftWorld) world).getHandle().serverLevelData.setGameType(EnumGamemode.getById(mode.getValue())); } } @Override public ConsoleCommandSender getConsoleSender() { return console.console; } public EntityMetadataStore getEntityMetadata() { return entityMetadata; } public PlayerMetadataStore getPlayerMetadata() { return playerMetadata; } public WorldMetadataStore getWorldMetadata() { return worldMetadata; } @Override public File getWorldContainer() { return this.getServer().storageSource.a(net.minecraft.world.level.World.OVERWORLD).getParentFile(); } @Override public OfflinePlayer[] getOfflinePlayers() { WorldNBTStorage storage = console.playerDataStorage; String[] files = storage.getPlayerDir().list(new DatFileFilter()); Set players = new HashSet(); for (String file : files) { try { players.add(getOfflinePlayer(UUID.fromString(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 getListeningPluginChannels() { Set result = new HashSet(); for (Player player : getOnlinePlayers()) { result.addAll(player.getListeningPluginChannels()); } return result; } @Override public Inventory createInventory(InventoryHolder owner, InventoryType type) { Validate.isTrue(type.isCreatable(), "Cannot open an inventory of type ", type); return CraftInventoryCreator.INSTANCE.createInventory(owner, type); } @Override public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { Validate.isTrue(type.isCreatable(), "Cannot open an inventory of type ", type); return CraftInventoryCreator.INSTANCE.createInventory(owner, type, title); } @Override public Inventory createInventory(InventoryHolder owner, int size) throws IllegalArgumentException { Validate.isTrue(9 <= size && size <= 54 && size % 9 == 0, "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")"); return CraftInventoryCreator.INSTANCE.createInventory(owner, size); } @Override public Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException { Validate.isTrue(9 <= size && size <= 54 && size % 9 == 0, "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")"); return CraftInventoryCreator.INSTANCE.createInventory(owner, size, title); } @Override public Merchant createMerchant(String title) { return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title); } @Override public HelpMap getHelpMap() { return helpMap; } 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 getWaterAmbientSpawnLimit() { return waterAmbientSpawn; } @Override public int getWaterUndergroundCreatureSpawnLimit() { return waterUndergroundCreatureSpawn; } @Override public int getAmbientSpawnLimit() { return ambientSpawn; } @Override public boolean isPrimaryThread() { return Thread.currentThread().equals(console.serverThread) || console.hasStopped(); // All bets are off if we have shut down (e.g. due to watchdog) } @Override public String getMotd() { return console.getMotd(); } @Override public WarningState getWarningState() { return warningState; } public List tabComplete(CommandSender sender, String message, WorldServer world, Vec3D pos, boolean forceCommand) { if (!(sender instanceof Player)) { return ImmutableList.of(); } List offers; Player player = (Player) sender; if (message.startsWith("/") || forceCommand) { offers = tabCompleteCommand(player, message, world, pos); } else { offers = tabCompleteChat(player, message); } TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers); getPluginManager().callEvent(tabEvent); return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); } public List tabCompleteCommand(Player player, String message, WorldServer world, Vec3D pos) { List completions = null; try { if (message.startsWith("/")) { // Trim leading '/' if present (won't always be present in command blocks) message = message.substring(1); } if (pos == null) { completions = getCommandMap().tabComplete(player, message); } else { completions = getCommandMap().tabComplete(player, message, new Location(world.getWorld(), pos.x, pos.y, pos.z)); } } 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) { List completions = new ArrayList(); 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)); ByteBuffer bytebuffer = Base64.getEncoder().encode(bytebuf.nioBuffer()); return new CraftIconCache("data:image/png;base64," + StandardCharsets.UTF_8.decode(bytebuffer)); } @Override public void setIdleTimeout(int threshold) { console.setIdleTimeout(threshold); } @Override public int getIdleTimeout() { return console.getIdleTimeout(); } @Override public ChunkGenerator.ChunkData createChunkData(World world) { Validate.notNull(world, "World cannot be null"); return new OldCraftChunkData(world); } @Override public BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) { return new CraftBossBar(title, color, style, flags); } @Override public KeyedBossBar createBossBar(NamespacedKey key, String title, BarColor barColor, BarStyle barStyle, BarFlag... barFlags) { Preconditions.checkArgument(key != null, "key"); BossBattleCustom bossBattleCustom = getServer().getBossBattleCustomData().register(CraftNamespacedKey.toMinecraft(key), CraftChatMessage.fromString(title, true)[0]); CraftKeyedBossbar craftKeyedBossbar = new CraftKeyedBossbar(bossBattleCustom); craftKeyedBossbar.setColor(barColor); craftKeyedBossbar.setStyle(barStyle); for (BarFlag flag : barFlags) { craftKeyedBossbar.addFlag(flag); } return craftKeyedBossbar; } @Override public Iterator getBossBars() { return Iterators.unmodifiableIterator(Iterators.transform(getServer().getBossBattleCustomData().getBattles().iterator(), new Function() { @Override public org.bukkit.boss.KeyedBossBar apply(BossBattleCustom bossBattleCustom) { return bossBattleCustom.getBukkitEntity(); } })); } @Override public KeyedBossBar getBossBar(NamespacedKey key) { Preconditions.checkArgument(key != null, "key"); net.minecraft.server.bossevents.BossBattleCustom bossBattleCustom = getServer().getBossBattleCustomData().a(CraftNamespacedKey.toMinecraft(key)); return (bossBattleCustom == null) ? null : bossBattleCustom.getBukkitEntity(); } @Override public boolean removeBossBar(NamespacedKey key) { Preconditions.checkArgument(key != null, "key"); net.minecraft.server.bossevents.BossBattleCustomData bossBattleCustomData = getServer().getBossBattleCustomData(); net.minecraft.server.bossevents.BossBattleCustom bossBattleCustom = bossBattleCustomData.a(CraftNamespacedKey.toMinecraft(key)); if (bossBattleCustom != null) { bossBattleCustomData.remove(bossBattleCustom); return true; } return false; } @Override public Entity getEntity(UUID uuid) { Validate.notNull(uuid, "UUID cannot be null"); for (WorldServer world : getServer().getWorlds()) { net.minecraft.world.entity.Entity entity = world.getEntity(uuid); if (entity != null) { return entity.getBukkitEntity(); } } return null; } @Override public org.bukkit.advancement.Advancement getAdvancement(NamespacedKey key) { Preconditions.checkArgument(key != null, "key"); Advancement advancement = console.getAdvancementData().a(CraftNamespacedKey.toMinecraft(key)); return (advancement == null) ? null : advancement.bukkit; } @Override public Iterator advancementIterator() { return Iterators.unmodifiableIterator(Iterators.transform(console.getAdvancementData().getAdvancements().iterator(), new Function() { @Override public org.bukkit.advancement.Advancement apply(Advancement advancement) { return advancement.bukkit; } })); } @Override public BlockData createBlockData(org.bukkit.Material material) { Validate.isTrue(material != null, "Must provide material"); return createBlockData(material, (String) null); } @Override public BlockData createBlockData(org.bukkit.Material material, Consumer consumer) { BlockData data = createBlockData(material); if (consumer != null) { consumer.accept(data); } return data; } @Override public BlockData createBlockData(String data) throws IllegalArgumentException { Validate.isTrue(data != null, "Must provide data"); return createBlockData(null, data); } @Override public BlockData createBlockData(org.bukkit.Material material, String data) { Validate.isTrue(material != null || data != null, "Must provide one of material or data"); return CraftBlockData.newData(material, data); } @Override @SuppressWarnings("unchecked") public org.bukkit.Tag getTag(String registry, NamespacedKey tag, Class clazz) { MinecraftKey key = CraftNamespacedKey.toMinecraft(tag); switch (registry) { case org.bukkit.Tag.REGISTRY_BLOCKS: Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Block namespace must have material type"); return (org.bukkit.Tag) new CraftBlockTag(TagsBlock.a(), key); case org.bukkit.Tag.REGISTRY_ITEMS: Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Item namespace must have material type"); return (org.bukkit.Tag) new CraftItemTag(TagsItem.a(), key); case org.bukkit.Tag.REGISTRY_FLUIDS: Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type"); return (org.bukkit.Tag) new CraftFluidTag(TagsFluid.a(), key); case org.bukkit.Tag.REGISTRY_ENTITY_TYPES: Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity type namespace must have entity type"); return (org.bukkit.Tag) new CraftEntityTag(TagsEntity.a(), key); default: throw new IllegalArgumentException(); } } @Override @SuppressWarnings("unchecked") public Iterable> getTags(String registry, Class clazz) { switch (registry) { case org.bukkit.Tag.REGISTRY_BLOCKS: Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Block namespace must have material type"); Tags blockTags = TagsBlock.a(); return blockTags.a().keySet().stream().map(key -> (org.bukkit.Tag) new CraftBlockTag(blockTags, key)).collect(ImmutableList.toImmutableList()); case org.bukkit.Tag.REGISTRY_ITEMS: Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Item namespace must have material type"); Tags itemTags = TagsItem.a(); return itemTags.a().keySet().stream().map(key -> (org.bukkit.Tag) new CraftItemTag(itemTags, key)).collect(ImmutableList.toImmutableList()); case org.bukkit.Tag.REGISTRY_FLUIDS: Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Fluid namespace must have fluid type"); Tags fluidTags = TagsFluid.a(); return fluidTags.a().keySet().stream().map(key -> (org.bukkit.Tag) new CraftFluidTag(fluidTags, key)).collect(ImmutableList.toImmutableList()); case org.bukkit.Tag.REGISTRY_ENTITY_TYPES: Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity type namespace must have entity type"); Tags> entityTags = TagsEntity.a(); return entityTags.a().keySet().stream().map(key -> (org.bukkit.Tag) new CraftEntityTag(entityTags, key)).collect(ImmutableList.toImmutableList()); default: throw new IllegalArgumentException(); } } @Override public LootTable getLootTable(NamespacedKey key) { Validate.notNull(key, "NamespacedKey cannot be null"); LootTableRegistry registry = getServer().getLootTableRegistry(); return new CraftLootTable(key, registry.getLootTable(CraftNamespacedKey.toMinecraft(key))); } @Override public List selectEntities(CommandSender sender, String selector) { Preconditions.checkArgument(selector != null, "Selector cannot be null"); Preconditions.checkArgument(sender != null, "Sender cannot be null"); ArgumentEntity arg = ArgumentEntity.multipleEntities(); List nms; try { StringReader reader = new StringReader(selector); nms = arg.parse(reader, true).getEntities(VanillaCommandWrapper.getListener(sender)); Preconditions.checkArgument(!reader.canRead(), "Spurious trailing data in selector: " + selector); } catch (CommandSyntaxException ex) { throw new IllegalArgumentException("Could not parse selector: " + selector, ex); } return new ArrayList<>(Lists.transform(nms, (entity) -> entity.getBukkitEntity())); } @Deprecated @Override public UnsafeValues getUnsafe() { return CraftMagicNumbers.INSTANCE; } }