diff --git a/pom.xml b/pom.xml index 568358b99..97f596d4e 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ jenkins - http://ci.codemc.org/job/BentoBoxWorld/job/BentoBox + https://ci.codemc.org/job/BentoBoxWorld/job/BentoBox @@ -87,8 +87,9 @@ - - + + ci @@ -101,10 +102,14 @@ - - - - + + + + master @@ -115,7 +120,7 @@ ${build.version} - + @@ -130,7 +135,7 @@ org.sonarsource.scanner.maven sonar-maven-plugin - + 3.6.0.1398 @@ -161,78 +166,81 @@ placeholderapi-repo - http://repo.extendedclip.com/content/repositories/placeholderapi/ + https://repo.extendedclip.com/content/repositories/placeholderapi/ dynmap-repo - http://repo.mikeprimm.com/ + https://repo.mikeprimm.com/ worldedit-repo - http://maven.sk89q.com/repo/ + https://maven.sk89q.com/repo/ + + + papermc + https://papermc.io/repo/repository/maven-public/ - - - - org.spigotmc - spigot-api - ${spigot.version} - provided - - - - org.bstats - bstats-bukkit - ${bstats.version} - - - - org.mockito - mockito-core - 3.1.0 - test - - - org.powermock - powermock-module-junit4 - ${powermock.version} - test - - - org.powermock - powermock-api-mockito2 - ${powermock.version} - test - - - - org.mongodb - mongodb-driver - ${mongodb.version} - - - - - com.github.MilkBowl - VaultAPI - ${vault.version} - provided - - - - me.clip - placeholderapi - ${placeholderapi.version} - provided - + + - - com.github.Prouser123-forks + org.spigotmc + spigot-api + ${spigot.version} + provided + + + + org.bstats + bstats-bukkit + ${bstats.version} + + + + org.mockito + mockito-core + 3.1.0 + test + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + org.powermock + powermock-api-mockito2 + ${powermock.version} + test + + + + org.mongodb + mongodb-driver + ${mongodb.version} + + + + + com.github.MilkBowl + VaultAPI + ${vault.version} + provided + + + + me.clip + placeholderapi + ${placeholderapi.version} + provided + + + + com.github.BentoBoxWorld MVdWPlaceholderAPI ${mvdwplaceholderapi.version} @@ -255,26 +263,35 @@ GitHubWebAPI4Java ${githubapi.version} - - + + org.eclipse.jdt org.eclipse.jdt.annotation 2.2.200 + + + io.papermc + paperlib + 1.0.2 + compile + - + - + ${project.name}-${revision}${build.number} - + clean package @@ -350,6 +367,10 @@ io.github.TheBusyBiscuit.GitHubWebAPI4Java world.bentobox.bentobox.api.github + + io.papermc.lib + world.bentobox.bentobox.paperlib + @@ -384,8 +405,8 @@ true - + **/*Names* diff --git a/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java b/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java index d871dcc82..f08ddc175 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java @@ -3,7 +3,6 @@ package world.bentobox.bentobox.listeners; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.UUID; import org.bukkit.Bukkit; @@ -15,7 +14,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.scheduler.BukkitRunnable; import org.eclipse.jdt.annotation.NonNull; import world.bentobox.bentobox.BentoBox; @@ -57,32 +55,32 @@ public class JoinLeaveListener implements Listener { players.addPlayer(playerUUID); plugin.getIWM().getOverWorlds().stream() - .filter(w -> plugin.getIWM().isCreateIslandOnFirstLoginEnabled(w)) - .forEach(w -> { - // Even if that'd be extremely unlikely, it's better to check if the player doesn't have an already. - if (!(plugin.getIslands().hasIsland(w, user) || plugin.getIslands().inTeam(w, user.getUniqueId()))) { - int delay = plugin.getIWM().getCreateIslandOnFirstLoginDelay(w); - user.sendMessage("commands.island.create.on-first-login", - TextVariables.NUMBER, String.valueOf(delay)); + .filter(w -> plugin.getIWM().isCreateIslandOnFirstLoginEnabled(w)) + .forEach(w -> { + // Even if that'd be extremely unlikely, it's better to check if the player doesn't have an already. + if (!(plugin.getIslands().hasIsland(w, user) || plugin.getIslands().inTeam(w, user.getUniqueId()))) { + int delay = plugin.getIWM().getCreateIslandOnFirstLoginDelay(w); + user.sendMessage("commands.island.create.on-first-login", + TextVariables.NUMBER, String.valueOf(delay)); - Runnable createIsland = () -> { - // should only execute if: - // - abort on logout is false - // - abort on logout is true && user is online - if (!plugin.getIWM().isCreateIslandOnFirstLoginAbortOnLogout(w) || user.isOnline()){ - plugin.getIWM().getAddon(w).ifPresent(addon -> addon.getPlayerCommand() - .map(command -> command.getSubCommand("create").orElse(null)) - .ifPresent(command -> command.execute(user, "create", Collections.singletonList(BlueprintsManager.DEFAULT_BUNDLE_NAME)))); - } - }; - - if (delay <= 0) { - createIsland.run(); - } else { - plugin.getServer().getScheduler().runTaskLater(plugin, createIsland, delay * 20L); - } + Runnable createIsland = () -> { + // should only execute if: + // - abort on logout is false + // - abort on logout is true && user is online + if (!plugin.getIWM().isCreateIslandOnFirstLoginAbortOnLogout(w) || user.isOnline()){ + plugin.getIWM().getAddon(w).ifPresent(addon -> addon.getPlayerCommand() + .map(command -> command.getSubCommand("create").orElse(null)) + .ifPresent(command -> command.execute(user, "create", Collections.singletonList(BlueprintsManager.DEFAULT_BUNDLE_NAME)))); } - }); + }; + + if (delay <= 0) { + createIsland.run(); + } else { + plugin.getServer().getScheduler().runTaskLater(plugin, createIsland, delay * 20L); + } + } + }); } // Make sure the player is loaded into the cache (doesn't impact performance) @@ -90,8 +88,8 @@ public class JoinLeaveListener implements Listener { // Reset island resets if required plugin.getIWM().getOverWorlds().stream() - .filter(w -> event.getPlayer().getLastPlayed() < plugin.getIWM().getResetEpoch(w)) - .forEach(w -> players.setResets(w, playerUUID, 0)); + .filter(w -> event.getPlayer().getLastPlayed() < plugin.getIWM().getResetEpoch(w)) + .forEach(w -> players.setResets(w, playerUUID, 0)); // Automated island ownership transfer if (plugin.getSettings().isEnableAutoOwnershipTransfer()) { @@ -157,53 +155,53 @@ public class JoinLeaveListener implements Listener { private void runAutomatedOwnershipTransfer(User user) { plugin.getIWM().getOverWorlds().stream() - .filter(world -> plugin.getIslands().hasIsland(world, user) && !plugin.getIslands().isOwner(world, user.getUniqueId())) - .forEach(world -> { - Island island = plugin.getIslands().getIsland(world, user); + .filter(world -> plugin.getIslands().hasIsland(world, user) && !plugin.getIslands().isOwner(world, user.getUniqueId())) + .forEach(world -> { + Island island = plugin.getIslands().getIsland(world, user); - OfflinePlayer owner = Bukkit.getOfflinePlayer(island.getOwner()); + OfflinePlayer owner = Bukkit.getOfflinePlayer(island.getOwner()); - // Converting the setting (in days) to milliseconds. - long inactivityThreshold = plugin.getSettings().getAutoOwnershipTransferInactivityThreshold() * 24 * 60 * 60 * 1000L; - long timestamp = System.currentTimeMillis() - inactivityThreshold; + // Converting the setting (in days) to milliseconds. + long inactivityThreshold = plugin.getSettings().getAutoOwnershipTransferInactivityThreshold() * 24 * 60 * 60 * 1000L; + long timestamp = System.currentTimeMillis() - inactivityThreshold; - // We make sure the current owner is inactive. - if (owner.getLastPlayed() != 0 && owner.getLastPlayed() < timestamp) { - // The current owner is inactive - // Now, let's run through all of the island members (except the player who's just joined) and see who's active. - // Sadly, this will make us calculate the owner inactivity again... :( - List candidates = Arrays.asList((UUID[]) island.getMemberSet().stream() - .filter(uuid -> !user.getUniqueId().equals(uuid)) - .filter(uuid -> Bukkit.getOfflinePlayer(uuid).getLastPlayed() != 0 - && Bukkit.getOfflinePlayer(uuid).getLastPlayed() < timestamp) - .toArray()); + // We make sure the current owner is inactive. + if (owner.getLastPlayed() != 0 && owner.getLastPlayed() < timestamp) { + // The current owner is inactive + // Now, let's run through all of the island members (except the player who's just joined) and see who's active. + // Sadly, this will make us calculate the owner inactivity again... :( + List candidates = Arrays.asList((UUID[]) island.getMemberSet().stream() + .filter(uuid -> !user.getUniqueId().equals(uuid)) + .filter(uuid -> Bukkit.getOfflinePlayer(uuid).getLastPlayed() != 0 + && Bukkit.getOfflinePlayer(uuid).getLastPlayed() < timestamp) + .toArray()); - if (!candidates.isEmpty() && !plugin.getSettings().isAutoOwnershipTransferIgnoreRanks()) { - // Ranks are not ignored, our candidates can only have the highest rank - // TODO Complete this section - } - } - }); + if (!candidates.isEmpty() && !plugin.getSettings().isAutoOwnershipTransferIgnoreRanks()) { + // Ranks are not ignored, our candidates can only have the highest rank + // TODO Complete this section + } + } + }); } private void updateIslandRange(User user) { plugin.getIWM().getOverWorlds().stream() - .filter(world -> plugin.getIslands().isOwner(world, user.getUniqueId())) - .forEach(world -> { - Island island = plugin.getIslands().getIsland(world, user); - if (island != null) { - // Check if new owner has a different range permission than the island size - int range = user.getPermissionValue(plugin.getIWM().getAddon(island.getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("") + "island.range", island.getProtectionRange()); + .filter(world -> plugin.getIslands().isOwner(world, user.getUniqueId())) + .forEach(world -> { + Island island = plugin.getIslands().getIsland(world, user); + if (island != null) { + // Check if new owner has a different range permission than the island size + int range = user.getPermissionValue(plugin.getIWM().getAddon(island.getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("") + "island.range", island.getProtectionRange()); - // Range can go up or down - if (range != island.getProtectionRange()) { - user.sendMessage("commands.admin.setrange.range-updated", TextVariables.NUMBER, String.valueOf(range)); - plugin.log("Island protection range changed from " + island.getProtectionRange() + " to " - + range + " for " + user.getName() + " due to permission."); - } - island.setProtectionRange(range); - } - }); + // Range can go up or down + if (range != island.getProtectionRange()) { + user.sendMessage("commands.admin.setrange.range-updated", TextVariables.NUMBER, String.valueOf(range)); + plugin.log("Island protection range changed from " + island.getProtectionRange() + " to " + + range + " for " + user.getName() + " due to permission."); + } + island.setProtectionRange(range); + } + }); } @EventHandler(priority = EventPriority.NORMAL) @@ -218,9 +216,9 @@ public class JoinLeaveListener implements Listener { // No, there are no more players online on this island // Tell players they are being removed island.getMembers().entrySet().stream() - .filter(e -> e.getValue() == RanksManager.COOP_RANK) - .forEach(e -> User.getInstance(e.getKey()) - .sendMessage("commands.island.team.uncoop.all-members-logged-off", TextVariables.NAME, plugin.getPlayers().getName(island.getOwner()))); + .filter(e -> e.getValue() == RanksManager.COOP_RANK) + .forEach(e -> User.getInstance(e.getKey()) + .sendMessage("commands.island.team.uncoop.all-members-logged-off", TextVariables.NAME, plugin.getPlayers().getName(island.getOwner()))); // Remove any coop players on this island island.removeRank(RanksManager.COOP_RANK); } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java index 7d26b8412..ef107d9f7 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java @@ -12,6 +12,7 @@ import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.vehicle.VehicleMoveEvent; import org.bukkit.util.Vector; +import io.papermc.lib.PaperLib; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.api.user.User; @@ -155,7 +156,7 @@ public class LockAndBanListener extends FlagListener { } else { // There's nothing much we can do. // We'll try to teleport him to the spawn... - player.teleport(player.getWorld().getSpawnLocation()); + PaperLib.teleportAsync(player, player.getWorld().getSpawnLocation()); // Switch him back to the default gamemode. He may die, sorry :( player.setGameMode(getIWM().getDefaultGameMode(player.getWorld())); diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index f3a870990..dd0198d14 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -20,6 +20,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.TreeSpecies; import org.bukkit.World; +import org.bukkit.attribute.Attribute; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.data.BlockData; @@ -36,6 +37,7 @@ import org.eclipse.jdt.annotation.Nullable; import com.google.common.collect.ImmutableMap; +import io.papermc.lib.PaperLib; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.events.IslandBaseEvent; import world.bentobox.bentobox.api.events.island.IslandEvent; @@ -664,27 +666,26 @@ public class IslandsManager { .build(); return; } - if (!home.getChunk().isLoaded()) { - home.getChunk().load(); - } - player.teleport(home); // Add home if (plugin.getPlayers().getHomeLocations(world, player.getUniqueId()).isEmpty()) { plugin.getPlayers().setHomeLocation(player.getUniqueId(), home); } - if (number == 1) { - user.sendMessage("commands.island.go.teleport"); - } else { + user.sendMessage("commands.island.go.teleport"); + PaperLib.teleportAsync(player, home).thenAccept(b -> teleported(world, user, number, newIsland)); + } + + private void teleported(World world, User user, int number, boolean newIsland) { + if (number > 1) { user.sendMessage("commands.island.go.teleported", TextVariables.NUMBER, String.valueOf(number)); } // If this is a new island, then run commands and do resets if (newIsland) { // Execute commands plugin.getIWM().getOnJoinCommands(world).forEach(command -> { - command = command.replace("[player]", player.getName()); + command = command.replace("[player]", user.getName()); if (command.startsWith("[SUDO]")) { // Execute the command by the player - player.performCommand(command.substring(6)); + user.performCommand(command.substring(6)); } else { // Otherwise execute as the server console plugin.getServer().dispatchCommand(Bukkit.getConsoleSender(), command); @@ -704,7 +705,7 @@ public class IslandsManager { // Reset the health if (plugin.getIWM().isOnJoinResetHealth(world)) { - user.getPlayer().setHealth(20.0D); + user.getPlayer().setHealth(user.getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getDefaultValue()); } // Reset the hunger @@ -789,7 +790,7 @@ public class IslandsManager { this.spawn.put(spawn.getWorld(), spawn); spawn.setSpawn(true); } - + /** * Clears the spawn island for this world * @param world - world @@ -986,7 +987,7 @@ public class IslandsManager { // Move player to spawn if (spawn.containsKey(w)) { // go to island spawn - p.teleport(spawn.get(w).getSpawnPoint(w.getEnvironment())); + PaperLib.teleportAsync(p, spawn.get(w).getSpawnPoint(w.getEnvironment())); } } }); diff --git a/src/main/java/world/bentobox/bentobox/panels/IslandCreationPanel.java b/src/main/java/world/bentobox/bentobox/panels/IslandCreationPanel.java index 9c6c30e9a..b4d5a21d5 100644 --- a/src/main/java/world/bentobox/bentobox/panels/IslandCreationPanel.java +++ b/src/main/java/world/bentobox/bentobox/panels/IslandCreationPanel.java @@ -9,7 +9,6 @@ import org.bukkit.ChatColor; import org.eclipse.jdt.annotation.NonNull; import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; diff --git a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java index 9ea677d39..457619c51 100644 --- a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java +++ b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java @@ -5,6 +5,8 @@ import java.util.Random; import org.bukkit.Bukkit; import org.bukkit.Chunk; +import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; @@ -12,6 +14,7 @@ import org.bukkit.generator.ChunkGenerator.ChunkData; import org.bukkit.inventory.InventoryHolder; import org.bukkit.scheduler.BukkitTask; +import io.papermc.lib.PaperLib; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.events.island.IslandEvent; @@ -25,13 +28,11 @@ import world.bentobox.bentobox.database.objects.IslandDeletion; */ public class DeleteIslandChunks { - /** - * This is how many chunks per world will be done in one tick. - */ private int chunkX; private int chunkZ; private BukkitTask task; private IslandDeletion di; + private boolean inDelete; public DeleteIslandChunks(BentoBox plugin, IslandDeletion di) { // Fire event @@ -42,16 +43,19 @@ public class DeleteIslandChunks { this.di = di; // Run through all chunks of the islands and regenerate them. task = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + if (inDelete) return; + inDelete = true; for (int i = 0; i < plugin.getSettings().getDeleteSpeed(); i++) { plugin.getIWM().getAddon(di.getWorld()).ifPresent(gm -> { - Chunk chunk = di.getWorld().getChunkAt(chunkX, chunkZ); - regenerateChunk(gm, chunk); - + // Overworld + processChunk(gm, di.getWorld(), chunkX, chunkZ); + // Nether if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) { - regenerateChunk(gm, plugin.getIWM().getNetherWorld(di.getWorld()).getChunkAt(chunkX, chunkZ)); + processChunk(gm, plugin.getIWM().getNetherWorld(di.getWorld()), chunkX, chunkZ); } + // End if (plugin.getIWM().isEndGenerate(di.getWorld()) && plugin.getIWM().isEndIslands(di.getWorld())) { - regenerateChunk(gm, plugin.getIWM().getEndWorld(di.getWorld()).getChunkAt(chunkX, chunkZ)); + processChunk(gm, plugin.getIWM().getEndWorld(di.getWorld()), chunkX, chunkZ); } chunkZ++; if (chunkZ > di.getMaxZChunk()) { @@ -66,9 +70,16 @@ public class DeleteIslandChunks { } }); } + inDelete = false; }, 0L, 1L); } + private void processChunk(GameModeAddon gm, World world, int x, int z) { + if (PaperLib.isChunkGenerated(world, x, z)) { + PaperLib.getChunkAtAsync(world, x, z).thenAccept(chunk -> regenerateChunk(gm, chunk)); + } + } + private void regenerateChunk(GameModeAddon gm, Chunk chunk) { boolean isLoaded = chunk.isLoaded(); // Clear all inventories @@ -88,6 +99,9 @@ public class DeleteIslandChunks { if (di.inBounds(baseX + x, baseZ + z)) { chunk.getBlock(x, 0, z).setBiome(grid.getBiome(x, z)); for (int y = 0; y < chunk.getWorld().getMaxHeight(); y++) { + // Note: setting block to air before setting it to something else stops a bug in the server + // where it reports a " + chunk.getBlock(x, y, z).setType(Material.AIR, false); chunk.getBlock(x, y, z).setBlockData(cd.getBlockData(x, y, z), false); } } diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index eb469580f..797c23746 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -6,14 +6,19 @@ import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; +import javax.annotation.Nonnull; + import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.World.Environment; +import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Animals; import org.bukkit.entity.Bat; @@ -28,10 +33,13 @@ import org.bukkit.entity.Shulker; import org.bukkit.entity.Slime; import org.bukkit.entity.Snowman; import org.bukkit.entity.WaterMob; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import io.papermc.lib.PaperLib; +import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; @@ -351,4 +359,164 @@ public class Util { return entity instanceof Animals || entity instanceof IronGolem || entity instanceof Snowman || entity instanceof WaterMob && !(entity instanceof PufferFish) || entity instanceof Bat; } + + /* + * PaperLib methods for addons to call + */ + + /** + * Teleports an Entity to the target location, loading the chunk asynchronously first if needed. + * @param entity The Entity to teleport + * @param location The Location to Teleport to + * @return Future that completes with the result of the teleport + */ + @Nonnull + public static CompletableFuture teleportAsync(@Nonnull Entity entity, @Nonnull Location location) { + return PaperLib.teleportAsync(entity, location); + } + + /** + * Teleports an Entity to the target location, loading the chunk asynchronously first if needed. + * @param entity The Entity to teleport + * @param location The Location to Teleport to + * @param cause The cause for the teleportation + * @return Future that completes with the result of the teleport + */ + @Nonnull + public static CompletableFuture teleportAsync(@Nonnull Entity entity, @Nonnull Location location, TeleportCause cause) { + return PaperLib.teleportAsync(entity, location, cause); + } + + /** + * Gets the chunk at the target location, loading it asynchronously if needed. + * @param loc Location to get chunk for + * @return Future that completes with the chunk + */ + @Nonnull + public static CompletableFuture getChunkAtAsync(@Nonnull Location loc) { + return getChunkAtAsync(loc.getWorld(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4, true); + } + + /** + * Gets the chunk at the target location, loading it asynchronously if needed. + * @param loc Location to get chunk for + * @param gen Should the chunk generate or not. Only respected on some MC versions, 1.13 for CB, 1.12 for Paper + * @return Future that completes with the chunk, or null if the chunk did not exists and generation was not requested. + */ + @Nonnull + public static CompletableFuture getChunkAtAsync(@Nonnull Location loc, boolean gen) { + return getChunkAtAsync(loc.getWorld(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4, gen); + } + + /** + * Gets the chunk at the target location, loading it asynchronously if needed. + * @param world World to load chunk for + * @param x X coordinate of the chunk to load + * @param z Z coordinate of the chunk to load + * @return Future that completes with the chunk + */ + @Nonnull + public static CompletableFuture getChunkAtAsync(@Nonnull World world, int x, int z) { + return getChunkAtAsync(world, x, z, true); + } + + /** + * Gets the chunk at the target location, loading it asynchronously if needed. + * @param world World to load chunk for + * @param x X coordinate of the chunk to load + * @param z Z coordinate of the chunk to load + * @param gen Should the chunk generate or not. Only respected on some MC versions, 1.13 for CB, 1.12 for Paper + * @return Future that completes with the chunk, or null if the chunk did not exists and generation was not requested. + */ + @Nonnull + public static CompletableFuture getChunkAtAsync(@Nonnull World world, int x, int z, boolean gen) { + return PaperLib.getChunkAtAsync(world, x, z, gen); + } + + /** + * Checks if the chunk has been generated or not. Only works on Paper 1.12+ or any 1.13.1+ version + * @param loc Location to check if the chunk is generated + * @return If the chunk is generated or not + */ + public static boolean isChunkGenerated(@Nonnull Location loc) { + return isChunkGenerated(loc.getWorld(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4); + } + + /** + * Checks if the chunk has been generated or not. Only works on Paper 1.12+ or any 1.13.1+ version + * @param world World to check for + * @param x X coordinate of the chunk to check + * @param z Z coordinate of the chunk to checl + * @return If the chunk is generated or not + */ + public static boolean isChunkGenerated(@Nonnull World world, int x, int z) { + return PaperLib.isChunkGenerated(world, x, z); + } + + /** + * Get's a BlockState, optionally not using a snapshot + * @param block The block to get a State of + * @param useSnapshot Whether or not to use a snapshot when supported + * @return The BlockState + */ + @Nonnull + public static BlockStateSnapshotResult getBlockState(@Nonnull Block block, boolean useSnapshot) { + return PaperLib.getBlockState(block, useSnapshot); + } + + /** + * Detects if the current MC version is at least the following version. + * + * Assumes 0 patch version. + * + * @param minor Min Minor Version + * @return Meets the version requested + */ + public static boolean isVersion(int minor) { + return PaperLib.isVersion(minor); + } + + /** + * Detects if the current MC version is at least the following version. + * @param minor Min Minor Version + * @param patch Min Patch Version + * @return Meets the version requested + */ + public static boolean isVersion(int minor, int patch) { + return PaperLib.isVersion(minor, patch); + } + + /** + * Gets the current Minecraft Minor version. IE: 1.13.1 returns 13 + * @return The Minor Version + */ + public static int getMinecraftVersion() { + return PaperLib.getMinecraftVersion(); + } + + /** + * Gets the current Minecraft Patch version. IE: 1.13.1 returns 1 + * @return The Patch Version + */ + public static int getMinecraftPatchVersion() { + return PaperLib.getMinecraftPatchVersion(); + } + + /** + * Check if the server has access to the Spigot API + * @return True for Spigot and Paper environments + */ + public static boolean isSpigot() { + return PaperLib.isSpigot(); + } + + /** + * Check if the server has access to the Paper API + * @return True for Paper environments + */ + public static boolean isPaper() { + return PaperLib.isPaper(); + } + + } diff --git a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java index b5b31001c..895280bd0 100644 --- a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java +++ b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java @@ -17,6 +17,7 @@ import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; +import io.papermc.lib.PaperLib; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -226,16 +227,13 @@ public class SafeSpotTeleport { */ private void teleportEntity(final Location loc) { task.cancel(); + if (!portal && entity instanceof Player && homeNumber > 0) { + // Set home if so marked + plugin.getPlayers().setHomeLocation(User.getInstance(entity), loc, homeNumber); + } + Vector velocity = entity.getVelocity(); // Return to main thread and teleport the player - Bukkit.getScheduler().runTask(plugin, () -> { - if (!portal && entity instanceof Player && homeNumber > 0) { - // Set home if so marked - plugin.getPlayers().setHomeLocation(User.getInstance(entity), loc, homeNumber); - } - Vector velocity = entity.getVelocity(); - entity.teleport(loc); - entity.setVelocity(velocity); - }); + Bukkit.getScheduler().runTask(plugin, () -> PaperLib.teleportAsync(entity, loc).thenAccept(b -> entity.setVelocity(velocity))); } /** diff --git a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java index a4682a7ab..3761b2744 100644 --- a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java @@ -66,6 +66,9 @@ import org.powermock.reflect.Whitebox; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; +import io.papermc.lib.PaperLib; +import io.papermc.lib.environments.CraftBukkitEnvironment; +import io.papermc.lib.environments.Environment; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.configuration.WorldSettings; @@ -131,6 +134,8 @@ public class IslandsManagerTest { private Material sign; private Material wallSign; + private Environment env; + /** * @throws java.lang.Exception @@ -188,6 +193,8 @@ public class IslandsManagerTest { BukkitScheduler sch = mock(BukkitScheduler.class); PowerMockito.mockStatic(Bukkit.class); when(Bukkit.getScheduler()).thenReturn(sch); + // version + when(Bukkit.getVersion()).thenReturn("Paper version git-Paper-225 (MC: 1.14.4) (Implementing API version 1.14.4-R0.1-SNAPSHOT)"); // Standard location manager = new IslandsManager(plugin); @@ -295,6 +302,10 @@ public class IslandsManagerTest { if (wallSign == null) { wallSign = Material.getMaterial("OAK_WALL_SIGN"); } + + // PaperLib + env = new CraftBukkitEnvironment(); + PaperLib.setCustomEnvironment(env); } @After @@ -716,7 +727,7 @@ public class IslandsManagerTest { when(pm.getHomeLocation(any(), any(User.class), eq(0))).thenReturn(null); when(pm.getHomeLocation(any(), any(User.class), eq(1))).thenReturn(location); im.homeTeleport(world, player, 0); - verify(player).teleport(location); + verify(player).teleport(eq(location), any()); }