diff --git a/sources/pom.xml b/sources/pom.xml
index 9470c5c28..2ac46299e 100644
--- a/sources/pom.xml
+++ b/sources/pom.xml
@@ -138,6 +138,11 @@
concurrent-locks
1.0.0
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 1.0.0
+
diff --git a/sources/src/main/java/io/akarin/server/mixin/optimization/MixinEntity.java b/sources/src/main/java/io/akarin/server/mixin/optimization/MixinEntity.java
index 5c34c28a6..4f6225cec 100644
--- a/sources/src/main/java/io/akarin/server/mixin/optimization/MixinEntity.java
+++ b/sources/src/main/java/io/akarin/server/mixin/optimization/MixinEntity.java
@@ -20,6 +20,10 @@ public abstract class MixinEntity {
@Overwrite // PAIL: isInLava
public boolean au() {
+ /*
+ * This is originally comes from Migot (https://github.com/Poweruser/Migot/commit/cafbf1707107d2a3aa6232879f305975bb1f0285)
+ * Thanks @Poweruser
+ */
int currentTick = MinecraftServer.currentTick;
if (this.lastLavaCheck != currentTick) {
this.lastLavaCheck = currentTick;
diff --git a/sources/src/main/java/net/minecraft/server/TileEntitySkull.java b/sources/src/main/java/net/minecraft/server/TileEntitySkull.java
new file mode 100644
index 000000000..df58de738
--- /dev/null
+++ b/sources/src/main/java/net/minecraft/server/TileEntitySkull.java
@@ -0,0 +1,261 @@
+package net.minecraft.server;
+
+import com.google.common.collect.Iterables;
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.minecraft.MinecraftSessionService;
+import com.mojang.authlib.properties.Property;
+import java.util.UUID;
+import javax.annotation.Nullable;
+
+// Spigot start
+import com.google.common.base.Predicate;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.util.concurrent.Futures;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.mojang.authlib.Agent;
+import com.mojang.authlib.ProfileLookupCallback;
+import java.util.concurrent.Callable;
+// Spigot end
+
+public class TileEntitySkull extends TileEntity /*implements ITickable*/ { // Paper - remove tickable
+
+ private int a;
+ public int rotation;
+ private GameProfile g;
+ private int h;
+ private boolean i;
+ private static UserCache j;
+ private static MinecraftSessionService k;
+ // Spigot start
+ public static final ExecutorService executor = Executors.newFixedThreadPool(3,
+ new ThreadFactoryBuilder()
+ .setNameFormat("Head Conversion Thread - %1$d")
+ .build()
+ );
+ public static final com.github.benmanes.caffeine.cache.LoadingCache skinCache = com.github.benmanes.caffeine.cache.Caffeine.newBuilder() // Akarin - caffeine
+ .maximumSize( 5000 )
+ .expireAfterAccess( 60, TimeUnit.MINUTES )
+ .build( new com.github.benmanes.caffeine.cache.CacheLoader() // Akarin - caffeine
+ {
+ @Override
+ public GameProfile load(String key) // Akarin - remove exception
+ {
+ final GameProfile[] profiles = new GameProfile[1];
+ ProfileLookupCallback gameProfileLookup = new ProfileLookupCallback() {
+
+ @Override
+ public void onProfileLookupSucceeded(GameProfile gp) {
+ profiles[0] = gp;
+ }
+
+ @Override
+ public void onProfileLookupFailed(GameProfile gp, Exception excptn) {
+ profiles[0] = gp;
+ }
+ };
+
+ MinecraftServer.getServer().getGameProfileRepository().findProfilesByNames(new String[] { key }, Agent.MINECRAFT, gameProfileLookup);
+
+ GameProfile profile = profiles[ 0 ];
+ if (profile == null) {
+ UUID uuid = EntityHuman.a(new GameProfile(null, key));
+ profile = new GameProfile(uuid, key);
+
+ gameProfileLookup.onProfileLookupSucceeded(profile);
+ } else
+ {
+
+ Property property = Iterables.getFirst( profile.getProperties().get( "textures" ), null );
+
+ if ( property == null )
+ {
+ profile = MinecraftServer.getServer().az().fillProfileProperties( profile, true );
+ }
+ }
+
+
+ return profile;
+ }
+ } );
+ // Spigot end
+
+ public TileEntitySkull() {}
+
+ public static void a(UserCache usercache) {
+ TileEntitySkull.j = usercache;
+ }
+
+ public static void a(MinecraftSessionService minecraftsessionservice) {
+ TileEntitySkull.k = minecraftsessionservice;
+ }
+
+ public NBTTagCompound save(NBTTagCompound nbttagcompound) {
+ super.save(nbttagcompound);
+ nbttagcompound.setByte("SkullType", (byte) (this.a & 255));
+ nbttagcompound.setByte("Rot", (byte) (this.rotation & 255));
+ if (this.g != null) {
+ NBTTagCompound nbttagcompound1 = new NBTTagCompound();
+
+ GameProfileSerializer.serialize(nbttagcompound1, this.g);
+ nbttagcompound.set("Owner", nbttagcompound1);
+ }
+
+ return nbttagcompound;
+ }
+
+ public void load(NBTTagCompound nbttagcompound) {
+ super.load(nbttagcompound);
+ this.a = nbttagcompound.getByte("SkullType");
+ this.rotation = nbttagcompound.getByte("Rot");
+ if (this.a == 3) {
+ if (nbttagcompound.hasKeyOfType("Owner", 10)) {
+ this.g = GameProfileSerializer.deserialize(nbttagcompound.getCompound("Owner"));
+ } else if (nbttagcompound.hasKeyOfType("ExtraType", 8)) {
+ String s = nbttagcompound.getString("ExtraType");
+
+ if (!UtilColor.b(s)) {
+ this.g = new GameProfile((UUID) null, s);
+ this.i();
+ }
+ }
+ }
+
+ }
+
+ public void e() {
+ if (this.a == 5) {
+ if (this.world.isBlockIndirectlyPowered(this.position)) {
+ this.i = true;
+ ++this.h;
+ } else {
+ this.i = false;
+ }
+ }
+
+ }
+
+ @Nullable
+ public GameProfile getGameProfile() {
+ return this.g;
+ }
+
+ @Nullable
+ public PacketPlayOutTileEntityData getUpdatePacket() {
+ return new PacketPlayOutTileEntityData(this.position, 4, this.d());
+ }
+
+ public NBTTagCompound d() {
+ return this.save(new NBTTagCompound());
+ }
+
+ public void setSkullType(int i) {
+ this.a = i;
+ this.g = null;
+ }
+
+ public void setGameProfile(@Nullable GameProfile gameprofile) {
+ this.a = 3;
+ this.g = gameprofile;
+ this.i();
+ }
+
+ private void i() {
+ // Spigot start
+ GameProfile profile = this.g;
+ setSkullType( 0 ); // Work around client bug
+ b(profile, new Predicate() {
+
+ @Override
+ public boolean apply(GameProfile input) {
+ setSkullType(3); // Work around client bug
+ g = input;
+ update();
+ if (world != null) {
+ world.m(position); // PAIL: notify
+ }
+ return false;
+ }
+ }, false);
+ // Spigot end
+ }
+
+ // Spigot start - Support async lookups
+ public static Future b(final GameProfile gameprofile, final Predicate callback, boolean sync) {
+ if (gameprofile != null && !UtilColor.b(gameprofile.getName())) {
+ if (gameprofile.isComplete() && gameprofile.getProperties().containsKey("textures")) {
+ callback.apply(gameprofile);
+ } else if (MinecraftServer.getServer() == null) {
+ callback.apply(gameprofile);
+ } else {
+ GameProfile profile = skinCache.getIfPresent(gameprofile.getName().toLowerCase(java.util.Locale.ROOT));
+ if (profile != null && Iterables.getFirst(profile.getProperties().get("textures"), (Object) null) != null) {
+ callback.apply(profile);
+
+ return Futures.immediateFuture(profile);
+ } else {
+ Callable callable = new Callable() {
+ @Override
+ public GameProfile call() {
+ final GameProfile profile = skinCache.getIfPresent(gameprofile.getName().toLowerCase(java.util.Locale.ROOT)); // Akarin - caffeine
+ MinecraftServer.getServer().processQueue.add(new Runnable() {
+ @Override
+ public void run() {
+ if (profile == null) {
+ callback.apply(gameprofile);
+ } else {
+ callback.apply(profile);
+ }
+ }
+ });
+ return profile;
+ }
+ };
+ if (sync) {
+ try {
+ return Futures.immediateFuture(callable.call());
+ } catch (Exception ex) {
+ com.google.common.base.Throwables.throwIfUnchecked(ex);
+ throw new RuntimeException(ex); // Not possible
+ }
+ } else {
+ return executor.submit(callable);
+ }
+ }
+ }
+ } else {
+ callback.apply(gameprofile);
+ }
+
+ return Futures.immediateFuture(gameprofile);
+ }
+ // Spigot end
+
+ public int getSkullType() {
+ return this.a;
+ }
+
+ public void setRotation(int i) {
+ this.rotation = i;
+ }
+
+ public void a(EnumBlockMirror enumblockmirror) {
+ if (this.world != null && this.world.getType(this.getPosition()).get(BlockSkull.FACING) == EnumDirection.UP) {
+ this.rotation = enumblockmirror.a(this.rotation, 16);
+ }
+
+ }
+
+ public void a(EnumBlockRotation enumblockrotation) {
+ if (this.world != null && this.world.getType(this.getPosition()).get(BlockSkull.FACING) == EnumDirection.UP) {
+ this.rotation = enumblockrotation.a(this.rotation, 16);
+ }
+
+ }
+}
diff --git a/sources/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/sources/src/main/java/org/bukkit/craftbukkit/CraftServer.java
new file mode 100644
index 000000000..c34dfc198
--- /dev/null
+++ b/sources/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -0,0 +1,1975 @@
+package org.bukkit.craftbukkit;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+import javax.imageio.ImageIO;
+
+import net.minecraft.server.*;
+
+import org.bukkit.BanList;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.GameMode;
+import org.bukkit.Location;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Server;
+import org.bukkit.UnsafeValues;
+import org.bukkit.Warning.WarningState;
+import org.bukkit.World;
+import org.bukkit.World.Environment;
+import org.bukkit.WorldCreator;
+import org.bukkit.boss.BarColor;
+import org.bukkit.boss.BarFlag;
+import org.bukkit.boss.BarStyle;
+import org.bukkit.boss.BossBar;
+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.boss.CraftBossBar;
+import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.craftbukkit.generator.CraftChunkData;
+import org.bukkit.craftbukkit.help.SimpleHelpMap;
+import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe;
+import org.bukkit.craftbukkit.inventory.CraftInventoryCustom;
+import org.bukkit.craftbukkit.inventory.CraftItemFactory;
+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.RecipeIterator;
+import org.bukkit.craftbukkit.map.CraftMapView;
+import org.bukkit.craftbukkit.metadata.EntityMetadataStore;
+import org.bukkit.craftbukkit.metadata.PlayerMetadataStore;
+import org.bukkit.craftbukkit.metadata.WorldMetadataStore;
+import org.bukkit.craftbukkit.potion.CraftPotionBrewer;
+import org.bukkit.craftbukkit.scheduler.CraftScheduler;
+import org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager;
+import org.bukkit.craftbukkit.util.CraftIconCache;
+import org.bukkit.craftbukkit.util.CraftMagicNumbers;
+import org.bukkit.craftbukkit.util.DatFileFilter;
+import org.bukkit.craftbukkit.util.Versioning;
+import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.command.UnknownCommandEvent; // Paper
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.event.player.PlayerChatTabCompleteEvent;
+import org.bukkit.event.server.BroadcastMessageEvent;
+import org.bukkit.event.world.WorldInitEvent;
+import org.bukkit.event.world.WorldLoadEvent;
+import org.bukkit.event.world.WorldUnloadEvent;
+import org.bukkit.generator.ChunkGenerator;
+import org.bukkit.help.HelpMap;
+import org.bukkit.inventory.FurnaceRecipe;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.Merchant;
+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.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.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+import com.mojang.authlib.GameProfile;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufOutputStream;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.base64.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.NamespacedKey;
+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
+import org.bukkit.event.server.TabCompleteEvent;
+import net.md_5.bungee.api.chat.BaseComponent;
+
+import javax.annotation.Nullable; // Paper
+import javax.annotation.Nonnull; // Paper
+
+
+public final class CraftServer implements Server {
+ private final String serverName = "Paper";
+ private final String serverVersion;
+ private final String bukkitVersion = Versioning.getBukkitVersion();
+ private final Logger logger = Logger.getLogger("Minecraft");
+ private final ServicesManager servicesManager = new SimpleServicesManager();
+ private final CraftScheduler scheduler = new CraftScheduler();
+ private final SimpleCommandMap commandMap = new SimpleCommandMap(this);
+ private final SimpleHelpMap helpMap = new SimpleHelpMap(this);
+ private final StandardMessenger messenger = new StandardMessenger();
+ private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap);
+ protected final MinecraftServer 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 com.github.benmanes.caffeine.cache.Cache offlinePlayers = com.github.benmanes.caffeine.cache.Caffeine.newBuilder().weakValues().build(); // Akarin - caffeine
+ 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 CraftIconCache icon;
+ private boolean overrideAllCommandBlockCommands = false;
+ private boolean unrestrictedAdvancements;
+ private boolean unrestrictedSignCommands; // Paper
+ private final List playerView;
+ public int reloadCount;
+ public static Exception excessiveVelEx; // Paper - Velocity warnings
+
+ private final class BooleanWrapper {
+ private boolean value = true;
+ }
+
+ static {
+ ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
+ CraftItemFactory.instance();
+ }
+
+ public CraftServer(MinecraftServer console, PlayerList playerList) {
+ this.console = console;
+ this.playerList = (DedicatedPlayerList) playerList;
+ this.playerView = Collections.unmodifiableList(Lists.transform(playerList.players, new Function() {
+ @Override
+ public CraftPlayer apply(EntityPlayer player) {
+ return player.getBukkitEntity();
+ }
+ }));
+ 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
+ Enchantments.DAMAGE_ALL.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("*");
+ unrestrictedAdvancements = commandsConfiguration.getBoolean("unrestricted-advancements");
+ // Paper start
+ unrestrictedSignCommands = commandsConfiguration.getBoolean("unrestricted-signs");
+ if (unrestrictedSignCommands) {
+ logger.warning("Warning: Commands are no longer restricted on signs. If you allow players to use Creative Mode, there may be risk of players bypassing permissions. Use this setting at your own risk!!!!");
+ }
+ // Paper end
+ 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();
+ }
+
+ public boolean getPermissionOverride(ICommandListener listener) {
+ while (listener instanceof CommandListenerWrapper) {
+ listener = ((CommandListenerWrapper) listener).base;
+ }
+
+ if (unrestrictedSignCommands && listener instanceof TileEntitySign.ISignCommandListener) return true; // Paper
+ return unrestrictedAdvancements && listener instanceof AdvancementRewards.AdvancementCommandListener;
+ }
+
+ 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();
+ if (com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); // Paper
+ }
+
+ Plugin[] plugins = pluginManager.getPlugins();
+
+ for (Plugin plugin : plugins) {
+ if ((!plugin.isEnabled()) && (plugin.getDescription().getLoad() == type)) {
+ enablePlugin(plugin);
+ }
+ }
+
+ if (type == PluginLoadOrder.POSTWORLD) {
+ // Spigot start - Allow vanilla commands to be forced to be the main command
+ setVanillaCommands(true);
+ commandMap.setFallbackCommands();
+ setVanillaCommands(false);
+ // Spigot end
+ commandMap.registerServerAliases();
+ if (!com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); // Paper
+ DefaultPermissions.registerCorePermissions();
+ CraftDefaultPermissions.registerCorePermissions();
+ helpMap.initializeCommands();
+ }
+ }
+
+ public void disablePlugins() {
+ pluginManager.disablePlugins();
+ }
+
+ private void setVanillaCommands(boolean first) { // Spigot
+ Map commands = new CommandDispatcher(console).getCommands();
+ for (ICommand cmd : commands.values()) {
+ // Spigot start
+ VanillaCommandWrapper wrapper = new VanillaCommandWrapper((CommandAbstract) cmd, LocaleI18n.get(cmd.getUsage(null)));
+ if (org.spigotmc.SpigotConfig.replaceCommands.contains( wrapper.getName() ) ) {
+ if (first) {
+ commandMap.register("minecraft", wrapper);
+ }
+ } else if (!first) {
+ commandMap.register("minecraft", wrapper);
+ }
+ // Spigot end
+ }
+ }
+
+ private void 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.a(id);
+
+ if (player != null) {
+ return player.getBukkitEntity();
+ }
+
+ return null;
+ }
+
+ @Override
+ public int broadcastMessage(String message) {
+ return broadcast(message, BROADCAST_CHANNEL_USERS);
+ }
+
+ public Player getPlayer(final EntityPlayer entity) {
+ return entity.getBukkitEntity();
+ }
+
+ @Override
+ @Deprecated
+ public List 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.getConfigInt("server-port", 25565);
+ }
+
+ @Override
+ public int getViewDistance() {
+ return this.getConfigInt("view-distance", 10);
+ }
+
+ @Override
+ public String getIp() {
+ return this.getConfigString("server-ip", "");
+ }
+
+ @Override
+ public String getServerName() {
+ return this.getConfigString("server-name", "Unknown Server");
+ }
+
+ @Override
+ public String getServerId() {
+ return this.getConfigString("server-id", "unnamed");
+ }
+
+ @Override
+ public String getWorldType() {
+ return this.getConfigString("level-type", "DEFAULT");
+ }
+
+ @Override
+ public boolean getGenerateStructures() {
+ return this.getConfigBoolean("generate-structures", true);
+ }
+
+ @Override
+ public boolean getAllowEnd() {
+ return this.configuration.getBoolean("settings.allow-end");
+ }
+
+ @Override
+ public boolean getAllowNether() {
+ return this.getConfigBoolean("allow-nether", true);
+ }
+
+ public boolean getWarnOnOverload() {
+ return this.configuration.getBoolean("settings.warn-on-overload");
+ }
+
+ public boolean getQueryPlugins() {
+ return this.configuration.getBoolean("settings.query-plugins");
+ }
+
+ @Override
+ public boolean hasWhitelist() {
+ return this.getConfigBoolean("white-list", false);
+ }
+
+ // NOTE: Temporary calls through to server.properies until its replaced
+ private String getConfigString(String variable, String defaultValue) {
+ return this.console.getPropertyManager().getString(variable, defaultValue);
+ }
+
+ private int getConfigInt(String variable, int defaultValue) {
+ return this.console.getPropertyManager().getInt(variable, defaultValue);
+ }
+
+ private boolean getConfigBoolean(String variable, boolean defaultValue) {
+ return this.console.getPropertyManager().getBoolean(variable, defaultValue);
+ }
+
+ // End Temporary calls
+
+ @Override
+ public String getUpdateFolder() {
+ return this.configuration.getString("settings.update-folder", "update");
+ }
+
+ @Override
+ public File getUpdateFolderFile() {
+ return new File((File) console.options.valueOf("plugins"), this.configuration.getString("settings.update-folder", "update"));
+ }
+
+ @Override
+ public long getConnectionThrottle() {
+ // Spigot Start - Automatically set connection throttle for bungee configurations
+ if (org.spigotmc.SpigotConfig.bungee) {
+ return -1;
+ } else {
+ return this.configuration.getInt("settings.connection-throttle");
+ }
+ // Spigot End
+ }
+
+ @Override
+ public int getTicksPerAnimalSpawns() {
+ return this.configuration.getInt("ticks-per.animal-spawns");
+ }
+
+ @Override
+ public int getTicksPerMonsterSpawns() {
+ return this.configuration.getInt("ticks-per.monster-spawns");
+ }
+
+ @Override
+ public PluginManager getPluginManager() {
+ return pluginManager;
+ }
+
+ @Override
+ public CraftScheduler getScheduler() {
+ return scheduler;
+ }
+
+ @Override
+ public ServicesManager getServicesManager() {
+ return servicesManager;
+ }
+
+ @Override
+ public List 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;
+ }
+ }
+
+ @Override
+ public boolean dispatchCommand(CommandSender sender, String commandLine) {
+ Validate.notNull(sender, "Sender cannot be null");
+ Validate.notNull(commandLine, "CommandLine cannot be null");
+
+ // Paper Start
+ if (!org.spigotmc.AsyncCatcher.shuttingDown && !Bukkit.isPrimaryThread()) {
+ final CommandSender fSender = sender;
+ final String fCommandLine = commandLine;
+ Bukkit.getLogger().log(Level.SEVERE, "Command Dispatched Async: " + commandLine);
+ Bukkit.getLogger().log(Level.SEVERE, "Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C", new Throwable());
+ org.bukkit.craftbukkit.util.Waitable wait = new org.bukkit.craftbukkit.util.Waitable() {
+ @Override
+ protected Boolean evaluate() {
+ return dispatchCommand(fSender, fCommandLine);
+ }
+ };
+ net.minecraft.server.MinecraftServer.getServer().processQueue.add(wait);
+ try {
+ return wait.get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on!
+ } catch (Exception e) {
+ throw new RuntimeException("Exception processing dispatch command", e.getCause());
+ }
+ }
+ // Paper End
+
+ if (commandMap.dispatch(sender, commandLine)) {
+ return true;
+ }
+
+ // Spigot start
+ if (StringUtils.isNotEmpty(org.spigotmc.SpigotConfig.unknownCommandMessage)) {
+ // Paper start
+ UnknownCommandEvent event = new UnknownCommandEvent(sender, commandLine, org.spigotmc.SpigotConfig.unknownCommandMessage);
+ Bukkit.getServer().getPluginManager().callEvent(event);
+ if (StringUtils.isNotEmpty(event.getMessage())) {
+ sender.sendMessage(event.getMessage());
+ }
+ // Paper end
+ }
+ // Spigot end
+
+ return false;
+ }
+
+ @Override
+ public void reload() {
+ reloadCount++;
+ configuration = YamlConfiguration.loadConfiguration(getConfigFile());
+ commandsConfiguration = YamlConfiguration.loadConfiguration(getCommandsConfigFile());
+ PropertyManager config = new PropertyManager(console.options);
+
+ ((DedicatedServer) console).propertyManager = config;
+
+ boolean animals = config.getBoolean("spawn-animals", console.getSpawnAnimals());
+ boolean monsters = config.getBoolean("spawn-monsters", console.worlds.get(0).getDifficulty() != EnumDifficulty.PEACEFUL);
+ EnumDifficulty difficulty = EnumDifficulty.getById(config.getInt("difficulty", console.worlds.get(0).getDifficulty().ordinal()));
+
+ online.value = config.getBoolean("online-mode", console.getOnlineMode());
+ console.setSpawnAnimals(config.getBoolean("spawn-animals", console.getSpawnAnimals()));
+ console.setPVP(config.getBoolean("pvp", console.getPVP()));
+ console.setAllowFlight(config.getBoolean("allow-flight", console.getAllowFlight()));
+ console.setMotd(config.getString("motd", console.getMotd()));
+ monsterSpawn = configuration.getInt("spawn-limits.monsters");
+ animalSpawn = configuration.getInt("spawn-limits.animals");
+ waterAnimalSpawn = configuration.getInt("spawn-limits.water-animals");
+ ambientSpawn = configuration.getInt("spawn-limits.ambient");
+ warningState = WarningState.value(configuration.getString("settings.deprecated-verbose"));
+ printSaveWarning = false;
+ console.autosavePeriod = configuration.getInt("ticks-per.autosave");
+ chunkGCPeriod = configuration.getInt("chunk-gc.period-in-ticks");
+ chunkGCLoadThresh = configuration.getInt("chunk-gc.load-threshold");
+ loadIcon();
+
+ try {
+ playerList.getIPBans().load();
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Failed to load banned-ips.json, " + ex.getMessage());
+ }
+ try {
+ playerList.getProfileBans().load();
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Failed to load banned-players.json, " + ex.getMessage());
+ }
+
+ org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
+ com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper
+ for (WorldServer world : console.worlds) {
+ world.worldData.setDifficulty(difficulty);
+ world.setSpawnFlags(monsters, animals);
+ if (this.getTicksPerAnimalSpawns() < 0) {
+ world.ticksPerAnimalSpawns = 400;
+ } else {
+ world.ticksPerAnimalSpawns = this.getTicksPerAnimalSpawns();
+ }
+
+ if (this.getTicksPerMonsterSpawns() < 0) {
+ world.ticksPerMonsterSpawns = 1;
+ } else {
+ world.ticksPerMonsterSpawns = this.getTicksPerMonsterSpawns();
+ }
+ world.spigotConfig.init(); // Spigot
+ world.paperConfig.init(); // Paper
+ }
+
+ Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
+ pluginManager.clearPlugins();
+ commandMap.clearCommands();
+
+ // Paper start
+ for (Plugin plugin : pluginClone) {
+ entityMetadata.removeAll(plugin);
+ worldMetadata.removeAll(plugin);
+ playerMetadata.removeAll(plugin);
+ }
+ // Paper end
+
+ resetRecipes();
+ reloadData();
+ org.spigotmc.SpigotConfig.registerCommands(); // Spigot
+ com.destroystokyo.paper.PaperConfig.registerCommands(); // Paper
+ overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*");
+
+ int pollCount = 0;
+
+ // Wait for at most 2.5 seconds for plugins to close their threads
+ while (pollCount < 50 && getScheduler().getActiveWorkers().size() > 0) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {}
+ pollCount++;
+ }
+
+ List 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);
+ }
+
+ @Override
+ public void reloadData() {
+ console.reload();
+ }
+
+ 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) {
+ 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(), getHandle().getServer().dataConverterManager);
+ if (converter.isConvertable(name)) {
+ getLogger().info("Converting world '" + name + "'");
+ converter.convert(name, new IProgressUpdate() {
+ private long b = System.currentTimeMillis();
+
+ public void a(String s) {}
+
+ public void a(int i) {
+ if (System.currentTimeMillis() - this.b >= 1000L) {
+ this.b = System.currentTimeMillis();
+ MinecraftServer.LOGGER.info("Converting... " + i + "%");
+ }
+
+ }
+
+ public void c(String s) {}
+ });
+ }
+
+ int dimension = CraftWorld.CUSTOM_DIMENSION_OFFSET + console.worlds.size();
+ boolean used = false;
+ do {
+ for (WorldServer server : console.worlds) {
+ used = server.dimension == dimension;
+ if (used) {
+ dimension++;
+ break;
+ }
+ }
+ } while(used);
+ boolean hardcore = false;
+
+ IDataManager sdm = new ServerNBTManager(getWorldContainer(), name, true, getHandle().getServer().dataConverterManager);
+ WorldData worlddata = sdm.getWorldData();
+ WorldSettings worldSettings = null;
+ if (worlddata == null) {
+ worldSettings = new WorldSettings(creator.seed(), EnumGamemode.getById(getDefaultGameMode().getValue()), generateStructures, hardcore, type);
+ worldSettings.setGeneratorSettings(creator.generatorSettings());
+ worlddata = new WorldData(worldSettings, name);
+ }
+ worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
+ WorldServer internal = (WorldServer) new WorldServer(console, sdm, worlddata, dimension, console.methodProfiler, creator.environment(), generator).b();
+
+ if (!(worlds.containsKey(name.toLowerCase(java.util.Locale.ENGLISH)))) {
+ return null;
+ }
+
+ if (worldSettings != null) {
+ internal.a(worldSettings);
+ }
+ internal.scoreboard = getScoreboardManager().getMainScoreboard().getHandle();
+
+ internal.tracker = new EntityTracker(internal);
+ internal.addIWorldAccess(new WorldManager(console, internal));
+ internal.worldData.setDifficulty(EnumDifficulty.EASY);
+ internal.setSpawnFlags(true, true);
+ console.worlds.add(internal);
+
+ pluginManager.callEvent(new WorldInitEvent(internal.getWorld()));
+ System.out.println("Preparing start region for level " + (console.worlds.size() - 1) + " (Seed: " + internal.getSeed() + ")");
+
+ if (internal.getWorld().getKeepSpawnInMemory()) {
+ short short1 = internal.paperConfig.keepLoadedRange; // Paper
+ long i = System.currentTimeMillis();
+ for (int j = -short1; j <= short1; j += 16) {
+ for (int k = -short1; k <= short1; k += 16) {
+ long l = System.currentTimeMillis();
+
+ if (l < i) {
+ i = l;
+ }
+
+ if (l > i + 1000L) {
+ int i1 = (short1 * 2 + 1) * (short1 * 2 + 1);
+ int j1 = (j + short1) * (short1 * 2 + 1) + k + 1;
+
+ System.out.println("Preparing spawn area for " + name + ", " + (j1 * 100 / i1) + "%");
+ i = l;
+ }
+
+ BlockPosition chunkcoordinates = internal.getSpawn();
+ internal.getChunkProviderServer().getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4);
+ }
+ }
+ }
+ pluginManager.callEvent(new WorldLoadEvent(internal.getWorld()));
+ return internal.getWorld();
+ }
+
+ @Override
+ public boolean unloadWorld(String name, boolean save) {
+ return unloadWorld(getWorld(name), save);
+ }
+
+ @Override
+ public boolean unloadWorld(World world, boolean save) {
+ if (world == null) {
+ return false;
+ }
+
+ WorldServer handle = ((CraftWorld) world).getHandle();
+
+ if (!(console.worlds.contains(handle))) {
+ return false;
+ }
+
+ if (handle.dimension == 0) {
+ return false;
+ }
+
+ if (handle.players.size() > 0) {
+ return false;
+ }
+
+ WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld());
+ pluginManager.callEvent(e);
+
+ if (e.isCancelled()) {
+ return false;
+ }
+
+ if (save) {
+ try {
+ handle.save(true, null);
+ handle.saveLevel();
+ } catch (ExceptionWorldConflict ex) {
+ getLogger().log(Level.SEVERE, null, ex);
+ }
+ }
+
+ worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH));
+ console.worlds.remove(console.worlds.indexOf(handle));
+
+ File parentFolder = world.getWorldFolder().getAbsoluteFile();
+
+ // Synchronized because access to RegionFileCache.a is guarded by this lock.
+ synchronized (RegionFileCache.class) {
+ // RegionFileCache.a should be RegionFileCache.cache
+ Iterator> i = RegionFileCache.a.entrySet().iterator();
+ while(i.hasNext()) {
+ Map.Entry entry = i.next();
+ File child = entry.getKey().getAbsoluteFile();
+ while (child != null) {
+ if (child.equals(parentFolder)) {
+ i.remove();
+ try {
+ entry.getValue().c(); // Should be RegionFile.close();
+ } catch (IOException ex) {
+ getLogger().log(Level.SEVERE, null, ex);
+ }
+ break;
+ }
+ child = child.getParentFile();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public MinecraftServer getServer() {
+ return console;
+ }
+
+ @Override
+ public World getWorld(String name) {
+ Validate.notNull(name, "Name cannot be null");
+
+ return worlds.get(name.toLowerCase(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;
+ }
+
+ // Paper start - JLine update
+ /*
+ public ConsoleReader getReader() {
+ return console.reader;
+ }
+ */
+ // Paper end
+
+ @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 {
+ 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 Iterator recipeIterator() {
+ return new RecipeIterator();
+ }
+
+ @Override
+ public void clearRecipes() {
+ CraftingManager.recipes = new RegistryMaterials();
+ RecipesFurnace.getInstance().recipes.clear();
+ RecipesFurnace.getInstance().customRecipes.clear();
+ RecipesFurnace.getInstance().customExperience.clear();
+ }
+
+ @Override
+ public void resetRecipes() {
+ CraftingManager.recipes = new RegistryMaterials();
+ CraftingManager.init();
+ RecipesFurnace.getInstance().recipes = new RecipesFurnace().recipes;
+ RecipesFurnace.getInstance().customRecipes.clear();
+ RecipesFurnace.getInstance().customExperience.clear();
+ }
+
+ @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 ((DedicatedServer) console).propertyManager.getInt("spawn-protection", 16);
+ }
+
+ @Override
+ public void setSpawnRadius(int value) {
+ configuration.set("settings.spawn-radius", value);
+ saveConfig();
+ }
+
+ @Override
+ public boolean getOnlineMode() {
+ return online.value;
+ }
+
+ @Override
+ public boolean getAllowFlight() {
+ return console.getAllowFlight();
+ }
+
+ @Override
+ public boolean isHardcore() {
+ return console.isHardcore();
+ }
+
+ 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;
+ }
+
+ @Override
+ @Deprecated
+ public CraftMapView getMap(short id) {
+ PersistentCollection collection = console.worlds.get(0).worldMaps;
+ WorldMap worldmap = (WorldMap) collection.get(WorldMap.class, "map_" + id);
+ if (worldmap == null) {
+ return null;
+ }
+ return worldmap.mapView;
+ }
+
+ @Override
+ public CraftMapView createMap(World world) {
+ Validate.notNull(world, "World cannot be null");
+
+ net.minecraft.server.ItemStack stack = new net.minecraft.server.ItemStack(Items.MAP, 1, -1);
+ WorldMap worldmap = Items.FILLED_MAP.getSavedMap(stack, ((CraftWorld) world).getHandle());
+ return worldmap.mapView;
+ }
+
+ @Override
+ public void shutdown() {
+ console.safeShutdown();
+ }
+
+ @Override
+ public int broadcast(String message, String permission) {
+ 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(message, recipients);
+ getPluginManager().callEvent(broadcastMessageEvent);
+
+ if (broadcastMessageEvent.isCancelled()) {
+ return 0;
+ }
+
+ message = broadcastMessageEvent.getMessage();
+
+ for (CommandSender recipient : recipients) {
+ recipient.sendMessage(message);
+ }
+
+ return recipients.size();
+ }
+
+ // Paper start
+ @Nullable
+ public UUID getPlayerUniqueId(String name) {
+ Player player = Bukkit.getPlayerExact(name);
+ if (player != null) {
+ return player.getUniqueId();
+ }
+ GameProfile profile;
+ // Only fetch an online UUID in online mode
+ if (MinecraftServer.getServer().getOnlineMode()
+ || (org.spigotmc.SpigotConfig.bungee && com.destroystokyo.paper.PaperConfig.bungeeOnlineMode)) {
+ profile = console.getUserCache().getProfile( name );
+ } else {
+ // Make an OfflinePlayer using an offline mode UUID since the name has no profile
+ profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name);
+ }
+ return profile != null ? profile.getId() : null;
+ }
+ // Paper end
+
+ @Override
+ @Deprecated
+ public OfflinePlayer getOfflinePlayer(String name) {
+ Validate.notNull(name, "Name cannot be null");
+ com.google.common.base.Preconditions.checkArgument( !org.apache.commons.lang.StringUtils.isBlank( name ), "Name cannot be blank" ); // Spigot
+
+ OfflinePlayer result = getPlayerExact(name);
+ if (result == null) {
+ // Spigot Start
+ GameProfile profile = null;
+ // Only fetch an online UUID in online mode
+ if ( MinecraftServer.getServer().getOnlineMode()
+ || (org.spigotmc.SpigotConfig.bungee && com.destroystokyo.paper.PaperConfig.bungeeOnlineMode)) // Paper - Handle via setting
+ {
+ profile = console.getUserCache().getProfile( name );
+ }
+ // Spigot end
+ if (profile == null) {
+ // Make an OfflinePlayer using an offline mode UUID since the name has no profile
+ result = getOfflinePlayer(new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name));
+ } else {
+ // Use the GameProfile even when we get a UUID so we ensure we still have a name
+ result = getOfflinePlayer(profile);
+ }
+ } else {
+ offlinePlayers.invalidate(result.getUniqueId()); // Akarin - caffeine
+ }
+
+ return result;
+ }
+
+ @Override
+ public OfflinePlayer getOfflinePlayer(UUID id) {
+ Validate.notNull(id, "UUID cannot be null");
+
+ OfflinePlayer result = getPlayer(id);
+ if (result == null) {
+ result = offlinePlayers.getIfPresent(id); // Akarin - caffeine
+ if (result == null) {
+ result = new CraftOfflinePlayer(this, new GameProfile(id, null));
+ offlinePlayers.put(id, result);
+ }
+ } else {
+ offlinePlayers.invalidate(id); // Akarin - caffeine
+ }
+
+ 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.getPropertyManager().setProperty("white-list", 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.worlds.get(0).getWorldData().getGameType().getId());
+ }
+
+ @Override
+ public void setDefaultGameMode(GameMode mode) {
+ Validate.notNull(mode, "Mode cannot be null");
+
+ for (World world : getWorlds()) {
+ ((CraftWorld) world).getHandle().worldData.setGameType(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() {
+ if (this.getServer().universe != null) {
+ return this.getServer().universe;
+ }
+
+ if (container == null) {
+ container = new File(configuration.getString("settings.world-container", "."));
+ }
+
+ return container;
+ }
+
+ @Override
+ public OfflinePlayer[] getOfflinePlayers() {
+ WorldNBTStorage storage = (WorldNBTStorage) console.worlds.get(0).getDataManager();
+ String[] files = storage.getPlayerDir().list(new DatFileFilter());
+ Set 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) {
+ // TODO: Create the appropriate type, rather than Custom?
+ return new CraftInventoryCustom(owner, type);
+ }
+
+ @Override
+ public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) {
+ return new CraftInventoryCustom(owner, type, title);
+ }
+
+ @Override
+ public Inventory createInventory(InventoryHolder owner, int size) throws IllegalArgumentException {
+ Validate.isTrue(size % 9 == 0, "Chests must have a size that is a multiple of 9!");
+ return new CraftInventoryCustom(owner, size);
+ }
+
+ @Override
+ public Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException {
+ Validate.isTrue(size % 9 == 0, "Chests must have a size that is a multiple of 9!");
+ return new CraftInventoryCustom(owner, size, title);
+ }
+
+ @Override
+ public Merchant createMerchant(String title) {
+ return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title);
+ }
+
+ @Override
+ public HelpMap getHelpMap() {
+ return helpMap;
+ }
+
+ @Override // Paper - add override
+ public SimpleCommandMap getCommandMap() {
+ return commandMap;
+ }
+
+ @Override
+ public int getMonsterSpawnLimit() {
+ return monsterSpawn;
+ }
+
+ @Override
+ public int getAnimalSpawnLimit() {
+ return animalSpawn;
+ }
+
+ @Override
+ public int getWaterAnimalSpawnLimit() {
+ return waterAnimalSpawn;
+ }
+
+ @Override
+ public int getAmbientSpawnLimit() {
+ return ambientSpawn;
+ }
+
+ @Override
+ public boolean isPrimaryThread() {
+ return Thread.currentThread().equals(console.primaryThread);
+ }
+
+ @Override
+ public String getMotd() {
+ return console.getMotd();
+ }
+
+ @Override
+ public WarningState getWarningState() {
+ return warningState;
+ }
+
+ public List tabComplete(net.minecraft.server.ICommandListener sender, String message, BlockPosition pos, boolean forceCommand) {
+ if (!(sender instanceof EntityPlayer)) {
+ return ImmutableList.of();
+ }
+
+ List offers;
+ Player player = ((EntityPlayer) sender).getBukkitEntity();
+ if (message.startsWith("/") || forceCommand) {
+ offers = tabCompleteCommand(player, message, pos);
+ } else {
+ offers = tabCompleteChat(player, message);
+ }
+
+ TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers, message.startsWith("/") || forceCommand, pos != null ? MCUtil.toLocation(((CraftWorld) player.getWorld()).getHandle(), pos) : null); // Paper
+ getPluginManager().callEvent(tabEvent);
+
+ return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions();
+ }
+
+ public List tabCompleteCommand(Player player, String message, BlockPosition pos) {
+ // Spigot Start
+ if ( (org.spigotmc.SpigotConfig.tabComplete < 0 || message.length() <= org.spigotmc.SpigotConfig.tabComplete) && !message.contains( " " ) )
+ {
+ return ImmutableList.of();
+ }
+ // Spigot End
+
+ 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(player.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
+ }
+ } catch (CommandException ex) {
+ player.sendMessage(ChatColor.RED + "An internal error occurred while attempting to tab-complete this command");
+ getLogger().log(Level.SEVERE, "Exception when " + player.getName() + " attempted to tab complete " + message, ex);
+ }
+
+ return completions == null ? ImmutableList.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));
+ ByteBuf bytebuf1 = Base64.encode(bytebuf);
+
+ return new CraftIconCache("data:image/png;base64," + bytebuf1.toString(Charsets.UTF_8));
+ }
+
+ @Override
+ public void setIdleTimeout(int threshold) {
+ console.setIdleTimeout(threshold);
+ }
+
+ @Override
+ public int getIdleTimeout() {
+ return console.getIdleTimeout();
+ }
+
+ @Override
+ public ChunkGenerator.ChunkData createChunkData(World world) {
+ return new CraftChunkData(world);
+ }
+
+ @Override
+ public BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) {
+ return new CraftBossBar(title, color, style, flags);
+ }
+
+ @Override
+ public Entity getEntity(UUID uuid) {
+ Validate.notNull(uuid, "UUID cannot be null");
+ net.minecraft.server.Entity entity = console.a(uuid); // PAIL: getEntity
+ return entity == null ? null : entity.getBukkitEntity();
+ }
+
+ @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().c().iterator(), new Function() { // PAIL: rename
+ @Override
+ public org.bukkit.advancement.Advancement apply(Advancement advancement) {
+ return advancement.bukkit;
+ }
+ }));
+ }
+
+ @Deprecated
+ @Override
+ public UnsafeValues getUnsafe() {
+ return CraftMagicNumbers.INSTANCE;
+ }
+
+ // Paper - Add getTPS API - Further improve tick loop
+ @Override
+ public double[] getTPS() {
+ return new double[] {
+ MinecraftServer.getServer().tps1.getAverage(),
+ MinecraftServer.getServer().tps5.getAverage(),
+ MinecraftServer.getServer().tps15.getAverage()
+ };
+ }
+ // Paper end
+
+ private final Spigot spigot = new Spigot()
+ {
+
+ @Deprecated
+ @Override
+ public YamlConfiguration getConfig()
+ {
+ return org.spigotmc.SpigotConfig.config;
+ }
+
+ @Override
+ public YamlConfiguration getBukkitConfig()
+ {
+ return configuration;
+ }
+
+ @Override
+ public YamlConfiguration getSpigotConfig()
+ {
+ return org.spigotmc.SpigotConfig.config;
+ }
+
+ @Override
+ public YamlConfiguration getPaperConfig()
+ {
+ return com.destroystokyo.paper.PaperConfig.config;
+ }
+
+ @Override
+ public void restart() {
+ org.spigotmc.RestartCommand.restart();
+ }
+
+ @Override
+ public void broadcast(BaseComponent component) {
+ for (Player player : getOnlinePlayers()) {
+ player.spigot().sendMessage(component);
+ }
+ }
+
+ @Override
+ public void broadcast(BaseComponent... components) {
+ for (Player player : getOnlinePlayers()) {
+ player.spigot().sendMessage(components);
+ }
+ }
+ };
+
+ public Spigot spigot()
+ {
+ return spigot;
+ }
+
+ // Paper start
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static boolean dumpHeap(File file) {
+ try {
+ if (file.getParentFile() != null) {
+ file.getParentFile().mkdirs();
+ }
+
+ Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
+ javax.management.MBeanServer server = java.lang.management.ManagementFactory.getPlatformMBeanServer();
+ Object hotspotMBean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", clazz);
+ java.lang.reflect.Method m = clazz.getMethod("dumpHeap", String.class, boolean.class);
+ m.invoke(hotspotMBean, file.getPath(), true);
+ return true;
+ } catch (Throwable t) {
+ Bukkit.getLogger().severe("Could not write heap to " + file);
+ t.printStackTrace();
+ return false;
+ }
+ }
+
+ @Override
+ public void reloadPermissions() {
+ ((SimplePluginManager) pluginManager).clearPermissions();
+ loadCustomPermissions();
+ for (Plugin plugin : pluginManager.getPlugins()) {
+ plugin.getDescription().getPermissions().forEach((perm) -> {
+ 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);
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean reloadCommandAliases() {
+ Set removals = getCommandAliases().keySet().stream()
+ .map(key -> key.toLowerCase(java.util.Locale.ENGLISH))
+ .collect(java.util.stream.Collectors.toSet());
+ getCommandMap().getKnownCommands().keySet().removeIf(removals::contains);
+ File file = getCommandsConfigFile();
+ try {
+ commandsConfiguration.load(file);
+ } catch (FileNotFoundException ex) {
+ return false;
+ } catch (IOException | org.bukkit.configuration.InvalidConfigurationException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex);
+ return false;
+ }
+ commandMap.registerServerAliases();
+ return true;
+ }
+
+ @Override
+ public boolean suggestPlayerNamesWhenNullTabCompletions() {
+ return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions;
+ }
+
+ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) {
+ return createProfile(uuid, null);
+ }
+
+ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name) {
+ return createProfile(null, name);
+ }
+
+ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) {
+ Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null);
+ if (player != null) {
+ return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer)player);
+ }
+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name);
+ }
+ // Paper end
+}