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 +}